diff --git a/Analyser/Dynamic/MultiMachine/Implementation/MultiCRTMachine.cpp b/Analyser/Dynamic/MultiMachine/Implementation/MultiCRTMachine.cpp index 2f075a1d0..53c2ae3b1 100644 --- a/Analyser/Dynamic/MultiMachine/Implementation/MultiCRTMachine.cpp +++ b/Analyser/Dynamic/MultiMachine/Implementation/MultiCRTMachine.cpp @@ -48,27 +48,16 @@ void MultiCRTMachine::perform_parallel(const std::function &function) { std::lock_guard machines_lock(machines_mutex_); for(const auto &machine: machines_) { - CRTMachine::Machine *crt_machine = machine->crt_machine(); + CRTMachine::Machine *const crt_machine = machine->crt_machine(); if(crt_machine) function(crt_machine); } } -void MultiCRTMachine::setup_output(float aspect_ratio) { - perform_serial([=](::CRTMachine::Machine *machine) { - machine->setup_output(aspect_ratio); - }); -} +void MultiCRTMachine::set_scan_target(Outputs::Display::ScanTarget *scan_target) { + scan_target_ = scan_target; -void MultiCRTMachine::close_output() { - perform_serial([=](::CRTMachine::Machine *machine) { - machine->close_output(); - }); -} - -Outputs::CRT::CRT *MultiCRTMachine::get_crt() { - std::lock_guard machines_lock(machines_mutex_); - CRTMachine::Machine *crt_machine = machines_.front()->crt_machine(); - return crt_machine ? crt_machine->get_crt() : nullptr; + CRTMachine::Machine *const crt_machine = machines_.front()->crt_machine(); + if(crt_machine) crt_machine->set_scan_target(scan_target); } Outputs::Speaker::Speaker *MultiCRTMachine::get_speaker() { @@ -84,6 +73,14 @@ void MultiCRTMachine::run_for(Time::Seconds duration) { } void MultiCRTMachine::did_change_machine_order() { + if(scan_target_) scan_target_->will_change_owner(); + + perform_serial([=](::CRTMachine::Machine *machine) { + machine->set_scan_target(nullptr); + }); + CRTMachine::Machine *const crt_machine = machines_.front()->crt_machine(); + if(crt_machine) crt_machine->set_scan_target(scan_target_); + if(speaker_) { speaker_->set_new_front_machine(machines_.front().get()); } diff --git a/Analyser/Dynamic/MultiMachine/Implementation/MultiCRTMachine.hpp b/Analyser/Dynamic/MultiMachine/Implementation/MultiCRTMachine.hpp index 93906d9ab..bf6b3d083 100644 --- a/Analyser/Dynamic/MultiMachine/Implementation/MultiCRTMachine.hpp +++ b/Analyser/Dynamic/MultiMachine/Implementation/MultiCRTMachine.hpp @@ -53,9 +53,7 @@ class MultiCRTMachine: public CRTMachine::Machine { } // Below is the standard CRTMachine::Machine interface; see there for documentation. - void setup_output(float aspect_ratio) override; - void close_output() override; - Outputs::CRT::CRT *get_crt() override; + void set_scan_target(Outputs::Display::ScanTarget *scan_target) override; Outputs::Speaker::Speaker *get_speaker() override; void run_for(Time::Seconds duration) override; @@ -66,6 +64,7 @@ class MultiCRTMachine: public CRTMachine::Machine { std::vector queues_; MultiSpeaker *speaker_ = nullptr; Delegate *delegate_ = nullptr; + Outputs::Display::ScanTarget *scan_target_ = nullptr; /*! Performs a parallel for operation across all machines, performing the supplied diff --git a/Analyser/Dynamic/MultiMachine/Implementation/MultiKeyboardMachine.cpp b/Analyser/Dynamic/MultiMachine/Implementation/MultiKeyboardMachine.cpp index f85035be5..560d18be3 100644 --- a/Analyser/Dynamic/MultiMachine/Implementation/MultiKeyboardMachine.cpp +++ b/Analyser/Dynamic/MultiMachine/Implementation/MultiKeyboardMachine.cpp @@ -11,7 +11,7 @@ using namespace Analyser::Dynamic; MultiKeyboardMachine::MultiKeyboardMachine(const std::vector> &machines) : - keyboard_(machines_) { + keyboard_(machines_) { for(const auto &machine: machines) { KeyboardMachine::Machine *keyboard_machine = machine->keyboard_machine(); if(keyboard_machine) machines_.push_back(keyboard_machine); diff --git a/Analyser/Static/Commodore/StaticAnalyser.cpp b/Analyser/Static/Commodore/StaticAnalyser.cpp index 7a3bd185f..c932b8d3a 100644 --- a/Analyser/Static/Commodore/StaticAnalyser.cpp +++ b/Analyser/Static/Commodore/StaticAnalyser.cpp @@ -80,7 +80,7 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media target->memory_model = Target::MemoryModel::Unexpanded; std::ostringstream string_stream; string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ","; - if(files.front().is_basic()) { + if(files.front().is_basic()) { string_stream << "0"; } else { string_stream << "1"; diff --git a/Analyser/Static/Disassembler/Z80.cpp b/Analyser/Static/Disassembler/Z80.cpp index f2be54a22..bcdf8b410 100644 --- a/Analyser/Static/Disassembler/Z80.cpp +++ b/Analyser/Static/Disassembler/Z80.cpp @@ -63,9 +63,9 @@ class Accessor { #define z(v) (v & 7) Instruction::Condition condition_table[] = { - Instruction::Condition::NZ, Instruction::Condition::Z, - Instruction::Condition::NC, Instruction::Condition::C, - Instruction::Condition::PO, Instruction::Condition::PE, + Instruction::Condition::NZ, Instruction::Condition::Z, + Instruction::Condition::NC, Instruction::Condition::C, + Instruction::Condition::PO, Instruction::Condition::PE, Instruction::Condition::P, Instruction::Condition::M }; diff --git a/ClockReceiver/ClockDeferrer.hpp b/ClockReceiver/ClockDeferrer.hpp index 838739e4b..cc41cbdd0 100644 --- a/ClockReceiver/ClockDeferrer.hpp +++ b/ClockReceiver/ClockDeferrer.hpp @@ -9,6 +9,7 @@ #ifndef ClockDeferrer_h #define ClockDeferrer_h +#include #include /*! diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index c030cb4df..6adf8c675 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -189,7 +189,6 @@ void WD1770::posit_event(int new_event_type) { interesting_event_mask_ &= ~new_event_type; } - Status new_status; BEGIN_SECTION() // Wait for a new command, branch to the appropriate handler. @@ -211,7 +210,7 @@ void WD1770::posit_event(int new_event_type) { status.interrupt_request = false; }); - LOG("Starting " << std::hex << command_ << std::endl); + LOG("Starting " << PADHEX(2) << int(command_)); if(!(command_ & 0x80)) goto begin_type_1; if(!(command_ & 0x40)) goto begin_type_2; @@ -329,7 +328,7 @@ void WD1770::posit_event(int new_event_type) { } if(header_[0] == track_) { - LOG("Reached track " << std::dec << track_); + LOG("Reached track " << std::dec << int(track_)); update_status([] (Status &status) { status.crc_error = false; }); @@ -398,18 +397,18 @@ void WD1770::posit_event(int new_event_type) { READ_ID(); if(index_hole_count_ == 5) { - LOG("Failed to find sector " << std::dec << sector_); + LOG("Failed to find sector " << std::dec << int(sector_)); update_status([] (Status &status) { status.record_not_found = true; }); goto wait_for_command; } if(distance_into_section_ == 7) { - LOG("Considering " << std::dec << header_[0] << "/" << header_[2]); + LOG("Considering " << std::dec << int(header_[0]) << "/" << int(header_[2])); set_data_mode(DataMode::Scanning); if( header_[0] == track_ && header_[2] == sector_ && (has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1])) { - LOG("Found " << std::dec << header_[0] << "/" << header_[2]); + LOG("Found " << std::dec << int(header_[0]) << "/" << int(header_[2])); if(get_crc_generator().get_value()) { LOG("CRC error; back to searching"); update_status([] (Status &status) { @@ -478,7 +477,7 @@ void WD1770::posit_event(int new_event_type) { sector_++; goto test_type2_write_protection; } - LOG("Finished reading sector " << std::dec << sector_); + LOG("Finished reading sector " << std::dec << int(sector_)); goto wait_for_command; } goto type2_check_crc; @@ -560,7 +559,7 @@ void WD1770::posit_event(int new_event_type) { sector_++; goto test_type2_write_protection; } - LOG("Wrote sector " << std::dec << sector_); + LOG("Wrote sector " << std::dec << int(sector_)); goto wait_for_command; diff --git a/Components/6560/6560.hpp b/Components/6560/6560.hpp index 23bb6e4d1..ac4dbd099 100644 --- a/Components/6560/6560.hpp +++ b/Components/6560/6560.hpp @@ -34,7 +34,7 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource { private: Concurrency::DeferringAsyncTaskQueue &audio_queue_; - unsigned int counters_[4] = {2, 1, 0, 0}; // create a slight phase offset for the three channels + unsigned int counters_[4] = {2, 1, 0, 0}; // create a slight phase offset for the three channels unsigned int shift_registers_[4] = {0, 0, 0, 0}; uint8_t control_registers_[4] = {0, 0, 0, 0}; int16_t volume_ = 0; @@ -64,23 +64,12 @@ template class MOS6560 { public: MOS6560(BusHandler &bus_handler) : bus_handler_(bus_handler), - crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::DisplayType::NTSC60, 2)), + crt_(65*4, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Luminance8Phase8), audio_generator_(audio_queue_), speaker_(audio_generator_) { - crt_->set_svideo_sampling_function( - "vec2 svideo_sample(usampler2D texID, vec2 coordinate, float phase, float amplitude)" - "{" - "vec2 yc = texture(texID, coordinate).rg / vec2(255.0);" - - "float phaseOffset = 6.283185308 * 2.0 * yc.y;" - "float chroma = step(yc.y, 0.75) * cos(phase + phaseOffset);" - - "return vec2(yc.x, chroma);" - "}"); - // default to s-video output - crt_->set_video_signal(Outputs::CRT::VideoSignal::SVideo); + crt_.set_display_type(Outputs::Display::DisplayType::SVideo); // default to NTSC set_output_mode(OutputMode::NTSC); @@ -94,7 +83,8 @@ template class MOS6560 { speaker_.set_input_rate(static_cast(clock_rate / 4.0)); } - Outputs::CRT::CRT *get_crt() { return crt_.get(); } + void set_scan_target(Outputs::Display::ScanTarget *scan_target) { crt_.set_scan_target(scan_target); } + void set_display_type(Outputs::Display::DisplayType display_type) { crt_.set_display_type(display_type); } Outputs::Speaker::Speaker *get_speaker() { return &speaker_; } void set_high_frequency_cutoff(float cutoff) { @@ -117,12 +107,12 @@ template class MOS6560 { // Chrominances are encoded such that 0-128 is a complete revolution of phase; // anything above 191 disables the colour subcarrier. Phase is relative to the - // colour burst, so 0 is green. + // colour burst, so 0 is green (NTSC) or blue/violet (PAL). const uint8_t pal_chrominances[16] = { - 255, 255, 37, 101, - 19, 86, 123, 59, - 46, 53, 37, 101, - 19, 86, 123, 59, + 255, 255, 90, 20, + 96, 42, 8, 72, + 84, 90, 90, 20, + 96, 42, 8, 72, }; const uint8_t ntsc_chrominances[16] = { 255, 255, 121, 57, @@ -131,12 +121,12 @@ template class MOS6560 { 103, 42, 80, 16, }; const uint8_t *chrominances; - Outputs::CRT::DisplayType display_type; + Outputs::Display::Type display_type; switch(output_mode) { default: chrominances = pal_chrominances; - display_type = Outputs::CRT::DisplayType::PAL50; + display_type = Outputs::Display::Type::PAL50; timing_.cycles_per_line = 71; timing_.line_counter_increment_offset = 4; timing_.final_line_increment_position = timing_.cycles_per_line - timing_.line_counter_increment_offset; @@ -146,7 +136,7 @@ template class MOS6560 { case OutputMode::NTSC: chrominances = ntsc_chrominances; - display_type = Outputs::CRT::DisplayType::NTSC60; + display_type = Outputs::Display::Type::NTSC60; timing_.cycles_per_line = 65; timing_.line_counter_increment_offset = 40; timing_.final_line_increment_position = 58; @@ -155,14 +145,14 @@ template class MOS6560 { break; } - crt_->set_new_display_type(static_cast(timing_.cycles_per_line*4), display_type); + crt_.set_new_display_type(timing_.cycles_per_line*4, display_type); switch(output_mode) { case OutputMode::PAL: - crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.07f, 0.9f, 0.9f)); + crt_.set_visible_area(Outputs::Display::Rect(0.1f, 0.07f, 0.9f, 0.9f)); break; case OutputMode::NTSC: - crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f)); + crt_.set_visible_area(Outputs::Display::Rect(0.05f, 0.05f, 0.9f, 0.9f)); break; } @@ -284,17 +274,17 @@ template class MOS6560 { // update the CRT if(this_state_ != output_state_) { switch(output_state_) { - case State::Sync: crt_->output_sync(cycles_in_state_ * 4); break; - case State::ColourBurst: crt_->output_colour_burst(cycles_in_state_ * 4, (is_odd_frame_ || is_odd_line_) ? 128 : 0); break; - case State::Border: output_border(cycles_in_state_ * 4); break; - case State::Pixels: crt_->output_data(cycles_in_state_ * 4); break; + case State::Sync: crt_.output_sync(cycles_in_state_ * 4); break; + case State::ColourBurst: crt_.output_colour_burst(cycles_in_state_ * 4, (is_odd_frame_ || is_odd_line_) ? 128 : 0); break; + case State::Border: output_border(cycles_in_state_ * 4); break; + case State::Pixels: crt_.output_data(cycles_in_state_ * 4); break; } output_state_ = this_state_; cycles_in_state_ = 0; pixel_pointer = nullptr; if(output_state_ == State::Pixels) { - pixel_pointer = reinterpret_cast(crt_->allocate_write_area(260)); + pixel_pointer = reinterpret_cast(crt_.begin_data(260)); } } cycles_in_state_++; @@ -438,7 +428,7 @@ template class MOS6560 { private: BusHandler &bus_handler_; - std::unique_ptr crt_; + Outputs::CRT::CRT crt_; Concurrency::DeferringAsyncTaskQueue audio_queue_; AudioGenerator audio_generator_; @@ -465,7 +455,7 @@ template class MOS6560 { enum State { Sync, ColourBurst, Border, Pixels } this_state_, output_state_; - unsigned int cycles_in_state_; + int cycles_in_state_; // counters that cover an entire field int horizontal_counter_ = 0, vertical_counter_ = 0; @@ -511,10 +501,10 @@ template class MOS6560 { uint16_t colours_[16]; uint16_t *pixel_pointer; - void output_border(unsigned int number_of_cycles) { - uint16_t *colour_pointer = reinterpret_cast(crt_->allocate_write_area(1)); + void output_border(int number_of_cycles) { + uint16_t *colour_pointer = reinterpret_cast(crt_.begin_data(1)); if(colour_pointer) *colour_pointer = registers_.borderColour; - crt_->output_level(number_of_cycles); + crt_.output_level(number_of_cycles); } struct { diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 26f5604cd..6818de294 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -50,7 +50,9 @@ struct ReverseTable { Base::Base(Personality p) : personality_(p), - crt_(new Outputs::CRT::CRT(CRTCyclesPerLine, CRTCyclesDivider, Outputs::CRT::DisplayType::NTSC60, 4)) { + crt_(CRTCyclesPerLine, CRTCyclesDivider, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Red8Green8Blue8) { + // Unimaginatively, this class just passes RGB through to the shader. Investigation is needed + // into whether there's a more natural form. It feels unlikely given the diversity of chips modelled. switch(p) { case TI::TMS::TMS9918A: @@ -83,22 +85,15 @@ Base::Base(Personality p) : } TMS9918::TMS9918(Personality p): - Base(p) { - // Unimaginatively, this class just passes RGB through to the shader. Investigation is needed - // into whether there's a more natural form. - crt_->set_rgb_sampling_function( - "vec3 rgb_sample(usampler2D sampler, vec2 coordinate)" - "{" - "return texture(sampler, coordinate).rgb / vec3(255.0);" - "}"); - crt_->set_video_signal(Outputs::CRT::VideoSignal::RGB); - crt_->set_visible_area(Outputs::CRT::Rect(0.055f, 0.025f, 0.9f, 0.9f)); + Base(p) { + crt_.set_display_type(Outputs::Display::DisplayType::RGB); + crt_.set_visible_area(Outputs::Display::Rect(0.07f, 0.0375f, 0.875f, 0.875f)); // The TMS remains in-phase with the NTSC colour clock; this is an empirical measurement // intended to produce the correct relationship between the hard edges between pixels and // the colour clock. It was eyeballed rather than derived from any knowledge of the TMS // colour burst generator because I've yet to find any. - crt_->set_immediate_default_phase(0.85f); + crt_.set_immediate_default_phase(0.85f); } void TMS9918::set_tv_standard(TVStandard standard) { @@ -107,18 +102,22 @@ void TMS9918::set_tv_standard(TVStandard standard) { case TVStandard::PAL: mode_timing_.total_lines = 313; mode_timing_.first_vsync_line = 253; - crt_->set_new_display_type(CRTCyclesPerLine, Outputs::CRT::DisplayType::PAL50); + crt_.set_new_display_type(CRTCyclesPerLine, Outputs::Display::Type::PAL50); break; default: mode_timing_.total_lines = 262; mode_timing_.first_vsync_line = 227; - crt_->set_new_display_type(CRTCyclesPerLine, Outputs::CRT::DisplayType::NTSC60); + crt_.set_new_display_type(CRTCyclesPerLine, Outputs::Display::Type::NTSC60); break; } } -Outputs::CRT::CRT *TMS9918::get_crt() { - return crt_.get(); +void TMS9918::set_scan_target(Outputs::Display::ScanTarget *scan_target) { + crt_.set_scan_target(scan_target); +} + +void TMS9918::set_display_type(Outputs::Display::DisplayType display_type) { + crt_.set_display_type(display_type); } void Base::LineBuffer::reset_sprite_collection() { @@ -367,7 +366,7 @@ void TMS9918::run_for(const HalfCycles cycles) { if(read_pointer_.row >= mode_timing_.first_vsync_line && read_pointer_.row < mode_timing_.first_vsync_line+4) { // Vertical sync. if(end_column == 342) { - crt_->output_sync(342 * 4); + crt_.output_sync(342 * 4); } } else { // Right border. @@ -377,11 +376,11 @@ void TMS9918::run_for(const HalfCycles cycles) { // and 58+15 = 73. So output the lot when the // cursor passes 73. if(read_pointer_.column < 73 && end_column >= 73) { - crt_->output_blank(8*4); - crt_->output_sync(26*4); - crt_->output_blank(2*4); - crt_->output_default_colour_burst(14*4); - crt_->output_blank(8*4); + crt_.output_blank(8*4); + crt_.output_sync(26*4); + crt_.output_blank(2*4); + crt_.output_default_colour_burst(14*4); + crt_.output_blank(8*4); } // Border colour for the rest of the line. @@ -393,11 +392,11 @@ void TMS9918::run_for(const HalfCycles cycles) { // Blanking region. if(read_pointer_.column < 73 && end_column >= 73) { - crt_->output_blank(8*4); - crt_->output_sync(26*4); - crt_->output_blank(2*4); - crt_->output_default_colour_burst(14*4); - crt_->output_blank(8*4); + crt_.output_blank(8*4); + crt_.output_sync(26*4); + crt_.output_blank(2*4); + crt_.output_default_colour_burst(14*4); + crt_.output_blank(8*4); } // Left border. @@ -410,7 +409,7 @@ void TMS9918::run_for(const HalfCycles cycles) { if(!asked_for_write_area_) { asked_for_write_area_ = true; pixel_origin_ = pixel_target_ = reinterpret_cast( - crt_->allocate_write_area(static_cast(line_buffer.next_border_column - line_buffer.first_pixel_output_column)) + crt_.begin_data(size_t(line_buffer.next_border_column - line_buffer.first_pixel_output_column)) ); } @@ -427,8 +426,8 @@ void TMS9918::run_for(const HalfCycles cycles) { } if(end == line_buffer.next_border_column) { - const unsigned int length = static_cast(line_buffer.next_border_column - line_buffer.first_pixel_output_column); - crt_->output_data(length * 4, length); + const int length = line_buffer.next_border_column - line_buffer.first_pixel_output_column; + crt_.output_data(length * 4, size_t(length)); pixel_origin_ = pixel_target_ = nullptr; asked_for_write_area_ = false; } @@ -470,16 +469,26 @@ void Base::output_border(int cycles, uint32_t cram_dot) { palette[background_colour_]; if(cram_dot) { - uint32_t *const pixel_target = reinterpret_cast(crt_->allocate_write_area(1)); - *pixel_target = border_colour | cram_dot; - crt_->output_level(4); + uint32_t *const pixel_target = reinterpret_cast(crt_.begin_data(1)); + if(pixel_target) { + *pixel_target = border_colour | cram_dot; + } + crt_.output_level(4); cycles -= 4; } if(cycles) { - uint32_t *const pixel_target = reinterpret_cast(crt_->allocate_write_area(1)); - *pixel_target = border_colour; - crt_->output_level(static_cast(cycles)); + // If the border colour is 0, that can be communicated + // more efficiently as an explicit blank. + if(border_colour) { + uint32_t *const pixel_target = reinterpret_cast(crt_.begin_data(1)); + if(pixel_target) { + *pixel_target = border_colour; + } + crt_.output_level(cycles); + } else { + crt_.output_blank(cycles); + } } } diff --git a/Components/9918/9918.hpp b/Components/9918/9918.hpp index e607d9947..30d1b5b1d 100644 --- a/Components/9918/9918.hpp +++ b/Components/9918/9918.hpp @@ -41,8 +41,11 @@ class TMS9918: public Base { /*! Sets the TV standard for this TMS, if that is hard-coded in hardware. */ void set_tv_standard(TVStandard standard); - /*! Provides the CRT this TMS is connected to. */ - Outputs::CRT::CRT *get_crt(); + /*! Sets the scan target this TMS will post content to. */ + void set_scan_target(Outputs::Display::ScanTarget *); + + /*! Sets the type of display the CRT will request. */ + void set_display_type(Outputs::Display::DisplayType); /*! Runs the VCP for the number of cycles indicate; it is an implicit assumption of the code diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index ac712ad1b..9aba6fcec 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -78,8 +78,8 @@ class Base { Base(Personality p); - Personality personality_; - std::unique_ptr crt_; + const Personality personality_; + Outputs::CRT::CRT crt_; TVStandard tv_standard_ = TVStandard::NTSC; // Holds the contents of this VDP's connected DRAM. @@ -391,7 +391,7 @@ class Base { /* Fetching routines follow below; they obey the following rules: - 1) input is a start position and an end position; they should perform the proper + 1) input is a start position and an end position; they should perform the proper operations for the period: start <= time < end. 2) times are measured relative to a 172-cycles-per-line clock (so: they directly count access windows on the TMS and Master System). @@ -411,7 +411,7 @@ class Base { Provided for the benefit of the methods below: - * the function external_slot(), which will perform any pending VRAM read/write. + * the function external_slot(), which will perform any pending VRAM read/write. * the macros slot(n) and external_slot(n) which can be used to schedule those things inside a switch(start)-based implementation. @@ -752,14 +752,14 @@ class Base { fetch_tile_name(column+1, row_info) \ sprite_y_read(location+5, sprite); \ slot(location+6): \ - slot(location+7): \ + slot(location+7): \ slot(location+8): \ fetch_tile(column+1) \ fetch_tile_name(column+2, row_info) \ sprite_y_read(location+9, sprite+2); \ slot(location+10): \ slot(location+11): \ - slot(location+12): \ + slot(location+12): \ fetch_tile(column+2) \ fetch_tile_name(column+3, row_info) \ sprite_y_read(location+13, sprite+4); \ diff --git a/Components/AY38910/AY38910.cpp b/Components/AY38910/AY38910.cpp index 56f9e10e9..d9e0ae4af 100644 --- a/Components/AY38910/AY38910.cpp +++ b/Components/AY38910/AY38910.cpp @@ -253,7 +253,7 @@ uint8_t AY38910::get_data_output() { const uint8_t mask = port_handler_ ? port_handler_->get_port_input(selected_register_ == 15) : 0xff; switch(selected_register_) { - default: break; + default: break; case 14: return mask & ((registers_[0x7] & 0x40) ? registers_[14] : 0xff); case 15: return mask & ((registers_[0x7] & 0x80) ? registers_[15] : 0xff); } @@ -280,7 +280,7 @@ void AY38910::update_bus() { // Assume no output, unless this turns out to be a read. data_output_ = 0xff; switch(control_state_) { - default: break; + default: break; case LatchAddress: select_register(data_input_); break; case Write: set_register_value(data_input_); break; case Read: data_output_ = get_register_value(); break; diff --git a/Components/DiskII/DiskII.hpp b/Components/DiskII/DiskII.hpp index dfd5b364a..6df8edf10 100644 --- a/Components/DiskII/DiskII.hpp +++ b/Components/DiskII/DiskII.hpp @@ -54,7 +54,7 @@ class DiskII: void run_for(const Cycles cycles); /*! - Supplies the image of the state machine (i.e. P6) ROM, + Supplies the image of the state machine (i.e. P6) ROM, which dictates how the Disk II will respond to input. To reduce processing costs, some assumptions are made by diff --git a/Configurable/StandardOptions.cpp b/Configurable/StandardOptions.cpp index 29036bca7..2aae5b393 100644 --- a/Configurable/StandardOptions.cpp +++ b/Configurable/StandardOptions.cpp @@ -33,11 +33,12 @@ bool get_bool(const Configurable::SelectionSet &selections_by_option, const std: std::vector> Configurable::standard_options(Configurable::StandardOptions mask) { std::vector> options; if(mask & QuickLoadTape) options.emplace_back(new Configurable::BooleanOption("Load Tapes Quickly", "quickload")); - if(mask & (DisplayRGB | DisplayComposite | DisplaySVideo)) { + if(mask & (DisplayRGB | DisplayCompositeColour | DisplayCompositeMonochrome | DisplaySVideo)) { std::vector display_options; - if(mask & DisplayComposite) display_options.emplace_back("composite"); - if(mask & DisplaySVideo) display_options.emplace_back("svideo"); - if(mask & DisplayRGB) display_options.emplace_back("rgb"); + if(mask & DisplayCompositeColour) display_options.emplace_back("composite"); + if(mask & DisplayCompositeMonochrome) display_options.emplace_back("composite-mono"); + if(mask & DisplaySVideo) display_options.emplace_back("svideo"); + if(mask & DisplayRGB) display_options.emplace_back("rgb"); options.emplace_back(new Configurable::ListOption("Display", "display", display_options)); } if(mask & AutomaticTapeMotorControl) options.emplace_back(new Configurable::BooleanOption("Automatic Tape Motor Control", "autotapemotor")); @@ -57,9 +58,10 @@ void Configurable::append_display_selection(Configurable::SelectionSet &selectio std::string string_selection; switch(selection) { default: - case Display::RGB: string_selection = "rgb"; break; - case Display::SVideo: string_selection = "svideo"; break; - case Display::Composite: string_selection = "composite"; break; + case Display::RGB: string_selection = "rgb"; break; + case Display::SVideo: string_selection = "svideo"; break; + case Display::CompositeMonochrome: string_selection = "composite-mono"; break; + case Display::CompositeColour: string_selection = "composite"; break; } selection_set["display"] = std::unique_ptr(new Configurable::ListSelection(string_selection)); } @@ -85,7 +87,11 @@ bool Configurable::get_display(const Configurable::SelectionSet &selections_by_o return true; } if(display->value == "composite") { - result = Configurable::Display::Composite; + result = Configurable::Display::CompositeColour; + return true; + } + if(display->value == "composite-mono") { + result = Configurable::Display::CompositeMonochrome; return true; } } diff --git a/Configurable/StandardOptions.hpp b/Configurable/StandardOptions.hpp index 8aac4a443..a6a4a915d 100644 --- a/Configurable/StandardOptions.hpp +++ b/Configurable/StandardOptions.hpp @@ -16,15 +16,17 @@ namespace Configurable { enum StandardOptions { DisplayRGB = (1 << 0), DisplaySVideo = (1 << 1), - DisplayComposite = (1 << 2), - QuickLoadTape = (1 << 3), - AutomaticTapeMotorControl = (1 << 4) + DisplayCompositeColour = (1 << 2), + DisplayCompositeMonochrome = (1 << 3), + QuickLoadTape = (1 << 4), + AutomaticTapeMotorControl = (1 << 5) }; enum class Display { RGB, SVideo, - Composite + CompositeColour, + CompositeMonochrome }; /*! diff --git a/Inputs/Joystick.hpp b/Inputs/Joystick.hpp index d86d9ac04..b1908486c 100644 --- a/Inputs/Joystick.hpp +++ b/Inputs/Joystick.hpp @@ -185,7 +185,7 @@ class ConcreteJoystick: public Joystick { // convenient hard-coded values. TODO: make these a function of time. using Type = Joystick::Input::Type; switch(input.type) { - default: did_set_input(input, is_active ? 1.0f : 0.0f); break; + default: did_set_input(input, is_active ? 1.0f : 0.0f); break; case Type::Left: did_set_input(Input(Type::Horizontal, input.info.control.index), is_active ? 0.1f : 0.5f); break; case Type::Right: did_set_input(Input(Type::Horizontal, input.info.control.index), is_active ? 0.9f : 0.5f); break; case Type::Up: did_set_input(Input(Type::Vertical, input.info.control.index), is_active ? 0.1f : 0.5f); break; @@ -203,7 +203,7 @@ class ConcreteJoystick: public Joystick { // Otherwise apply a threshold test to convert to digital, with remapping from axes to digital inputs. using Type = Joystick::Input::Type; switch(input.type) { - default: did_set_input(input, value > 0.5f); break; + default: did_set_input(input, value > 0.5f); break; case Type::Horizontal: did_set_input(Input(Type::Left, input.info.control.index), value <= 0.25f); did_set_input(Input(Type::Right, input.info.control.index), value >= 0.75f); diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index 2a3935039..9698aa3b1 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -30,6 +30,7 @@ #include "../../ClockReceiver/ForceInline.hpp" #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" +#include "../../Outputs/CRT/CRT.hpp" #include "../../Analyser/Static/AmstradCPC/Target.hpp" @@ -40,7 +41,7 @@ namespace AmstradCPC { std::vector> get_options() { return Configurable::standard_options( - static_cast(Configurable::DisplayRGB | Configurable::DisplayComposite) + static_cast(Configurable::DisplayRGB | Configurable::DisplayCompositeColour) ); } @@ -171,10 +172,14 @@ class AYDeferrer { class CRTCBusHandler { public: CRTCBusHandler(uint8_t *ram, InterruptTimer &interrupt_timer) : + crt_(1024, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red2Green2Blue2), ram_(ram), interrupt_timer_(interrupt_timer) { establish_palette_hits(); build_mode_table(); + crt_.set_visible_area(Outputs::Display::Rect(0.1072f, 0.1f, 0.842105263157895f, 0.842105263157895f)); + crt_.set_brightness(3.0f / 2.0f); // As only the values 0, 1 and 2 will be used in each channel, + // whereas Red2Green2Blue2 defines a range of 0-3. } /*! @@ -217,12 +222,12 @@ class CRTCBusHandler { if(cycles_) { switch(previous_output_mode_) { default: - case OutputMode::Blank: crt_->output_blank(cycles_ * 16); break; - case OutputMode::Sync: crt_->output_sync(cycles_ * 16); break; + case OutputMode::Blank: crt_.output_blank(cycles_ * 16); break; + case OutputMode::Sync: crt_.output_sync(cycles_ * 16); break; case OutputMode::Border: output_border(cycles_); break; - case OutputMode::ColourBurst: crt_->output_default_colour_burst(cycles_ * 16); break; + case OutputMode::ColourBurst: crt_.output_default_colour_burst(cycles_ * 16); break; case OutputMode::Pixels: - crt_->output_data(cycles_ * 16, cycles_ * 16 / pixel_divider_); + crt_.output_data(cycles_ * 16, size_t(cycles_ * 16 / pixel_divider_)); pixel_pointer_ = pixel_data_ = nullptr; break; } @@ -238,7 +243,7 @@ class CRTCBusHandler { // collect some more pixels if output is ongoing if(previous_output_mode_ == OutputMode::Pixels) { if(!pixel_data_) { - pixel_pointer_ = pixel_data_ = crt_->allocate_write_area(320, 8); + pixel_pointer_ = pixel_data_ = crt_.begin_data(320, 8); } if(pixel_pointer_) { // the CPC shuffles output lines as: @@ -283,7 +288,7 @@ class CRTCBusHandler { // widths so it's not necessarily possible to predict the correct number in advance // and using the upper bound could lead to inefficient behaviour if(pixel_pointer_ == pixel_data_ + 320) { - crt_->output_data(cycles_ * 16, cycles_ * 16 / pixel_divider_); + crt_.output_data(cycles_ * 16, size_t(cycles_ * 16 / pixel_divider_)); pixel_pointer_ = pixel_data_ = nullptr; cycles_ = 0; } @@ -323,27 +328,14 @@ class CRTCBusHandler { was_hsync_ = state.hsync; } - /// Constructs an appropriate CRT for video output. - void setup_output(float aspect_ratio) { - crt_.reset(new Outputs::CRT::CRT(1024, 16, Outputs::CRT::DisplayType::PAL50, 1)); - crt_->set_rgb_sampling_function( - "vec3 rgb_sample(usampler2D sampler, vec2 coordinate)" - "{" - "uint sample = texture(texID, coordinate).r;" - "return vec3(float((sample >> 4) & 3u), float((sample >> 2) & 3u), float(sample & 3u)) / 2.0;" - "}"); - crt_->set_visible_area(Outputs::CRT::Rect(0.1072f, 0.1f, 0.842105263157895f, 0.842105263157895f)); - crt_->set_video_signal(Outputs::CRT::VideoSignal::RGB); + /// Sets the destination for output. + void set_scan_target(Outputs::Display::ScanTarget *scan_target) { + crt_.set_scan_target(scan_target); } - /// Destructs the CRT. - void close_output() { - crt_.reset(); - } - - /// @returns the CRT. - Outputs::CRT::CRT *get_crt() { - return crt_.get(); + /// Sets the type of display. + void set_display_type(Outputs::Display::DisplayType display_type) { + crt_.set_display_type(display_type); } /*! @@ -376,10 +368,10 @@ class CRTCBusHandler { } private: - void output_border(unsigned int length) { - uint8_t *colour_pointer = static_cast(crt_->allocate_write_area(1)); + void output_border(int length) { + uint8_t *colour_pointer = static_cast(crt_.begin_data(1)); if(colour_pointer) *colour_pointer = border_; - crt_->output_level(length * 16); + crt_.output_level(length * 16); } #define Mode0Colour0(c) ((c & 0x80) >> 7) | ((c & 0x20) >> 3) | ((c & 0x08) >> 2) | ((c & 0x02) << 2) @@ -528,19 +520,19 @@ class CRTCBusHandler { Border, Pixels } previous_output_mode_ = OutputMode::Sync; - unsigned int cycles_ = 0; + int cycles_ = 0; bool was_hsync_ = false, was_vsync_ = false; int cycles_into_hsync_ = 0; - std::unique_ptr crt_; + Outputs::CRT::CRT crt_; uint8_t *pixel_data_ = nullptr, *pixel_pointer_ = nullptr; uint8_t *ram_ = nullptr; int next_mode_ = 2, mode_ = 2; - unsigned int pixel_divider_ = 1; + int pixel_divider_ = 1; uint16_t mode0_output_[256]; uint32_t mode1_output_[256]; uint64_t mode2_output_[256]; @@ -981,19 +973,14 @@ template class ConcreteMachine: flush_fdc(); } - /// A CRTMachine function; indicates that outputs should be created now. - void setup_output(float aspect_ratio) override final { - crtc_bus_handler_.setup_output(aspect_ratio); + /// A CRTMachine function; sets the destination for video. + void set_scan_target(Outputs::Display::ScanTarget *scan_target) override final { + crtc_bus_handler_.set_scan_target(scan_target); } - /// A CRTMachine function; indicates that outputs should be destroyed now. - void close_output() override final { - crtc_bus_handler_.close_output(); - } - - /// @returns the CRT in use. - Outputs::CRT::CRT *get_crt() override final { - return crtc_bus_handler_.get_crt(); + /// A CRTMachine function; sets the output display type. + void set_display_type(Outputs::Display::DisplayType display_type) override final { + crtc_bus_handler_.set_display_type(display_type); } /// @returns the speaker in use. @@ -1192,7 +1179,7 @@ Machine *Machine::AmstradCPC(const Analyser::Static::Target *target, const ROMMa using Target = Analyser::Static::AmstradCPC::Target; const Target *const cpc_target = dynamic_cast(target); switch(cpc_target->model) { - default: return new AmstradCPC::ConcreteMachine(*cpc_target, rom_fetcher); + default: return new AmstradCPC::ConcreteMachine(*cpc_target, rom_fetcher); case Target::Model::CPC464: return new AmstradCPC::ConcreteMachine(*cpc_target, rom_fetcher); } } diff --git a/Machines/AppleII/AppleII.cpp b/Machines/AppleII/AppleII.cpp index 63371a316..a8121e534 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/AppleII/AppleII.cpp @@ -28,12 +28,19 @@ #include "../../Analyser/Static/AppleII/Target.hpp" #include "../../ClockReceiver/ForceInline.hpp" +#include "../../Configurable/StandardOptions.hpp" #include #include #include -namespace { +namespace AppleII { + +std::vector> get_options() { + return Configurable::standard_options( + static_cast(Configurable::DisplayCompositeMonochrome | Configurable::DisplayCompositeColour) + ); +} #define is_iie() ((model == Analyser::Static::AppleII::Target::Model::IIe) || (model == Analyser::Static::AppleII::Target::Model::EnhancedIIe)) @@ -43,6 +50,7 @@ template class ConcreteMachine: public KeyboardMachine::MappedMachine, public CPU::MOS6502::BusHandler, public Inputs::Keyboard, + public Configurable::Device, public AppleII::Machine, public Activity::Source, public JoystickMachine::Machine, @@ -63,12 +71,12 @@ template class ConcreteMachine: CPU::MOS6502::Processor<(model == Analyser::Static::AppleII::Target::Model::EnhancedIIe) ? CPU::MOS6502::Personality::PSynertek65C02 : CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_; VideoBusHandler video_bus_handler_; - std::unique_ptr> video_; + AppleII::Video::Video video_; int cycles_into_current_line_ = 0; Cycles cycles_since_video_update_; void update_video() { - video_->run_for(cycles_since_video_update_.flush()); + video_.run_for(cycles_since_video_update_.flush()); } static const int audio_divider = 8; void update_audio() { @@ -84,7 +92,6 @@ template class ConcreteMachine: uint8_t ram_[65536], aux_ram_[65536]; std::vector rom_; - std::vector character_rom_; uint8_t keyboard_input_ = 0x00; bool key_is_down_ = false; @@ -123,7 +130,7 @@ template class ConcreteMachine: void pick_card_messaging_group(AppleII::Card *card) { const bool is_every_cycle = is_every_cycle_card(card); std::vector &intended = is_every_cycle ? every_cycle_cards_ : just_in_time_cards_; - std::vector &undesired = is_every_cycle ? just_in_time_cards_ : every_cycle_cards_; + std::vector &undesired = is_every_cycle ? just_in_time_cards_ : every_cycle_cards_; if(std::find(intended.begin(), intended.end(), card) != intended.end()) return; auto old_membership = std::find(undesired.begin(), undesired.end(), card); @@ -154,7 +161,7 @@ template class ConcreteMachine: On a IIe with auxiliary memory the following orthogonal changes also need to be factored in: - 0000 to 0200 : can be paged independently of the rest of RAM, other than part of the language card area which pages with it + 0000 to 0200 : can be paged independently of the rest of RAM, other than part of the language card area which pages with it 0400 to 0800 : the text screen, can be configured to write to auxiliary RAM 2000 to 4000 : the graphics screen, which can be configured to write to auxiliary RAM c100 to d000 : can be used to page an additional 3.75kb of ROM, replacing the IO area @@ -234,13 +241,13 @@ template class ConcreteMachine: read_auxiliary_memory_ ? &aux_ram_[0x0200] : &ram_[0x0200], write_auxiliary_memory_ ? &aux_ram_[0x0200] : &ram_[0x0200]); - if(video_ && video_->get_80_store()) { - bool use_aux_ram = video_->get_page2(); + if(video_.get_80_store()) { + bool use_aux_ram = video_.get_page2(); page(0x04, 0x08, use_aux_ram ? &aux_ram_[0x0400] : &ram_[0x0400], use_aux_ram ? &aux_ram_[0x0400] : &ram_[0x0400]); - if(video_->get_high_resolution()) { + if(video_.get_high_resolution()) { page(0x20, 0x40, use_aux_ram ? &aux_ram_[0x2000] : &ram_[0x2000], use_aux_ram ? &aux_ram_[0x2000] : &ram_[0x2000]); @@ -308,15 +315,16 @@ template class ConcreteMachine: public: ConcreteMachine(const Analyser::Static::AppleII::Target &target, const ROMMachine::ROMFetcher &rom_fetcher): m6502_(*this), - video_bus_handler_(ram_, aux_ram_), - audio_toggle_(audio_queue_), - speaker_(audio_toggle_) { - // The system's master clock rate. - const float master_clock = 14318180.0; + video_bus_handler_(ram_, aux_ram_), + video_(video_bus_handler_), + audio_toggle_(audio_queue_), + speaker_(audio_toggle_) { + // The system's master clock rate. + const float master_clock = 14318180.0; - // This is where things get slightly convoluted: establish the machine as having a clock rate - // equal to the number of cycles of work the 6502 will actually achieve. Which is less than - // the master clock rate divided by 14 because every 65th cycle is extended by one seventh. + // This is where things get slightly convoluted: establish the machine as having a clock rate + // equal to the number of cycles of work the 6502 will actually achieve. Which is less than + // the master clock rate divided by 14 because every 65th cycle is extended by one seventh. set_clock_rate((master_clock / 14.0) * 65.0 / (65.0 + 1.0 / 7.0)); // The speaker, however, should think it is clocked at half the master clock, per a general @@ -333,8 +341,8 @@ template class ConcreteMachine: Memory::Fuzz(aux_ram_, sizeof(aux_ram_)); // Add a couple of joysticks. - joysticks_.emplace_back(new Joystick); - joysticks_.emplace_back(new Joystick); + joysticks_.emplace_back(new Joystick); + joysticks_.emplace_back(new Joystick); // Pick the required ROMs. using Target = Analyser::Static::AppleII::Target; @@ -371,7 +379,7 @@ template class ConcreteMachine: rom_.erase(rom_.begin(), rom_.end() - static_cast(rom_size)); } - character_rom_ = std::move(*roms[0]); + video_.set_character_rom(*roms[0]); if(target.disk_controller != Target::DiskController::None) { // Apple recommended slot 6 for the (first) Disk II. @@ -396,17 +404,13 @@ template class ConcreteMachine: audio_queue_.flush(); } - void setup_output(float aspect_ratio) override { - video_.reset(new AppleII::Video::Video(video_bus_handler_)); - video_->set_character_rom(character_rom_); + void set_scan_target(Outputs::Display::ScanTarget *scan_target) override { + video_.set_scan_target(scan_target); } - void close_output() override { - video_.reset(); - } - - Outputs::CRT::CRT *get_crt() override { - return video_->get_crt(); + /// Sets the type of display. + void set_display_type(Outputs::Display::DisplayType display_type) override { + video_.set_display_type(display_type); } Outputs::Speaker::Speaker *get_speaker() override { @@ -463,7 +467,7 @@ template class ConcreteMachine: // actor, but this will actually be the result most of the time so it's not // too terrible. if(isReadOperation(operation) && address != 0xc000) { - *value = video_->get_last_read_value(cycles_since_video_update_); + *value = video_.get_last_read_value(cycles_since_video_update_); } switch(address) { @@ -523,18 +527,18 @@ template class ConcreteMachine: case 0xc015: IIeSwitchRead(internal_CX_rom_); break; case 0xc016: IIeSwitchRead(alternative_zero_page_); break; case 0xc017: IIeSwitchRead(slot_C3_rom_); break; - case 0xc018: IIeSwitchRead(video_->get_80_store()); break; - case 0xc019: IIeSwitchRead(video_->get_is_vertical_blank(cycles_since_video_update_)); break; - case 0xc01a: IIeSwitchRead(video_->get_text()); break; - case 0xc01b: IIeSwitchRead(video_->get_mixed()); break; - case 0xc01c: IIeSwitchRead(video_->get_page2()); break; - case 0xc01d: IIeSwitchRead(video_->get_high_resolution()); break; - case 0xc01e: IIeSwitchRead(video_->get_alternative_character_set()); break; - case 0xc01f: IIeSwitchRead(video_->get_80_columns()); break; + case 0xc018: IIeSwitchRead(video_.get_80_store()); break; + case 0xc019: IIeSwitchRead(video_.get_is_vertical_blank(cycles_since_video_update_)); break; + case 0xc01a: IIeSwitchRead(video_.get_text()); break; + case 0xc01b: IIeSwitchRead(video_.get_mixed()); break; + case 0xc01c: IIeSwitchRead(video_.get_page2()); break; + case 0xc01d: IIeSwitchRead(video_.get_high_resolution()); break; + case 0xc01e: IIeSwitchRead(video_.get_alternative_character_set()); break; + case 0xc01f: IIeSwitchRead(video_.get_80_columns()); break; #undef IIeSwitchRead case 0xc07f: - if(is_iie()) *value = (*value & 0x7f) | (video_->get_annunciator_3() ? 0x80 : 0x00); + if(is_iie()) *value = (*value & 0x7f) | (video_.get_annunciator_3() ? 0x80 : 0x00); break; } } else { @@ -546,7 +550,7 @@ template class ConcreteMachine: case 0xc000: case 0xc001: update_video(); - video_->set_80_store(!!(address&1)); + video_.set_80_store(!!(address&1)); set_main_paging(); break; @@ -586,13 +590,13 @@ template class ConcreteMachine: case 0xc00c: case 0xc00d: update_video(); - video_->set_80_columns(!!(address&1)); + video_.set_80_columns(!!(address&1)); break; case 0xc00e: case 0xc00f: update_video(); - video_->set_alternative_character_set(!!(address&1)); + video_.set_alternative_character_set(!!(address&1)); break; } } @@ -615,20 +619,20 @@ template class ConcreteMachine: case 0xc050: case 0xc051: update_video(); - video_->set_text(!!(address&1)); + video_.set_text(!!(address&1)); break; - case 0xc052: update_video(); video_->set_mixed(false); break; - case 0xc053: update_video(); video_->set_mixed(true); break; + case 0xc052: update_video(); video_.set_mixed(false); break; + case 0xc053: update_video(); video_.set_mixed(true); break; case 0xc054: case 0xc055: update_video(); - video_->set_page2(!!(address&1)); + video_.set_page2(!!(address&1)); set_main_paging(); break; case 0xc056: case 0xc057: update_video(); - video_->set_high_resolution(!!(address&1)); + video_.set_high_resolution(!!(address&1)); set_main_paging(); break; @@ -636,7 +640,7 @@ template class ConcreteMachine: case 0xc05f: if(is_iie()) { update_video(); - video_->set_annunciator_3(!(address&1)); + video_.set_annunciator_3(!(address&1)); } break; @@ -812,6 +816,28 @@ template class ConcreteMachine: string_serialiser_.reset(new Utility::StringSerialiser(string, true)); } + // MARK:: Configuration options. + std::vector> get_options() override { + return AppleII::get_options(); + } + + void set_selections(const Configurable::SelectionSet &selections_by_option) override { + Configurable::Display display; + if(Configurable::get_display(selections_by_option, display)) { + set_video_signal_configurable(display); + } + } + + Configurable::SelectionSet get_accurate_selections() override { + Configurable::SelectionSet selection_set; + Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour); + return selection_set; + } + + Configurable::SelectionSet get_user_friendly_selections() override { + return get_accurate_selections(); + } + // MARK: MediaTarget bool insert_media(const Analyser::Static::Media &media) override { if(!media.disks.empty()) { diff --git a/Machines/AppleII/AppleII.hpp b/Machines/AppleII/AppleII.hpp index b0b8180aa..c680b3163 100644 --- a/Machines/AppleII/AppleII.hpp +++ b/Machines/AppleII/AppleII.hpp @@ -18,6 +18,9 @@ namespace AppleII { +/// @returns The options available for an Apple II. +std::vector> get_options(); + class Machine { public: virtual ~Machine(); diff --git a/Machines/AppleII/Card.hpp b/Machines/AppleII/Card.hpp index 42fb4fd23..2b1ae664f 100644 --- a/Machines/AppleII/Card.hpp +++ b/Machines/AppleII/Card.hpp @@ -39,6 +39,7 @@ namespace AppleII { */ class Card { public: + virtual ~Card() {} enum Select: int { None = 0, // No select line is active IO = 1 << 0, // IO select is active diff --git a/Machines/AppleII/Video.cpp b/Machines/AppleII/Video.cpp index fb064673b..22474c8ae 100644 --- a/Machines/AppleII/Video.cpp +++ b/Machines/AppleII/Video.cpp @@ -11,22 +11,19 @@ using namespace AppleII::Video; VideoBase::VideoBase(bool is_iie, std::function &&target) : - crt_(new Outputs::CRT::CRT(910, 1, Outputs::CRT::DisplayType::NTSC60, 1)), + crt_(910, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Luminance1), is_iie_(is_iie), deferrer_(std::move(target)) { - // Set a composite sampling function that assumes one byte per pixel input, and - // accepts any non-zero value as being fully on, zero being fully off. - crt_->set_composite_sampling_function( - "float composite_sample(usampler2D sampler, vec2 coordinate, float phase, float amplitude)" - "{" - "return clamp(texture(sampler, coordinate).r, 0.0, 0.66);" - "}"); - // Show only the centre 75% of the TV frame. - crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite); - crt_->set_visible_area(Outputs::CRT::Rect(0.118f, 0.122f, 0.77f, 0.77f)); - crt_->set_immediate_default_phase(0.0f); + crt_.set_display_type(Outputs::Display::DisplayType::CompositeColour); + crt_.set_visible_area(Outputs::Display::Rect(0.118f, 0.122f, 0.77f, 0.77f)); + + // TODO: there seems to be some sort of bug whereby switching modes can cause + // a signal discontinuity that knocks phase out of whack. So it isn't safe to + // use default_colour_bursts elsewhere, though it otherwise should be. If/when + // it is, start doing so and return to setting the immediate phase up here. +// crt_.set_immediate_default_phase(0.5f); character_zones[0].xor_mask = 0; character_zones[0].address_mask = 0x3f; @@ -46,8 +43,12 @@ VideoBase::VideoBase(bool is_iie, std::function &&target) : } } -Outputs::CRT::CRT *VideoBase::get_crt() { - return crt_.get(); +void VideoBase::set_scan_target(Outputs::Display::ScanTarget *scan_target) { + crt_.set_scan_target(scan_target); +} + +void VideoBase::set_display_type(Outputs::Display::DisplayType display_type) { + crt_.set_display_type(display_type); } /* diff --git a/Machines/AppleII/Video.hpp b/Machines/AppleII/Video.hpp index 3df0e48f7..3eccde46e 100644 --- a/Machines/AppleII/Video.hpp +++ b/Machines/AppleII/Video.hpp @@ -36,8 +36,11 @@ class VideoBase { public: VideoBase(bool is_iie, std::function &&target); - /// @returns The CRT this video feed is feeding. - Outputs::CRT::CRT *get_crt(); + /// Sets the scan target. + void set_scan_target(Outputs::Display::ScanTarget *scan_target); + + /// Sets the type of output. + void set_display_type(Outputs::Display::DisplayType); /* Descriptions for the setters below are taken verbatim from @@ -146,7 +149,7 @@ class VideoBase { void set_character_rom(const std::vector &); protected: - std::unique_ptr crt_; + Outputs::CRT::CRT crt_; // State affecting output video stream generation. uint8_t *pixel_pointer_ = nullptr; @@ -343,22 +346,23 @@ template class Video: public VideoBase { while(int_cycles) { const int cycles_this_line = std::min(65 - column_, int_cycles); const int ending_column = column_ + cycles_this_line; + const bool is_vertical_sync_line = (row_ >= first_sync_line && row_ < first_sync_line + 3); - if(row_ >= first_sync_line && row_ < first_sync_line + 3) { + if(is_vertical_sync_line) { // In effect apply an XOR to HSYNC and VSYNC flags in order to include equalising // pulses (and hencce keep hsync approximately where it should be during vsync). const int blank_start = std::max(first_sync_column - sync_length, column_); const int blank_end = std::min(first_sync_column, ending_column); if(blank_end > blank_start) { if(blank_start > column_) { - crt_->output_sync(static_cast(blank_start - column_) * 14); + crt_.output_sync((blank_start - column_) * 14); } - crt_->output_blank(static_cast(blank_end - blank_start) * 14); + crt_.output_blank((blank_end - blank_start) * 14); if(blank_end < ending_column) { - crt_->output_sync(static_cast(ending_column - blank_end) * 14); + crt_.output_sync((ending_column - blank_end) * 14); } } else { - crt_->output_sync(static_cast(cycles_this_line) * 14); + crt_.output_sync(cycles_this_line * 14); } } else { const GraphicsMode line_mode = graphics_mode(row_); @@ -394,7 +398,6 @@ template class Video: public VideoBase { static_cast(fetch_end - column_), &base_stream_[static_cast(column_)], &auxiliary_stream_[static_cast(column_)]); - // TODO: should character modes be mapped to character pixel outputs here? } if(row_ < 192) { @@ -402,7 +405,7 @@ template class Video: public VideoBase { // remain where they would naturally be but auxiliary // graphics appear to the left of that. if(!column_) { - pixel_pointer_ = crt_->allocate_write_area(568); + pixel_pointer_ = crt_.begin_data(568); graphics_carry_ = 0; was_double_ = true; } @@ -413,7 +416,7 @@ template class Video: public VideoBase { const int pixel_row = row_ & 7; const bool is_double = Video::is_double_mode(line_mode); - if(!is_double && was_double_) { + if(!is_double && was_double_ && pixel_pointer_) { pixel_pointer_[pixel_start*14 + 0] = pixel_pointer_[pixel_start*14 + 1] = pixel_pointer_[pixel_start*14 + 2] = @@ -424,88 +427,92 @@ template class Video: public VideoBase { } was_double_ = is_double; - switch(line_mode) { - case GraphicsMode::Text: - output_text( - &pixel_pointer_[pixel_start * 14 + 7], - &base_stream_[static_cast(pixel_start)], - static_cast(pixel_end - pixel_start), - static_cast(pixel_row)); - break; + if(pixel_pointer_) { + switch(line_mode) { + case GraphicsMode::Text: + output_text( + &pixel_pointer_[pixel_start * 14 + 7], + &base_stream_[static_cast(pixel_start)], + static_cast(pixel_end - pixel_start), + static_cast(pixel_row)); + break; - case GraphicsMode::DoubleText: - output_double_text( - &pixel_pointer_[pixel_start * 14], - &base_stream_[static_cast(pixel_start)], - &auxiliary_stream_[static_cast(pixel_start)], - static_cast(pixel_end - pixel_start), - static_cast(pixel_row)); - break; + case GraphicsMode::DoubleText: + output_double_text( + &pixel_pointer_[pixel_start * 14], + &base_stream_[static_cast(pixel_start)], + &auxiliary_stream_[static_cast(pixel_start)], + static_cast(pixel_end - pixel_start), + static_cast(pixel_row)); + break; - case GraphicsMode::LowRes: - output_low_resolution( - &pixel_pointer_[pixel_start * 14 + 7], - &base_stream_[static_cast(pixel_start)], - static_cast(pixel_end - pixel_start), - pixel_start, - pixel_row); - break; + case GraphicsMode::LowRes: + output_low_resolution( + &pixel_pointer_[pixel_start * 14 + 7], + &base_stream_[static_cast(pixel_start)], + static_cast(pixel_end - pixel_start), + pixel_start, + pixel_row); + break; - case GraphicsMode::FatLowRes: - output_fat_low_resolution( - &pixel_pointer_[pixel_start * 14 + 7], - &base_stream_[static_cast(pixel_start)], - static_cast(pixel_end - pixel_start), - pixel_start, - pixel_row); - break; + case GraphicsMode::FatLowRes: + output_fat_low_resolution( + &pixel_pointer_[pixel_start * 14 + 7], + &base_stream_[static_cast(pixel_start)], + static_cast(pixel_end - pixel_start), + pixel_start, + pixel_row); + break; - case GraphicsMode::DoubleLowRes: - output_double_low_resolution( - &pixel_pointer_[pixel_start * 14], - &base_stream_[static_cast(pixel_start)], - &auxiliary_stream_[static_cast(pixel_start)], - static_cast(pixel_end - pixel_start), - pixel_start, - pixel_row); - break; + case GraphicsMode::DoubleLowRes: + output_double_low_resolution( + &pixel_pointer_[pixel_start * 14], + &base_stream_[static_cast(pixel_start)], + &auxiliary_stream_[static_cast(pixel_start)], + static_cast(pixel_end - pixel_start), + pixel_start, + pixel_row); + break; - case GraphicsMode::HighRes: - output_high_resolution( - &pixel_pointer_[pixel_start * 14 + 7], - &base_stream_[static_cast(pixel_start)], - static_cast(pixel_end - pixel_start)); - break; + case GraphicsMode::HighRes: + output_high_resolution( + &pixel_pointer_[pixel_start * 14 + 7], + &base_stream_[static_cast(pixel_start)], + static_cast(pixel_end - pixel_start)); + break; - case GraphicsMode::DoubleHighRes: - output_double_high_resolution( - &pixel_pointer_[pixel_start * 14], - &base_stream_[static_cast(pixel_start)], - &auxiliary_stream_[static_cast(pixel_start)], - static_cast(pixel_end - pixel_start)); - break; + case GraphicsMode::DoubleHighRes: + output_double_high_resolution( + &pixel_pointer_[pixel_start * 14], + &base_stream_[static_cast(pixel_start)], + &auxiliary_stream_[static_cast(pixel_start)], + static_cast(pixel_end - pixel_start)); + break; - default: break; + default: break; + } } if(pixel_end == 40) { - if(was_double_) { - pixel_pointer_[560] = pixel_pointer_[561] = pixel_pointer_[562] = pixel_pointer_[563] = - pixel_pointer_[564] = pixel_pointer_[565] = pixel_pointer_[566] = pixel_pointer_[567] = 0; - } else { - if(line_mode == GraphicsMode::HighRes && base_stream_[39]&0x80) - pixel_pointer_[567] = graphics_carry_; - else - pixel_pointer_[567] = 0; + if(pixel_pointer_) { + if(was_double_) { + pixel_pointer_[560] = pixel_pointer_[561] = pixel_pointer_[562] = pixel_pointer_[563] = + pixel_pointer_[564] = pixel_pointer_[565] = pixel_pointer_[566] = pixel_pointer_[567] = 0; + } else { + if(line_mode == GraphicsMode::HighRes && base_stream_[39]&0x80) + pixel_pointer_[567] = graphics_carry_; + else + pixel_pointer_[567] = 0; + } } - crt_->output_data(568, 568); + crt_.output_data(568, 568); pixel_pointer_ = nullptr; } } } else { if(column_ < 40 && ending_column >= 40) { - crt_->output_blank(568); + crt_.output_blank(568); } } @@ -515,11 +522,11 @@ template class Video: public VideoBase { */ if(column_ < first_sync_column && ending_column >= first_sync_column) { - crt_->output_blank((first_sync_column - 41)*14 - 1); + crt_.output_blank(first_sync_column*14 - 568); } if(column_ < (first_sync_column + sync_length) && ending_column >= (first_sync_column + sync_length)) { - crt_->output_sync(sync_length*14); + crt_.output_sync(sync_length*14); } int second_blank_start; @@ -527,7 +534,7 @@ template class Video: public VideoBase { const int colour_burst_start = std::max(first_sync_column + sync_length + 1, column_); const int colour_burst_end = std::min(first_sync_column + sync_length + 4, ending_column); if(colour_burst_end > colour_burst_start) { - crt_->output_colour_burst(static_cast(colour_burst_end - colour_burst_start) * 14, 192); + crt_.output_colour_burst((colour_burst_end - colour_burst_start) * 14, 0); } second_blank_start = std::max(first_sync_column + sync_length + 3, column_); @@ -536,7 +543,7 @@ template class Video: public VideoBase { } if(ending_column > second_blank_start) { - crt_->output_blank(static_cast(ending_column - second_blank_start) * 14); + crt_.output_blank((ending_column - second_blank_start) * 14); } } @@ -550,8 +557,13 @@ template class Video: public VideoBase { } // Add an extra half a colour cycle of blank; this isn't counted in the run_for - // count explicitly but is promised. - crt_->output_blank(2); + // count explicitly but is promised. If this is a vertical sync line, output sync + // instead of blank, taking that to be the default level. + if(is_vertical_sync_line) { + crt_.output_sync(2); + } else { + crt_.output_blank(2); + } } } } diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 1a60148aa..a9ac5d483 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -122,10 +122,6 @@ class ConcreteMachine: joysticks_.emplace_back(new Joystick(bus_.get(), 4, 1)); } - ~ConcreteMachine() { - close_output(); - } - std::vector> &get_joysticks() override { return joysticks_; } @@ -157,18 +153,10 @@ class ConcreteMachine: } // to satisfy CRTMachine::Machine - void setup_output(float aspect_ratio) override { - bus_->tia_.reset(new TIA); + void set_scan_target(Outputs::Display::ScanTarget *scan_target) override { bus_->speaker_.set_input_rate(static_cast(get_clock_rate() / static_cast(CPUTicksPerAudioTick))); - bus_->tia_->get_crt()->set_delegate(this); - } - - void close_output() override { - bus_.reset(); - } - - Outputs::CRT::CRT *get_crt() override { - return bus_->tia_->get_crt(); + bus_->tia_.set_crt_delegate(this); + bus_->tia_.set_scan_target(scan_target); } Outputs::Speaker::Speaker *get_speaker() override { @@ -181,15 +169,15 @@ class ConcreteMachine: } // to satisfy Outputs::CRT::Delegate - void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs) override { + void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, int number_of_frames, int number_of_unexpected_vertical_syncs) override { const std::size_t number_of_frame_records = sizeof(frame_records_) / sizeof(frame_records_[0]); frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_frames = number_of_frames; frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_unexpected_vertical_syncs = number_of_unexpected_vertical_syncs; frame_record_pointer_ ++; if(frame_record_pointer_ >= 6) { - unsigned int total_number_of_frames = 0; - unsigned int total_number_of_unexpected_vertical_syncs = 0; + int total_number_of_frames = 0; + int total_number_of_unexpected_vertical_syncs = 0; for(std::size_t c = 0; c < number_of_frame_records; c++) { total_number_of_frames += frame_records_[c].number_of_frames; total_number_of_unexpected_vertical_syncs += frame_records_[c].number_of_unexpected_vertical_syncs; @@ -205,10 +193,10 @@ class ConcreteMachine: double clock_rate; if(is_ntsc_) { clock_rate = NTSC_clock_rate; - bus_->tia_->set_output_mode(TIA::OutputMode::NTSC); + bus_->tia_.set_output_mode(TIA::OutputMode::NTSC); } else { clock_rate = PAL_clock_rate; - bus_->tia_->set_output_mode(TIA::OutputMode::PAL); + bus_->tia_.set_output_mode(TIA::OutputMode::PAL); } bus_->speaker_.set_input_rate(static_cast(clock_rate / static_cast(CPUTicksPerAudioTick))); @@ -228,10 +216,8 @@ class ConcreteMachine: // output frame rate tracker struct FrameRecord { - unsigned int number_of_frames; - unsigned int number_of_unexpected_vertical_syncs; - - FrameRecord() : number_of_frames(0), number_of_unexpected_vertical_syncs(0) {} + int number_of_frames = 0; + int number_of_unexpected_vertical_syncs = 0; } frame_records_[4]; unsigned int frame_record_pointer_ = 0; bool is_ntsc_ = true; diff --git a/Machines/Atari2600/Bus.hpp b/Machines/Atari2600/Bus.hpp index e08b33211..ba58c81c5 100644 --- a/Machines/Atari2600/Bus.hpp +++ b/Machines/Atari2600/Bus.hpp @@ -36,7 +36,7 @@ class Bus { // the RIOT, TIA and speaker PIA mos6532_; - std::shared_ptr tia_; + TIA tia_; Concurrency::DeferringAsyncTaskQueue audio_queue_; TIASound tia_sound_; @@ -55,7 +55,7 @@ class Bus { // video backlog accumulation counter Cycles cycles_since_video_update_; inline void update_video() { - tia_->run_for(cycles_since_video_update_.flush()); + tia_.run_for(cycles_since_video_update_.flush()); } // RIOT backlog accumulation counter diff --git a/Machines/Atari2600/Cartridges/Cartridge.hpp b/Machines/Atari2600/Cartridges/Cartridge.hpp index 4276bdc7b..b25f5a3aa 100644 --- a/Machines/Atari2600/Cartridges/Cartridge.hpp +++ b/Machines/Atari2600/Cartridges/Cartridge.hpp @@ -68,7 +68,7 @@ template class Cartridge: // effect until the next read; therefore it isn't safe to assume that signalling ready immediately // skips to the end of the line. if(operation == CPU::MOS6502::BusOperation::Ready) - cycles_run_for = tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_); + cycles_run_for = tia_.get_cycles_until_horizontal_blank(cycles_since_video_update_); cycles_since_speaker_update_ += Cycles(cycles_run_for); cycles_since_video_update_ += Cycles(cycles_run_for); @@ -101,7 +101,7 @@ template class Cartridge: case 0x05: // missile 1 / playfield / ball collisions case 0x06: // ball / playfield collisions case 0x07: // player / player, missile / missile collisions - returnValue &= tia_->get_collision_flags(decodedAddress); + returnValue &= tia_.get_collision_flags(decodedAddress); break; case 0x08: @@ -120,52 +120,52 @@ template class Cartridge: } else { const uint16_t decodedAddress = address & 0x3f; switch(decodedAddress) { - case 0x00: update_video(); tia_->set_sync(*value & 0x02); break; - case 0x01: update_video(); tia_->set_blank(*value & 0x02); break; + case 0x00: update_video(); tia_.set_sync(*value & 0x02); break; + case 0x01: update_video(); tia_.set_blank(*value & 0x02); break; case 0x02: m6502_.set_ready_line(true); break; case 0x03: update_video(); - tia_->reset_horizontal_counter(); + tia_.reset_horizontal_counter(); horizontal_counter_resets_++; break; // TODO: audio will now be out of synchronisation. Fix. case 0x04: - case 0x05: update_video(); tia_->set_player_number_and_size(decodedAddress - 0x04, *value); break; + case 0x05: update_video(); tia_.set_player_number_and_size(decodedAddress - 0x04, *value); break; case 0x06: - case 0x07: update_video(); tia_->set_player_missile_colour(decodedAddress - 0x06, *value); break; - case 0x08: update_video(); tia_->set_playfield_ball_colour(*value); break; - case 0x09: update_video(); tia_->set_background_colour(*value); break; - case 0x0a: update_video(); tia_->set_playfield_control_and_ball_size(*value); break; + case 0x07: update_video(); tia_.set_player_missile_colour(decodedAddress - 0x06, *value); break; + case 0x08: update_video(); tia_.set_playfield_ball_colour(*value); break; + case 0x09: update_video(); tia_.set_background_colour(*value); break; + case 0x0a: update_video(); tia_.set_playfield_control_and_ball_size(*value); break; case 0x0b: - case 0x0c: update_video(); tia_->set_player_reflected(decodedAddress - 0x0b, !((*value)&8)); break; + case 0x0c: update_video(); tia_.set_player_reflected(decodedAddress - 0x0b, !((*value)&8)); break; case 0x0d: case 0x0e: - case 0x0f: update_video(); tia_->set_playfield(decodedAddress - 0x0d, *value); break; + case 0x0f: update_video(); tia_.set_playfield(decodedAddress - 0x0d, *value); break; case 0x10: - case 0x11: update_video(); tia_->set_player_position(decodedAddress - 0x10); break; + case 0x11: update_video(); tia_.set_player_position(decodedAddress - 0x10); break; case 0x12: - case 0x13: update_video(); tia_->set_missile_position(decodedAddress - 0x12); break; - case 0x14: update_video(); tia_->set_ball_position(); break; + case 0x13: update_video(); tia_.set_missile_position(decodedAddress - 0x12); break; + case 0x14: update_video(); tia_.set_ball_position(); break; case 0x1b: - case 0x1c: update_video(); tia_->set_player_graphic(decodedAddress - 0x1b, *value); break; + case 0x1c: update_video(); tia_.set_player_graphic(decodedAddress - 0x1b, *value); break; case 0x1d: - case 0x1e: update_video(); tia_->set_missile_enable(decodedAddress - 0x1d, (*value)&2); break; - case 0x1f: update_video(); tia_->set_ball_enable((*value)&2); break; + case 0x1e: update_video(); tia_.set_missile_enable(decodedAddress - 0x1d, (*value)&2); break; + case 0x1f: update_video(); tia_.set_ball_enable((*value)&2); break; case 0x20: - case 0x21: update_video(); tia_->set_player_motion(decodedAddress - 0x20, *value); break; + case 0x21: update_video(); tia_.set_player_motion(decodedAddress - 0x20, *value); break; case 0x22: - case 0x23: update_video(); tia_->set_missile_motion(decodedAddress - 0x22, *value); break; - case 0x24: update_video(); tia_->set_ball_motion(*value); break; + case 0x23: update_video(); tia_.set_missile_motion(decodedAddress - 0x22, *value); break; + case 0x24: update_video(); tia_.set_ball_motion(*value); break; case 0x25: - case 0x26: tia_->set_player_delay(decodedAddress - 0x25, (*value)&1); break; - case 0x27: tia_->set_ball_delay((*value)&1); break; + case 0x26: tia_.set_player_delay(decodedAddress - 0x25, (*value)&1); break; + case 0x27: tia_.set_ball_delay((*value)&1); break; case 0x28: - case 0x29: update_video(); tia_->set_missile_position_to_player(decodedAddress - 0x28, (*value)&2); break; - case 0x2a: update_video(); tia_->move(); break; - case 0x2b: update_video(); tia_->clear_motion(); break; - case 0x2c: update_video(); tia_->clear_collision_flags(); break; + case 0x29: update_video(); tia_.set_missile_position_to_player(decodedAddress - 0x28, (*value)&2); break; + case 0x2a: update_video(); tia_.move(); break; + case 0x2b: update_video(); tia_.clear_motion(); break; + case 0x2c: update_video(); tia_.clear_collision_flags(); break; case 0x15: case 0x16: update_audio(); tia_sound_.set_control(decodedAddress - 0x15, *value); break; @@ -192,7 +192,7 @@ template class Cartridge: } } - if(!tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_)) m6502_.set_ready_line(false); + if(!tia_.get_cycles_until_horizontal_blank(cycles_since_video_update_)) m6502_.set_ready_line(false); return Cycles(cycles_run_for / 3); } diff --git a/Machines/Atari2600/TIA.cpp b/Machines/Atari2600/TIA.cpp index 2d540e97a..60aef0f2c 100644 --- a/Machines/Atari2600/TIA.cpp +++ b/Machines/Atari2600/TIA.cpp @@ -22,12 +22,10 @@ namespace { uint8_t reverse_table[256]; } -TIA::TIA(bool create_crt) { - if(create_crt) { - crt_.reset(new Outputs::CRT::CRT(cycles_per_line * 2 - 1, 1, Outputs::CRT::DisplayType::NTSC60, 1)); - crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite); - set_output_mode(OutputMode::NTSC); - } +TIA::TIA(): + crt_(cycles_per_line * 2 - 1, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Luminance8Phase8) { + + set_output_mode(OutputMode::NTSC); for(int c = 0; c < 256; c++) { reverse_table[c] = static_cast( @@ -113,51 +111,35 @@ TIA::TIA(bool create_crt) { } } -TIA::TIA() : TIA(true) {} - -TIA::TIA(std::function line_end_function) : TIA(false) { - line_end_function_ = line_end_function; -} - void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) { - Outputs::CRT::DisplayType display_type; + Outputs::Display::Type display_type; + tv_standard_ = output_mode; if(output_mode == OutputMode::NTSC) { - crt_->set_svideo_sampling_function( - "vec2 svideo_sample(usampler2D texID, vec2 coordinate, float phase, float amplitude)" - "{" - "uint c = texture(texID, coordinate).r;" - "uint y = c & 14u;" - "uint iPhase = (c >> 4);" - - "float phaseOffset = 6.283185308 * float(iPhase) / 13.0 + 5.074880441076923;" - "return vec2(float(y) / 14.0, step(1, iPhase) * cos(phase - phaseOffset));" - "}"); - display_type = Outputs::CRT::DisplayType::NTSC60; + display_type = Outputs::Display::Type::NTSC60; } else { - crt_->set_svideo_sampling_function( - "vec2 svideo_sample(usampler2D texID, vec2 coordinate, float phase, float amplitude)" - "{" - "uint c = texture(texID, coordinate).r;" - "uint y = c & 14u;" - "uint iPhase = (c >> 4);" - - "uint direction = iPhase & 1u;" - "float phaseOffset = float(7u - direction) + (float(direction) - 0.5) * 2.0 * float(iPhase >> 1);" - "phaseOffset *= 6.283185308 / 12.0;" - "return vec2(float(y) / 14.0, step(4, (iPhase + 2u) & 15u) * cos(phase + phaseOffset));" - "}"); - display_type = Outputs::CRT::DisplayType::PAL50; + display_type = Outputs::Display::Type::PAL50; } - crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite); + crt_.set_display_type(Outputs::Display::DisplayType::CompositeColour); // line number of cycles in a line of video is one less than twice the number of clock cycles per line; the Atari // outputs 228 colour cycles of material per line when an NTSC line 227.5. Since all clock numbers will be doubled // later, cycles_per_line * 2 - 1 is therefore the real length of an NTSC line, even though we're going to supply // cycles_per_line * 2 cycles of information from one sync edge to the next - crt_->set_new_display_type(cycles_per_line * 2 - 1, display_type); + crt_.set_new_display_type(cycles_per_line * 2 - 1, display_type); -/* speaker_->set_input_rate(static_cast(get_clock_rate() / 38.0));*/ + // Update the luminance/phase mappings of the current palette. + for(size_t c = 0; c < colour_palette_.size(); ++c) { + set_colour_palette_entry(c, colour_palette_[c].original); + } +} + +void TIA::set_crt_delegate(Outputs::CRT::Delegate *delegate) { + crt_.set_delegate(delegate); +} + +void TIA::set_scan_target(Outputs::Display::ScanTarget *scan_target) { + crt_.set_scan_target(scan_target); } void TIA::run_for(const Cycles cycles) { @@ -198,7 +180,40 @@ int TIA::get_cycles_until_horizontal_blank(const Cycles from_offset) { } void TIA::set_background_colour(uint8_t colour) { - colour_palette_[static_cast(ColourIndex::Background)] = colour; + set_colour_palette_entry(size_t(ColourIndex::Background), colour); +} + +void TIA::set_colour_palette_entry(size_t index, uint8_t colour) { + const uint8_t luminance = ((colour & 14) * 255) / 14; + + uint8_t phase = colour >> 4; + + if(tv_standard_ == OutputMode::NTSC) { + if(!phase) phase = 255; + else { + phase = -(phase * 127) / 13; + phase -= 102; + phase &= 127; + } + } else { + if(phase < 2 || phase > 13) { + phase = 255; + } else { + const auto direction = phase & 1; + + phase >>= 1; + if(direction) phase ^= 0xf; + phase = (phase + 6 + direction) & 0xf; + + phase = (phase * 127) / 12; + phase &= 127; + } + } + + colour_palette_[index].original = colour; + uint8_t *target = reinterpret_cast(&colour_palette_[index].luminance_phase); + target[0] = luminance; + target[1] = phase; } void TIA::set_playfield(uint16_t offset, uint8_t value) { @@ -238,7 +253,7 @@ void TIA::set_playfield_control_and_ball_size(uint8_t value) { } void TIA::set_playfield_ball_colour(uint8_t colour) { - colour_palette_[static_cast(ColourIndex::PlayfieldBall)] = colour; + set_colour_palette_entry(size_t(ColourIndex::PlayfieldBall), colour); } void TIA::set_player_number_and_size(int player, uint8_t value) { @@ -300,7 +315,7 @@ void TIA::set_player_motion(int player, uint8_t motion) { void TIA::set_player_missile_colour(int player, uint8_t colour) { assert(player >= 0 && player < 2); - colour_palette_[static_cast(ColourIndex::PlayerMissile0) + player] = colour; + set_colour_palette_entry(size_t(ColourIndex::PlayerMissile0) + size_t(player), colour); } void TIA::set_missile_enable(int missile, bool enabled) { @@ -382,7 +397,6 @@ void TIA::output_for_cycles(int number_of_cycles) { bool is_reset = output_cursor < 224 && horizontal_counter_ >= 224; if(!output_cursor) { - if(line_end_function_) line_end_function_(collision_buffer_); std::memset(collision_buffer_, 0, sizeof(collision_buffer_)); ball_.motion_time %= 228; @@ -407,11 +421,11 @@ void TIA::output_for_cycles(int number_of_cycles) { #define Period(function, target) \ if(output_cursor < target) { \ if(horizontal_counter_ <= target) { \ - if(crt_) crt_->function(static_cast((horizontal_counter_ - output_cursor) * 2)); \ + crt_.function((horizontal_counter_ - output_cursor) * 2); \ horizontal_counter_ %= cycles_per_line; \ return; \ } else { \ - if(crt_) crt_->function(static_cast((target - output_cursor) * 2)); \ + crt_.function((target - output_cursor) * 2); \ output_cursor = target; \ } \ } @@ -437,19 +451,17 @@ void TIA::output_for_cycles(int number_of_cycles) { if(output_mode_ & blank_flag) { if(pixel_target_) { output_pixels(pixels_start_location_, output_cursor); - if(crt_) { - const unsigned int data_length = static_cast(output_cursor - pixels_start_location_); - crt_->output_data(data_length * 2, data_length); - } + const int data_length = int(output_cursor - pixels_start_location_); + crt_.output_data(data_length * 2, size_t(data_length)); pixel_target_ = nullptr; pixels_start_location_ = 0; } int duration = std::min(228, horizontal_counter_) - output_cursor; - if(crt_) crt_->output_blank(static_cast(duration * 2)); + crt_.output_blank(duration * 2); } else { - if(!pixels_start_location_ && crt_) { + if(!pixels_start_location_) { pixels_start_location_ = output_cursor; - pixel_target_ = crt_->allocate_write_area(160); + pixel_target_ = reinterpret_cast(crt_.begin_data(160)); } // convert that into pixels @@ -461,9 +473,9 @@ void TIA::output_for_cycles(int number_of_cycles) { output_cursor++; } - if(horizontal_counter_ == cycles_per_line && crt_) { - const unsigned int data_length = static_cast(output_cursor - pixels_start_location_); - crt_->output_data(data_length * 2, data_length); + if(horizontal_counter_ == cycles_per_line) { + const int data_length = int(output_cursor - pixels_start_location_); + crt_.output_data(data_length * 2, size_t(data_length)); pixel_target_ = nullptr; pixels_start_location_ = 0; } @@ -480,7 +492,7 @@ void TIA::output_pixels(int start, int end) { if(start < first_pixel_cycle+8 && horizontal_blank_extend_) { while(start < end && start < first_pixel_cycle+8) { - pixel_target_[target_position] = 0; + pixel_target_[target_position] = 0xff00; // TODO: this assumes little endianness. start++; target_position++; } @@ -489,13 +501,13 @@ void TIA::output_pixels(int start, int end) { if(playfield_priority_ == PlayfieldPriority::Score) { while(start < end && start < first_pixel_cycle + 80) { uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle]; - pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[static_cast(ColourMode::ScoreLeft)][buffer_value]]; + pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[static_cast(ColourMode::ScoreLeft)][buffer_value]].luminance_phase; start++; target_position++; } while(start < end) { uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle]; - pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[static_cast(ColourMode::ScoreRight)][buffer_value]]; + pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[static_cast(ColourMode::ScoreRight)][buffer_value]].luminance_phase; start++; target_position++; } @@ -503,7 +515,7 @@ void TIA::output_pixels(int start, int end) { int table_index = static_cast((playfield_priority_ == PlayfieldPriority::Standard) ? ColourMode::Standard : ColourMode::OnTop); while(start < end) { uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle]; - pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[table_index][buffer_value]]; + pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[table_index][buffer_value]].luminance_phase; start++; target_position++; } @@ -518,20 +530,16 @@ void TIA::output_line() { break; case sync_flag: case sync_flag | blank_flag: - if(crt_) { - crt_->output_sync(32); - crt_->output_blank(32); - crt_->output_sync(392); - } + crt_.output_sync(32); + crt_.output_blank(32); + crt_.output_sync(392); horizontal_blank_extend_ = false; break; case blank_flag: - if(crt_) { - crt_->output_blank(32); - crt_->output_sync(32); - crt_->output_default_colour_burst(32); - crt_->output_blank(360); - } + crt_.output_blank(32); + crt_.output_sync(32); + crt_.output_default_colour_burst(32); + crt_.output_blank(360); horizontal_blank_extend_ = false; break; } diff --git a/Machines/Atari2600/TIA.hpp b/Machines/Atari2600/TIA.hpp index a860b0dd1..c2d9a25a0 100644 --- a/Machines/Atari2600/TIA.hpp +++ b/Machines/Atari2600/TIA.hpp @@ -9,19 +9,19 @@ #ifndef TIA_hpp #define TIA_hpp +#include +#include #include +#include -#include "../CRTMachine.hpp" +#include "../../Outputs/CRT/CRT.hpp" +#include "../../ClockReceiver/ClockReceiver.hpp" namespace Atari2600 { class TIA { public: TIA(); - // The supplied hook is for unit testing only; if instantiated with a line_end_function then it will - // be called with the latest collision buffer upon the conclusion of each line. What's a collision - // buffer? It's an implementation detail. If you're not writing a unit test, leave it alone. - TIA(std::function line_end_function); enum class OutputMode { NTSC, PAL @@ -35,7 +35,7 @@ class TIA { void set_sync(bool sync); void set_blank(bool blank); - void reset_horizontal_counter(); // Reset is delayed by four cycles. + void reset_horizontal_counter(); // Reset is delayed by four cycles. /*! @returns the number of cycles between (current TIA time) + from_offset to the current or @@ -73,12 +73,11 @@ class TIA { uint8_t get_collision_flags(int offset); void clear_collision_flags(); - Outputs::CRT::CRT *get_crt() { return crt_.get(); } + void set_crt_delegate(Outputs::CRT::Delegate *); + void set_scan_target(Outputs::Display::ScanTarget *); private: - TIA(bool create_crt); - std::unique_ptr crt_; - std::function line_end_function_; + Outputs::CRT::CRT crt_; // the master counter; counts from 0 to 228 with all visible pixels being in the final 160 int horizontal_counter_ = 0; @@ -107,7 +106,7 @@ class TIA { ScoreRight, OnTop }; - uint8_t colour_mask_by_mode_collision_flags_[4][64]; // maps from [ColourMode][CollisionMark] to colour_pallete_ entry + uint8_t colour_mask_by_mode_collision_flags_[4][64]; // maps from [ColourMode][CollisionMark] to colour_palette_ entry enum class ColourIndex { Background = 0, @@ -115,7 +114,13 @@ class TIA { PlayerMissile0, PlayerMissile1 }; - uint8_t colour_palette_[4]; + struct Colour { + uint16_t luminance_phase; + uint8_t original; + }; + std::array colour_palette_; + void set_colour_palette_entry(size_t index, uint8_t colour); + OutputMode tv_standard_; // playfield state int background_half_mask_ = 0; @@ -302,7 +307,7 @@ class TIA { inline void output_line(); int pixels_start_location_ = 0; - uint8_t *pixel_target_ = nullptr; + uint16_t *pixel_target_ = nullptr; inline void output_pixels(int start, int end); }; diff --git a/Machines/CRTMachine.hpp b/Machines/CRTMachine.hpp index 9c7346782..413032bbe 100644 --- a/Machines/CRTMachine.hpp +++ b/Machines/CRTMachine.hpp @@ -9,7 +9,7 @@ #ifndef CRTMachine_hpp #define CRTMachine_hpp -#include "../Outputs/CRT/CRT.hpp" +#include "../Outputs/ScanTarget.hpp" #include "../Outputs/Speaker/Speaker.hpp" #include "../ClockReceiver/ClockReceiver.hpp" #include "../ClockReceiver/TimeTypes.hpp" @@ -19,6 +19,7 @@ #include +// TODO: rename. namespace CRTMachine { /*! @@ -29,19 +30,12 @@ namespace CRTMachine { class Machine { public: /*! - Causes the machine to set up its CRT and, if it has one, speaker. The caller guarantees - that an OpenGL context is bound. - */ - virtual void setup_output(float aspect_ratio) = 0; + Causes the machine to set up its display and, if it has one, speaker. - /*! - Gives the machine a chance to release all owned resources. The caller guarantees that the - OpenGL context is bound. + The @c scan_target will receive all video output; the caller guarantees + that it is non-null. */ - virtual void close_output() = 0; - - /// @returns The CRT this machine is drawing to. Should not be @c nullptr. - virtual Outputs::CRT::CRT *get_crt() = 0; + virtual void set_scan_target(Outputs::Display::ScanTarget *scan_target) = 0; /// @returns The speaker that receives this machine's output, or @c nullptr if this machine is mute. virtual Outputs::Speaker::Speaker *get_speaker() = 0; @@ -68,32 +62,33 @@ class Machine { } /*! - Maps from Configurable::Display to Outputs::CRT::VideoSignal and calls - @c set_video_signal with the result. + Maps from Configurable::Display to Outputs::Display::VideoSignal and calls + @c set_display_type with the result. */ void set_video_signal_configurable(Configurable::Display type) { - Outputs::CRT::VideoSignal signal; + Outputs::Display::DisplayType display_type; switch(type) { default: case Configurable::Display::RGB: - signal = Outputs::CRT::VideoSignal::RGB; + display_type = Outputs::Display::DisplayType::RGB; break; case Configurable::Display::SVideo: - signal = Outputs::CRT::VideoSignal::SVideo; + display_type = Outputs::Display::DisplayType::SVideo; break; - case Configurable::Display::Composite: - signal = Outputs::CRT::VideoSignal::Composite; + case Configurable::Display::CompositeColour: + display_type = Outputs::Display::DisplayType::CompositeColour; + break; + case Configurable::Display::CompositeMonochrome: + display_type = Outputs::Display::DisplayType::CompositeMonochrome; break; } - set_video_signal(signal); + set_display_type(display_type); } /*! - Forwards the video signal to the CRT returned by get_crt(). + Forwards the video signal to the target returned by get_crt(). */ - virtual void set_video_signal(Outputs::CRT::VideoSignal video_signal) { - get_crt()->set_video_signal(video_signal); - } + virtual void set_display_type(Outputs::Display::DisplayType display_type) {} private: double clock_rate_ = 1.0; diff --git a/Machines/ColecoVision/ColecoVision.cpp b/Machines/ColecoVision/ColecoVision.cpp index 6fe10319c..a8853657c 100644 --- a/Machines/ColecoVision/ColecoVision.cpp +++ b/Machines/ColecoVision/ColecoVision.cpp @@ -76,7 +76,7 @@ class Joystick: public Inputs::ConcreteJoystick { } break; - case Input::Up: if(is_active) direction_ &= ~0x01; else direction_ |= 0x01; break; + case Input::Up: if(is_active) direction_ &= ~0x01; else direction_ |= 0x01; break; case Input::Right: if(is_active) direction_ &= ~0x02; else direction_ |= 0x02; break; case Input::Down: if(is_active) direction_ &= ~0x04; else direction_ |= 0x04; break; case Input::Left: if(is_active) direction_ &= ~0x08; else direction_ |= 0x08; break; @@ -112,6 +112,7 @@ class ConcreteMachine: public: ConcreteMachine(const Analyser::Static::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : z80_(*this), + vdp_(TI::TMS::TMS9918A), sn76489_(TI::SN76489::Personality::SN76489, audio_queue_, sn76489_divider), ay_(audio_queue_), mixer_(sn76489_, ay_), @@ -159,6 +160,9 @@ class ConcreteMachine: is_megacart_ = false; } } + + // ColecoVisions have composite output only. + vdp_.set_display_type(Outputs::Display::DisplayType::CompositeColour); } ~ConcreteMachine() { @@ -169,17 +173,12 @@ class ConcreteMachine: return joysticks_; } - void setup_output(float aspect_ratio) override { - vdp_.reset(new TI::TMS::TMS9918(TI::TMS::TMS9918A)); - get_crt()->set_video_signal(Outputs::CRT::VideoSignal::Composite); + void set_scan_target(Outputs::Display::ScanTarget *scan_target) override { + vdp_.set_scan_target(scan_target); } - void close_output() override { - vdp_.reset(); - } - - Outputs::CRT::CRT *get_crt() override { - return vdp_->get_crt(); + void set_display_type(Outputs::Display::DisplayType display_type) override { + vdp_.set_display_type(display_type); } Outputs::Speaker::Speaker *get_speaker() override { @@ -249,9 +248,9 @@ class ConcreteMachine: switch((address >> 5) & 7) { case 5: update_video(); - *cycle.value = vdp_->get_register(address); - z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line()); - time_until_interrupt_ = vdp_->get_time_until_interrupt(); + *cycle.value = vdp_.get_register(address); + z80_.set_non_maskable_interrupt_line(vdp_.get_interrupt_line()); + time_until_interrupt_ = vdp_.get_time_until_interrupt(); break; case 7: { @@ -293,9 +292,9 @@ class ConcreteMachine: case 5: update_video(); - vdp_->set_register(address, *cycle.value); - z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line()); - time_until_interrupt_ = vdp_->get_time_until_interrupt(); + vdp_.set_register(address, *cycle.value); + z80_.set_non_maskable_interrupt_line(vdp_.get_interrupt_line()); + time_until_interrupt_ = vdp_.get_time_until_interrupt(); break; case 7: @@ -366,11 +365,11 @@ class ConcreteMachine: speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(sn76489_divider))); } inline void update_video() { - vdp_->run_for(time_since_vdp_update_.flush()); + vdp_.run_for(time_since_vdp_update_.flush()); } CPU::Z80::Processor z80_; - std::unique_ptr vdp_; + TI::TMS::TMS9918 vdp_; Concurrency::DeferringAsyncTaskQueue audio_queue_; TI::SN76489 sn76489_; diff --git a/Machines/Commodore/Vic-20/Keyboard.hpp b/Machines/Commodore/Vic-20/Keyboard.hpp index a847e14f8..7437f3420 100644 --- a/Machines/Commodore/Vic-20/Keyboard.hpp +++ b/Machines/Commodore/Vic-20/Keyboard.hpp @@ -34,7 +34,7 @@ enum Key: uint16_t { Key1 = key(0, 0x01), Key3 = key(0, 0x02), Key5 = key(0, 0x04), Key7 = key(0, 0x08), Key9 = key(0, 0x10), KeyPlus = key(0, 0x20), KeyGBP = key(0, 0x40), KeyDelete = key(0, 0x80), - KeyRestore = 0xfffd + KeyRestore = 0xfffd #undef key }; diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index 9a28bb588..027b0f9b5 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -50,7 +50,7 @@ enum ROMSlot { std::vector> get_options() { return Configurable::standard_options( - static_cast(Configurable::DisplaySVideo | Configurable::DisplayComposite | Configurable::QuickLoadTape) + static_cast(Configurable::DisplaySVideo | Configurable::DisplayCompositeColour | Configurable::QuickLoadTape) ); } @@ -294,6 +294,7 @@ class ConcreteMachine: public: ConcreteMachine(const Analyser::Static::Commodore::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : m6502_(*this), + mos6560_(mos6560_bus_handler_), user_port_via_port_handler_(new UserPortVIA), keyboard_via_port_handler_(new KeyboardVIA), serial_port_(new SerialPort), @@ -377,13 +378,16 @@ class ConcreteMachine: if(target.region == Analyser::Static::Commodore::Target::Region::American || target.region == Analyser::Static::Commodore::Target::Region::Japanese) { // NTSC set_clock_rate(1022727); - output_mode_ = MOS::MOS6560::OutputMode::NTSC; + mos6560_.set_output_mode(MOS::MOS6560::OutputMode::NTSC); } else { // PAL set_clock_rate(1108404); - output_mode_ = MOS::MOS6560::OutputMode::PAL; + mos6560_.set_output_mode(MOS::MOS6560::OutputMode::PAL); } + mos6560_.set_high_frequency_cutoff(1600); // There is a 1.6Khz low-pass filter in the Vic-20. + mos6560_.set_clock_rate(get_clock_rate()); + // Initialise the memory maps as all pointing to nothing memset(processor_read_memory_map_, 0, sizeof(processor_read_memory_map_)); memset(processor_write_memory_map_, 0, sizeof(processor_write_memory_map_)); @@ -501,7 +505,7 @@ class ConcreteMachine: if((address&0xfc00) == 0x9000) { if(!(address&0x100)) { update_video(); - result &= mos6560_->get_register(address); + result &= mos6560_.get_register(address); } if(address & 0x10) result &= user_port_via_.get_register(address); if(address & 0x20) result &= keyboard_via_.get_register(address); @@ -588,7 +592,7 @@ class ConcreteMachine: // The VIC is selected by bit 8 = 0 if(!(address&0x100)) { update_video(); - mos6560_->set_register(address, *value); + mos6560_.set_register(address, *value); } // The first VIA is selected by bit 4 = 1. if(address & 0x10) user_port_via_.set_register(address, *value); @@ -613,30 +617,23 @@ class ConcreteMachine: void flush() { update_video(); - mos6560_->flush(); + mos6560_.flush(); } void run_for(const Cycles cycles) override final { m6502_.run_for(cycles); } - void setup_output(float aspect_ratio) override final { - mos6560_.reset(new MOS::MOS6560::MOS6560(mos6560_bus_handler_)); - mos6560_->set_high_frequency_cutoff(1600); // There is a 1.6Khz low-pass filter in the Vic-20. - mos6560_->set_output_mode(output_mode_); - mos6560_->set_clock_rate(get_clock_rate()); + void set_scan_target(Outputs::Display::ScanTarget *scan_target) override final { + mos6560_.set_scan_target(scan_target); } - void close_output() override final { - mos6560_ = nullptr; - } - - Outputs::CRT::CRT *get_crt() override final { - return mos6560_->get_crt(); + void set_display_type(Outputs::Display::DisplayType display_type) override final { + mos6560_.set_display_type(display_type); } Outputs::Speaker::Speaker *get_speaker() override final { - return mos6560_->get_speaker(); + return mos6560_.get_speaker(); } void mos6522_did_change_interrupt_status(void *mos6522) override final { @@ -678,7 +675,7 @@ class ConcreteMachine: Configurable::SelectionSet get_accurate_selections() override { Configurable::SelectionSet selection_set; Configurable::append_quick_load_tape_selection(selection_set, false); - Configurable::append_display_selection(selection_set, Configurable::Display::Composite); + Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour); return selection_set; } @@ -701,7 +698,7 @@ class ConcreteMachine: private: void update_video() { - mos6560_->run_for(cycles_since_mos6560_update_.flush()); + mos6560_.run_for(cycles_since_mos6560_update_.flush()); } CPU::MOS6502::Processor m6502_; @@ -731,8 +728,7 @@ class ConcreteMachine: Cycles cycles_since_mos6560_update_; Vic6560BusHandler mos6560_bus_handler_; - MOS::MOS6560::OutputMode output_mode_; - std::unique_ptr> mos6560_; + MOS::MOS6560::MOS6560 mos6560_; std::shared_ptr user_port_via_port_handler_; std::shared_ptr keyboard_via_port_handler_; std::shared_ptr serial_port_; diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 74dc06721..75269411a 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -34,7 +34,7 @@ namespace Electron { std::vector> get_options() { return Configurable::standard_options( - static_cast(Configurable::DisplayRGB | Configurable::DisplayComposite | Configurable::QuickLoadTape) + static_cast(Configurable::DisplayRGB | Configurable::DisplayCompositeColour | Configurable::QuickLoadTape) ); } @@ -51,6 +51,7 @@ class ConcreteMachine: public: ConcreteMachine(const Analyser::Static::Acorn::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : m6502_(*this), + video_output_(ram_), sound_generator_(audio_queue_), speaker_(sound_generator_) { memset(key_states_, 0, sizeof(key_states_)); @@ -160,7 +161,7 @@ class ConcreteMachine: // 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 += video_output_->get_cycles_until_next_ram_availability(cycles_since_display_update_.as_int() + 1); + cycles += video_output_.get_cycles_until_next_ram_availability(cycles_since_display_update_.as_int() + 1); } else { switch(address & 0xff0f) { case 0xfe00: @@ -198,8 +199,8 @@ class ConcreteMachine: case 0xfe0c: case 0xfe0d: case 0xfe0e: case 0xfe0f: if(!isReadOperation(operation)) { update_display(); - video_output_->set_register(address, *value); - video_access_range_ = video_output_->get_memory_access_range(); + video_output_.set_register(address, *value); + video_access_range_ = video_output_.get_memory_access_range(); queue_next_display_interrupt(); } break; @@ -373,16 +374,12 @@ class ConcreteMachine: audio_queue_.perform(); } - void setup_output(float aspect_ratio) override final { - video_output_.reset(new VideoOutput(ram_)); + void set_scan_target(Outputs::Display::ScanTarget *scan_target) override final { + video_output_.set_scan_target(scan_target); } - void close_output() override final { - video_output_.reset(); - } - - Outputs::CRT::CRT *get_crt() override final { - return video_output_->get_crt(); + void set_display_type(Outputs::Display::DisplayType display_type) override { + video_output_.set_display_type(display_type); } Outputs::Speaker::Speaker *get_speaker() override final { @@ -436,7 +433,7 @@ class ConcreteMachine: Configurable::SelectionSet get_accurate_selections() override { Configurable::SelectionSet selection_set; Configurable::append_quick_load_tape_selection(selection_set, false); - Configurable::append_display_selection(selection_set, Configurable::Display::Composite); + Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour); return selection_set; } @@ -508,12 +505,12 @@ class ConcreteMachine: // MARK: - Work deferral updates. inline void update_display() { if(cycles_since_display_update_ > 0) { - video_output_->run_for(cycles_since_display_update_.flush()); + video_output_.run_for(cycles_since_display_update_.flush()); } } inline void queue_next_display_interrupt() { - VideoOutput::Interrupt next_interrupt = video_output_->get_next_interrupt(); + VideoOutput::Interrupt next_interrupt = video_output_.get_next_interrupt(); cycles_until_display_interrupt_ = next_interrupt.cycles; next_display_interrupt_ = next_interrupt.interrupt; } @@ -583,7 +580,7 @@ class ConcreteMachine: int shift_restart_counter_ = 0; // Outputs - std::unique_ptr video_output_; + VideoOutput video_output_; Concurrency::DeferringAsyncTaskQueue audio_queue_; SoundGenerator sound_generator_; diff --git a/Machines/Electron/Video.cpp b/Machines/Electron/Video.cpp index 82cabd225..0327e2d43 100644 --- a/Machines/Electron/Video.cpp +++ b/Machines/Electron/Video.cpp @@ -38,26 +38,26 @@ namespace { // MARK: - Lifecycle -VideoOutput::VideoOutput(uint8_t *memory) : ram_(memory) { +VideoOutput::VideoOutput(uint8_t *memory) : + ram_(memory), + crt_(crt_cycles_per_line, + 1, + Outputs::Display::Type::PAL50, + Outputs::Display::InputDataType::Red1Green1Blue1) { memset(palette_, 0xf, sizeof(palette_)); setup_screen_map(); setup_base_address(); - 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)" - "{" - "uint texValue = texture(sampler, coordinate).r;" - "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 - 1, 256, (first_graphics_cycle+1) * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f)); + crt_.set_visible_area(crt_.get_rect_for_area(first_graphics_line - 1, 256, (first_graphics_cycle+1) * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f)); } -// MARK: - CRT getter +void VideoOutput::set_scan_target(Outputs::Display::ScanTarget *scan_target) { + crt_.set_scan_target(scan_target); +} -Outputs::CRT::CRT *VideoOutput::get_crt() { - return crt_.get(); +void VideoOutput::set_display_type(Outputs::Display::DisplayType display_type) { + crt_.set_display_type(display_type); } // MARK: - Display update methods @@ -87,20 +87,20 @@ void VideoOutput::start_pixel_line() { } void VideoOutput::end_pixel_line() { - if(current_output_target_) { - const unsigned int data_length = static_cast(current_output_target_ - initial_output_target_); - crt_->output_data(data_length * current_output_divider_, data_length); + const int data_length = int(current_output_target_ - initial_output_target_); + if(data_length) { + crt_.output_data(data_length * current_output_divider_, size_t(data_length)); } current_character_row_++; } -void VideoOutput::output_pixels(unsigned int number_of_cycles) { +void VideoOutput::output_pixels(int number_of_cycles) { if(!number_of_cycles) return; if(is_blank_line_) { - crt_->output_blank(number_of_cycles * crt_cycles_multiplier); + crt_.output_blank(number_of_cycles * crt_cycles_multiplier); } else { - unsigned int divider = 1; + int divider = 1; switch(screen_mode_) { case 0: case 3: divider = 1; break; case 1: case 4: case 6: divider = 2; break; @@ -108,12 +108,12 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) { } if(!initial_output_target_ || divider != current_output_divider_) { - if(current_output_target_) { - const unsigned int data_length = static_cast(current_output_target_ - initial_output_target_); - crt_->output_data(data_length * current_output_divider_, data_length); + const int data_length = int(current_output_target_ - initial_output_target_); + if(data_length) { + crt_.output_data(data_length * current_output_divider_, size_t(data_length)); } current_output_divider_ = divider; - initial_output_target_ = current_output_target_ = crt_->allocate_write_area(640 / current_output_divider_, 8 / divider); + initial_output_target_ = current_output_target_ = crt_.begin_data(size_t(640 / current_output_divider_), size_t(8 / divider)); } #define get_pixel() \ @@ -132,7 +132,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) { current_output_target_ += 8; current_pixel_column_++; } - } else current_output_target_ += 4*number_of_cycles; + } else current_output_target_ += 8*number_of_cycles; break; case 1: @@ -143,7 +143,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) { current_output_target_ += 4; current_pixel_column_++; } - } else current_output_target_ += 2*number_of_cycles; + } else current_output_target_ += 4*number_of_cycles; break; case 2: @@ -154,7 +154,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) { current_output_target_ += 2; current_pixel_column_++; } - } else current_output_target_ += number_of_cycles; + } else current_output_target_ += 2*number_of_cycles; break; case 4: case 6: @@ -185,7 +185,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) { current_output_target_ += 4; current_pixel_column_++; } - } else current_output_target_ += 2 * number_of_cycles; + } else current_output_target_ += 4 * number_of_cycles; break; case 5: @@ -216,7 +216,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) { current_output_target_ += 2; current_pixel_column_++; } - } else current_output_target_ += number_of_cycles; + } else current_output_target_ += 2*number_of_cycles; break; } @@ -230,16 +230,16 @@ void VideoOutput::run_for(const Cycles cycles) { while(number_of_cycles) { int draw_action_length = screen_map_[screen_map_pointer_].length; int time_left_in_action = std::min(number_of_cycles, draw_action_length - cycles_into_draw_action_); - if(screen_map_[screen_map_pointer_].type == DrawAction::Pixels) output_pixels(static_cast(time_left_in_action)); + if(screen_map_[screen_map_pointer_].type == DrawAction::Pixels) output_pixels(time_left_in_action); number_of_cycles -= time_left_in_action; cycles_into_draw_action_ += time_left_in_action; if(cycles_into_draw_action_ == draw_action_length) { switch(screen_map_[screen_map_pointer_].type) { - case DrawAction::Sync: crt_->output_sync(static_cast(draw_action_length * crt_cycles_multiplier)); break; - case DrawAction::ColourBurst: crt_->output_default_colour_burst(static_cast(draw_action_length * crt_cycles_multiplier)); break; - case DrawAction::Blank: crt_->output_blank(static_cast(draw_action_length * crt_cycles_multiplier)); break; - case DrawAction::Pixels: end_pixel_line(); break; + case DrawAction::Sync: crt_.output_sync(draw_action_length * crt_cycles_multiplier); break; + case DrawAction::ColourBurst: crt_.output_default_colour_burst(draw_action_length * crt_cycles_multiplier); break; + case DrawAction::Blank: crt_.output_blank(draw_action_length * crt_cycles_multiplier); break; + case DrawAction::Pixels: end_pixel_line(); break; } screen_map_pointer_ = (screen_map_pointer_ + 1) % screen_map_.size(); cycles_into_draw_action_ = 0; diff --git a/Machines/Electron/Video.hpp b/Machines/Electron/Video.hpp index 04aa939f2..7313268c7 100644 --- a/Machines/Electron/Video.hpp +++ b/Machines/Electron/Video.hpp @@ -13,6 +13,8 @@ #include "../../ClockReceiver/ClockReceiver.hpp" #include "Interrupts.hpp" +#include + namespace Electron { /*! @@ -25,17 +27,21 @@ namespace Electron { class VideoOutput { public: /*! - Instantiates a VideoOutput that will read its pixels from @c memory. The pointer supplied - should be to address 0 in the unexpanded Electron's memory map. + Instantiates a VideoOutput that will read its pixels from @c memory. + + The pointer supplied should be to address 0 in the unexpanded Electron's memory map. */ VideoOutput(uint8_t *memory); - /// @returns the CRT to which output is being painted. - Outputs::CRT::CRT *get_crt(); - /// Produces the next @c cycles of video output. void run_for(const Cycles cycles); + /// Sets the destination for output. + void set_scan_target(Outputs::Display::ScanTarget *scan_target); + + /// Sets the type of output. + void set_display_type(Outputs::Display::DisplayType); + /*! Writes @c value to the register at @c address. May mutate the results of @c get_next_interrupt, @c get_cycles_until_next_ram_availability and @c get_memory_access_range. @@ -77,7 +83,7 @@ class VideoOutput { private: inline void start_pixel_line(); inline void end_pixel_line(); - inline void output_pixels(unsigned int number_of_cycles); + inline void output_pixels(int number_of_cycles); inline void setup_base_address(); int output_position_ = 0; @@ -109,9 +115,8 @@ class VideoOutput { // CRT output uint8_t *current_output_target_ = nullptr; uint8_t *initial_output_target_ = nullptr; - unsigned int current_output_divider_ = 1; - - std::unique_ptr crt_; + int current_output_divider_ = 1; + Outputs::CRT::CRT crt_; struct DrawAction { enum Type { diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index d7a34cf3a..755675da0 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -51,7 +51,7 @@ namespace MSX { std::vector> get_options() { return Configurable::standard_options( - static_cast(Configurable::DisplayRGB | Configurable::DisplaySVideo | Configurable::DisplayComposite | Configurable::QuickLoadTape) + static_cast(Configurable::DisplayRGB | Configurable::DisplaySVideo | Configurable::DisplayCompositeColour | Configurable::QuickLoadTape) ); } @@ -148,6 +148,7 @@ class ConcreteMachine: public: ConcreteMachine(const Analyser::Static::MSX::Target &target, const ROMMachine::ROMFetcher &rom_fetcher): z80_(*this), + vdp_(TI::TMS::TMS9918A), i8255_(i8255_port_handler_), ay_(audio_queue_), audio_toggle_(audio_queue_), @@ -218,16 +219,12 @@ class ConcreteMachine: audio_queue_.flush(); } - void setup_output(float aspect_ratio) override { - vdp_.reset(new TI::TMS::TMS9918(TI::TMS::TMS9918A)); + void set_scan_target(Outputs::Display::ScanTarget *scan_target) override { + vdp_.set_scan_target(scan_target); } - void close_output() override { - vdp_.reset(); - } - - Outputs::CRT::CRT *get_crt() override { - return vdp_->get_crt(); + void set_display_type(Outputs::Display::DisplayType display_type) override { + vdp_.set_display_type(display_type); } Outputs::Speaker::Speaker *get_speaker() override { @@ -451,10 +448,10 @@ class ConcreteMachine: case CPU::Z80::PartialMachineCycle::Input: switch(address & 0xff) { case 0x98: case 0x99: - vdp_->run_for(time_since_vdp_update_.flush()); - *cycle.value = vdp_->get_register(address); - z80_.set_interrupt_line(vdp_->get_interrupt_line()); - time_until_interrupt_ = vdp_->get_time_until_interrupt(); + vdp_.run_for(time_since_vdp_update_.flush()); + *cycle.value = vdp_.get_register(address); + z80_.set_interrupt_line(vdp_.get_interrupt_line()); + time_until_interrupt_ = vdp_.get_time_until_interrupt(); break; case 0xa2: @@ -479,10 +476,10 @@ class ConcreteMachine: const int port = address & 0xff; switch(port) { case 0x98: case 0x99: - vdp_->run_for(time_since_vdp_update_.flush()); - vdp_->set_register(address, *cycle.value); - z80_.set_interrupt_line(vdp_->get_interrupt_line()); - time_until_interrupt_ = vdp_->get_time_until_interrupt(); + vdp_.run_for(time_since_vdp_update_.flush()); + vdp_.set_register(address, *cycle.value); + z80_.set_interrupt_line(vdp_.get_interrupt_line()); + time_until_interrupt_ = vdp_.get_time_until_interrupt(); break; case 0xa0: case 0xa1: @@ -555,7 +552,7 @@ class ConcreteMachine: } void flush() { - vdp_->run_for(time_since_vdp_update_.flush()); + vdp_.run_for(time_since_vdp_update_.flush()); update_audio(); audio_queue_.perform(); } @@ -603,7 +600,7 @@ class ConcreteMachine: Configurable::SelectionSet get_accurate_selections() override { Configurable::SelectionSet selection_set; Configurable::append_quick_load_tape_selection(selection_set, false); - Configurable::append_display_selection(selection_set, Configurable::Display::Composite); + Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour); return selection_set; } @@ -652,7 +649,7 @@ class ConcreteMachine: case 2: { // TODO: // b6 caps lock LED - // b5 audio output + // b5 audio output // b4: cassette motor relay tape_player_.set_motor_control(!(value & 0x10)); @@ -685,7 +682,7 @@ class ConcreteMachine: }; CPU::Z80::Processor z80_; - std::unique_ptr vdp_; + TI::TMS::TMS9918 vdp_; Intel::i8255::i8255 i8255_; Concurrency::DeferringAsyncTaskQueue audio_queue_; diff --git a/Machines/MSX/ROMSlotHandler.hpp b/Machines/MSX/ROMSlotHandler.hpp index a617fce9b..e0fadbc64 100644 --- a/Machines/MSX/ROMSlotHandler.hpp +++ b/Machines/MSX/ROMSlotHandler.hpp @@ -43,6 +43,8 @@ class MemoryMap { class ROMSlotHandler { public: + virtual ~ROMSlotHandler() {} + /*! Advances time by @c half_cycles. */ virtual void run_for(HalfCycles half_cycles) {} diff --git a/Machines/MasterSystem/MasterSystem.cpp b/Machines/MasterSystem/MasterSystem.cpp index e49689cb0..7400c6dae 100644 --- a/Machines/MasterSystem/MasterSystem.cpp +++ b/Machines/MasterSystem/MasterSystem.cpp @@ -36,7 +36,7 @@ namespace MasterSystem { std::vector> get_options() { return Configurable::standard_options( - static_cast(Configurable::DisplayRGB | Configurable::DisplayComposite) + static_cast(Configurable::DisplayRGB | Configurable::DisplayCompositeColour) ); } @@ -57,7 +57,7 @@ class Joystick: public Inputs::ConcreteJoystick { switch(digital_input.type) { default: return; - case Input::Up: if(is_active) state_ &= ~0x01; else state_ |= 0x01; break; + case Input::Up: if(is_active) state_ &= ~0x01; else state_ |= 0x01; break; case Input::Down: if(is_active) state_ &= ~0x02; else state_ |= 0x02; break; case Input::Left: if(is_active) state_ &= ~0x04; else state_ |= 0x04; break; case Input::Right: if(is_active) state_ &= ~0x08; else state_ |= 0x08; break; @@ -94,6 +94,7 @@ class ConcreteMachine: region_(target.region), paging_scheme_(target.paging_scheme), z80_(*this), + vdp_(tms_personality_for_model(target.model)), sn76489_( (target.model == Target::Model::SG1000) ? TI::SN76489::Personality::SN76489 : TI::SN76489::Personality::SMS, audio_queue_, @@ -161,28 +162,17 @@ class ConcreteMachine: audio_queue_.flush(); } - void setup_output(float aspect_ratio) override { - TI::TMS::Personality personality = TI::TMS::TMS9918A; - switch(model_) { - case Target::Model::SG1000: personality = TI::TMS::TMS9918A; break; - case Target::Model::MasterSystem: personality = TI::TMS::SMSVDP; break; - case Target::Model::MasterSystem2: personality = TI::TMS::SMS2VDP; break; - } - vdp_.reset(new TI::TMS::TMS9918(personality)); - vdp_->set_tv_standard( + void set_scan_target(Outputs::Display::ScanTarget *scan_target) override { + vdp_.set_tv_standard( (region_ == Target::Region::Europe) ? TI::TMS::TVStandard::PAL : TI::TMS::TVStandard::NTSC); - get_crt()->set_video_signal(Outputs::CRT::VideoSignal::Composite); + time_until_debounce_ = vdp_.get_time_until_line(-1); - time_until_debounce_ = vdp_->get_time_until_line(-1); + vdp_.set_scan_target(scan_target); } - void close_output() override { - vdp_.reset(); - } - - Outputs::CRT::CRT *get_crt() override { - return vdp_->get_crt(); + void set_display_type(Outputs::Display::DisplayType display_type) override { + vdp_.set_display_type(display_type); } Outputs::Speaker::Speaker *get_speaker() override { @@ -239,16 +229,16 @@ class ConcreteMachine: break; case 0x40: update_video(); - *cycle.value = vdp_->get_current_line(); + *cycle.value = vdp_.get_current_line(); break; case 0x41: - *cycle.value = vdp_->get_latched_horizontal_counter(); + *cycle.value = vdp_.get_latched_horizontal_counter(); break; case 0x80: case 0x81: update_video(); - *cycle.value = vdp_->get_register(address); - z80_.set_interrupt_line(vdp_->get_interrupt_line()); - time_until_interrupt_ = vdp_->get_time_until_interrupt(); + *cycle.value = vdp_.get_register(address); + z80_.set_interrupt_line(vdp_.get_interrupt_line()); + time_until_interrupt_ = vdp_.get_time_until_interrupt(); break; case 0xc0: { Joystick *const joypad1 = static_cast(joysticks_[0].get()); @@ -290,7 +280,7 @@ class ConcreteMachine: // Latch if either TH has newly gone to 1. if((new_ths^previous_ths)&new_ths) { update_video(); - vdp_->latch_horizontal_counter(); + vdp_.latch_horizontal_counter(); } } break; case 0x40: case 0x41: @@ -299,9 +289,9 @@ class ConcreteMachine: break; case 0x80: case 0x81: update_video(); - vdp_->set_register(address, *cycle.value); - z80_.set_interrupt_line(vdp_->get_interrupt_line()); - time_until_interrupt_ = vdp_->get_time_until_interrupt(); + vdp_.set_register(address, *cycle.value); + z80_.set_interrupt_line(vdp_.get_interrupt_line()); + time_until_interrupt_ = vdp_.get_time_until_interrupt(); break; case 0xc0: LOG("TODO: [output] I/O port A/N; " << int(*cycle.value)); @@ -337,7 +327,7 @@ class ConcreteMachine: if(time_until_debounce_ <= HalfCycles(0)) { z80_.set_non_maskable_interrupt_line(pause_is_pressed_); update_video(); - time_until_debounce_ = vdp_->get_time_until_line(-1); + time_until_debounce_ = vdp_.get_time_until_line(-1); } return HalfCycles(0); @@ -366,7 +356,6 @@ class ConcreteMachine: } } - void reset_all_keys(Inputs::Keyboard *) override { } @@ -384,7 +373,7 @@ class ConcreteMachine: Configurable::SelectionSet get_accurate_selections() override { Configurable::SelectionSet selection_set; - Configurable::append_display_selection(selection_set, Configurable::Display::Composite); + Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour); return selection_set; } @@ -395,6 +384,15 @@ class ConcreteMachine: } private: + static TI::TMS::Personality tms_personality_for_model(Analyser::Static::Sega::Target::Model model) { + switch(model) { + default: + case Target::Model::SG1000: return TI::TMS::TMS9918A; + case Target::Model::MasterSystem: return TI::TMS::SMSVDP; + case Target::Model::MasterSystem2: return TI::TMS::SMS2VDP; + } + } + inline uint8_t get_th_values() { // Quick not on TH inputs here: if either is setup as an output, then the // currently output level is returned. Otherwise they're fixed at 1. @@ -410,7 +408,7 @@ class ConcreteMachine: speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(sn76489_divider))); } inline void update_video() { - vdp_->run_for(time_since_vdp_update_.flush()); + vdp_.run_for(time_since_vdp_update_.flush()); } using Target = Analyser::Static::Sega::Target; @@ -418,7 +416,7 @@ class ConcreteMachine: Target::Region region_; Target::PagingScheme paging_scheme_; CPU::Z80::Processor z80_; - std::unique_ptr vdp_; + TI::TMS::TMS9918 vdp_; Concurrency::DeferringAsyncTaskQueue audio_queue_; TI::SN76489 sn76489_; diff --git a/Machines/Oric/Keyboard.cpp b/Machines/Oric/Keyboard.cpp index 0dea3bc57..58b25179c 100644 --- a/Machines/Oric/Keyboard.cpp +++ b/Machines/Oric/Keyboard.cpp @@ -29,10 +29,10 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) { BIND(Hyphen, KeyMinus); BIND(Equals, KeyEquals); BIND(BackSlash, KeyBackSlash); BIND(OpenSquareBracket, KeyOpenSquare); BIND(CloseSquareBracket, KeyCloseSquare); - BIND(BackSpace, KeyDelete); BIND(Delete, KeyDelete); + BIND(BackSpace, KeyDelete); BIND(Delete, KeyDelete); BIND(Semicolon, KeySemiColon); BIND(Quote, KeyQuote); - BIND(Comma, KeyComma); BIND(FullStop, KeyFullStop); BIND(ForwardSlash, KeyForwardSlash); + BIND(Comma, KeyComma); BIND(FullStop, KeyFullStop); BIND(ForwardSlash, KeyForwardSlash); BIND(Escape, KeyEscape); BIND(Tab, KeyEscape); BIND(CapsLock, KeyControl); BIND(LeftControl, KeyControl); BIND(RightControl, KeyControl); diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index 1527973cb..34b847688 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -46,7 +46,11 @@ enum ROM { std::vector> get_options() { return Configurable::standard_options( - static_cast(Configurable::DisplayRGB | Configurable::DisplayComposite | Configurable::QuickLoadTape) + static_cast( + Configurable::DisplayRGB | + Configurable::DisplayCompositeColour | + Configurable::DisplayCompositeMonochrome | + Configurable::QuickLoadTape) ); } @@ -208,12 +212,14 @@ template class Co public: ConcreteMachine(const Analyser::Static::Oric::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : m6502_(*this), + video_output_(ram_), ay8910_(audio_queue_), speaker_(ay8910_), via_port_handler_(audio_queue_, ay8910_, speaker_, tape_player_, keyboard_), via_(via_port_handler_), diskii_(2000000) { set_clock_rate(1000000); + speaker_.set_input_rate(1000000.0f); via_port_handler_.set_interrupt_delegate(this); tape_player_.set_delegate(this); Memory::Fuzz(ram_, sizeof(ram_)); @@ -242,7 +248,7 @@ template class Co } } - colour_rom_ = std::move(*roms[0]); + video_output_.set_colour_rom(*roms[0]); rom_ = std::move(*roms[1]); switch(disk_interface) { @@ -263,7 +269,6 @@ template class Co } break; } - colour_rom_.resize(128); rom_.resize(16384); paged_rom_ = rom_.data(); @@ -456,20 +461,12 @@ template class Co } // to satisfy CRTMachine::Machine - void setup_output(float aspect_ratio) override final { - speaker_.set_input_rate(1000000.0f); - - video_output_.reset(new VideoOutput(ram_)); - if(!colour_rom_.empty()) video_output_->set_colour_rom(colour_rom_); - set_video_signal(Outputs::CRT::VideoSignal::RGB); + void set_scan_target(Outputs::Display::ScanTarget *scan_target) override final { + video_output_.set_scan_target(scan_target); } - void close_output() override final { - video_output_.reset(); - } - - Outputs::CRT::CRT *get_crt() override final { - return video_output_->get_crt(); + void set_display_type(Outputs::Display::DisplayType display_type) override { + video_output_.set_display_type(display_type); } Outputs::Speaker::Speaker *get_speaker() override final { @@ -537,14 +534,10 @@ template class Co } } - void set_video_signal(Outputs::CRT::VideoSignal video_signal) override { - video_output_->set_video_signal(video_signal); - } - Configurable::SelectionSet get_accurate_selections() override { Configurable::SelectionSet selection_set; Configurable::append_quick_load_tape_selection(selection_set, false); - Configurable::append_display_selection(selection_set, Configurable::Display::Composite); + Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour); return selection_set; } @@ -578,11 +571,11 @@ template class Co CPU::MOS6502::Processor m6502_; // RAM and ROM - std::vector rom_, microdisc_rom_, colour_rom_; + std::vector rom_, microdisc_rom_; uint8_t ram_[65536]; Cycles cycles_since_video_update_; inline void update_video() { - video_output_->run_for(cycles_since_video_update_.flush()); + video_output_.run_for(cycles_since_video_update_.flush()); } // ROM bookkeeping @@ -590,7 +583,7 @@ template class Co int keyboard_read_count_ = 0; // Outputs - std::unique_ptr video_output_; + VideoOutput video_output_; Concurrency::DeferringAsyncTaskQueue audio_queue_; GI::AY38910::AY38910 ay8910_; diff --git a/Machines/Oric/Video.cpp b/Machines/Oric/Video.cpp index e28d7cfe0..270c489ed 100644 --- a/Machines/Oric/Video.cpp +++ b/Machines/Oric/Video.cpp @@ -8,6 +8,10 @@ #include "Video.hpp" +#include + +//#define SUPPLY_COMPOSITE + using namespace Oric; namespace { @@ -21,54 +25,66 @@ namespace { VideoOutput::VideoOutput(uint8_t *memory) : ram_(memory), - crt_(new Outputs::CRT::CRT(64*6, 6, Outputs::CRT::DisplayType::PAL50, 2)), + crt_(64*6, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red1Green1Blue1), v_sync_start_position_(PAL50VSyncStartPosition), v_sync_end_position_(PAL50VSyncEndPosition), counter_period_(PAL50Period) { - crt_->set_rgb_sampling_function( - "vec3 rgb_sample(usampler2D sampler, vec2 coordinate)" - "{" - "uint texValue = texture(sampler, coordinate).r;" - "return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));" - "}"); - crt_->set_composite_sampling_function( - "float composite_sample(usampler2D sampler, vec2 coordinate, float phase, float amplitude)" - "{" - "uint texValue = uint(dot(texture(sampler, coordinate).rg, uvec2(1, 256)));" - "uint iPhase = uint((phase + 3.141592654 + 0.39269908175) * 2.0 / 3.141592654) & 3u;" - "texValue = (texValue >> (4u*(3u - iPhase))) & 15u;" - "return (float(texValue) - 4.0) / 20.0;" - "}" - ); - crt_->set_composite_function_type(Outputs::CRT::CRT::CompositeSourceType::DiscreteFourSamplesPerCycle, 0.0f); - - set_video_signal(Outputs::CRT::VideoSignal::Composite); - crt_->set_visible_area(crt_->get_rect_for_area(54, 224, 16 * 6, 40 * 6, 4.0f / 3.0f)); + crt_.set_visible_area(crt_.get_rect_for_area(54, 224, 16 * 6, 40 * 6, 4.0f / 3.0f)); + crt_.set_phase_linked_luminance_offset(-1.0f / 8.0f); + data_type_ = Outputs::Display::InputDataType::Red1Green1Blue1; + crt_.set_input_data_type(data_type_); } -void VideoOutput::set_video_signal(Outputs::CRT::VideoSignal video_signal) { - video_signal_ = video_signal; - crt_->set_video_signal(video_signal); +void VideoOutput::set_display_type(Outputs::Display::DisplayType display_type) { + crt_.set_display_type(display_type); + +#ifdef SUPPLY_COMPOSITE + const auto data_type = + (display_type == Outputs::Display::DisplayType::RGB) ? + Outputs::Display::InputDataType::Red1Green1Blue1 : + Outputs::Display::InputDataType::PhaseLinkedLuminance8; +#else + const auto data_type = Outputs::Display::InputDataType::Red1Green1Blue1; +#endif + + if(data_type_ != data_type) { + data_type_ = data_type; + crt_.set_input_data_type(data_type_); + } +} + +void VideoOutput::set_scan_target(Outputs::Display::ScanTarget *scan_target) { + crt_.set_scan_target(scan_target); } void VideoOutput::set_colour_rom(const std::vector &rom) { for(std::size_t c = 0; c < 8; c++) { - std::size_t index = (c << 2); - uint16_t rom_value = static_cast((static_cast(rom[index]) << 8) | static_cast(rom[index+1])); - rom_value = (rom_value & 0xff00) | ((rom_value >> 4)&0x000f) | ((rom_value << 4)&0x00f0); - colour_forms_[c] = rom_value; - } + colour_forms_[c] = 0; - // check for big endianness and byte swap if required - uint16_t test_value = 0x0001; - if(*reinterpret_cast(&test_value) != 0x01) { - for(std::size_t c = 0; c < 8; c++) { - colour_forms_[c] = static_cast((colour_forms_[c] >> 8) | (colour_forms_[c] << 8)); + uint8_t *const colour = reinterpret_cast(&colour_forms_[c]); + const std::size_t index = (c << 2); + + // Values in the ROM are encoded for indexing by two square waves + // in quadrature, which means that they're indexed in the order + // 0, 1, 3, 2. + colour[0] = uint8_t((rom[index] & 0x0f) << 4); + colour[1] = uint8_t(rom[index] & 0xf0); + colour[2] = uint8_t(rom[index+1] & 0xf0); + colour[3] = uint8_t((rom[index+1] & 0x0f) << 4); + + // Extracting just the visible part of the stored range of values + // means extracting the range 0x40 to 0xe0. + for(int sub = 0; sub < 4; ++sub) { + colour[sub] = ((colour[sub] - 0x40) * 255) / 0xa0; } } -} -Outputs::CRT::CRT *VideoOutput::get_crt() { - return crt_.get(); + // Check for big endianness and byte swap if required. +// uint32_t test_value = 0x0001; +// if(*reinterpret_cast(&test_value) != 0x01) { +// for(std::size_t c = 0; c < 8; c++) { +// colour_forms_[c] = static_cast((colour_forms_[c] >> 8) | (colour_forms_[c] << 8)); +// } +// } } void VideoOutput::run_for(const Cycles cycles) { @@ -86,7 +102,7 @@ void VideoOutput::run_for(const Cycles cycles) { if(counter_ >= v_sync_start_position_ && counter_ < v_sync_end_position_) { // this is a sync line cycles_run_for = v_sync_end_position_ - counter_; - clamp(crt_->output_sync(static_cast(v_sync_end_position_ - v_sync_start_position_) * 6)); + clamp(crt_.output_sync((v_sync_end_position_ - v_sync_start_position_) * 6)); } else if(counter_ < 224*64 && h_counter < 40) { // this is a pixel line if(!h_counter) { @@ -94,7 +110,12 @@ void VideoOutput::run_for(const Cycles cycles) { paper_ = 0x0; use_alternative_character_set_ = use_double_height_characters_ = blink_text_ = false; set_character_set_base_address(); - pixel_target_ = reinterpret_cast(crt_->allocate_write_area(240)); + + if(data_type_ == Outputs::Display::InputDataType::Red1Green1Blue1) { + rgb_pixel_target_ = reinterpret_cast(crt_.begin_data(240)); + } else { + composite_pixel_target_ = reinterpret_cast(crt_.begin_data(240)); + } if(!counter_) { frame_counter_++; @@ -109,7 +130,7 @@ void VideoOutput::run_for(const Cycles cycles) { int columns = cycles_run_for; int pixel_base_address = 0xa000 + (counter_ >> 6) * 40; int character_base_address = 0xbb80 + (counter_ >> 9) * 40; - uint8_t blink_mask = (blink_text_ && (frame_counter_&32)) ? 0x00 : 0xff; + const uint8_t blink_mask = (blink_text_ && (frame_counter_&32)) ? 0x00 : 0xff; while(columns--) { uint8_t pixels, control_byte; @@ -119,29 +140,36 @@ void VideoOutput::run_for(const Cycles cycles) { } else { int address = character_base_address + h_counter; control_byte = ram_[address]; - int line = use_double_height_characters_ ? ((counter_ >> 7) & 7) : ((counter_ >> 6) & 7); + const int line = use_double_height_characters_ ? ((counter_ >> 7) & 7) : ((counter_ >> 6) & 7); pixels = ram_[character_set_base_address_ + (control_byte&127) * 8 + line]; } - uint8_t inverse_mask = (control_byte & 0x80) ? 0x7 : 0x0; + const uint8_t inverse_mask = (control_byte & 0x80) ? 0x7 : 0x0; pixels &= blink_mask; if(control_byte & 0x60) { - if(pixel_target_) { - uint16_t colours[2]; - if(video_signal_ == Outputs::CRT::VideoSignal::RGB) { - colours[0] = static_cast(paper_ ^ inverse_mask); - colours[1] = static_cast(ink_ ^ inverse_mask); - } else { - colours[0] = colour_forms_[paper_ ^ inverse_mask]; - colours[1] = colour_forms_[ink_ ^ inverse_mask]; - } - pixel_target_[0] = colours[(pixels >> 5)&1]; - pixel_target_[1] = colours[(pixels >> 4)&1]; - pixel_target_[2] = colours[(pixels >> 3)&1]; - pixel_target_[3] = colours[(pixels >> 2)&1]; - pixel_target_[4] = colours[(pixels >> 1)&1]; - pixel_target_[5] = colours[(pixels >> 0)&1]; + if(data_type_ == Outputs::Display::InputDataType::Red1Green1Blue1 && rgb_pixel_target_) { + const uint8_t colours[2] = { + uint8_t(paper_ ^ inverse_mask), + uint8_t(ink_ ^ inverse_mask) + }; + rgb_pixel_target_[0] = colours[(pixels >> 5)&1]; + rgb_pixel_target_[1] = colours[(pixels >> 4)&1]; + rgb_pixel_target_[2] = colours[(pixels >> 3)&1]; + rgb_pixel_target_[3] = colours[(pixels >> 2)&1]; + rgb_pixel_target_[4] = colours[(pixels >> 1)&1]; + rgb_pixel_target_[5] = colours[(pixels >> 0)&1]; + } else if(composite_pixel_target_) { + const uint32_t colours[2] = { + colour_forms_[paper_ ^ inverse_mask], + colour_forms_[ink_ ^ inverse_mask] + }; + composite_pixel_target_[0] = colours[(pixels >> 5)&1]; + composite_pixel_target_[1] = colours[(pixels >> 4)&1]; + composite_pixel_target_[2] = colours[(pixels >> 3)&1]; + composite_pixel_target_[3] = colours[(pixels >> 2)&1]; + composite_pixel_target_[4] = colours[(pixels >> 1)&1]; + composite_pixel_target_[5] = colours[(pixels >> 0)&1]; } } else { switch(control_byte & 0x1f) { @@ -179,19 +207,26 @@ void VideoOutput::run_for(const Cycles cycles) { default: break; } - if(pixel_target_) { - pixel_target_[0] = pixel_target_[1] = - pixel_target_[2] = pixel_target_[3] = - pixel_target_[4] = pixel_target_[5] = - (video_signal_ == Outputs::CRT::VideoSignal::RGB) ? paper_ ^ inverse_mask : colour_forms_[paper_ ^ inverse_mask]; + + if(data_type_ == Outputs::Display::InputDataType::Red1Green1Blue1 && rgb_pixel_target_) { + rgb_pixel_target_[0] = rgb_pixel_target_[1] = + rgb_pixel_target_[2] = rgb_pixel_target_[3] = + rgb_pixel_target_[4] = rgb_pixel_target_[5] = paper_ ^ inverse_mask; + } else if(composite_pixel_target_) { + composite_pixel_target_[0] = composite_pixel_target_[1] = + composite_pixel_target_[2] = composite_pixel_target_[3] = + composite_pixel_target_[4] = composite_pixel_target_[5] = colour_forms_[paper_ ^ inverse_mask]; } } - if(pixel_target_) pixel_target_ += 6; + if(rgb_pixel_target_) rgb_pixel_target_ += 6; + if(composite_pixel_target_) composite_pixel_target_ += 6; h_counter++; } if(h_counter == 40) { - crt_->output_data(40 * 6); + crt_.output_data(40 * 6); + rgb_pixel_target_ = nullptr; + composite_pixel_target_ = nullptr; } } else { // this is a blank line (or the equivalent part of a pixel line) @@ -199,17 +234,17 @@ void VideoOutput::run_for(const Cycles cycles) { cycles_run_for = 48 - h_counter; clamp( int period = (counter_ < 224*64) ? 8 : 48; - crt_->output_blank(static_cast(period) * 6); + crt_.output_blank(period * 6); ); } else if(h_counter < 54) { cycles_run_for = 54 - h_counter; - clamp(crt_->output_sync(6 * 6)); + clamp(crt_.output_sync(6 * 6)); } else if(h_counter < 56) { cycles_run_for = 56 - h_counter; - clamp(crt_->output_default_colour_burst(2 * 6)); + clamp(crt_.output_default_colour_burst(2 * 6)); } else { cycles_run_for = 64 - h_counter; - clamp(crt_->output_blank(8 * 6)); + clamp(crt_.output_blank(8 * 6)); } } diff --git a/Machines/Oric/Video.hpp b/Machines/Oric/Video.hpp index e65a54fc0..2a6e18f3a 100644 --- a/Machines/Oric/Video.hpp +++ b/Machines/Oric/Video.hpp @@ -12,28 +12,35 @@ #include "../../Outputs/CRT/CRT.hpp" #include "../../ClockReceiver/ClockReceiver.hpp" +#include +#include +#include + namespace Oric { class VideoOutput { public: VideoOutput(uint8_t *memory); - Outputs::CRT::CRT *get_crt(); + void set_colour_rom(const std::vector &colour_rom); + void run_for(const Cycles cycles); - void set_colour_rom(const std::vector &rom); - void set_video_signal(Outputs::CRT::VideoSignal output_device); + + void set_scan_target(Outputs::Display::ScanTarget *scan_target); + void set_display_type(Outputs::Display::DisplayType display_type); private: uint8_t *ram_; - std::unique_ptr crt_; + Outputs::CRT::CRT crt_; // Counters and limits int counter_ = 0, frame_counter_ = 0; int v_sync_start_position_, v_sync_end_position_, counter_period_; // Output target and device - uint16_t *pixel_target_; - uint16_t colour_forms_[8]; - Outputs::CRT::VideoSignal video_signal_; + uint8_t *rgb_pixel_target_; + uint32_t *composite_pixel_target_; + uint32_t colour_forms_[8]; + Outputs::Display::InputDataType data_type_; // Registers uint8_t ink_, paper_; diff --git a/Machines/Utility/MachineForTarget.cpp b/Machines/Utility/MachineForTarget.cpp index 44f0c43db..2ab1f0dac 100644 --- a/Machines/Utility/MachineForTarget.cpp +++ b/Machines/Utility/MachineForTarget.cpp @@ -132,6 +132,7 @@ std::map>> Machin std::map>> options; options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AmstradCPC), AmstradCPC::get_options())); + options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AppleII), AppleII::get_options())); options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), Electron::get_options())); options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::MSX), MSX::get_options())); options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Oric), Oric::get_options())); diff --git a/Machines/Utility/Typer.hpp b/Machines/Utility/Typer.hpp index 5304b1a73..93e02d0f3 100644 --- a/Machines/Utility/Typer.hpp +++ b/Machines/Utility/Typer.hpp @@ -23,6 +23,8 @@ namespace Utility { */ class CharacterMapper { public: + virtual ~CharacterMapper() {} + /// @returns The EndSequence-terminated sequence of keys that would cause @c character to be typed. virtual uint16_t *sequence_for_character(char character) = 0; diff --git a/Machines/ZX8081/Video.cpp b/Machines/ZX8081/Video.cpp index ce2fcbc55..9fe5ba77c 100644 --- a/Machines/ZX8081/Video.cpp +++ b/Machines/ZX8081/Video.cpp @@ -8,6 +8,8 @@ #include "Video.hpp" +#include + using namespace ZX8081; namespace { @@ -21,19 +23,11 @@ const std::size_t StandardAllocationSize = 320; } Video::Video() : - crt_(new Outputs::CRT::CRT(207 * 2, 1, Outputs::CRT::DisplayType::PAL50, 1)) { - - // Set a composite sampling function that assumes two-level input; either a byte is 0, which is black, - // or it is non-zero, which is white. - crt_->set_composite_sampling_function( - "float composite_sample(usampler2D sampler, vec2 coordinate, float phase, float amplitude)" - "{" - "return texture(sampler, coordinate).r;" - "}"); + crt_(207 * 2, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Luminance1) { // Show only the centre 80% of the TV frame. - crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite); - crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.1f, 0.8f, 0.8f)); + crt_.set_display_type(Outputs::Display::DisplayType::CompositeMonochrome); + crt_.set_visible_area(Outputs::Display::Rect(0.1f, 0.1f, 0.8f, 0.8f)); } void Video::run_for(const HalfCycles half_cycles) { @@ -48,7 +42,7 @@ void Video::flush() { void Video::flush(bool next_sync) { if(sync_) { // If in sync, that takes priority. Output the proper amount of sync. - crt_->output_sync(static_cast(time_since_update_.as_int())); + crt_.output_sync(time_since_update_.as_int()); } else { // If not presently in sync, then... @@ -58,16 +52,16 @@ void Video::flush(bool next_sync) { int data_length = static_cast(line_data_pointer_ - line_data_); if(data_length < time_since_update_.as_int() || next_sync) { auto output_length = std::min(data_length, time_since_update_.as_int()); - crt_->output_data(static_cast(output_length), static_cast(output_length)); + crt_.output_data(output_length); line_data_pointer_ = line_data_ = nullptr; time_since_update_ -= HalfCycles(output_length); } else return; } // Any pending pixels being dealt with, pad with the white level. - uint8_t *colour_pointer = static_cast(crt_->allocate_write_area(1)); + uint8_t *colour_pointer = static_cast(crt_.begin_data(1)); if(colour_pointer) *colour_pointer = 0xff; - crt_->output_level(static_cast(time_since_update_.as_int())); + crt_.output_level(time_since_update_.as_int()); } time_since_update_ = 0; @@ -89,16 +83,16 @@ void Video::output_byte(uint8_t byte) { // Grab a buffer if one isn't already available. if(!line_data_) { - line_data_pointer_ = line_data_ = crt_->allocate_write_area(StandardAllocationSize); + line_data_pointer_ = line_data_ = crt_.begin_data(StandardAllocationSize); } // If a buffer was obtained, serialise the new pixels. if(line_data_) { // If the buffer is full, output it now and obtain a new one if(line_data_pointer_ - line_data_ == StandardAllocationSize) { - crt_->output_data(StandardAllocationSize, StandardAllocationSize); + crt_.output_data(StandardAllocationSize, StandardAllocationSize); time_since_update_ -= StandardAllocationSize; - line_data_pointer_ = line_data_ = crt_->allocate_write_area(StandardAllocationSize); + line_data_pointer_ = line_data_ = crt_.begin_data(StandardAllocationSize); if(!line_data_) return; } @@ -112,6 +106,6 @@ void Video::output_byte(uint8_t byte) { } } -Outputs::CRT::CRT *Video::get_crt() { - return crt_.get(); +void Video::set_scan_target(Outputs::Display::ScanTarget *scan_target) { + crt_.set_scan_target(scan_target); } diff --git a/Machines/ZX8081/Video.hpp b/Machines/ZX8081/Video.hpp index dac52b361..b58c6c149 100644 --- a/Machines/ZX8081/Video.hpp +++ b/Machines/ZX8081/Video.hpp @@ -26,10 +26,8 @@ namespace ZX8081 { */ class Video { public: - /// Constructs an instance of the video feed; a CRT is also created. + /// Constructs an instance of the video feed. Video(); - /// @returns The CRT this video feed is feeding. - Outputs::CRT::CRT *get_crt(); /// Advances time by @c half-cycles. void run_for(const HalfCycles); @@ -41,12 +39,15 @@ class Video { /// Causes @c byte to be serialised into pixels and output over the next four cycles. void output_byte(uint8_t byte); + /// Sets the scan target. + void set_scan_target(Outputs::Display::ScanTarget *scan_target); + private: bool sync_ = false; uint8_t *line_data_ = nullptr; uint8_t *line_data_pointer_ = nullptr; HalfCycles time_since_update_ = 0; - std::unique_ptr crt_; + Outputs::CRT::CRT crt_; void flush(bool next_sync); }; diff --git a/Machines/ZX8081/ZX8081.cpp b/Machines/ZX8081/ZX8081.cpp index dfc59bb3d..ab9818006 100644 --- a/Machines/ZX8081/ZX8081.cpp +++ b/Machines/ZX8081/ZX8081.cpp @@ -135,23 +135,23 @@ template class ConcreteMachine: time_since_ay_update_ += cycle.length; if(previous_counter < vsync_start_ && horizontal_counter_ >= vsync_start_) { - video_->run_for(vsync_start_ - previous_counter); + video_.run_for(vsync_start_ - previous_counter); set_hsync(true); line_counter_ = (line_counter_ + 1) & 7; if(nmi_is_enabled_) { z80_.set_non_maskable_interrupt_line(true); } - video_->run_for(horizontal_counter_ - vsync_start_); + video_.run_for(horizontal_counter_ - vsync_start_); } else if(previous_counter < vsync_end_ && horizontal_counter_ >= vsync_end_) { - video_->run_for(vsync_end_ - previous_counter); + video_.run_for(vsync_end_ - previous_counter); set_hsync(false); if(nmi_is_enabled_) { z80_.set_non_maskable_interrupt_line(false); z80_.set_wait_line(false); } - video_->run_for(horizontal_counter_ - vsync_end_); + video_.run_for(horizontal_counter_ - vsync_end_); } else { - video_->run_for(cycle.length); + video_.run_for(cycle.length); } if(is_zx81) horizontal_counter_ %= HalfCycles(Cycles(207)); @@ -240,7 +240,7 @@ template class ConcreteMachine: latched_video_byte_ = ram_[address & ram_mask_] ^ mask; } - video_->output_byte(latched_video_byte_); + video_.output_byte(latched_video_byte_); has_latched_video_byte_ = false; } break; @@ -303,23 +303,15 @@ template class ConcreteMachine: } forceinline void flush() { - video_->flush(); + video_.flush(); if(is_zx81) { update_audio(); audio_queue_.perform(); } } - void setup_output(float aspect_ratio) override final { - video_.reset(new Video); - } - - void close_output() override final { - video_.reset(); - } - - Outputs::CRT::CRT *get_crt() override final { - return video_->get_crt(); + void set_scan_target(Outputs::Display::ScanTarget *scan_target) override final { + video_.set_scan_target(scan_target); } Outputs::Speaker::Speaker *get_speaker() override final { @@ -415,8 +407,7 @@ template class ConcreteMachine: private: CPU::Z80::Processor z80_; - - std::unique_ptr