1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-30 07:29:06 +00:00

Merge pull request #167 from TomHarte/VerticalSync

Adjusts vertical sync detection
This commit is contained in:
Thomas Harte 2017-07-29 21:44:47 -04:00 committed by GitHub
commit a2aec39633
2 changed files with 87 additions and 49 deletions

View File

@ -15,10 +15,9 @@
using namespace Outputs::CRT; 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); 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 millisecondsHorizontalRetraceTime = 7; // source: Dictionary of Video and Television Technology, p. 234
const unsigned int scanlinesVerticalRetraceTime = 10; // source: ibid 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; cycles_per_line_ = cycles_per_line;
unsigned int multiplied_cycles_per_line = cycles_per_line * time_multiplier_; unsigned int multiplied_cycles_per_line = cycles_per_line * time_multiplier_;
// generate timing values implied by the given arguments sync_capacitor_charge_threshold_ = (vertical_sync_half_lines * cycles_per_line) >> 1;
sync_capacitor_charge_threshold_ = ((unsigned int)(syncCapacityLineChargeThreshold * cycles_per_line) * 3) / 4;
// create the two flywheels // create the two flywheels
horizontal_flywheel_.reset(new Flywheel(multiplied_cycles_per_line, (millisecondsHorizontalRetraceTime * multiplied_cycles_per_line) >> 6, multiplied_cycles_per_line >> 6)); horizontal_flywheel_.reset(new Flywheel(multiplied_cycles_per_line, (millisecondsHorizontalRetraceTime * multiplied_cycles_per_line) >> 6, multiplied_cycles_per_line >> 6));
@ -54,11 +52,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) { void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType displayType) {
switch(displayType) { switch(displayType) {
case DisplayType::PAL50: 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; break;
case DisplayType::NTSC60: 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; break;
} }
} }
@ -72,9 +70,7 @@ void CRT::set_composite_function_type(CompositeSourceType type, float offset_of_
} }
CRT::CRT(unsigned int common_output_divisor, unsigned int buffer_depth) : CRT::CRT(unsigned int common_output_divisor, unsigned int buffer_depth) :
sync_capacitor_charge_level_(0),
is_receiving_sync_(false), is_receiving_sync_(false),
sync_period_(0),
common_output_divisor_(common_output_divisor), common_output_divisor_(common_output_divisor),
is_writing_composite_run_(false), is_writing_composite_run_(false),
delegate_(nullptr), delegate_(nullptr),
@ -82,9 +78,16 @@ CRT::CRT(unsigned int common_output_divisor, unsigned int buffer_depth) :
openGL_output_builder_(buffer_depth), openGL_output_builder_(buffer_depth),
is_alernate_line_(false) {} 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) { 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) : CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, DisplayType displayType, unsigned int buffer_depth) :
@ -257,31 +260,6 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo
#pragma mark - stream feeding methods #pragma mark - stream feeding methods
void CRT::output_scan(const Scan *const scan) { 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 // simplified colour burst logic: if it's within the back porch we'll take it
if(scan->type == Scan::Type::ColourBurst) { if(scan->type == Scan::Type::ColourBurst) {
if(!colour_burst_amplitude_ && horizontal_flywheel_->get_current_time() < (horizontal_flywheel_->get_standard_period() * 12) >> 6) { if(!colour_burst_amplitude_ && horizontal_flywheel_->get_current_time() < (horizontal_flywheel_->get_standard_period() * 12) >> 6) {
@ -293,11 +271,59 @@ void CRT::output_scan(const Scan *const scan) {
colour_burst_phase_ = (colour_burst_phase_ & ~63) + colour_burst_phase_adjustment_; 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; // horizontal sync is recognised on any leading edge that is not 'near' the expected vertical sync;
advance_cycles(scan->number_of_cycles, hsync_requested, vsync_requested, scan->type); // 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;
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_ >> 2)) {
cycles_of_sync_ = 0;
is_accumulating_sync_ = false;
is_refusing_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_ && !is_refusing_sync_) {
cycles_of_sync_ += scan->number_of_cycles;
if(this_is_sync && cycles_of_sync_ >= sync_capacitor_charge_threshold_) {
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;
vsync_requested = true;
}
}
advance_cycles(number_of_cycles, hsync_requested, vsync_requested, scan->type);
} }
/* /*

View File

@ -40,12 +40,6 @@ class CRT {
std::unique_ptr<Flywheel> horizontal_flywheel_, vertical_flywheel_; std::unique_ptr<Flywheel> horizontal_flywheel_, vertical_flywheel_;
uint16_t vertical_flywheel_output_divider_; 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
unsigned int sync_period_;
struct Scan { struct Scan {
enum Type { enum Type {
Sync, Level, Data, Blank, ColourBurst Sync, Level, Data, Blank, ColourBurst
@ -94,8 +88,13 @@ class CRT {
} }
// sync counter, for determining vertical sync // sync counter, for determining vertical sync
unsigned int cycles_of_sync_; bool is_receiving_sync_; // true if the CRT is currently receiving sync (i.e. this is for edge triggering of horizontal sync)
unsigned int cycles_since_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_; unsigned int cycles_per_line_;
public: public:
@ -119,6 +118,9 @@ class CRT {
@param colour_cycle_denominator Specifies the denominator for the per-line frequency of the colour subcarrier. @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. 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 @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 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 function to convert between their data format and either a composite or RGB signal, allowing that
@ -127,7 +129,14 @@ class CRT {
@see @c set_rgb_sampling_function , @c set_composite_sampling_function @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 /*! 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 subcarrier frequency dictated by a standard display type and with the requested number of
@ -136,11 +145,14 @@ class CRT {
Exactly identical to calling the designated constructor with colour subcarrier information Exactly identical to calling the designated constructor with colour subcarrier information
looked up by display type. 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 /*! Resets the CRT with new timing information. The CRT then continues as though the new timing had
been provided at construction. */ 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 /*! 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. */ as though the new timing had been provided at construction. */