From 54bcc4019209ad53ea861f91592c5d1417f3d8a2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 29 Jul 2017 14:53:53 -0400 Subject: [PATCH 1/3] With an eye towards being more accurate as to vertical sync recognition: acknowledged that the detection period varies between PAL and NTSC. --- Outputs/CRT/CRT.cpp | 17 ++++++++++++----- Outputs/CRT/CRT.hpp | 19 ++++++++++++++++--- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 387c28f8d..9e2e44185 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -15,7 +15,7 @@ using namespace Outputs::CRT; -void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, bool should_alternate) { +void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, unsigned int vertical_sync_half_lines, bool should_alternate) { openGL_output_builder_.set_colour_format(colour_space, colour_cycle_numerator, colour_cycle_denominator); const unsigned int syncCapacityLineChargeThreshold = 2; @@ -54,11 +54,11 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType displayType) { switch(displayType) { case DisplayType::PAL50: - set_new_timing(cycles_per_line, 312, ColourSpace::YUV, 709379, 2500, true); // i.e. 283.7516 + set_new_timing(cycles_per_line, 312, ColourSpace::YUV, 709379, 2500, 5, true); // i.e. 283.7516; 2.5 lines = vertical sync break; case DisplayType::NTSC60: - set_new_timing(cycles_per_line, 262, ColourSpace::YIQ, 455, 2, false); // i.e. 227.5 + set_new_timing(cycles_per_line, 262, ColourSpace::YIQ, 455, 2, 6, false); // i.e. 227.5, 3 lines = vertical sync break; } } @@ -82,9 +82,16 @@ CRT::CRT(unsigned int common_output_divisor, unsigned int buffer_depth) : openGL_output_builder_(buffer_depth), is_alernate_line_(false) {} -CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, bool should_alternate, unsigned int buffer_depth) : +CRT::CRT( unsigned int cycles_per_line, + unsigned int common_output_divisor, + unsigned int height_of_display, + ColourSpace colour_space, + unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, + unsigned int vertical_sync_half_lines, + bool should_alternate, + unsigned int buffer_depth) : CRT(common_output_divisor, buffer_depth) { - set_new_timing(cycles_per_line, height_of_display, colour_space, colour_cycle_numerator, colour_cycle_denominator, should_alternate); + set_new_timing(cycles_per_line, height_of_display, colour_space, colour_cycle_numerator, colour_cycle_denominator, vertical_sync_half_lines, should_alternate); } CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, DisplayType displayType, unsigned int buffer_depth) : diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index f8267a570..160b1547a 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -119,6 +119,9 @@ class CRT { @param colour_cycle_denominator Specifies the denominator for the per-line frequency of the colour subcarrier. The colour subcarrier is taken to have colour_cycle_numerator/colour_cycle_denominator cycles per line. + @param vertical_sync_half_lines The expected length of vertical synchronisation (equalisation pulses aside), + in multiples of half a line. + @param buffer_depth The depth per pixel of source data buffers to create for this machine. Machines may provide per-clock-cycle data in the depth that they consider convenient, supplying a sampling function to convert between their data format and either a composite or RGB signal, allowing that @@ -127,7 +130,14 @@ class CRT { @see @c set_rgb_sampling_function , @c set_composite_sampling_function */ - CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, bool should_alternate, unsigned int buffer_depth); + CRT(unsigned int cycles_per_line, + unsigned int common_output_divisor, + unsigned int height_of_display, + ColourSpace colour_space, + unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, + unsigned int vertical_sync_half_lines, + bool should_alternate, + unsigned int buffer_depth); /*! Constructs the CRT with the specified clock rate, with the display height and colour subcarrier frequency dictated by a standard display type and with the requested number of @@ -136,11 +146,14 @@ class CRT { Exactly identical to calling the designated constructor with colour subcarrier information looked up by display type. */ - CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, DisplayType displayType, unsigned int buffer_depth); + CRT(unsigned int cycles_per_line, + unsigned int common_output_divisor, + DisplayType displayType, + unsigned int buffer_depth); /*! Resets the CRT with new timing information. The CRT then continues as though the new timing had been provided at construction. */ - void set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, bool should_alternate); + void set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, unsigned int vertical_sync_half_lines, bool should_alternate); /*! Resets the CRT with new timing information derived from a new display type. The CRT then continues as though the new timing had been provided at construction. */ From 3528a7f78b9cfa6daba3a247140e1af95b9a54bb Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 29 Jul 2017 17:33:52 -0400 Subject: [PATCH 2/3] Made an attempt at triggering vertical sync the expected number of time after it begins, regardless of total length. --- Outputs/CRT/CRT.cpp | 79 +++++++++++++++++++++++++++------------------ Outputs/CRT/CRT.hpp | 2 +- 2 files changed, 48 insertions(+), 33 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 9e2e44185..98ada585c 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -18,7 +18,6 @@ using namespace Outputs::CRT; void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, unsigned int vertical_sync_half_lines, bool should_alternate) { openGL_output_builder_.set_colour_format(colour_space, colour_cycle_numerator, colour_cycle_denominator); - const unsigned int syncCapacityLineChargeThreshold = 2; const unsigned int millisecondsHorizontalRetraceTime = 7; // source: Dictionary of Video and Television Technology, p. 234 const unsigned int scanlinesVerticalRetraceTime = 10; // source: ibid @@ -37,8 +36,7 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di cycles_per_line_ = cycles_per_line; unsigned int multiplied_cycles_per_line = cycles_per_line * time_multiplier_; - // generate timing values implied by the given arguments - sync_capacitor_charge_threshold_ = ((unsigned int)(syncCapacityLineChargeThreshold * cycles_per_line) * 3) / 4; + sync_capacitor_charge_threshold_ = (vertical_sync_half_lines * cycles_per_line) >> 1; // create the two flywheels horizontal_flywheel_.reset(new Flywheel(multiplied_cycles_per_line, (millisecondsHorizontalRetraceTime * multiplied_cycles_per_line) >> 6, multiplied_cycles_per_line >> 6)); @@ -74,7 +72,6 @@ void CRT::set_composite_function_type(CompositeSourceType type, float offset_of_ CRT::CRT(unsigned int common_output_divisor, unsigned int buffer_depth) : sync_capacitor_charge_level_(0), is_receiving_sync_(false), - sync_period_(0), common_output_divisor_(common_output_divisor), is_writing_composite_run_(false), delegate_(nullptr), @@ -264,31 +261,6 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo #pragma mark - stream feeding methods void CRT::output_scan(const Scan *const scan) { - const bool this_is_sync = (scan->type == Scan::Type::Sync); - const bool is_leading_edge = (!is_receiving_sync_ && this_is_sync); - is_receiving_sync_ = this_is_sync; - - // Accumulate: (i) a total of the amount of time in sync; and (ii) the amount of time since sync. - if(this_is_sync) { cycles_of_sync_ += scan->number_of_cycles; cycles_since_sync_ = 0; } - else cycles_since_sync_ += scan->number_of_cycles; - - bool vsync_requested = false; - // If it has been at least half a line since sync ended, then it is safe to decide whether what ended - // was vertical sync. - if(cycles_since_sync_ > (cycles_per_line_ >> 1)) { - // If it was vertical sync, set that flag. If it wasn't, clear the summed amount of sync to avoid - // a mistaken vertical sync due to an aggregate of horizontals. - vsync_requested = (cycles_of_sync_ > sync_capacitor_charge_threshold_); - if(vsync_requested || cycles_of_sync_ < (cycles_per_line_ >> 2)) - cycles_of_sync_ = 0; - } - - // This introduces a blackout period close to the expected vertical sync point in which horizontal syncs are not - // recognised, effectively causing the horizontal flywheel to freewheel during that period. This attempts to seek - // the problem that vertical sync otherwise often starts halfway through a scanline, which confuses the horizontal - // flywheel. I'm currently unclear whether this is an accurate solution to this problem. - const bool hsync_requested = is_leading_edge && !vertical_flywheel_->is_near_expected_sync(); - // 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) { @@ -300,11 +272,54 @@ void CRT::output_scan(const Scan *const scan) { colour_burst_phase_ = (colour_burst_phase_ & ~63) + colour_burst_phase_adjustment_; } } + // TODO: inspect raw data for potential colour burst if required; the DPLL and some zero crossing logic + // will probably be sufficient but some test data would be helpful - // TODO: inspect raw data for potential colour burst if required + // sync logic: mark whether this is currently sync and check for a leading edge + const bool this_is_sync = (scan->type == Scan::Type::Sync); + const bool is_leading_edge = (!is_receiving_sync_ && this_is_sync); + is_receiving_sync_ = this_is_sync; - sync_period_ = is_receiving_sync_ ? (sync_period_ + scan->number_of_cycles) : 0; - advance_cycles(scan->number_of_cycles, hsync_requested, vsync_requested, scan->type); + // horizontal sync is recognised on any leading edge that is not 'near' the expected vertical sync; + // the second limb is to avoid slightly horizontal sync shifting from the common pattern of + // equalisation pulses as the inverse of ordinary horizontal sync + bool hsync_requested = is_leading_edge && !vertical_flywheel_->is_near_expected_sync(); + + if(this_is_sync) { + // if this is sync then either begin or continue a sync accumulation phase + is_accumulating_sync_ = true; + } else { + // if this is not sync then check how long it has been since sync. If it's more than + // half a line then end sync accumulation and zero out the accumulating count + cycles_since_sync_ += scan->number_of_cycles; + if(cycles_since_sync_ > (cycles_per_line_ >> 1)) { + cycles_of_sync_ = 0; + is_accumulating_sync_ = false; + } + } + + unsigned int number_of_cycles = scan->number_of_cycles; + bool vsync_requested = false; + + // if sync is being accumulated then accumulate it; if it crosses the vertical sync threshold then + // divide this line at the crossing point and indicate vertical sync there + if(is_accumulating_sync_) { + cycles_of_sync_ += scan->number_of_cycles; + + if(this_is_sync && cycles_of_sync_ >= sync_capacitor_charge_threshold_) { + unsigned int overshoot = std::max(cycles_of_sync_ - sync_capacitor_charge_threshold_, number_of_cycles); + number_of_cycles -= overshoot; + advance_cycles(number_of_cycles, hsync_requested, false, scan->type); + + cycles_of_sync_ = 0; + is_accumulating_sync_ = false; + number_of_cycles = overshoot; + hsync_requested = false; + vsync_requested = true; + } + } + + advance_cycles(number_of_cycles, hsync_requested, vsync_requested, scan->type); } /* diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 160b1547a..9fe8e3bb3 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -44,7 +44,6 @@ class CRT { bool is_receiving_sync_; // true if the CRT is currently receiving sync (i.e. this is for edge triggering of horizontal sync) int sync_capacitor_charge_level_; // this charges up during times of sync and depletes otherwise; needs to hit a required threshold to trigger a vertical sync unsigned int sync_capacitor_charge_threshold_; // this charges up during times of sync and depletes otherwise; needs to hit a required threshold to trigger a vertical sync - unsigned int sync_period_; struct Scan { enum Type { @@ -94,6 +93,7 @@ class CRT { } // sync counter, for determining vertical sync + bool is_accumulating_sync_; unsigned int cycles_of_sync_; unsigned int cycles_since_sync_; unsigned int cycles_per_line_; From ed8c73eb14761e98f822d3aeb0288eb581cfe84e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 29 Jul 2017 18:25:04 -0400 Subject: [PATCH 3/3] Ensured lengthy constant sync can't appear to be two sync pulses, regardless of other interruption. --- Outputs/CRT/CRT.cpp | 20 ++++++++++++-------- Outputs/CRT/CRT.hpp | 15 +++++++-------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 98ada585c..c111c2e0f 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -70,7 +70,6 @@ void CRT::set_composite_function_type(CompositeSourceType type, float offset_of_ } CRT::CRT(unsigned int common_output_divisor, unsigned int buffer_depth) : - sync_capacitor_charge_level_(0), is_receiving_sync_(false), common_output_divisor_(common_output_divisor), is_writing_composite_run_(false), @@ -288,13 +287,15 @@ void CRT::output_scan(const Scan *const scan) { if(this_is_sync) { // if this is sync then either begin or continue a sync accumulation phase is_accumulating_sync_ = true; + cycles_since_sync_ = 0; } else { // if this is not sync then check how long it has been since sync. If it's more than // half a line then end sync accumulation and zero out the accumulating count cycles_since_sync_ += scan->number_of_cycles; - if(cycles_since_sync_ > (cycles_per_line_ >> 1)) { + if(cycles_since_sync_ > (cycles_per_line_ >> 2)) { cycles_of_sync_ = 0; is_accumulating_sync_ = false; + is_refusing_sync_ = false; } } @@ -303,18 +304,21 @@ void CRT::output_scan(const Scan *const scan) { // if sync is being accumulated then accumulate it; if it crosses the vertical sync threshold then // divide this line at the crossing point and indicate vertical sync there - if(is_accumulating_sync_) { + if(is_accumulating_sync_ && !is_refusing_sync_) { cycles_of_sync_ += scan->number_of_cycles; if(this_is_sync && cycles_of_sync_ >= sync_capacitor_charge_threshold_) { - unsigned int overshoot = std::max(cycles_of_sync_ - sync_capacitor_charge_threshold_, number_of_cycles); - number_of_cycles -= overshoot; - advance_cycles(number_of_cycles, hsync_requested, false, scan->type); + is_refusing_sync_ = true; + unsigned int overshoot = std::min(cycles_of_sync_ - sync_capacitor_charge_threshold_, number_of_cycles); + if(overshoot) { + number_of_cycles -= overshoot; + advance_cycles(number_of_cycles, hsync_requested, false, scan->type); + hsync_requested = false; + number_of_cycles = overshoot; + } cycles_of_sync_ = 0; is_accumulating_sync_ = false; - number_of_cycles = overshoot; - hsync_requested = false; vsync_requested = true; } } diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 9fe8e3bb3..13e22085b 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -40,11 +40,6 @@ class CRT { std::unique_ptr horizontal_flywheel_, vertical_flywheel_; uint16_t vertical_flywheel_output_divider_; - // elements of sync separation - bool is_receiving_sync_; // true if the CRT is currently receiving sync (i.e. this is for edge triggering of horizontal sync) - int sync_capacitor_charge_level_; // this charges up during times of sync and depletes otherwise; needs to hit a required threshold to trigger a vertical sync - unsigned int sync_capacitor_charge_threshold_; // this charges up during times of sync and depletes otherwise; needs to hit a required threshold to trigger a vertical sync - struct Scan { enum Type { Sync, Level, Data, Blank, ColourBurst @@ -93,9 +88,13 @@ class CRT { } // sync counter, for determining vertical sync - bool is_accumulating_sync_; - unsigned int cycles_of_sync_; - unsigned int cycles_since_sync_; + bool is_receiving_sync_; // true if the CRT is currently receiving sync (i.e. this is for edge triggering of horizontal sync) + bool is_accumulating_sync_; // true if a sync level has triggered the suspicion that a vertical sync might be in progress + bool is_refusing_sync_; // true once a vertical sync has been detected, until a prolonged period of non-sync has ended suspicion of an ongoing vertical sync + unsigned int sync_capacitor_charge_threshold_; // this charges up during times of sync and depletes otherwise; needs to hit a required threshold to trigger a vertical sync + unsigned int cycles_of_sync_; // the number of cycles since the potential vertical sync began + unsigned int cycles_since_sync_; // the number of cycles since last in sync, for defeating the possibility of this being a vertical sync + unsigned int cycles_per_line_; public: