From 9d340599a68372524a642c3d816a1a8d6aa2261b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 27 Jan 2020 23:00:30 -0500 Subject: [PATCH 01/25] Towards ST 1bpp support: puts vsync in an appropriate location, starts experimenting with proper CRT timings. --- Machines/Atari/ST/AtariST.cpp | 2 +- Machines/Atari/ST/Video.cpp | 32 +++++-- .../Clock Signal.xcodeproj/project.pbxproj | 9 +- .../xcschemes/Clock Signal Kiosk.xcscheme | 93 +++++++++++++++++++ .../xcschemes/Clock Signal.xcscheme | 2 +- .../xcschemes/Clock SignalTests.xcscheme | 71 ++++++++++++++ 6 files changed, 196 insertions(+), 13 deletions(-) create mode 100644 OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme create mode 100644 OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock SignalTests.xcscheme diff --git a/Machines/Atari/ST/AtariST.cpp b/Machines/Atari/ST/AtariST.cpp index c7e9a1a05..bcf0c09d6 100644 --- a/Machines/Atari/ST/AtariST.cpp +++ b/Machines/Atari/ST/AtariST.cpp @@ -567,7 +567,7 @@ class ConcreteMachine: GPIP 0: centronics busy */ mfp_->set_port_input( - 0x80 | // b7: Monochrome monitor detect (1 = is monochrome). + 0x00 | // b7: Monochrome monitor detect (0 = is monochrome). 0x40 | // b6: RS-232 ring indicator. (dma_->get_interrupt_line() ? 0x00 : 0x20) | // b5: FD/HS interrupt (0 = interrupt requested). ((keyboard_acia_->get_interrupt_line() || midi_acia_->get_interrupt_line()) ? 0x00 : 0x10) | // b4: Keyboard/MIDI interrupt (0 = interrupt requested). diff --git a/Machines/Atari/ST/Video.cpp b/Machines/Atari/ST/Video.cpp index 1170928f1..eb15ca8eb 100644 --- a/Machines/Atari/ST/Video.cpp +++ b/Machines/Atari/ST/Video.cpp @@ -29,7 +29,7 @@ const struct VerticalParams { } vertical_params[3] = { {63, 263, 313}, // 47 rather than 63 on early machines. {34, 234, 263}, - {1, 401, 500} // 72 Hz mode: who knows? + {51, 451, 500} // 72 Hz mode: who knows? }; /// @returns The correct @c VerticalParams for output at @c frequency. @@ -57,13 +57,20 @@ const struct HorizontalParams { const int set_blank; const int reset_blank; + const int vertical_decision; + const int length; } horizontal_params[3] = { - {CYCLE(56), CYCLE(376), CYCLE(450), CYCLE(28), CYCLE(512)}, - {CYCLE(52), CYCLE(372), CYCLE(450), CYCLE(24), CYCLE(508)}, - {CYCLE(4), CYCLE(164), CYCLE(999), CYCLE(999), CYCLE(224)} // 72Hz mode doesn't set or reset blank. + {CYCLE(56), CYCLE(376), CYCLE(450), CYCLE(28), CYCLE(502), CYCLE(512)}, + {CYCLE(52), CYCLE(372), CYCLE(450), CYCLE(24), CYCLE(502), CYCLE(508)}, + {CYCLE(4), CYCLE(164), CYCLE(999), CYCLE(999), CYCLE(214), CYCLE(224)} // 72Hz mode doesn't set or reset blank. }; +// Re: 'vertical_decision': +// This is cycle 502 if in 50 or 60 Hz mode; in 70 Hz mode I've put it on cycle 214 +// in order to be analogous to 50 and 60 Hz mode. I have no idea where it should +// actually go. + const HorizontalParams &horizontal_parameters(Video::FieldFrequency frequency) { return horizontal_params[int(frequency)]; } @@ -111,12 +118,13 @@ const int vsync_delay_period = hsync_delay_period; // Signal vsync with the same Video::Video() : deferrer_([=] (HalfCycles duration) { advance(duration); }), - crt_(1024, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4), +// crt_(1024, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4), + crt_(448, 1, 500, Outputs::Display::ColourSpace::YIQ, 100, 50, 5, false, Outputs::Display::InputDataType::Red4Green4Blue4), video_stream_(crt_, palette_) { // Show a total of 260 lines; a little short for PAL but a compromise between that and the ST's // usual output height of 200 lines. - crt_.set_visible_area(crt_.get_rect_for_area(33, 260, 220, 850, 4.0f / 3.0f)); +// crt_.set_visible_area(crt_.get_rect_for_area(33, 260, 220, 850, 4.0f / 3.0f)); } void Video::set_ram(uint16_t *ram, size_t size) { @@ -256,8 +264,8 @@ void Video::advance(HalfCycles duration) { // Check for whether line length should have been latched during this run. if(x_ <= CYCLE(54) && (x_ + run_length) > CYCLE(54)) line_length_ = horizontal_timings.length; - // Make a decision about vertical state on cycle 502. - if(x_ <= CYCLE(502) && (x_ + run_length) > CYCLE(502)) { + // Make a decision about vertical state on the appropriate cycle. + if(x_ <= horizontal_timings.vertical_decision && (x_ + run_length) > horizontal_timings.vertical_decision) { next_y_ = y_ + 1; next_vertical_ = vertical_; next_vertical_.sync_schedule = VerticalState::SyncSchedule::None; @@ -761,7 +769,13 @@ void Video::VideoStream::set_bpp(OutputBpp bpp) { } void Video::VideoStream::load(uint64_t value) { - output_shifter_ = value; + // In 1bpp mode, a 0 bit is white and a 1 bit is black. + // Invert the input so that the 'just output the border colour + // when the shifter is empty' optimisation works. + if(bpp_ == OutputBpp::One) + output_shifter_ = ~value; + else + output_shifter_ = value; } // MARK: - Range observer. diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index feced1631..a829f5f20 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -3858,6 +3858,7 @@ }; 4BB73EB11B587A5100552FC2 = { CreatedOnToolsVersion = 7.0; + DevelopmentTeam = CP2SKEB3XT; LastSwiftMigration = 1020; TestTargetID = 4BB73E9D1B587A5100552FC2; }; @@ -5059,7 +5060,6 @@ CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements"; - CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = CP2SKEB3XT; ENABLE_HARDENED_RUNTIME = YES; @@ -5104,7 +5104,6 @@ CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements"; - CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = CP2SKEB3XT; ENABLE_HARDENED_RUNTIME = YES; @@ -5144,7 +5143,10 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "c++17"; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements"; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = CP2SKEB3XT; + ENABLE_HARDENED_RUNTIME = NO; INFOPLIST_FILE = "Clock SignalTests/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-SignalTests"; @@ -5162,7 +5164,10 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "c++17"; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements"; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = CP2SKEB3XT; + ENABLE_HARDENED_RUNTIME = NO; GCC_OPTIMIZATION_LEVEL = 2; INFOPLIST_FILE = "Clock SignalTests/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme new file mode 100644 index 000000000..e4fe3267b --- /dev/null +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme index 1465a4f62..47f9c7286 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme @@ -67,7 +67,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + From c8fd00217d8f6be1d287c9a5ef750b9fbb20cf0f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 27 Jan 2020 23:08:28 -0500 Subject: [PATCH 02/25] Resolves loss of horizontal resolution in 1bpp mode. --- Machines/Atari/ST/Video.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Machines/Atari/ST/Video.cpp b/Machines/Atari/ST/Video.cpp index eb15ca8eb..f1a4f556b 100644 --- a/Machines/Atari/ST/Video.cpp +++ b/Machines/Atari/ST/Video.cpp @@ -118,13 +118,13 @@ const int vsync_delay_period = hsync_delay_period; // Signal vsync with the same Video::Video() : deferrer_([=] (HalfCycles duration) { advance(duration); }), -// crt_(1024, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4), - crt_(448, 1, 500, Outputs::Display::ColourSpace::YIQ, 100, 50, 5, false, Outputs::Display::InputDataType::Red4Green4Blue4), +// crt_(2048, 2, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4), + crt_(896, 1, 500, Outputs::Display::ColourSpace::YIQ, 100, 50, 5, false, Outputs::Display::InputDataType::Red4Green4Blue4), video_stream_(crt_, palette_) { // Show a total of 260 lines; a little short for PAL but a compromise between that and the ST's // usual output height of 200 lines. -// crt_.set_visible_area(crt_.get_rect_for_area(33, 260, 220, 850, 4.0f / 3.0f)); +// crt_.set_visible_area(crt_.get_rect_for_area(33, 260, 440, 1700, 4.0f / 3.0f)); } void Video::set_ram(uint16_t *ram, size_t size) { @@ -136,7 +136,7 @@ void Video::set_scan_target(Outputs::Display::ScanTarget *scan_target) { } Outputs::Display::ScanStatus Video::get_scaled_scan_status() const { - return crt_.get_scaled_scan_status() / 2.0f; + return crt_.get_scaled_scan_status() / 4.0f; } void Video::set_display_type(Outputs::Display::DisplayType display_type) { @@ -567,9 +567,9 @@ void Video::VideoStream::generate(int duration, OutputMode mode, bool is_termina if(mode != OutputMode::Pixels) { switch(mode) { default: - case OutputMode::Sync: crt_.output_sync(duration_); break; - case OutputMode::Blank: crt_.output_blank(duration_); break; - case OutputMode::ColourBurst: crt_.output_default_colour_burst(duration_); break; + case OutputMode::Sync: crt_.output_sync(duration_*2); break; + case OutputMode::Blank: crt_.output_blank(duration_*2); break; + case OutputMode::ColourBurst: crt_.output_default_colour_burst(duration_*2); break; } // Reseed duration @@ -622,7 +622,7 @@ void Video::VideoStream::flush_border() { // Output colour 0 for the entirety of duration_ (or black, if this is 1bpp mode). uint16_t *const colour_pointer = reinterpret_cast(crt_.begin_data(1)); if(colour_pointer) *colour_pointer = (bpp_ != OutputBpp::One) ? palette_[0] : 0; - crt_.output_level(duration_); + crt_.output_level(duration_*2); duration_ = 0; } @@ -738,7 +738,7 @@ void Video::VideoStream::output_pixels(int duration) { case OutputBpp::Four: leftover_duration <<= 1; break; } shift(leftover_duration); - crt_.output_data(leftover_duration); + crt_.output_data(leftover_duration*2); } } @@ -746,9 +746,9 @@ void Video::VideoStream::flush_pixels() { // Flush only if there's something to flush. if(pixel_pointer_) { switch(bpp_) { - case OutputBpp::One: crt_.output_data(pixel_pointer_ >> 1, size_t(pixel_pointer_)); break; - default: crt_.output_data(pixel_pointer_); break; - case OutputBpp::Four: crt_.output_data(pixel_pointer_ << 1, size_t(pixel_pointer_)); break; + case OutputBpp::One: crt_.output_data(pixel_pointer_); break; + default: crt_.output_data(pixel_pointer_ << 1, size_t(pixel_pointer_)); break; + case OutputBpp::Four: crt_.output_data(pixel_pointer_ << 2, size_t(pixel_pointer_)); break; } } From 7e8405e68a05a5475438d04417aa8aca839399e7 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 27 Jan 2020 23:40:01 -0500 Subject: [PATCH 03/25] Makes 72Hz horizontal sync independently relocatable. ... and moves and shortens it, based on my guesswork as to requirements. --- Machines/Atari/ST/Video.cpp | 47 ++++++++++++++++++++----------------- Machines/Atari/ST/Video.hpp | 8 ++++++- 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/Machines/Atari/ST/Video.cpp b/Machines/Atari/ST/Video.cpp index f1a4f556b..095f9c708 100644 --- a/Machines/Atari/ST/Video.cpp +++ b/Machines/Atari/ST/Video.cpp @@ -13,6 +13,8 @@ #include #include +#define CYCLE(x) ((x) * 2) + using namespace Atari::ST; namespace { @@ -37,8 +39,6 @@ const VerticalParams &vertical_parameters(Video::FieldFrequency frequency) { return vertical_params[int(frequency)]; } - -#define CYCLE(x) ((x) * 2) /*! Defines the horizontal counts at which mode-specific events will occur: horizontal enable being set and being reset, blank being set and reset, and the @@ -59,17 +59,20 @@ const struct HorizontalParams { const int vertical_decision; - const int length; + LineLength length; } horizontal_params[3] = { - {CYCLE(56), CYCLE(376), CYCLE(450), CYCLE(28), CYCLE(502), CYCLE(512)}, - {CYCLE(52), CYCLE(372), CYCLE(450), CYCLE(24), CYCLE(502), CYCLE(508)}, - {CYCLE(4), CYCLE(164), CYCLE(999), CYCLE(999), CYCLE(214), CYCLE(224)} // 72Hz mode doesn't set or reset blank. + {CYCLE(56), CYCLE(376), CYCLE(450), CYCLE(28), CYCLE(502), { CYCLE(512), CYCLE(464), CYCLE(504) }}, + {CYCLE(52), CYCLE(372), CYCLE(450), CYCLE(24), CYCLE(502), { CYCLE(508), CYCLE(460), CYCLE(500) }}, + {CYCLE(4), CYCLE(164), CYCLE(999), CYCLE(999), CYCLE(214), { CYCLE(224), CYCLE(194), CYCLE(212) }} + // 72Hz mode doesn't set or reset blank. }; // Re: 'vertical_decision': // This is cycle 502 if in 50 or 60 Hz mode; in 70 Hz mode I've put it on cycle 214 // in order to be analogous to 50 and 60 Hz mode. I have no idea where it should // actually go. +// +// Ditto the horizontal sync timings for 72Hz are plucked out of thin air. const HorizontalParams &horizontal_parameters(Video::FieldFrequency frequency) { return horizontal_params[int(frequency)]; @@ -104,10 +107,10 @@ struct Checker { const int de_delay_period = CYCLE(28); // Amount of time after DE that observed DE changes. NB: HACK HERE. This currently incorporates the MFP recognition delay. MUST FIX. const int vsync_x_position = CYCLE(56); // Horizontal cycle on which vertical sync changes happen. -const int hsync_start = CYCLE(48); // Cycles before end of line when hsync starts. -const int hsync_end = CYCLE(8); // Cycles before end of line when hsync ends. +//const int hsync_start = CYCLE(48); // Cycles before end of line when hsync starts. +//const int hsync_end = CYCLE(8); // Cycles before end of line when hsync ends. -const int hsync_delay_period = hsync_end; // Signal hsync at the end of the line. +const int hsync_delay_period = CYCLE(8); // Signal hsync at the end of the line. const int vsync_delay_period = hsync_delay_period; // Signal vsync with the same delay as hsync. // "VSYNC starts 104 cycles after the start of the previous line's HSYNC, so that's 4 cycles before DE would be activated. "; @@ -118,13 +121,13 @@ const int vsync_delay_period = hsync_delay_period; // Signal vsync with the same Video::Video() : deferrer_([=] (HalfCycles duration) { advance(duration); }), -// crt_(2048, 2, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4), - crt_(896, 1, 500, Outputs::Display::ColourSpace::YIQ, 100, 50, 5, false, Outputs::Display::InputDataType::Red4Green4Blue4), + crt_(2048, 2, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4), +// crt_(896, 1, 500, Outputs::Display::ColourSpace::YIQ, 100, 50, 5, false, Outputs::Display::InputDataType::Red4Green4Blue4), video_stream_(crt_, palette_) { // Show a total of 260 lines; a little short for PAL but a compromise between that and the ST's // usual output height of 200 lines. -// crt_.set_visible_area(crt_.get_rect_for_area(33, 260, 440, 1700, 4.0f / 3.0f)); + crt_.set_visible_area(crt_.get_rect_for_area(33, 260, 440, 1700, 4.0f / 3.0f)); } void Video::set_ram(uint16_t *ram, size_t size) { @@ -173,7 +176,7 @@ void Video::advance(HalfCycles duration) { while(integer_duration) { // Seed next event to end of line. - int next_event = line_length_; + int next_event = line_length_.length; // Check the explicitly-placed events. if(horizontal_timings.reset_blank > x_) next_event = std::min(next_event, horizontal_timings.reset_blank); @@ -183,8 +186,8 @@ void Video::advance(HalfCycles duration) { if(next_load_toggle_ > x_) next_event = std::min(next_event, next_load_toggle_); // Check for events that are relative to existing latched state. - if(line_length_ - hsync_start > x_) next_event = std::min(next_event, line_length_ - hsync_start); - if(line_length_ - hsync_end > x_) next_event = std::min(next_event, line_length_ - hsync_end); + if(line_length_.hsync_start > x_) next_event = std::min(next_event, line_length_.hsync_start); + if(line_length_.hsync_end > x_) next_event = std::min(next_event, line_length_.hsync_end); // Also, a vertical sync event might intercede. if(vertical_.sync_schedule != VerticalState::SyncSchedule::None && x_ < vsync_x_position && next_event >= vsync_x_position) { @@ -297,8 +300,8 @@ void Video::advance(HalfCycles duration) { else if(horizontal_timings.set_blank == x_) horizontal_.blank = true; else if(horizontal_timings.reset_enable == x_) horizontal_.enable = false; else if(horizontal_timings.set_enable == x_) horizontal_.enable = true; - else if(line_length_ - hsync_start == x_) { horizontal_.sync = true; horizontal_.enable = false; } - else if(line_length_ - hsync_end == x_) horizontal_.sync = false; + else if(line_length_.hsync_start == x_) { horizontal_.sync = true; horizontal_.enable = false; } + else if(line_length_.hsync_end == x_) horizontal_.sync = false; // next_load_toggle_ is less predictable; test separately because it may coincide // with one of the above tests. @@ -318,7 +321,7 @@ void Video::advance(HalfCycles duration) { // Check whether the terminating event was end-of-line; if so then advance // the vertical bits of state. - if(x_ == line_length_) { + if(x_ == line_length_.length) { x_ = 0; vertical_ = next_vertical_; y_ = next_y_; @@ -408,7 +411,7 @@ HalfCycles Video::get_next_sequence_point() { const auto horizontal_timings = horizontal_parameters(field_frequency_); - int event_time = line_length_; // Worst case: report end of line. + int event_time = line_length_.length; // Worst case: report end of line. // If any events are pending, give the first of those the chance to be next. if(!pending_events_.empty()) { @@ -431,11 +434,11 @@ HalfCycles Video::get_next_sequence_point() { } // Test for beginning and end of horizontal sync. - if(x_ < line_length_ - hsync_start + hsync_delay_period) { - event_time = std::min(line_length_ - hsync_start + hsync_delay_period, event_time); + if(x_ < line_length_.hsync_start + hsync_delay_period) { + event_time = std::min(line_length_.hsync_start + hsync_delay_period, event_time); } /* Hereby assumed: hsync end will be communicated at end of line: */ - static_assert(hsync_end == hsync_delay_period); +// static_assert(line_length_.hsync_end == hsync_delay_period); // It wasn't any of those, just supply end of line. That's when the static_assert above assumes a visible hsync transition. return HalfCycles(event_time - x_); diff --git a/Machines/Atari/ST/Video.hpp b/Machines/Atari/ST/Video.hpp index 46196ceed..719e4f4b3 100644 --- a/Machines/Atari/ST/Video.hpp +++ b/Machines/Atari/ST/Video.hpp @@ -21,6 +21,12 @@ class VideoTester; namespace Atari { namespace ST { +struct LineLength { + int length = 1024; + int hsync_start; + int hsync_end; +}; + /*! Models a combination of the parts of the GLUE, MMU and Shifter that in net form the video subsystem of the Atari ST. So not accurate to a real chip, but @@ -163,7 +169,7 @@ class Video { } sync_schedule = SyncSchedule::None; bool sync = false; } vertical_, next_vertical_; - int line_length_ = 1024; + LineLength line_length_; int data_latch_position_ = 0; int data_latch_read_position_ = 0; From b1ff031b54f32514226372d763ff32e6949265c3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 27 Jan 2020 23:41:08 -0500 Subject: [PATCH 04/25] Fixes runtime test. --- Machines/Atari/ST/Video.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Machines/Atari/ST/Video.cpp b/Machines/Atari/ST/Video.cpp index 095f9c708..0e9f3d092 100644 --- a/Machines/Atari/ST/Video.cpp +++ b/Machines/Atari/ST/Video.cpp @@ -89,10 +89,10 @@ struct Checker { assert(horizontal.reset_blank < horizontal.set_enable); assert(horizontal.set_enable < horizontal.reset_enable); assert(horizontal.reset_enable < horizontal.set_blank); - assert(horizontal.set_blank+50 < horizontal.length); + assert(horizontal.set_blank+50 < horizontal.length.length); } else { assert(horizontal.set_enable < horizontal.reset_enable); - assert(horizontal.set_enable+50 Date: Tue, 28 Jan 2020 20:09:46 -0500 Subject: [PATCH 05/25] Reintroduces CSHighPrecisionTimer. --- .../Clock Signal.xcodeproj/project.pbxproj | 44 ++++++++++++++++--- .../xcschemes/Clock Signal.xcscheme | 2 +- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index a829f5f20..f4165bef8 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -160,6 +160,7 @@ 4B2B3A4C1F9B8FA70062DABF /* MemoryFuzzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B3A481F9B8FA70062DABF /* MemoryFuzzer.cpp */; }; 4B2BF19123DCC6A200C3AD60 /* BD500.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA03523CEB86000B98D9E /* BD500.cpp */; }; 4B2BF19223DCC6A800C3AD60 /* STX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA03323C58B1E00B98D9E /* STX.cpp */; }; + 4B2BF19623E10F0100C3AD60 /* CSHighPrecisionTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BF19523E10F0000C3AD60 /* CSHighPrecisionTimer.m */; }; 4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */; }; 4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */; }; 4B2C45421E3C3896002A2389 /* cartridge.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B2C45411E3C3896002A2389 /* cartridge.png */; }; @@ -1002,6 +1003,8 @@ 4B2B3A481F9B8FA70062DABF /* MemoryFuzzer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MemoryFuzzer.cpp; sourceTree = ""; }; 4B2B3A491F9B8FA70062DABF /* MemoryFuzzer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MemoryFuzzer.hpp; sourceTree = ""; }; 4B2B3A4A1F9B8FA70062DABF /* Typer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Typer.hpp; sourceTree = ""; }; + 4B2BF19423E10F0000C3AD60 /* CSHighPrecisionTimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSHighPrecisionTimer.h; sourceTree = ""; }; + 4B2BF19523E10F0000C3AD60 /* CSHighPrecisionTimer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSHighPrecisionTimer.m; sourceTree = ""; }; 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapePRG.cpp; sourceTree = ""; }; 4B2BFC5E1D613E0200BA3AA9 /* TapePRG.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapePRG.hpp; sourceTree = ""; }; 4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Oric/Video.cpp; sourceTree = ""; }; @@ -2063,6 +2066,15 @@ path = Utility; sourceTree = ""; }; + 4B2BF19323E10F0000C3AD60 /* High Precision Timer */ = { + isa = PBXGroup; + children = ( + 4B2BF19423E10F0000C3AD60 /* CSHighPrecisionTimer.h */, + 4B2BF19523E10F0000C3AD60 /* CSHighPrecisionTimer.m */, + ); + path = "High Precision Timer"; + sourceTree = ""; + }; 4B2E2D9E1C3A070900138695 /* Electron */ = { isa = PBXGroup; children = ( @@ -3257,6 +3269,7 @@ 4B2A538F1D117D36003C6002 /* Audio */, 4B643F3D1D77B88000D431D6 /* Document Controller */, 4B55CE551C3B7D360093A61B /* Documents */, + 4B2BF19323E10F0000C3AD60 /* High Precision Timer */, 4BBFE83B21015D9C00BF1C40 /* Joystick Manager */, 4B2A53921D117D36003C6002 /* Machine */, 4B55DD7F20DF06680043F2E5 /* MachinePicker */, @@ -3839,17 +3852,19 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0700; - LastUpgradeCheck = 1100; + LastUpgradeCheck = 1130; ORGANIZATIONNAME = "Thomas Harte"; TargetAttributes = { 4B055A691FAE763F0060FFFF = { CreatedOnToolsVersion = 9.1; + DevelopmentTeam = CP2SKEB3XT; ProvisioningStyle = Automatic; }; 4BB73E9D1B587A5100552FC2 = { CreatedOnToolsVersion = 7.0; DevelopmentTeam = CP2SKEB3XT; LastSwiftMigration = 1020; + ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.Sandbox = { enabled = 1; @@ -3860,10 +3875,12 @@ CreatedOnToolsVersion = 7.0; DevelopmentTeam = CP2SKEB3XT; LastSwiftMigration = 1020; + ProvisioningStyle = Automatic; TestTargetID = 4BB73E9D1B587A5100552FC2; }; 4BB73EBC1B587A5100552FC2 = { CreatedOnToolsVersion = 7.0; + DevelopmentTeam = CP2SKEB3XT; LastSwiftMigration = 1020; TestTargetID = 4BB73E9D1B587A5100552FC2; }; @@ -4443,6 +4460,7 @@ 4B54C0CB1F8D92590050900F /* Keyboard.cpp in Sources */, 4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */, 4B07835A1FC11D10001D12BB /* Configurable.cpp in Sources */, + 4B2BF19623E10F0100C3AD60 /* CSHighPrecisionTimer.m in Sources */, 4B8334951F5E25B60097E338 /* C1540.cpp in Sources */, 4B89453C201967B4007DE474 /* StaticAnalyser.cpp in Sources */, 4B595FAD2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */, @@ -4912,6 +4930,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = CP2SKEB3XT; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(USER_LIBRARY_DIR)/Frameworks", @@ -4932,6 +4951,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = CP2SKEB3XT; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(USER_LIBRARY_DIR)/Frameworks", @@ -5060,8 +5080,10 @@ CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = CP2SKEB3XT; + DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -5086,6 +5108,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-Signal"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Clock Signal/ClockSignal-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -5104,8 +5127,10 @@ CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = CP2SKEB3XT; + DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -5132,6 +5157,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-Signal"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Clock Signal/ClockSignal-Bridging-Header.h"; SWIFT_VERSION = 5.0; }; @@ -5144,13 +5170,16 @@ CLANG_CXX_LANGUAGE_STANDARD = "c++17"; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = CP2SKEB3XT; + DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = NO; INFOPLIST_FILE = "Clock SignalTests/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-SignalTests"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Clock SignalTests/Bridges/Clock SignalTests-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -5165,14 +5194,17 @@ CLANG_CXX_LANGUAGE_STANDARD = "c++17"; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = CP2SKEB3XT; + DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = NO; GCC_OPTIMIZATION_LEVEL = 2; INFOPLIST_FILE = "Clock SignalTests/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-SignalTests"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Clock SignalTests/Bridges/Clock SignalTests-Bridging-Header.h"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Clock Signal.app/Contents/MacOS/Clock Signal"; @@ -5183,6 +5215,7 @@ isa = XCBuildConfiguration; buildSettings = { COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = CP2SKEB3XT; INFOPLIST_FILE = "Clock SignalUITests/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-SignalUITests"; @@ -5197,6 +5230,7 @@ isa = XCBuildConfiguration; buildSettings = { COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = CP2SKEB3XT; INFOPLIST_FILE = "Clock SignalUITests/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-SignalUITests"; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme index 47f9c7286..63be2c037 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme @@ -1,6 +1,6 @@ Date: Tue, 28 Jan 2020 20:22:37 -0500 Subject: [PATCH 06/25] Adds line length latching as a line event. --- Machines/Atari/ST/Video.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Machines/Atari/ST/Video.cpp b/Machines/Atari/ST/Video.cpp index 0e9f3d092..e8ae8cfe0 100644 --- a/Machines/Atari/ST/Video.cpp +++ b/Machines/Atari/ST/Video.cpp @@ -107,8 +107,7 @@ struct Checker { const int de_delay_period = CYCLE(28); // Amount of time after DE that observed DE changes. NB: HACK HERE. This currently incorporates the MFP recognition delay. MUST FIX. const int vsync_x_position = CYCLE(56); // Horizontal cycle on which vertical sync changes happen. -//const int hsync_start = CYCLE(48); // Cycles before end of line when hsync starts. -//const int hsync_end = CYCLE(8); // Cycles before end of line when hsync ends. +const int line_length_latch_position = CYCLE(54); const int hsync_delay_period = CYCLE(8); // Signal hsync at the end of the line. const int vsync_delay_period = hsync_delay_period; // Signal vsync with the same delay as hsync. @@ -265,10 +264,12 @@ void Video::advance(HalfCycles duration) { } // Check for whether line length should have been latched during this run. - if(x_ <= CYCLE(54) && (x_ + run_length) > CYCLE(54)) line_length_ = horizontal_timings.length; + if(x_ < line_length_latch_position && (x_ + run_length) >= line_length_latch_position) { + line_length_ = horizontal_timings.length; + } // Make a decision about vertical state on the appropriate cycle. - if(x_ <= horizontal_timings.vertical_decision && (x_ + run_length) > horizontal_timings.vertical_decision) { + if(x_ < horizontal_timings.vertical_decision && (x_ + run_length) >= horizontal_timings.vertical_decision) { next_y_ = y_ + 1; next_vertical_ = vertical_; next_vertical_.sync_schedule = VerticalState::SyncSchedule::None; @@ -440,6 +441,11 @@ HalfCycles Video::get_next_sequence_point() { /* Hereby assumed: hsync end will be communicated at end of line: */ // static_assert(line_length_.hsync_end == hsync_delay_period); + // Also factor in the line length latching time. + if(x_ < line_length_latch_position) { + event_time = std::min(line_length_latch_position, event_time); + } + // It wasn't any of those, just supply end of line. That's when the static_assert above assumes a visible hsync transition. return HalfCycles(event_time - x_); } From 5c4623e9f77e7cbaea11eb5393c3cadafdcd0c3e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 28 Jan 2020 20:27:24 -0500 Subject: [PATCH 07/25] Adds a sequence-point test for 72Hz mode. Which immediately appears to trigger the hsync issue I'm also seeing in manual testing. --- .../Mac/Clock SignalTests/AtariSTVideoTests.mm | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/OSBindings/Mac/Clock SignalTests/AtariSTVideoTests.mm b/OSBindings/Mac/Clock SignalTests/AtariSTVideoTests.mm index 7bba7b73e..8749a3b78 100644 --- a/OSBindings/Mac/Clock SignalTests/AtariSTVideoTests.mm +++ b/OSBindings/Mac/Clock SignalTests/AtariSTVideoTests.mm @@ -95,11 +95,22 @@ struct VideoTester { // MARK: - Sequence Point Prediction Tests /// Tests that no events occur outside of the sequence points the video predicts. -- (void)testSequencePoints { +- (void)testSequencePoints50 { // Set 4bpp, 50Hz. _video->write(0x05, 0x0200); _video->write(0x30, 0x0000); + [self runSequencePointsTest]; +} + +- (void)testSequencePoints72 { + // Set 1bpp, 72Hz. + _video->write(0x30, 0x0200); + + [self runSequencePointsTest]; +} + +- (void)runSequencePointsTest { // Run for [more than] a whole frame making sure that no observeable outputs // change at any time other than a sequence point. HalfCycles next_event; From 65309e60c4818faca56438cc8110cfed84da6038 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 28 Jan 2020 20:38:20 -0500 Subject: [PATCH 08/25] Corrects sequence point generation by allowing for hsync_end != end of line. --- Machines/Atari/ST/Video.cpp | 5 +++-- Machines/Atari/ST/Video.hpp | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Machines/Atari/ST/Video.cpp b/Machines/Atari/ST/Video.cpp index e8ae8cfe0..064fa98b2 100644 --- a/Machines/Atari/ST/Video.cpp +++ b/Machines/Atari/ST/Video.cpp @@ -438,8 +438,9 @@ HalfCycles Video::get_next_sequence_point() { if(x_ < line_length_.hsync_start + hsync_delay_period) { event_time = std::min(line_length_.hsync_start + hsync_delay_period, event_time); } - /* Hereby assumed: hsync end will be communicated at end of line: */ -// static_assert(line_length_.hsync_end == hsync_delay_period); + if(x_ < line_length_.hsync_end + hsync_delay_period) { + event_time = std::min(line_length_.hsync_end + hsync_delay_period, event_time); + } // Also factor in the line length latching time. if(x_ < line_length_latch_position) { diff --git a/Machines/Atari/ST/Video.hpp b/Machines/Atari/ST/Video.hpp index 719e4f4b3..299f3941c 100644 --- a/Machines/Atari/ST/Video.hpp +++ b/Machines/Atari/ST/Video.hpp @@ -23,8 +23,8 @@ namespace ST { struct LineLength { int length = 1024; - int hsync_start; - int hsync_end; + int hsync_start = 1024; + int hsync_end = 1024; }; /*! From c5edc879b62d9ad41c6a5ddaf3c893614c461276 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 28 Jan 2020 22:12:57 -0500 Subject: [PATCH 09/25] Switches back to testing the monochrome monitor. --- Machines/Atari/ST/Video.cpp | 6 +++--- .../xcshareddata/xcschemes/Clock Signal.xcscheme | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Machines/Atari/ST/Video.cpp b/Machines/Atari/ST/Video.cpp index 064fa98b2..f96e00abc 100644 --- a/Machines/Atari/ST/Video.cpp +++ b/Machines/Atari/ST/Video.cpp @@ -120,13 +120,13 @@ const int vsync_delay_period = hsync_delay_period; // Signal vsync with the same Video::Video() : deferrer_([=] (HalfCycles duration) { advance(duration); }), - crt_(2048, 2, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4), -// crt_(896, 1, 500, Outputs::Display::ColourSpace::YIQ, 100, 50, 5, false, Outputs::Display::InputDataType::Red4Green4Blue4), +// crt_(2048, 2, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4), + crt_(896, 1, 500, Outputs::Display::ColourSpace::YIQ, 100, 50, 5, false, Outputs::Display::InputDataType::Red4Green4Blue4), video_stream_(crt_, palette_) { // Show a total of 260 lines; a little short for PAL but a compromise between that and the ST's // usual output height of 200 lines. - crt_.set_visible_area(crt_.get_rect_for_area(33, 260, 440, 1700, 4.0f / 3.0f)); +// crt_.set_visible_area(crt_.get_rect_for_area(33, 260, 440, 1700, 4.0f / 3.0f)); } void Video::set_ram(uint16_t *ram, size_t size) { diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme index 63be2c037..a99eb0a5d 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme @@ -67,7 +67,7 @@ Date: Tue, 28 Jan 2020 23:23:51 -0500 Subject: [PATCH 10/25] Documents units. --- .../Clock Signal/High Precision Timer/CSHighPrecisionTimer.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/OSBindings/Mac/Clock Signal/High Precision Timer/CSHighPrecisionTimer.h b/OSBindings/Mac/Clock Signal/High Precision Timer/CSHighPrecisionTimer.h index 9ceeded01..5942fa4f1 100644 --- a/OSBindings/Mac/Clock Signal/High Precision Timer/CSHighPrecisionTimer.h +++ b/OSBindings/Mac/Clock Signal/High Precision Timer/CSHighPrecisionTimer.h @@ -19,6 +19,9 @@ /// Initialises a new instance of the high precision timer; the timer will begin /// ticking immediately. +/// +/// @param task The block to perform each time the timer fires. +/// @param interval The interval at which to fire the timer, in nanoseconds. - (instancetype)initWithTask:(dispatch_block_t)task interval:(uint64_t)interval; /// Stops the timer. From 8b1f1831983a382d012866e5af24619fe6bddbbb Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 28 Jan 2020 23:25:01 -0500 Subject: [PATCH 11/25] Reduce test duration much closer to two frames. --- OSBindings/Mac/Clock SignalTests/AtariSTVideoTests.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/AtariSTVideoTests.mm b/OSBindings/Mac/Clock SignalTests/AtariSTVideoTests.mm index 8749a3b78..03c92a221 100644 --- a/OSBindings/Mac/Clock SignalTests/AtariSTVideoTests.mm +++ b/OSBindings/Mac/Clock SignalTests/AtariSTVideoTests.mm @@ -111,13 +111,13 @@ struct VideoTester { } - (void)runSequencePointsTest { - // Run for [more than] a whole frame making sure that no observeable outputs + // Run for [more than] two frames making sure that no observeable outputs // change at any time other than a sequence point. HalfCycles next_event; bool display_enable = false; bool vsync = false; bool hsync = false; - for(size_t c = 0; c < 10 * 1000 * 1000; ++c) { + for(size_t c = 0; c < 8000000 / 20; ++c) { const bool is_transition_point = next_event == HalfCycles(0); if(is_transition_point) { From 1b27eedf6b05c5d8c28d7e2544e4c2d6324f81e3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 28 Jan 2020 23:25:21 -0500 Subject: [PATCH 12/25] Ensure this can definitely never divide by 0. --- Storage/Disk/DPLL/DigitalPhaseLockedLoop.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Storage/Disk/DPLL/DigitalPhaseLockedLoop.hpp b/Storage/Disk/DPLL/DigitalPhaseLockedLoop.hpp index 487303763..e6123fd23 100644 --- a/Storage/Disk/DPLL/DigitalPhaseLockedLoop.hpp +++ b/Storage/Disk/DPLL/DigitalPhaseLockedLoop.hpp @@ -106,7 +106,7 @@ template class DigitalPhaseL // In net: use an unweighted average of the stored offsets to compute current window size, // bucketing them by rounding to the nearest multiple of the base clocks per bit - window_length_ = total_spacing_ / total_divisor_; + window_length_ = std::max(total_spacing_ / total_divisor_, Cycles::IntType(1)); // Also apply a difference to phase, use a simple spring mechanism as a lowpass filter. const auto error = new_phase - (window_length_ >> 1); From 0e29c6b0ab47d3cc0b754a0b18a771e4882e180c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 28 Jan 2020 23:26:37 -0500 Subject: [PATCH 13/25] On further reflection, I think events should occur after the running period. I'm testing this now for sanity in 2/4bpp mode. --- Machines/Atari/ST/AtariST.cpp | 2 +- Machines/Atari/ST/Video.cpp | 46 ++++++++++++++++++----------------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/Machines/Atari/ST/AtariST.cpp b/Machines/Atari/ST/AtariST.cpp index bcf0c09d6..187ee2af1 100644 --- a/Machines/Atari/ST/AtariST.cpp +++ b/Machines/Atari/ST/AtariST.cpp @@ -567,7 +567,7 @@ class ConcreteMachine: GPIP 0: centronics busy */ mfp_->set_port_input( - 0x00 | // b7: Monochrome monitor detect (0 = is monochrome). + 0x80 | // b7: Monochrome monitor detect (0 = is monochrome). 0x40 | // b6: RS-232 ring indicator. (dma_->get_interrupt_line() ? 0x00 : 0x20) | // b5: FD/HS interrupt (0 = interrupt requested). ((keyboard_acia_->get_interrupt_line() || midi_acia_->get_interrupt_line()) ? 0x00 : 0x10) | // b4: Keyboard/MIDI interrupt (0 = interrupt requested). diff --git a/Machines/Atari/ST/Video.cpp b/Machines/Atari/ST/Video.cpp index f96e00abc..df2bea066 100644 --- a/Machines/Atari/ST/Video.cpp +++ b/Machines/Atari/ST/Video.cpp @@ -120,13 +120,13 @@ const int vsync_delay_period = hsync_delay_period; // Signal vsync with the same Video::Video() : deferrer_([=] (HalfCycles duration) { advance(duration); }), -// crt_(2048, 2, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4), - crt_(896, 1, 500, Outputs::Display::ColourSpace::YIQ, 100, 50, 5, false, Outputs::Display::InputDataType::Red4Green4Blue4), + crt_(2048, 2, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4), +// crt_(896, 1, 500, Outputs::Display::ColourSpace::YIQ, 100, 50, 5, false, Outputs::Display::InputDataType::Red4Green4Blue4), video_stream_(crt_, palette_) { // Show a total of 260 lines; a little short for PAL but a compromise between that and the ST's // usual output height of 200 lines. -// crt_.set_visible_area(crt_.get_rect_for_area(33, 260, 440, 1700, 4.0f / 3.0f)); + crt_.set_visible_area(crt_.get_rect_for_area(33, 260, 440, 1700, 4.0f / 3.0f)); } void Video::set_ram(uint16_t *ram, size_t size) { @@ -154,25 +154,6 @@ void Video::advance(HalfCycles duration) { const auto vertical_timings = vertical_parameters(field_frequency_); int integer_duration = int(duration.as_integral()); - // Effect any changes in visible state out here; they're not relevant in the inner loop. - if(!pending_events_.empty()) { - auto erase_iterator = pending_events_.begin(); - int duration_remaining = integer_duration; - while(erase_iterator != pending_events_.end()) { - erase_iterator->delay -= duration_remaining; - if(erase_iterator->delay <= 0) { - duration_remaining = -erase_iterator->delay; - erase_iterator->apply(public_state_); - ++erase_iterator; - } else { - break; - } - } - if(erase_iterator != pending_events_.begin()) { - pending_events_.erase(pending_events_.begin(), erase_iterator); - } - } - while(integer_duration) { // Seed next event to end of line. int next_event = line_length_.length; @@ -362,6 +343,27 @@ void Video::advance(HalfCycles duration) { add_event(vsync_delay_period - integer_duration, vertical_.sync ? Event::Type::SetVsync : Event::Type::ResetVsync); } } + + // Effect any changes in visible state out here; they've been supplied as sequence points, so + // a conforming caller can't hit them within the inner loop. + integer_duration = int(duration.as_integral()); + if(!pending_events_.empty()) { + auto erase_iterator = pending_events_.begin(); + int duration_remaining = integer_duration; + while(erase_iterator != pending_events_.end()) { + erase_iterator->delay -= duration_remaining; + if(erase_iterator->delay <= 0) { + duration_remaining = -erase_iterator->delay; + erase_iterator->apply(public_state_); + ++erase_iterator; + } else { + break; + } + } + if(erase_iterator != pending_events_.begin()) { + pending_events_.erase(pending_events_.begin(), erase_iterator); + } + } } void Video::push_latched_data() { From baa51853c4cca1857933265a4b1bf215bf7346f9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 29 Jan 2020 21:26:15 -0500 Subject: [PATCH 14/25] Introduces `RealTimeActor`, providing the same interface as `JustInTimeActor`. --- ClockReceiver/JustInTime.hpp | 42 ++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/ClockReceiver/JustInTime.hpp b/ClockReceiver/JustInTime.hpp index ad328dbe4..90150138e 100644 --- a/ClockReceiver/JustInTime.hpp +++ b/ClockReceiver/JustInTime.hpp @@ -76,6 +76,48 @@ template class RealTimeActor { + public: + template RealTimeActor(Args&&... args) : object_(std::forward(args)...) {} + + forceinline void operator += (const LocalTimeScale &rhs) { + if constexpr (multiplier == 1 && divider == 1) { + object_.run_for(TargetTimeScale(rhs)); + return; + } + + if constexpr (multiplier == 1) { + accumulated_time_ += rhs; + } else { + accumulated_time_ += rhs * multiplier; + } + + if constexpr (divider == 1) { + const auto duration = accumulated_time_.template flush(); + object_.run_for(duration); + } else { + const auto duration = accumulated_time_.template divide(LocalTimeScale(divider)); + if(duration > TargetTimeScale(0)) + object_.run_for(duration); + } + } + + forceinline T *operator->() { return &object_; } + forceinline const T *operator->() const { return &object_; } + forceinline T *last_valid() { return &object_; } + forceinline void flush() {} + + private: + T object_; + LocalTimeScale accumulated_time_; +}; + /*! A AsyncJustInTimeActor acts like a JustInTimeActor but additionally contains an AsyncTaskQueue. Any time the amount of accumulated time crosses a threshold provided at construction time, From 8c4fb0f688fc946917169151c7e845c9ae111712 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 29 Jan 2020 21:49:52 -0500 Subject: [PATCH 15/25] Extends the DeferredQueue to allow out-of-order enqueing. --- ClockReceiver/DeferredQueue.hpp | 59 ++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/ClockReceiver/DeferredQueue.hpp b/ClockReceiver/DeferredQueue.hpp index d2680f322..92293cb0b 100644 --- a/ClockReceiver/DeferredQueue.hpp +++ b/ClockReceiver/DeferredQueue.hpp @@ -16,6 +16,8 @@ A DeferredQueue maintains a list of ordered actions and the times at which they should happen, and divides a total execution period up into the portions that occur between those actions, triggering each action when it is reached. + + This list is efficient only for short queues. */ template class DeferredQueue { public: @@ -24,12 +26,30 @@ template class DeferredQueue { /*! Schedules @c action to occur in @c delay units of time. - - Actions must be scheduled in the order they will occur. It is undefined behaviour - to schedule them out of order. */ void defer(TimeUnit delay, const std::function &action) { - pending_actions_.emplace_back(delay, action); + // Apply immediately if there's no delay (or a negative delay). + if(delay <= 0) { + action(); + return; + } + + if(!pending_actions_.empty()) { + // Otherwise enqueue, having subtracted the delay for any preceding events, + // and subtracting from the subsequent, if any. + auto insertion_point = pending_actions_.begin(); + while(insertion_point != pending_actions_.end() && insertion_point->delay < delay) { + delay -= insertion_point->delay; + ++insertion_point; + } + if(insertion_point != pending_actions_.end()) { + insertion_point->delay -= delay; + } + + pending_actions_.emplace(insertion_point, delay, action); + } else { + pending_actions_.emplace_back(delay, action); + } } /*! @@ -47,23 +67,24 @@ template class DeferredQueue { } // Divide the time to run according to the pending actions. - while(length > TimeUnit(0)) { - TimeUnit next_period = pending_actions_.empty() ? length : std::min(length, pending_actions_[0].delay); - target_(next_period); - length -= next_period; - - off_t performances = 0; - for(auto &action: pending_actions_) { - action.delay -= next_period; - if(!action.delay) { - action.action(); - ++performances; - } - } - if(performances) { - pending_actions_.erase(pending_actions_.begin(), pending_actions_.begin() + performances); + auto erase_iterator = pending_actions_.begin(); + while(erase_iterator != pending_actions_.end()) { + erase_iterator->delay -= length; + if(erase_iterator->delay <= TimeUnit(0)) { + target_(length + erase_iterator->delay); + length = -erase_iterator->delay; + erase_iterator->action(); + ++erase_iterator; + } else { + break; } } + if(erase_iterator != pending_actions_.begin()) { + pending_actions_.erase(pending_actions_.begin(), erase_iterator); + } + if(length != TimeUnit(0)) { + target_(length); + } } private: From f0a6e0f3d50b74ebd116c91685a517cc60c3a18d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 29 Jan 2020 22:18:41 -0500 Subject: [PATCH 16/25] Splits out the queue management stuff from queue+action. Temporarily breaks ST video in the endeavour. --- ClockReceiver/DeferredQueue.hpp | 87 ++++++++++++++++++++++---------- Machines/Apple/AppleII/Video.hpp | 2 +- Machines/Atari/ST/Video.cpp | 47 ++++++++--------- Machines/Atari/ST/Video.hpp | 6 +-- 4 files changed, 88 insertions(+), 54 deletions(-) diff --git a/ClockReceiver/DeferredQueue.hpp b/ClockReceiver/DeferredQueue.hpp index 92293cb0b..d061fd7ce 100644 --- a/ClockReceiver/DeferredQueue.hpp +++ b/ClockReceiver/DeferredQueue.hpp @@ -13,17 +13,10 @@ #include /*! - A DeferredQueue maintains a list of ordered actions and the times at which - they should happen, and divides a total execution period up into the portions - that occur between those actions, triggering each action when it is reached. - - This list is efficient only for short queues. + Provides the logic to insert into and traverse a list of future scheduled items. */ template class DeferredQueue { public: - /// Constructs a DeferredQueue that will call target(period) in between deferred actions. - DeferredQueue(std::function &&target) : target_(std::move(target)) {} - /*! Schedules @c action to occur in @c delay units of time. */ @@ -53,26 +46,23 @@ template class DeferredQueue { } /*! - Runs for @c length units of time. - - The constructor-supplied target will be called with one or more periods that add up to @c length; - any scheduled actions will be called between periods. + @returns The amount of time until the next enqueued action will occur, + or TimeUnit(-1) if the queue is empty. */ - void run_for(TimeUnit length) { - // If there are no pending actions, just run for the entire length. - // This should be the normal branch. - if(pending_actions_.empty()) { - target_(length); - return; - } + TimeUnit time_until_next_action() { + if(pending_actions_.empty()) return TimeUnit(-1); + return pending_actions_.front().delay; + } - // Divide the time to run according to the pending actions. + /*! + Advances the queue the specified amount of time, performing any actions it reaches. + */ + void advance(TimeUnit time) { auto erase_iterator = pending_actions_.begin(); while(erase_iterator != pending_actions_.end()) { - erase_iterator->delay -= length; + erase_iterator->delay -= time; if(erase_iterator->delay <= TimeUnit(0)) { - target_(length + erase_iterator->delay); - length = -erase_iterator->delay; + time = -erase_iterator->delay; erase_iterator->action(); ++erase_iterator; } else { @@ -82,14 +72,23 @@ template class DeferredQueue { if(erase_iterator != pending_actions_.begin()) { pending_actions_.erase(pending_actions_.begin(), erase_iterator); } - if(length != TimeUnit(0)) { - target_(length); + } + + /*! + Advances the queue by @c min(time_until_next_action(),duration) time. + */ + void advance_to_next(TimeUnit duration) { + if(pending_actions_.empty()) return; + + auto front = pending_actions_.front(); + front.delay -= duration; + if(front.delay <= TimeUnit(0)) { + front.action(); + pending_actions_.erase(pending_actions_.begin()); } } private: - std::function target_; - // The list of deferred actions. struct DeferredAction { TimeUnit delay; @@ -100,4 +99,38 @@ template class DeferredQueue { std::vector pending_actions_; }; +/*! + A DeferredQueue maintains a list of ordered actions and the times at which + they should happen, and divides a total execution period up into the portions + that occur between those actions, triggering each action when it is reached. + + This list is efficient only for short queues. +*/ +template class DeferredQueuePerformer: public DeferredQueue { + public: + /// Constructs a DeferredQueue that will call target(period) in between deferred actions. + DeferredQueuePerformer(std::function &&target) : target_(std::move(target)) {} + + /*! + Runs for @c length units of time. + + The constructor-supplied target will be called with one or more periods that add up to @c length; + any scheduled actions will be called between periods. + */ + void run_for(TimeUnit length) { + auto time_to_next = DeferredQueue::time_until_next_action(); + while(time_to_next != TimeUnit(-1) && time_to_next <= length) { + target_(time_to_next); + length -= time_to_next; + DeferredQueue::advance_to_next(time_to_next); + } + + DeferredQueue::advance_to_next(length); + target_(length); + } + + private: + std::function target_; +}; + #endif /* DeferredQueue_h */ diff --git a/Machines/Apple/AppleII/Video.hpp b/Machines/Apple/AppleII/Video.hpp index ac7085a07..ec61d5da0 100644 --- a/Machines/Apple/AppleII/Video.hpp +++ b/Machines/Apple/AppleII/Video.hpp @@ -255,7 +255,7 @@ class VideoBase { void output_fat_low_resolution(uint8_t *target, const uint8_t *source, size_t length, int column, int row) const; // Maintain a DeferredQueue for delayed mode switches. - DeferredQueue deferrer_; + DeferredQueuePerformer deferrer_; }; template class Video: public VideoBase { diff --git a/Machines/Atari/ST/Video.cpp b/Machines/Atari/ST/Video.cpp index df2bea066..16fc65f09 100644 --- a/Machines/Atari/ST/Video.cpp +++ b/Machines/Atari/ST/Video.cpp @@ -327,7 +327,7 @@ void Video::advance(HalfCycles duration) { const bool next_display_enable = vertical_.enable && horizontal_.enable; if(display_enable != next_display_enable) { // Schedule change in outwardly-visible DE line. - add_event(de_delay_period - integer_duration, next_display_enable ? Event::Type::SetDisplayEnable : Event::Type::ResetDisplayEnable); +// add_event(de_delay_period - integer_duration, next_display_enable ? Event::Type::SetDisplayEnable : Event::Type::ResetDisplayEnable); // Schedule change in inwardly-visible effect. next_load_toggle_ = x_ + 8; // 4 cycles = 8 half-cycles @@ -335,35 +335,35 @@ void Video::advance(HalfCycles duration) { if(horizontal_.sync != hsync) { // Schedule change in outwardly-visible hsync line. - add_event(hsync_delay_period - integer_duration, horizontal_.sync ? Event::Type::SetHsync : Event::Type::ResetHsync); +// add_event(hsync_delay_period - integer_duration, horizontal_.sync ? Event::Type::SetHsync : Event::Type::ResetHsync); } if(vertical_.sync != vsync) { // Schedule change in outwardly-visible hsync line. - add_event(vsync_delay_period - integer_duration, vertical_.sync ? Event::Type::SetVsync : Event::Type::ResetVsync); +// add_event(vsync_delay_period - integer_duration, vertical_.sync ? Event::Type::SetVsync : Event::Type::ResetVsync); } } // Effect any changes in visible state out here; they've been supplied as sequence points, so // a conforming caller can't hit them within the inner loop. - integer_duration = int(duration.as_integral()); - if(!pending_events_.empty()) { - auto erase_iterator = pending_events_.begin(); - int duration_remaining = integer_duration; - while(erase_iterator != pending_events_.end()) { - erase_iterator->delay -= duration_remaining; - if(erase_iterator->delay <= 0) { - duration_remaining = -erase_iterator->delay; - erase_iterator->apply(public_state_); - ++erase_iterator; - } else { - break; - } - } - if(erase_iterator != pending_events_.begin()) { - pending_events_.erase(pending_events_.begin(), erase_iterator); - } - } +// integer_duration = int(duration.as_integral()); +// if(!pending_events_.empty()) { +// auto erase_iterator = pending_events_.begin(); +// int duration_remaining = integer_duration; +// while(erase_iterator != pending_events_.end()) { +// erase_iterator->delay -= duration_remaining; +// if(erase_iterator->delay <= 0) { +// duration_remaining = -erase_iterator->delay; +// erase_iterator->apply(public_state_); +// ++erase_iterator; +// } else { +// break; +// } +// } +// if(erase_iterator != pending_events_.begin()) { +// pending_events_.erase(pending_events_.begin(), erase_iterator); +// } +// } } void Video::push_latched_data() { @@ -417,8 +417,9 @@ HalfCycles Video::get_next_sequence_point() { int event_time = line_length_.length; // Worst case: report end of line. // If any events are pending, give the first of those the chance to be next. - if(!pending_events_.empty()) { - event_time = std::min(event_time, x_ + pending_events_.front().delay); + const auto next_deferred_item = deferrer_.time_until_next_action(); + if(next_deferred_item != HalfCycles(-1)) { + event_time = std::min(event_time, next_deferred_item.as()); } // If this is a vertically-enabled line, check for the display enable boundaries, + the standard delay. diff --git a/Machines/Atari/ST/Video.hpp b/Machines/Atari/ST/Video.hpp index 299f3941c..c6926640d 100644 --- a/Machines/Atari/ST/Video.hpp +++ b/Machines/Atari/ST/Video.hpp @@ -122,7 +122,7 @@ class Video { private: void advance(HalfCycles duration); - DeferredQueue deferrer_; + DeferredQueuePerformer deferrer_; Outputs::CRT::CRT crt_; RangeObserver *range_observer_ = nullptr; @@ -270,7 +270,7 @@ class Video { } }; - std::vector pending_events_; +/* std::vector pending_events_; void add_event(int delay, Event::Type type) { // Apply immediately if there's no delay (or a negative delay). if(delay <= 0) { @@ -294,7 +294,7 @@ class Video { } else { pending_events_.emplace_back(type, delay); } - } + }*/ friend class ::VideoTester; }; From ee16095863c61e2d131d3a42bacdaa1720da7cc8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 29 Jan 2020 22:45:10 -0500 Subject: [PATCH 17/25] Withdraws `advance_to_next`; once it has to cope with simultaneous events it stops being faster than `advance`. I could possibly try to deal with those at insertion time, but it'd get messy. --- ClockReceiver/DeferredQueue.hpp | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/ClockReceiver/DeferredQueue.hpp b/ClockReceiver/DeferredQueue.hpp index d061fd7ce..be1c7b01e 100644 --- a/ClockReceiver/DeferredQueue.hpp +++ b/ClockReceiver/DeferredQueue.hpp @@ -22,7 +22,7 @@ template class DeferredQueue { */ void defer(TimeUnit delay, const std::function &action) { // Apply immediately if there's no delay (or a negative delay). - if(delay <= 0) { + if(delay <= TimeUnit(0)) { action(); return; } @@ -74,20 +74,6 @@ template class DeferredQueue { } } - /*! - Advances the queue by @c min(time_until_next_action(),duration) time. - */ - void advance_to_next(TimeUnit duration) { - if(pending_actions_.empty()) return; - - auto front = pending_actions_.front(); - front.delay -= duration; - if(front.delay <= TimeUnit(0)) { - front.action(); - pending_actions_.erase(pending_actions_.begin()); - } - } - private: // The list of deferred actions. struct DeferredAction { @@ -122,11 +108,13 @@ template class DeferredQueuePerformer: public DeferredQueue< while(time_to_next != TimeUnit(-1) && time_to_next <= length) { target_(time_to_next); length -= time_to_next; - DeferredQueue::advance_to_next(time_to_next); + DeferredQueue::advance(time_to_next); } - DeferredQueue::advance_to_next(length); + DeferredQueue::advance(length); target_(length); + + // TODO: optimise this to avoid the multiple std::vector deletes. Find a neat way to expose that solution, maybe? } private: From f9ce50d2bb477edc122e2053f51beb65aa495bde Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 29 Jan 2020 22:45:44 -0500 Subject: [PATCH 18/25] Adds some debugging `asserts. --- Machines/Atari/ST/AtariST.cpp | 1 + Outputs/CRT/CRT.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Machines/Atari/ST/AtariST.cpp b/Machines/Atari/ST/AtariST.cpp index 187ee2af1..16583a7f5 100644 --- a/Machines/Atari/ST/AtariST.cpp +++ b/Machines/Atari/ST/AtariST.cpp @@ -469,6 +469,7 @@ class ConcreteMachine: length -= cycles_until_video_event_; video_ += cycles_until_video_event_; cycles_until_video_event_ = video_->get_next_sequence_point(); + assert(cycles_until_video_event_ > HalfCycles(0)); mfp_->set_timer_event_input(1, video_->display_enabled()); update_interrupt_input(); diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 84f30480d..a6e02aed9 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -294,6 +294,8 @@ void CRT::advance_cycles(int number_of_cycles, bool hsync_requested, bool vsync_ // MARK: - stream feeding methods void CRT::output_scan(const Scan *const scan) { + assert(scan->number_of_cycles >= 0); + // Simplified colour burst logic: if it's within the back porch we'll take it. if(scan->type == Scan::Type::ColourBurst) { if(!colour_burst_amplitude_ && horizontal_flywheel_->get_current_time() < (horizontal_flywheel_->get_standard_period() * 12) >> 6) { From ce28213a5e0bb463c68e7948c30a01cf3041185d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 29 Jan 2020 22:46:08 -0500 Subject: [PATCH 19/25] [Mostly] unifies deferral process. --- Machines/Atari/ST/Video.cpp | 57 ++++++++++++++++--------------------- Machines/Atari/ST/Video.hpp | 56 +----------------------------------- 2 files changed, 26 insertions(+), 87 deletions(-) diff --git a/Machines/Atari/ST/Video.cpp b/Machines/Atari/ST/Video.cpp index 16fc65f09..a83cc6f4f 100644 --- a/Machines/Atari/ST/Video.cpp +++ b/Machines/Atari/ST/Video.cpp @@ -119,7 +119,6 @@ const int vsync_delay_period = hsync_delay_period; // Signal vsync with the same } Video::Video() : - deferrer_([=] (HalfCycles duration) { advance(duration); }), crt_(2048, 2, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4), // crt_(896, 1, 500, Outputs::Display::ColourSpace::YIQ, 100, 50, 5, false, Outputs::Display::InputDataType::Red4Green4Blue4), video_stream_(crt_, palette_) { @@ -146,18 +145,23 @@ void Video::set_display_type(Outputs::Display::DisplayType display_type) { } void Video::run_for(HalfCycles duration) { - deferrer_.run_for(duration); -} - -void Video::advance(HalfCycles duration) { - const auto horizontal_timings = horizontal_parameters(field_frequency_); - const auto vertical_timings = vertical_parameters(field_frequency_); int integer_duration = int(duration.as_integral()); + assert(integer_duration >= 0); while(integer_duration) { + const auto horizontal_timings = horizontal_parameters(field_frequency_); + const auto vertical_timings = vertical_parameters(field_frequency_); + + // Determine time to next event; this'll either be one of the ones informally scheduled in here, + // or something from the deferral queue. + // Seed next event to end of line. int next_event = line_length_.length; + const int next_deferred_event = deferrer_.time_until_next_action().as(); + if(next_deferred_event >= 0) + next_event = std::min(next_event, next_deferred_event + x_); + // Check the explicitly-placed events. if(horizontal_timings.reset_blank > x_) next_event = std::min(next_event, horizontal_timings.reset_blank); if(horizontal_timings.set_blank > x_) next_event = std::min(next_event, horizontal_timings.set_blank); @@ -180,6 +184,8 @@ void Video::advance(HalfCycles duration) { const bool hsync = horizontal_.sync; const bool vsync = vertical_.sync; + assert(run_length > 0); + // Ensure proper fetching irrespective of the output. if(load_) { const int since_load = x_ - load_base_; @@ -275,7 +281,9 @@ void Video::advance(HalfCycles duration) { // Apply the next event. x_ += run_length; + assert(integer_duration >= run_length); integer_duration -= run_length; + deferrer_.advance(HalfCycles(run_length)); // Check horizontal events; the first six are guaranteed to occur separately. if(horizontal_timings.reset_blank == x_) horizontal_.blank = false; @@ -327,7 +335,9 @@ void Video::advance(HalfCycles duration) { const bool next_display_enable = vertical_.enable && horizontal_.enable; if(display_enable != next_display_enable) { // Schedule change in outwardly-visible DE line. -// add_event(de_delay_period - integer_duration, next_display_enable ? Event::Type::SetDisplayEnable : Event::Type::ResetDisplayEnable); + deferrer_.defer(de_delay_period, [this, next_display_enable] { + this->public_state_.display_enable = next_display_enable; + }); // Schedule change in inwardly-visible effect. next_load_toggle_ = x_ + 8; // 4 cycles = 8 half-cycles @@ -335,35 +345,18 @@ void Video::advance(HalfCycles duration) { if(horizontal_.sync != hsync) { // Schedule change in outwardly-visible hsync line. -// add_event(hsync_delay_period - integer_duration, horizontal_.sync ? Event::Type::SetHsync : Event::Type::ResetHsync); + deferrer_.defer(hsync_delay_period, [this, next_horizontal_sync = horizontal_.sync] { + this->public_state_.hsync = next_horizontal_sync; + }); } if(vertical_.sync != vsync) { // Schedule change in outwardly-visible hsync line. -// add_event(vsync_delay_period - integer_duration, vertical_.sync ? Event::Type::SetVsync : Event::Type::ResetVsync); + deferrer_.defer(vsync_delay_period, [this, next_vertical_sync = vertical_.sync] { + this->public_state_.vsync = next_vertical_sync; + }); } } - - // Effect any changes in visible state out here; they've been supplied as sequence points, so - // a conforming caller can't hit them within the inner loop. -// integer_duration = int(duration.as_integral()); -// if(!pending_events_.empty()) { -// auto erase_iterator = pending_events_.begin(); -// int duration_remaining = integer_duration; -// while(erase_iterator != pending_events_.end()) { -// erase_iterator->delay -= duration_remaining; -// if(erase_iterator->delay <= 0) { -// duration_remaining = -erase_iterator->delay; -// erase_iterator->apply(public_state_); -// ++erase_iterator; -// } else { -// break; -// } -// } -// if(erase_iterator != pending_events_.begin()) { -// pending_events_.erase(pending_events_.begin(), erase_iterator); -// } -// } } void Video::push_latched_data() { @@ -419,7 +412,7 @@ HalfCycles Video::get_next_sequence_point() { // If any events are pending, give the first of those the chance to be next. const auto next_deferred_item = deferrer_.time_until_next_action(); if(next_deferred_item != HalfCycles(-1)) { - event_time = std::min(event_time, next_deferred_item.as()); + event_time = std::min(event_time, x_ + next_deferred_item.as()); } // If this is a vertically-enabled line, check for the display enable boundaries, + the standard delay. diff --git a/Machines/Atari/ST/Video.hpp b/Machines/Atari/ST/Video.hpp index c6926640d..bde5c5f41 100644 --- a/Machines/Atari/ST/Video.hpp +++ b/Machines/Atari/ST/Video.hpp @@ -121,8 +121,7 @@ class Video { Range get_memory_access_range(); private: - void advance(HalfCycles duration); - DeferredQueuePerformer deferrer_; + DeferredQueue deferrer_; Outputs::CRT::CRT crt_; RangeObserver *range_observer_ = nullptr; @@ -243,59 +242,6 @@ class Video { bool vsync = false; } public_state_; - struct Event { - int delay; - enum class Type { - SetDisplayEnable, ResetDisplayEnable, - SetHsync, ResetHsync, - SetVsync, ResetVsync, - } type; - - Event(Type type, int delay) : delay(delay), type(type) {} - - void apply(PublicState &state) { - apply(type, state); - } - - static void apply(Type type, PublicState &state) { - switch(type) { - default: - case Type::SetDisplayEnable: state.display_enable = true; break; - case Type::ResetDisplayEnable: state.display_enable = false; break; - case Type::SetHsync: state.hsync = true; break; - case Type::ResetHsync: state.hsync = false; break; - case Type::SetVsync: state.vsync = true; break; - case Type::ResetVsync: state.vsync = false; break; - } - } - }; - -/* std::vector pending_events_; - void add_event(int delay, Event::Type type) { - // Apply immediately if there's no delay (or a negative delay). - if(delay <= 0) { - Event::apply(type, public_state_); - return; - } - - if(!pending_events_.empty()) { - // Otherwise enqueue, having subtracted the delay for any preceding events, - // and subtracting from the subsequent, if any. - auto insertion_point = pending_events_.begin(); - while(insertion_point != pending_events_.end() && insertion_point->delay < delay) { - delay -= insertion_point->delay; - ++insertion_point; - } - if(insertion_point != pending_events_.end()) { - insertion_point->delay -= delay; - } - - pending_events_.emplace(insertion_point, type, delay); - } else { - pending_events_.emplace_back(type, delay); - } - }*/ - friend class ::VideoTester; }; From f3db1a0c605f6243c6f7467610e656b2145ddf73 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 29 Jan 2020 22:50:22 -0500 Subject: [PATCH 20/25] Eliminates ad hoc scheduling for delayed DE -> LOAD. --- Machines/Atari/ST/Video.cpp | 20 ++++++++------------ Machines/Atari/ST/Video.hpp | 1 - 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/Machines/Atari/ST/Video.cpp b/Machines/Atari/ST/Video.cpp index a83cc6f4f..f6ea3eada 100644 --- a/Machines/Atari/ST/Video.cpp +++ b/Machines/Atari/ST/Video.cpp @@ -112,6 +112,8 @@ const int line_length_latch_position = CYCLE(54); const int hsync_delay_period = CYCLE(8); // Signal hsync at the end of the line. const int vsync_delay_period = hsync_delay_period; // Signal vsync with the same delay as hsync. +const int load_delay_period = CYCLE(4); // Amount of time after DE that observed DE changes. NB: HACK HERE. This currently incorporates the MFP recognition delay. MUST FIX. + // "VSYNC starts 104 cycles after the start of the previous line's HSYNC, so that's 4 cycles before DE would be activated. "; // that's an inconsistent statement since it would imply VSYNC at +54, which is 2 cycles before DE in 60Hz mode and 6 before // in 50Hz mode. I've gone with 56, to be four cycles ahead of DE in 50Hz mode. @@ -167,7 +169,6 @@ void Video::run_for(HalfCycles duration) { if(horizontal_timings.set_blank > x_) next_event = std::min(next_event, horizontal_timings.set_blank); if(horizontal_timings.reset_enable > x_) next_event = std::min(next_event, horizontal_timings.reset_enable); if(horizontal_timings.set_enable > x_) next_event = std::min(next_event, horizontal_timings.set_enable); - if(next_load_toggle_ > x_) next_event = std::min(next_event, next_load_toggle_); // Check for events that are relative to existing latched state. if(line_length_.hsync_start > x_) next_event = std::min(next_event, line_length_.hsync_start); @@ -293,14 +294,6 @@ void Video::run_for(HalfCycles duration) { else if(line_length_.hsync_start == x_) { horizontal_.sync = true; horizontal_.enable = false; } else if(line_length_.hsync_end == x_) horizontal_.sync = false; - // next_load_toggle_ is less predictable; test separately because it may coincide - // with one of the above tests. - if(next_load_toggle_ == x_) { - next_load_toggle_ = -1; - load_ ^= true; - load_base_ = x_; - } - // Check vertical events. if(vertical_.sync_schedule != VerticalState::SyncSchedule::None && x_ == vsync_x_position) { vertical_.sync = vertical_.sync_schedule == VerticalState::SyncSchedule::Begin; @@ -334,13 +327,16 @@ void Video::run_for(HalfCycles duration) { // Chuck any deferred output changes into the queue. const bool next_display_enable = vertical_.enable && horizontal_.enable; if(display_enable != next_display_enable) { + // Schedule change in load line. + deferrer_.defer(load_delay_period, [this, next_display_enable] { + this->load_ = next_display_enable; + this->load_base_ = this->x_; + }); + // Schedule change in outwardly-visible DE line. deferrer_.defer(de_delay_period, [this, next_display_enable] { this->public_state_.display_enable = next_display_enable; }); - - // Schedule change in inwardly-visible effect. - next_load_toggle_ = x_ + 8; // 4 cycles = 8 half-cycles } if(horizontal_.sync != hsync) { diff --git a/Machines/Atari/ST/Video.hpp b/Machines/Atari/ST/Video.hpp index bde5c5f41..a13ad8af1 100644 --- a/Machines/Atari/ST/Video.hpp +++ b/Machines/Atari/ST/Video.hpp @@ -136,7 +136,6 @@ class Video { uint16_t line_buffer_[256]; int x_ = 0, y_ = 0, next_y_ = 0; - int next_load_toggle_ = -1; bool load_ = false; int load_base_ = 0; From af976b8b3da299a5220497b0b505d1def0e6e08a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 30 Jan 2020 23:09:24 -0500 Subject: [PATCH 21/25] Eliminates modulus operation per ROM access. --- Machines/Atari/ST/AtariST.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Machines/Atari/ST/AtariST.cpp b/Machines/Atari/ST/AtariST.cpp index 16583a7f5..f1ed580bc 100644 --- a/Machines/Atari/ST/AtariST.cpp +++ b/Machines/Atari/ST/AtariST.cpp @@ -101,8 +101,10 @@ class ConcreteMachine: const bool is_early_tos = true; if(is_early_tos) { + rom_start_ = 0xfc0000; for(c = 0xfc; c < 0xff; ++c) memory_map_[c] = BusDevice::ROM; } else { + rom_start_ = 0xe00000; for(c = 0xe0; c < 0xe4; ++c) memory_map_[c] = BusDevice::ROM; } @@ -245,7 +247,7 @@ class ConcreteMachine: case BusDevice::ROM: memory = rom_.data(); - address %= rom_.size(); + address -= rom_start_; break; case BusDevice::Floating: @@ -505,6 +507,7 @@ class ConcreteMachine: std::vector ram_; std::vector rom_; + uint32_t rom_start_ = 0; enum class BusDevice { /// A mostly RAM page is one that returns ROM for the first 8 bytes, RAM elsewhere. From 019474300d16f6bf01d639b165e25c7a791fe69a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 30 Jan 2020 23:26:02 -0500 Subject: [PATCH 22/25] Centralises responsibility for picking irrelevant numbers for a computer-style monitor. --- Machines/Atari/ST/AtariST.cpp | 2 +- Machines/Atari/ST/Video.cpp | 6 +++--- .../xcshareddata/xcschemes/Clock Signal.xcscheme | 2 +- Outputs/CRT/CRT.cpp | 12 ++++++++++++ Outputs/CRT/CRT.hpp | 9 +++++++++ 5 files changed, 26 insertions(+), 5 deletions(-) diff --git a/Machines/Atari/ST/AtariST.cpp b/Machines/Atari/ST/AtariST.cpp index f1ed580bc..76d1d1a2d 100644 --- a/Machines/Atari/ST/AtariST.cpp +++ b/Machines/Atari/ST/AtariST.cpp @@ -571,7 +571,7 @@ class ConcreteMachine: GPIP 0: centronics busy */ mfp_->set_port_input( - 0x80 | // b7: Monochrome monitor detect (0 = is monochrome). + 0x00 | // b7: Monochrome monitor detect (0 = is monochrome). 0x40 | // b6: RS-232 ring indicator. (dma_->get_interrupt_line() ? 0x00 : 0x20) | // b5: FD/HS interrupt (0 = interrupt requested). ((keyboard_acia_->get_interrupt_line() || midi_acia_->get_interrupt_line()) ? 0x00 : 0x10) | // b4: Keyboard/MIDI interrupt (0 = interrupt requested). diff --git a/Machines/Atari/ST/Video.cpp b/Machines/Atari/ST/Video.cpp index f6ea3eada..77a7900a4 100644 --- a/Machines/Atari/ST/Video.cpp +++ b/Machines/Atari/ST/Video.cpp @@ -121,13 +121,13 @@ const int load_delay_period = CYCLE(4); // Amount of time after DE that observe } Video::Video() : - crt_(2048, 2, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4), -// crt_(896, 1, 500, Outputs::Display::ColourSpace::YIQ, 100, 50, 5, false, Outputs::Display::InputDataType::Red4Green4Blue4), +// crt_(2048, 2, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4), + crt_(896, 1, 500, 5, Outputs::Display::InputDataType::Red4Green4Blue4), video_stream_(crt_, palette_) { // Show a total of 260 lines; a little short for PAL but a compromise between that and the ST's // usual output height of 200 lines. - crt_.set_visible_area(crt_.get_rect_for_area(33, 260, 440, 1700, 4.0f / 3.0f)); +// crt_.set_visible_area(crt_.get_rect_for_area(33, 260, 440, 1700, 4.0f / 3.0f)); } void Video::set_ram(uint16_t *ram, size_t size) { diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme index a99eb0a5d..63be2c037 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme @@ -67,7 +67,7 @@ Date: Thu, 30 Jan 2020 23:29:04 -0500 Subject: [PATCH 23/25] Eliminates meaningless constants from the Macintosh video's CRT setup. --- Machines/Apple/Macintosh/Video.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/Apple/Macintosh/Video.cpp b/Machines/Apple/Macintosh/Video.cpp index 00276a419..211401e36 100644 --- a/Machines/Apple/Macintosh/Video.cpp +++ b/Machines/Apple/Macintosh/Video.cpp @@ -26,7 +26,7 @@ using namespace Apple::Macintosh; Video::Video(DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator) : audio_(audio), drive_speed_accumulator_(drive_speed_accumulator), - crt_(704, 1, 370, Outputs::Display::ColourSpace::YIQ, 1, 1, 6, false, Outputs::Display::InputDataType::Luminance1) { + crt_(704, 1, 370, 6, Outputs::Display::InputDataType::Luminance1) { crt_.set_display_type(Outputs::Display::DisplayType::RGB); crt_.set_visible_area(Outputs::Display::Rect(0.08f, -0.025f, 0.82f, 0.82f)); From 8aabf1b37420f8a67b88f7cecbbd09f888a27bdf Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 1 Feb 2020 21:43:48 -0500 Subject: [PATCH 24/25] Allows receivers of nullptr from begin_data to output any quantity of data. --- Outputs/CRT/CRT.hpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 018f83c5b..be07a50ba 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -236,10 +236,15 @@ class CRT { @returns A pointer to the allocated area if room is available; @c nullptr otherwise. */ inline uint8_t *begin_data(std::size_t required_length, std::size_t required_alignment = 1) { + const auto result = scan_target_->begin_data(required_length, required_alignment); #ifndef NDEBUG - allocated_data_length_ = required_length; + // If data was allocated, make a record of how much so as to be able to hold the caller to that + // contract later. If allocation failed, don't constrain the caller. This allows callers that + // allocate on demand but may allow one failure to hold for a longer period — e.g. until the + // next line. + allocated_data_length_ = result ? required_length : std::numeric_limits::max(); #endif - return scan_target_->begin_data(required_length, required_alignment); + return result; } /*! Sets the gamma exponent for the simulated screen. */ From 085529ed7211064b8e490f62f3fc87cc04305b3d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 2 Feb 2020 17:26:39 -0500 Subject: [PATCH 25/25] Makes the shifter behaviour conform to its documentation. --- Machines/Atari/ST/AtariST.cpp | 2 +- Machines/Atari/ST/Video.cpp | 48 +++++++++++-------- .../xcschemes/Clock Signal.xcscheme | 2 +- .../Clock SignalTests/AtariSTVideoTests.mm | 10 ++++ 4 files changed, 39 insertions(+), 23 deletions(-) diff --git a/Machines/Atari/ST/AtariST.cpp b/Machines/Atari/ST/AtariST.cpp index 76d1d1a2d..f1ed580bc 100644 --- a/Machines/Atari/ST/AtariST.cpp +++ b/Machines/Atari/ST/AtariST.cpp @@ -571,7 +571,7 @@ class ConcreteMachine: GPIP 0: centronics busy */ mfp_->set_port_input( - 0x00 | // b7: Monochrome monitor detect (0 = is monochrome). + 0x80 | // b7: Monochrome monitor detect (0 = is monochrome). 0x40 | // b6: RS-232 ring indicator. (dma_->get_interrupt_line() ? 0x00 : 0x20) | // b5: FD/HS interrupt (0 = interrupt requested). ((keyboard_acia_->get_interrupt_line() || midi_acia_->get_interrupt_line()) ? 0x00 : 0x10) | // b4: Keyboard/MIDI interrupt (0 = interrupt requested). diff --git a/Machines/Atari/ST/Video.cpp b/Machines/Atari/ST/Video.cpp index 77a7900a4..ca84ed3c2 100644 --- a/Machines/Atari/ST/Video.cpp +++ b/Machines/Atari/ST/Video.cpp @@ -31,7 +31,7 @@ const struct VerticalParams { } vertical_params[3] = { {63, 263, 313}, // 47 rather than 63 on early machines. {34, 234, 263}, - {51, 451, 500} // 72 Hz mode: who knows? + {34, 434, 500} // Guesswork: (i) nobody ever recommends 72Hz mode for opening the top border, so it's likely to be the same as another mode; (ii) being the same as PAL feels too late. }; /// @returns The correct @c VerticalParams for output at @c frequency. @@ -121,13 +121,13 @@ const int load_delay_period = CYCLE(4); // Amount of time after DE that observe } Video::Video() : -// crt_(2048, 2, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4), - crt_(896, 1, 500, 5, Outputs::Display::InputDataType::Red4Green4Blue4), + crt_(2048, 2, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4), +// crt_(896, 1, 500, 5, Outputs::Display::InputDataType::Red4Green4Blue4), video_stream_(crt_, palette_) { // Show a total of 260 lines; a little short for PAL but a compromise between that and the ST's // usual output height of 200 lines. -// crt_.set_visible_area(crt_.get_rect_for_area(33, 260, 440, 1700, 4.0f / 3.0f)); + crt_.set_visible_area(crt_.get_rect_for_area(33, 260, 440, 1700, 4.0f / 3.0f)); } void Video::set_ram(uint16_t *ram, size_t size) { @@ -212,13 +212,16 @@ void Video::run_for(HalfCycles duration) { } else if(!load_) { video_stream_.output(run_length, VideoStream::OutputMode::Pixels); } else { - const int since_load = x_ - load_base_; + const int start = x_ - load_base_; + const int end = start + run_length; // There will be pixels this line, subject to the shifter pipeline. // Divide into 8-[half-]cycle windows; at the start of each window fetch a word, // and during the rest of the window, shift out. - int start_column = since_load >> 3; - const int end_column = (since_load + run_length) >> 3; + int start_column = start >> 3; + const int end_column = end >> 3; + const int start_offset = start & 7; + const int end_offset = end & 7; // Rules obeyed below: // @@ -227,26 +230,29 @@ void Video::run_for(HalfCycles duration) { // was reloaded by the fetch depends on the FIFO. if(start_column == end_column) { + if(!start_offset) { + push_latched_data(); + } video_stream_.output(run_length, VideoStream::OutputMode::Pixels); } else { // Continue the current column if partway across. - if(since_load&7) { + if(start_offset) { // If at least one column boundary is crossed, complete this column. - video_stream_.output(8 - (since_load & 7), VideoStream::OutputMode::Pixels); + video_stream_.output(8 - start_offset, VideoStream::OutputMode::Pixels); ++start_column; // This starts a new column, so latch a new word. - push_latched_data(); } // Run for all columns that have their starts in this time period. int complete_columns = end_column - start_column; while(complete_columns--) { - video_stream_.output(8, VideoStream::OutputMode::Pixels); push_latched_data(); + video_stream_.output(8, VideoStream::OutputMode::Pixels); } // Output the start of the next column, if necessary. - if((since_load + run_length) & 7) { - video_stream_.output((since_load + run_length) & 7, VideoStream::OutputMode::Pixels); + if(end_offset) { + push_latched_data(); + video_stream_.output(end_offset, VideoStream::OutputMode::Pixels); } } } @@ -574,7 +580,7 @@ void Video::VideoStream::generate(int duration, OutputMode mode, bool is_termina case OutputMode::ColourBurst: crt_.output_default_colour_burst(duration_*2); break; } - // Reseed duration + // Reseed duration. duration_ = duration; // The shifter should keep running, so throw away the proper amount of content. @@ -725,7 +731,7 @@ void Video::VideoStream::output_pixels(int duration) { } // Check whether the limit has been reached. - if(pixel_pointer_ == allocation_size) { + if(pixel_pointer_ >= allocation_size - 32) { flush_pixels(); } } @@ -735,9 +741,9 @@ void Video::VideoStream::output_pixels(int duration) { if(pixels) { int leftover_duration = pixels; switch(bpp_) { - case OutputBpp::One: leftover_duration >>= 1; break; - default: break; - case OutputBpp::Four: leftover_duration <<= 1; break; + default: leftover_duration >>= 1; break; + case OutputBpp::Two: break; + case OutputBpp::Four: leftover_duration <<= 1; break; } shift(leftover_duration); crt_.output_data(leftover_duration*2); @@ -748,9 +754,9 @@ void Video::VideoStream::flush_pixels() { // Flush only if there's something to flush. if(pixel_pointer_) { switch(bpp_) { - case OutputBpp::One: crt_.output_data(pixel_pointer_); break; - default: crt_.output_data(pixel_pointer_ << 1, size_t(pixel_pointer_)); break; - case OutputBpp::Four: crt_.output_data(pixel_pointer_ << 2, size_t(pixel_pointer_)); break; + case OutputBpp::One: crt_.output_data(pixel_pointer_); break; + default: crt_.output_data(pixel_pointer_ << 1, size_t(pixel_pointer_)); break; + case OutputBpp::Four: crt_.output_data(pixel_pointer_ << 2, size_t(pixel_pointer_)); break; } } diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme index 63be2c037..a99eb0a5d 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme @@ -67,7 +67,7 @@ write(0x30, 0x0200); + + [self syncToStartOfLine]; + _video->run_for(HalfCycles(400)); // 392, 399, 406 +} + // MARK: - Tests Correlating To Exact Pieces of Software - (void)testUnionDemoScroller {