From c43e481a3367f76f38e1824707fd670af2c663b5 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 10 Dec 2016 21:07:52 -0500 Subject: [PATCH] Started factoring video out of the Electron. --- Machines/Electron/Electron.cpp | 470 ++---------------- Machines/Electron/Electron.hpp | 38 +- Machines/Electron/Video.cpp | 469 +++++++++++++++++ Machines/Electron/Video.hpp | 64 +++ .../Clock Signal.xcodeproj/project.pbxproj | 6 + 5 files changed, 581 insertions(+), 466 deletions(-) create mode 100644 Machines/Electron/Video.cpp create mode 100644 Machines/Electron/Video.hpp diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index c8e156915..45868ce32 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -12,8 +12,6 @@ #include #include -using namespace Electron; - namespace { static const unsigned int cycles_per_line = 128; static const unsigned int lines_per_frame = 625; @@ -33,6 +31,8 @@ namespace { static const unsigned int real_time_clock_interrupt_2 = 56704; } +using namespace Electron; + #define graphics_line(v) ((((v) >> 7) - first_graphics_line + field_divider_line) % field_divider_line) #define graphics_column(v) ((((v) & 127) - first_graphics_cycle + 128) & 127) @@ -42,11 +42,9 @@ Machine::Machine() : frame_cycles_(0), display_output_position_(0), audio_output_position_(0), - current_pixel_line_(-1), use_fast_tape_hack_(false) { memset(key_states_, 0, sizeof(key_states_)); - memset(palette_, 0xf, sizeof(palette_)); for(int c = 0; c < 16; c++) memset(roms_[c], 0xff, 16384); @@ -56,28 +54,18 @@ Machine::Machine() : void Machine::setup_output(float aspect_ratio) { - speaker_.reset(new Speaker); - crt_.reset(new Outputs::CRT::CRT(crt_cycles_per_line, 8, Outputs::CRT::DisplayType::PAL50, 1)); - crt_->set_rgb_sampling_function( - "vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)" - "{" - "uint texValue = texture(sampler, coordinate).r;" - "texValue >>= 4 - (int(icoordinate.x * 8) & 4);" - "return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));" - "}"); - - // TODO: as implied below, I've introduced a clock's latency into the graphics pipeline somehow. Investigate. - crt_->set_visible_area(crt_->get_rect_for_area(first_graphics_line - 3, 256, (first_graphics_cycle+1) * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f)); + video_output_.reset(new VideoOutput(ram_)); // The maximum output frequency is 62500Hz and all other permitted output frequencies are integral divisions of that; // however setting the speaker on or off can happen on any 2Mhz cycle, and probably (?) takes effect immediately. So // run the speaker at a 2000000Hz input rate, at least for the time being. + speaker_.reset(new Speaker); speaker_->set_input_rate(2000000 / Speaker::clock_rate_divider); } void Machine::close_output() { - crt_ = nullptr; + video_output_.reset(); } unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) @@ -106,13 +94,13 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // for the entire frame, RAM is accessible only on odd cycles; in modes below 4 // it's also accessible only outside of the pixel regions cycles += 1 + (frame_cycles_&1); - if(screen_mode_ < 4) - { - const int current_line = graphics_line(frame_cycles_ + (frame_cycles_&1)); - const int current_column = graphics_column(frame_cycles_ + (frame_cycles_&1)); - if(current_line < 256 && current_column < 80 && !is_blank_line_) - cycles += (unsigned int)(80 - current_column); - } +// if(screen_mode_ < 4) +// { +// const int current_line = graphics_line(frame_cycles_ + (frame_cycles_&1)); +// const int current_column = graphics_column(frame_cycles_ + (frame_cycles_&1)); +// if(current_line < 256 && current_column < 80 && !is_blank_line_) +// cycles += (unsigned int)(80 - current_column); +// } } else { @@ -134,18 +122,13 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin evaluate_interrupts(); } break; - case 0xfe02: + case 0xfe02: case 0xfe03: + case 0xfe08: case 0xfe09: case 0xfe0a: case 0xfe0b: + case 0xfe0c: case 0xfe0d: case 0xfe0e: case 0xfe0f: if(!isReadOperation(operation)) { - start_screen_address_ = (start_screen_address_ & 0xfe00) | (uint16_t)(((*value) & 0xe0) << 1); - if(!start_screen_address_) start_screen_address_ |= 0x8000; - } - break; - case 0xfe03: - if(!isReadOperation(operation)) - { - start_screen_address_ = (start_screen_address_ & 0x01ff) | (uint16_t)(((*value) & 0x3f) << 9); - if(!start_screen_address_) start_screen_address_ |= 0x8000; + update_display(); + video_output_->set_register(address, *value); } break; case 0xfe04: @@ -204,21 +187,8 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0xfe07: if(!isReadOperation(operation)) { - // update screen mode - uint8_t new_screen_mode = ((*value) >> 3)&7; - if(new_screen_mode == 7) new_screen_mode = 4; - if(new_screen_mode != screen_mode_) - { - update_display(); - screen_mode_ = new_screen_mode; - switch(screen_mode_) - { - case 0: case 1: case 2: screen_mode_base_address_ = 0x3000; break; - case 3: screen_mode_base_address_ = 0x4000; break; - case 4: case 5: screen_mode_base_address_ = 0x5800; break; - case 6: screen_mode_base_address_ = 0x6000; break; - } - } + update_display(); + video_output_->set_register(address, *value); // update speaker mode bool new_speaker_is_enabled = (*value & 6) == 2; @@ -236,67 +206,6 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // TODO: caps lock LED } break; - case 0xfe08: case 0xfe09: case 0xfe0a: case 0xfe0b: case 0xfe0c: case 0xfe0d: case 0xfe0e: case 0xfe0f: - { - if(!isReadOperation(operation)) - { - update_display(); - - static const int registers[4][4] = { - {10, 8, 2, 0}, - {14, 12, 6, 4}, - {15, 13, 7, 5}, - {11, 9, 3, 1}, - }; - const int index = (address >> 1)&3; - const uint8_t colour = ~(*value); - if(address&1) - { - palette_[registers[index][0]] = (palette_[registers[index][0]]&3) | ((colour >> 1)&4); - palette_[registers[index][1]] = (palette_[registers[index][1]]&3) | ((colour >> 0)&4); - palette_[registers[index][2]] = (palette_[registers[index][2]]&3) | ((colour << 1)&4); - palette_[registers[index][3]] = (palette_[registers[index][3]]&3) | ((colour << 2)&4); - - palette_[registers[index][2]] = (palette_[registers[index][2]]&5) | ((colour >> 4)&2); - palette_[registers[index][3]] = (palette_[registers[index][3]]&5) | ((colour >> 3)&2); - } - else - { - palette_[registers[index][0]] = (palette_[registers[index][0]]&6) | ((colour >> 7)&1); - palette_[registers[index][1]] = (palette_[registers[index][1]]&6) | ((colour >> 6)&1); - palette_[registers[index][2]] = (palette_[registers[index][2]]&6) | ((colour >> 5)&1); - palette_[registers[index][3]] = (palette_[registers[index][3]]&6) | ((colour >> 4)&1); - - palette_[registers[index][0]] = (palette_[registers[index][0]]&5) | ((colour >> 2)&2); - palette_[registers[index][1]] = (palette_[registers[index][1]]&5) | ((colour >> 1)&2); - } - - // regenerate all palette tables for now -#define pack(a, b) (uint8_t)((a << 4) | (b)) - for(int byte = 0; byte < 256; byte++) - { - uint8_t *target = (uint8_t *)&palette_tables_.forty1bpp[byte]; - target[0] = pack(palette_[(byte&0x80) >> 4], palette_[(byte&0x40) >> 3]); - target[1] = pack(palette_[(byte&0x20) >> 2], palette_[(byte&0x10) >> 1]); - - target = (uint8_t *)&palette_tables_.eighty2bpp[byte]; - target[0] = pack(palette_[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], palette_[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]); - target[1] = pack(palette_[((byte&0x20) >> 2) | ((byte&0x02) >> 0)], palette_[((byte&0x10) >> 1) | ((byte&0x01) << 1)]); - - target = (uint8_t *)&palette_tables_.eighty1bpp[byte]; - target[0] = pack(palette_[(byte&0x80) >> 4], palette_[(byte&0x40) >> 3]); - target[1] = pack(palette_[(byte&0x20) >> 2], palette_[(byte&0x10) >> 1]); - target[2] = pack(palette_[(byte&0x08) >> 0], palette_[(byte&0x04) << 1]); - target[3] = pack(palette_[(byte&0x02) << 2], palette_[(byte&0x01) << 3]); - - palette_tables_.forty2bpp[byte] = pack(palette_[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], palette_[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]); - palette_tables_.eighty4bpp[byte] = pack( palette_[((byte&0x80) >> 4) | ((byte&0x20) >> 3) | ((byte&0x08) >> 2) | ((byte&0x02) >> 1)], - palette_[((byte&0x40) >> 3) | ((byte&0x10) >> 2) | ((byte&0x04) >> 1) | ((byte&0x01) >> 0)]); - } -#undef pack - } - } - break; case 0xfc04: case 0xfc05: case 0xfc06: case 0xfc07: if(plus3_ && (address&0x00f0) == 0x00c0) @@ -573,6 +482,12 @@ inline void Machine::evaluate_interrupts() set_irq_line(interrupt_status_ & 1); } +inline void Machine::update_display() +{ + video_output_->run_for_cycles(frame_cycles_ - display_output_position_); + display_output_position_ = frame_cycles_; +} + inline void Machine::update_audio() { unsigned int difference = frame_cycles_ - audio_output_position_ + audio_output_position_error_; @@ -581,331 +496,6 @@ inline void Machine::update_audio() audio_output_position_error_ = difference % Speaker::clock_rate_divider; } -inline void Machine::start_pixel_line() -{ - current_pixel_line_ = (current_pixel_line_+1)&255; - if(!current_pixel_line_) - { - start_line_address_ = start_screen_address_; - current_character_row_ = 0; - is_blank_line_ = false; - } - else - { - bool mode_has_blank_lines = (screen_mode_ == 6) || (screen_mode_ == 3); - is_blank_line_ = (mode_has_blank_lines && ((current_character_row_ > 7 && current_character_row_ < 10) || (current_pixel_line_ > 249))); - - if(!is_blank_line_) - { - start_line_address_++; - - if(current_character_row_ > 7) - { - start_line_address_ += ((screen_mode_ < 4) ? 80 : 40) * 8 - 8; - current_character_row_ = 0; - } - } - } - current_screen_address_ = start_line_address_; - current_pixel_column_ = 0; - initial_output_target_ = current_output_target_ = nullptr; -} - -inline void Machine::end_pixel_line() -{ - if(current_output_target_) crt_->output_data((unsigned int)((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_); - current_character_row_++; -} - -inline void Machine::output_pixels(unsigned int number_of_cycles) -{ - if(!number_of_cycles) return; - - if(is_blank_line_) - { - crt_->output_blank(number_of_cycles * crt_cycles_multiplier); - } - else - { - unsigned int divider = 0; - switch(screen_mode_) - { - case 0: case 3: divider = 2; break; - case 1: case 4: case 6: divider = 4; break; - case 2: case 5: divider = 8; break; - } - - if(!initial_output_target_ || divider != current_output_divider_) - { - if(current_output_target_) crt_->output_data((unsigned int)((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_); - current_output_divider_ = divider; - initial_output_target_ = current_output_target_ = crt_->allocate_write_area(640 / current_output_divider_); - } - -#define get_pixel() \ - if(current_screen_address_&32768)\ - {\ - current_screen_address_ = (screen_mode_base_address_ + current_screen_address_)&32767;\ - }\ - last_pixel_byte_ = ram_[current_screen_address_];\ - current_screen_address_ = current_screen_address_+8 - - switch(screen_mode_) - { - case 0: case 3: - if(initial_output_target_) - { - while(number_of_cycles--) - { - get_pixel(); - *(uint32_t *)current_output_target_ = palette_tables_.eighty1bpp[last_pixel_byte_]; - current_output_target_ += 4; - current_pixel_column_++; - } - } else current_output_target_ += 4*number_of_cycles; - break; - - case 1: - if(initial_output_target_) - { - while(number_of_cycles--) - { - get_pixel(); - *(uint16_t *)current_output_target_ = palette_tables_.eighty2bpp[last_pixel_byte_]; - current_output_target_ += 2; - current_pixel_column_++; - } - } else current_output_target_ += 2*number_of_cycles; - break; - - case 2: - if(initial_output_target_) - { - while(number_of_cycles--) - { - get_pixel(); - *current_output_target_ = palette_tables_.eighty4bpp[last_pixel_byte_]; - current_output_target_ += 1; - current_pixel_column_++; - } - } else current_output_target_ += number_of_cycles; - break; - - case 4: case 6: - if(initial_output_target_) - { - if(current_pixel_column_&1) - { - last_pixel_byte_ <<= 4; - *(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_]; - current_output_target_ += 2; - - number_of_cycles--; - current_pixel_column_++; - } - while(number_of_cycles > 1) - { - get_pixel(); - *(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_]; - current_output_target_ += 2; - - last_pixel_byte_ <<= 4; - *(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_]; - current_output_target_ += 2; - - number_of_cycles -= 2; - current_pixel_column_+=2; - } - if(number_of_cycles) - { - get_pixel(); - *(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_]; - current_output_target_ += 2; - current_pixel_column_++; - } - } else current_output_target_ += 2 * number_of_cycles; - break; - - case 5: - if(initial_output_target_) - { - if(current_pixel_column_&1) - { - last_pixel_byte_ <<= 2; - *current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_]; - current_output_target_ += 1; - - number_of_cycles--; - current_pixel_column_++; - } - while(number_of_cycles > 1) - { - get_pixel(); - *current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_]; - current_output_target_ += 1; - - last_pixel_byte_ <<= 2; - *current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_]; - current_output_target_ += 1; - - number_of_cycles -= 2; - current_pixel_column_+=2; - } - if(number_of_cycles) - { - get_pixel(); - *current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_]; - current_output_target_ += 1; - current_pixel_column_++; - } - } else current_output_target_ += number_of_cycles; - break; - } - -#undef get_pixel - } -} - -inline void Machine::update_display() -{ - /* - - Odd field: Even field: - - |--S--| -S-| - |--S--| |--S--| - |-S-B-| = 3 |--S--| = 2.5 - |--B--| |--B--| - |--P--| |--P--| - |--B--| = 312 |--B--| = 312.5 - |-B- - - */ - - int final_line = frame_cycles_ >> 7; - while(display_output_position_ < frame_cycles_) - { - int line = display_output_position_ >> 7; - - // Priority one: sync. - // =================== - - // full sync lines are 0, 1, field_divider_line+1 and field_divider_line+2 - if(line == 0 || line == 1 || line == field_divider_line+1 || line == field_divider_line+2) - { - // wait for the line to complete before signalling - if(final_line == line) return; - crt_->output_sync(128 * crt_cycles_multiplier); - display_output_position_ += 128; - continue; - } - - // line 2 is a left-sync line - if(line == 2) - { - // wait for the line to complete before signalling - if(final_line == line) return; - crt_->output_sync(64 * crt_cycles_multiplier); - crt_->output_blank(64 * crt_cycles_multiplier); - display_output_position_ += 128; - continue; - } - - // line field_divider_line is a right-sync line - if(line == field_divider_line) - { - // wait for the line to complete before signalling - if(final_line == line) return; - crt_->output_sync(9 * crt_cycles_multiplier); - crt_->output_blank(55 * crt_cycles_multiplier); - crt_->output_sync(64 * crt_cycles_multiplier); - display_output_position_ += 128; - continue; - } - - // Priority two: blank lines. - // ========================== - // - // Given that it is not a sync line, this is a blank line if it is less than first_graphics_line, or greater - // than first_graphics_line+255 and less than first_graphics_line+field_divider_line, or greater than - // first_graphics_line+field_divider_line+255 (TODO: or this is Mode 3 or 6 and this should be blank) - if( - line < first_graphics_line || - (line > first_graphics_line+255 && line < first_graphics_line+field_divider_line) || - line > first_graphics_line+field_divider_line+255) - { - if(final_line == line) return; - crt_->output_sync(9 * crt_cycles_multiplier); - crt_->output_blank(119 * crt_cycles_multiplier); - display_output_position_ += 128; - continue; - } - - // Final possibility: this is a pixel line. - // ======================================== - - // determine how far we're going from left to right - unsigned int this_cycle = display_output_position_&127; - unsigned int final_cycle = frame_cycles_&127; - if(final_line > line) - { - final_cycle = 128; - } - - // output format is: - // 9 cycles: sync - // ... to 24 cycles: colour burst - // ... to first_graphics_cycle: blank - // ... for 80 cycles: pixels - // ... until end of line: blank - while(this_cycle < final_cycle) - { - if(this_cycle < 9) - { - if(final_cycle < 9) return; - crt_->output_sync(9 * crt_cycles_multiplier); - display_output_position_ += 9; - this_cycle = 9; - } - - if(this_cycle < 24) - { - if(final_cycle < 24) return; - crt_->output_default_colour_burst((24-9) * crt_cycles_multiplier); - display_output_position_ += 24-9; - this_cycle = 24; - // TODO: phase shouldn't be zero on every line - } - - if(this_cycle < first_graphics_cycle) - { - if(final_cycle < first_graphics_cycle) return; - crt_->output_blank((first_graphics_cycle - 24) * crt_cycles_multiplier); - display_output_position_ += first_graphics_cycle - 24; - this_cycle = first_graphics_cycle; - start_pixel_line(); - } - - if(this_cycle < first_graphics_cycle + 80) - { - unsigned int length_to_output = std::min(final_cycle, (first_graphics_cycle + 80)) - this_cycle; - output_pixels(length_to_output); - display_output_position_ += length_to_output; - this_cycle += length_to_output; - } - - if(this_cycle >= first_graphics_cycle + 80) - { - if(final_cycle < 128) return; - end_pixel_line(); - crt_->output_blank((128 - (first_graphics_cycle + 80)) * crt_cycles_multiplier); - display_output_position_ += 128 - (first_graphics_cycle + 80); - this_cycle = 128; - } - } - } -} - void Machine::clear_all_keys() { memset(key_states_, 0, sizeof(key_states_)); @@ -925,3 +515,13 @@ void Machine::set_key_state(uint16_t key, bool isPressed) key_states_[key >> 4] &= ~(key&0xf); } } + +std::shared_ptr Machine::get_crt() +{ + return video_output_->get_crt(); +} + +std::shared_ptr Machine::get_speaker() +{ + return speaker_; +} diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 980de1e7d..4647d4430 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -15,10 +15,12 @@ #include "../ConfigurationTarget.hpp" #include "../CRTMachine.hpp" #include "../Typer.hpp" + +#include "Interrupts.hpp" #include "Plus3.hpp" #include "Speaker.hpp" #include "Tape.hpp" -#include "Interrupts.hpp" +#include "Video.hpp" #include #include @@ -92,8 +94,8 @@ class Machine: // to satisfy CRTMachine::Machine virtual void setup_output(float aspect_ratio); virtual void close_output(); - virtual std::shared_ptr get_crt() { return crt_; } - virtual std::shared_ptr get_speaker() { return speaker_; } + virtual std::shared_ptr get_crt(); + virtual std::shared_ptr get_speaker(); virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor::run_for_cycles(number_of_cycles); } // to satisfy Tape::Delegate @@ -105,13 +107,9 @@ class Machine: uint16_t *sequence_for_character(Utility::Typer *typer, char character); private: - inline void update_display(); - inline void start_pixel_line(); - inline void end_pixel_line(); - inline void output_pixels(unsigned int number_of_cycles); - inline void update_audio(); + inline void signal_interrupt(Interrupt interrupt); inline void clear_interrupt(Interrupt interrupt); inline void evaluate_interrupts(); @@ -124,36 +122,14 @@ class Machine: // Things affected by registers, explicitly or otherwise. uint8_t interrupt_status_, interrupt_control_; - uint8_t palette_[16]; uint8_t key_states_[14]; ROMSlot active_rom_; bool keyboard_is_active_, basic_is_active_; - uint8_t screen_mode_; - uint16_t screen_mode_base_address_; - uint16_t start_screen_address_; // Counters related to simultaneous subsystems unsigned int frame_cycles_, display_output_position_; unsigned int audio_output_position_, audio_output_position_error_; - struct { - uint16_t forty1bpp[256]; - uint8_t forty2bpp[256]; - uint32_t eighty1bpp[256]; - uint16_t eighty2bpp[256]; - uint8_t eighty4bpp[256]; - } palette_tables_; - - // Display generation. - uint16_t start_line_address_, current_screen_address_; - int current_pixel_line_, current_pixel_column_, current_character_row_; - uint8_t last_pixel_byte_; - bool is_blank_line_; - - // CRT output - uint8_t *current_output_target_, *initial_output_target_; - unsigned int current_output_divider_; - // Tape Tape tape_; bool use_fast_tape_hack_; @@ -164,7 +140,7 @@ class Machine: bool is_holding_shift_; // Outputs - std::shared_ptr crt_; + std::unique_ptr video_output_; std::shared_ptr speaker_; bool speaker_is_enabled_; }; diff --git a/Machines/Electron/Video.cpp b/Machines/Electron/Video.cpp new file mode 100644 index 000000000..df1b87223 --- /dev/null +++ b/Machines/Electron/Video.cpp @@ -0,0 +1,469 @@ +// +// Video.cpp +// Clock Signal +// +// Created by Thomas Harte on 10/12/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "Video.hpp" + +using namespace Electron; + +namespace { + static const unsigned int cycles_per_line = 128; + static const unsigned int lines_per_frame = 625; + static const unsigned int cycles_per_frame = lines_per_frame * cycles_per_line; + static const unsigned int crt_cycles_multiplier = 8; + static const unsigned int crt_cycles_per_line = crt_cycles_multiplier * cycles_per_line; + + static const unsigned int field_divider_line = 312; // i.e. the line, simultaneous with which, the first field's sync ends. So if + // the first line with pixels in field 1 is the 20th in the frame, the first line + // with pixels in field 2 will be 20+field_divider_line + static const unsigned int first_graphics_line = 31; + static const unsigned int first_graphics_cycle = 33; + + static const unsigned int display_end_interrupt_line = 256; + + static const unsigned int real_time_clock_interrupt_1 = 16704; + static const unsigned int real_time_clock_interrupt_2 = 56704; +} + +VideoOutput::VideoOutput(uint8_t *memory) : + ram_(memory), + current_pixel_line_(-1) +{ + memset(palette_, 0xf, sizeof(palette_)); + + crt_.reset(new Outputs::CRT::CRT(crt_cycles_per_line, 8, Outputs::CRT::DisplayType::PAL50, 1)); + crt_->set_rgb_sampling_function( + "vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)" + "{" + "uint texValue = texture(sampler, coordinate).r;" + "texValue >>= 4 - (int(icoordinate.x * 8) & 4);" + "return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));" + "}"); + + // TODO: as implied below, I've introduced a clock's latency into the graphics pipeline somehow. Investigate. + crt_->set_visible_area(crt_->get_rect_for_area(first_graphics_line - 3, 256, (first_graphics_cycle+1) * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f)); +} + +std::shared_ptr VideoOutput::get_crt() +{ + return crt_; +} + +void VideoOutput::start_pixel_line() +{ + current_pixel_line_ = (current_pixel_line_+1)&255; + if(!current_pixel_line_) + { + start_line_address_ = start_screen_address_; + current_character_row_ = 0; + is_blank_line_ = false; + } + else + { + bool mode_has_blank_lines = (screen_mode_ == 6) || (screen_mode_ == 3); + is_blank_line_ = (mode_has_blank_lines && ((current_character_row_ > 7 && current_character_row_ < 10) || (current_pixel_line_ > 249))); + + if(!is_blank_line_) + { + start_line_address_++; + + if(current_character_row_ > 7) + { + start_line_address_ += ((screen_mode_ < 4) ? 80 : 40) * 8 - 8; + current_character_row_ = 0; + } + } + } + current_screen_address_ = start_line_address_; + current_pixel_column_ = 0; + initial_output_target_ = current_output_target_ = nullptr; +} + +void VideoOutput::end_pixel_line() +{ + if(current_output_target_) crt_->output_data((unsigned int)((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_); + current_character_row_++; +} + +void VideoOutput::output_pixels(unsigned int number_of_cycles) +{ + if(!number_of_cycles) return; + + if(is_blank_line_) + { + crt_->output_blank(number_of_cycles * crt_cycles_multiplier); + } + else + { + unsigned int divider = 0; + switch(screen_mode_) + { + case 0: case 3: divider = 2; break; + case 1: case 4: case 6: divider = 4; break; + case 2: case 5: divider = 8; break; + } + + if(!initial_output_target_ || divider != current_output_divider_) + { + if(current_output_target_) crt_->output_data((unsigned int)((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_); + current_output_divider_ = divider; + initial_output_target_ = current_output_target_ = crt_->allocate_write_area(640 / current_output_divider_); + } + +#define get_pixel() \ + if(current_screen_address_&32768)\ + {\ + current_screen_address_ = (screen_mode_base_address_ + current_screen_address_)&32767;\ + }\ + last_pixel_byte_ = ram_[current_screen_address_];\ + current_screen_address_ = current_screen_address_+8 + + switch(screen_mode_) + { + case 0: case 3: + if(initial_output_target_) + { + while(number_of_cycles--) + { + get_pixel(); + *(uint32_t *)current_output_target_ = palette_tables_.eighty1bpp[last_pixel_byte_]; + current_output_target_ += 4; + current_pixel_column_++; + } + } else current_output_target_ += 4*number_of_cycles; + break; + + case 1: + if(initial_output_target_) + { + while(number_of_cycles--) + { + get_pixel(); + *(uint16_t *)current_output_target_ = palette_tables_.eighty2bpp[last_pixel_byte_]; + current_output_target_ += 2; + current_pixel_column_++; + } + } else current_output_target_ += 2*number_of_cycles; + break; + + case 2: + if(initial_output_target_) + { + while(number_of_cycles--) + { + get_pixel(); + *current_output_target_ = palette_tables_.eighty4bpp[last_pixel_byte_]; + current_output_target_ += 1; + current_pixel_column_++; + } + } else current_output_target_ += number_of_cycles; + break; + + case 4: case 6: + if(initial_output_target_) + { + if(current_pixel_column_&1) + { + last_pixel_byte_ <<= 4; + *(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_]; + current_output_target_ += 2; + + number_of_cycles--; + current_pixel_column_++; + } + while(number_of_cycles > 1) + { + get_pixel(); + *(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_]; + current_output_target_ += 2; + + last_pixel_byte_ <<= 4; + *(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_]; + current_output_target_ += 2; + + number_of_cycles -= 2; + current_pixel_column_+=2; + } + if(number_of_cycles) + { + get_pixel(); + *(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_]; + current_output_target_ += 2; + current_pixel_column_++; + } + } else current_output_target_ += 2 * number_of_cycles; + break; + + case 5: + if(initial_output_target_) + { + if(current_pixel_column_&1) + { + last_pixel_byte_ <<= 2; + *current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_]; + current_output_target_ += 1; + + number_of_cycles--; + current_pixel_column_++; + } + while(number_of_cycles > 1) + { + get_pixel(); + *current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_]; + current_output_target_ += 1; + + last_pixel_byte_ <<= 2; + *current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_]; + current_output_target_ += 1; + + number_of_cycles -= 2; + current_pixel_column_+=2; + } + if(number_of_cycles) + { + get_pixel(); + *current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_]; + current_output_target_ += 1; + current_pixel_column_++; + } + } else current_output_target_ += number_of_cycles; + break; + } + +#undef get_pixel + } +} + +void VideoOutput::run_for_cycles(int number_of_cycles) +{ + /* + + Odd field: Even field: + + |--S--| -S-| + |--S--| |--S--| + |-S-B-| = 3 |--S--| = 2.5 + |--B--| |--B--| + |--P--| |--P--| + |--B--| = 312 |--B--| = 312.5 + |-B- + + */ + int final_position = output_position_ + number_of_cycles; + int final_line = final_position >> 7; + while(output_position_ < final_position) + { + int line = output_position_ >> 7; + + // Priority one: sync. + // =================== + + // full sync lines are 0, 1, field_divider_line+1 and field_divider_line+2 + if(line == 0 || line == 1 || line == field_divider_line+1 || line == field_divider_line+2) + { + // wait for the line to complete before signalling + if(final_line == line) return; + crt_->output_sync(128 * crt_cycles_multiplier); + output_position_ += 128; + continue; + } + + // line 2 is a left-sync line + if(line == 2) + { + // wait for the line to complete before signalling + if(final_line == line) return; + crt_->output_sync(64 * crt_cycles_multiplier); + crt_->output_blank(64 * crt_cycles_multiplier); + output_position_ += 128; + continue; + } + + // line field_divider_line is a right-sync line + if(line == field_divider_line) + { + // wait for the line to complete before signalling + if(final_line == line) return; + crt_->output_sync(9 * crt_cycles_multiplier); + crt_->output_blank(55 * crt_cycles_multiplier); + crt_->output_sync(64 * crt_cycles_multiplier); + output_position_ += 128; + continue; + } + + // Priority two: blank lines. + // ========================== + // + // Given that it is not a sync line, this is a blank line if it is less than first_graphics_line, or greater + // than first_graphics_line+255 and less than first_graphics_line+field_divider_line, or greater than + // first_graphics_line+field_divider_line+255 (TODO: or this is Mode 3 or 6 and this should be blank) + if( + line < first_graphics_line || + (line > first_graphics_line+255 && line < first_graphics_line+field_divider_line) || + line > first_graphics_line+field_divider_line+255) + { + if(final_line == line) return; + crt_->output_sync(9 * crt_cycles_multiplier); + crt_->output_blank(119 * crt_cycles_multiplier); + output_position_ += 128; + continue; + } + + // Final possibility: this is a pixel line. + // ======================================== + + // determine how far we're going from left to right + unsigned int this_cycle = output_position_&127; + unsigned int final_cycle = output_position_&127; + if(final_line > line) + { + final_cycle = 128; + } + + // output format is: + // 9 cycles: sync + // ... to 24 cycles: colour burst + // ... to first_graphics_cycle: blank + // ... for 80 cycles: pixels + // ... until end of line: blank + while(this_cycle < final_cycle) + { + if(this_cycle < 9) + { + if(final_cycle < 9) return; + crt_->output_sync(9 * crt_cycles_multiplier); + output_position_ += 9; + this_cycle = 9; + } + + if(this_cycle < 24) + { + if(final_cycle < 24) return; + crt_->output_default_colour_burst((24-9) * crt_cycles_multiplier); + output_position_ += 24-9; + this_cycle = 24; + // TODO: phase shouldn't be zero on every line + } + + if(this_cycle < first_graphics_cycle) + { + if(final_cycle < first_graphics_cycle) return; + crt_->output_blank((first_graphics_cycle - 24) * crt_cycles_multiplier); + output_position_ += first_graphics_cycle - 24; + this_cycle = first_graphics_cycle; + start_pixel_line(); + } + + if(this_cycle < first_graphics_cycle + 80) + { + unsigned int length_to_output = std::min(final_cycle, (first_graphics_cycle + 80)) - this_cycle; + output_pixels(length_to_output); + output_position_ += length_to_output; + this_cycle += length_to_output; + } + + if(this_cycle >= first_graphics_cycle + 80) + { + if(final_cycle < 128) return; + end_pixel_line(); + crt_->output_blank((128 - (first_graphics_cycle + 80)) * crt_cycles_multiplier); + output_position_ += 128 - (first_graphics_cycle + 80); + this_cycle = 128; + } + } + } +} + +void VideoOutput::set_register(int address, uint8_t value) +{ + switch(address & 0xf) + { + case 0x02: + start_screen_address_ = (start_screen_address_ & 0xfe00) | (uint16_t)((value & 0xe0) << 1); + if(!start_screen_address_) start_screen_address_ |= 0x8000; + break; + case 0x03: + start_screen_address_ = (start_screen_address_ & 0x01ff) | (uint16_t)((value & 0x3f) << 9); + if(!start_screen_address_) start_screen_address_ |= 0x8000; + break; + case 0x07: + { + // update screen mode + uint8_t new_screen_mode = (value >> 3)&7; + if(new_screen_mode == 7) new_screen_mode = 4; + if(new_screen_mode != screen_mode_) + { + screen_mode_ = new_screen_mode; + switch(screen_mode_) + { + case 0: case 1: case 2: screen_mode_base_address_ = 0x3000; break; + case 3: screen_mode_base_address_ = 0x4000; break; + case 4: case 5: screen_mode_base_address_ = 0x5800; break; + case 6: screen_mode_base_address_ = 0x6000; break; + } + } + } + break; + case 0x08: case 0x09: case 0x0a: case 0x0b: + case 0x0c: case 0x0d: case 0x0e: case 0x0f: + { + static const int registers[4][4] = { + {10, 8, 2, 0}, + {14, 12, 6, 4}, + {15, 13, 7, 5}, + {11, 9, 3, 1}, + }; + const int index = (address >> 1)&3; + const uint8_t colour = ~value; + if(address&1) + { + palette_[registers[index][0]] = (palette_[registers[index][0]]&3) | ((colour >> 1)&4); + palette_[registers[index][1]] = (palette_[registers[index][1]]&3) | ((colour >> 0)&4); + palette_[registers[index][2]] = (palette_[registers[index][2]]&3) | ((colour << 1)&4); + palette_[registers[index][3]] = (palette_[registers[index][3]]&3) | ((colour << 2)&4); + + palette_[registers[index][2]] = (palette_[registers[index][2]]&5) | ((colour >> 4)&2); + palette_[registers[index][3]] = (palette_[registers[index][3]]&5) | ((colour >> 3)&2); + } + else + { + palette_[registers[index][0]] = (palette_[registers[index][0]]&6) | ((colour >> 7)&1); + palette_[registers[index][1]] = (palette_[registers[index][1]]&6) | ((colour >> 6)&1); + palette_[registers[index][2]] = (palette_[registers[index][2]]&6) | ((colour >> 5)&1); + palette_[registers[index][3]] = (palette_[registers[index][3]]&6) | ((colour >> 4)&1); + + palette_[registers[index][0]] = (palette_[registers[index][0]]&5) | ((colour >> 2)&2); + palette_[registers[index][1]] = (palette_[registers[index][1]]&5) | ((colour >> 1)&2); + } + + // regenerate all palette tables for now +#define pack(a, b) (uint8_t)((a << 4) | (b)) + for(int byte = 0; byte < 256; byte++) + { + uint8_t *target = (uint8_t *)&palette_tables_.forty1bpp[byte]; + target[0] = pack(palette_[(byte&0x80) >> 4], palette_[(byte&0x40) >> 3]); + target[1] = pack(palette_[(byte&0x20) >> 2], palette_[(byte&0x10) >> 1]); + + target = (uint8_t *)&palette_tables_.eighty2bpp[byte]; + target[0] = pack(palette_[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], palette_[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]); + target[1] = pack(palette_[((byte&0x20) >> 2) | ((byte&0x02) >> 0)], palette_[((byte&0x10) >> 1) | ((byte&0x01) << 1)]); + + target = (uint8_t *)&palette_tables_.eighty1bpp[byte]; + target[0] = pack(palette_[(byte&0x80) >> 4], palette_[(byte&0x40) >> 3]); + target[1] = pack(palette_[(byte&0x20) >> 2], palette_[(byte&0x10) >> 1]); + target[2] = pack(palette_[(byte&0x08) >> 0], palette_[(byte&0x04) << 1]); + target[3] = pack(palette_[(byte&0x02) << 2], palette_[(byte&0x01) << 3]); + + palette_tables_.forty2bpp[byte] = pack(palette_[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], palette_[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]); + palette_tables_.eighty4bpp[byte] = pack( palette_[((byte&0x80) >> 4) | ((byte&0x20) >> 3) | ((byte&0x08) >> 2) | ((byte&0x02) >> 1)], + palette_[((byte&0x40) >> 3) | ((byte&0x10) >> 2) | ((byte&0x04) >> 1) | ((byte&0x01) >> 0)]); + } +#undef pack + } + break; + } +} diff --git a/Machines/Electron/Video.hpp b/Machines/Electron/Video.hpp new file mode 100644 index 000000000..49474d311 --- /dev/null +++ b/Machines/Electron/Video.hpp @@ -0,0 +1,64 @@ +// +// Video.hpp +// Clock Signal +// +// Created by Thomas Harte on 10/12/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef Machines_Electron_Video_hpp +#define Machines_Electron_Video_hpp + +#include "../../Outputs/CRT/CRT.hpp" +#include "Interrupts.hpp" + +namespace Electron { + +class VideoOutput { + public: + VideoOutput(uint8_t *memory); + std::shared_ptr get_crt(); + void run_for_cycles(int number_of_cycles); + + int get_cycles_until_next_interrupt(); + Interrupt get_next_interrupt(); + + void set_register(int address, uint8_t value); + + private: + inline void start_pixel_line(); + inline void end_pixel_line(); + inline void output_pixels(unsigned int number_of_cycles); + + int output_position_; + + uint8_t palette_[16]; + uint8_t screen_mode_; + uint16_t screen_mode_base_address_; + uint16_t start_screen_address_; + + uint8_t *ram_; + struct { + uint16_t forty1bpp[256]; + uint8_t forty2bpp[256]; + uint32_t eighty1bpp[256]; + uint16_t eighty2bpp[256]; + uint8_t eighty4bpp[256]; + } palette_tables_; + + // Display generation. + uint16_t start_line_address_, current_screen_address_; + int current_pixel_line_, current_pixel_column_, current_character_row_; + uint8_t last_pixel_byte_; + bool is_blank_line_; + + // CRT output + uint8_t *current_output_target_, *initial_output_target_; + unsigned int current_output_divider_; + + std::shared_ptr crt_; +}; + +} + +#endif /* Video_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 86fbb6bba..ac97ad808 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -62,6 +62,7 @@ 4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; }; 4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; }; 4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */; }; + 4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7913CA1DFCD80E00175A82 /* Video.cpp */; }; 4B8805F01DCFC99C003085B1 /* Acorn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805EE1DCFC99C003085B1 /* Acorn.cpp */; }; 4B8805F41DCFD22A003085B1 /* Commodore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F21DCFD22A003085B1 /* Commodore.cpp */; }; 4B8805F71DCFF6C9003085B1 /* Commodore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F51DCFF6C9003085B1 /* Commodore.cpp */; }; @@ -522,6 +523,8 @@ 4B69FB451C4D950F00B5F0AA /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; 4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DiskController.cpp; sourceTree = ""; }; 4B6C73BC1D387AE500AFCFCA /* DiskController.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskController.hpp; sourceTree = ""; }; + 4B7913CA1DFCD80E00175A82 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Electron/Video.cpp; sourceTree = ""; }; + 4B7913CB1DFCD80E00175A82 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Video.hpp; path = Electron/Video.hpp; sourceTree = ""; }; 4B8805EE1DCFC99C003085B1 /* Acorn.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Acorn.cpp; path = Parsers/Acorn.cpp; sourceTree = ""; }; 4B8805EF1DCFC99C003085B1 /* Acorn.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Acorn.hpp; path = Parsers/Acorn.hpp; sourceTree = ""; }; 4B8805F21DCFD22A003085B1 /* Commodore.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Commodore.cpp; path = Parsers/Commodore.cpp; sourceTree = ""; }; @@ -1070,11 +1073,13 @@ 4BEA52611DF339D7007E74F2 /* Speaker.cpp */, 4BEA525D1DF33323007E74F2 /* Tape.cpp */, 4BC8A62B1DCE60E000DAC693 /* Typer.cpp */, + 4B7913CA1DFCD80E00175A82 /* Video.cpp */, 4B2E2D9C1C3A070400138695 /* Electron.hpp */, 4BEA52601DF3343A007E74F2 /* Interrupts.hpp */, 4B30512F1D98ACC600B4FED8 /* Plus3.hpp */, 4BEA52621DF339D7007E74F2 /* Speaker.hpp */, 4BEA525F1DF333D8007E74F2 /* Tape.hpp */, + 4B7913CB1DFCD80E00175A82 /* Video.hpp */, ); name = Electron; sourceTree = ""; @@ -2353,6 +2358,7 @@ 4B2A332A1DB8544D002876E3 /* MemoryFuzzer.cpp in Sources */, 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */, 4B2A332F1DB86869002876E3 /* OricOptionsPanel.swift in Sources */, + 4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */, 4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */, 4BF829661D8F732B001BAE39 /* Disk.cpp in Sources */, 4BEA52631DF339D7007E74F2 /* Speaker.cpp in Sources */,