mirror of
https://github.com/TomHarte/CLK.git
synced 2024-09-08 18:55:14 +00:00
Merge pull request #167 from TomHarte/VerticalSync
Adjusts vertical sync detection
This commit is contained in:
commit
a2aec39633
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -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. */
|
||||||
|
Loading…
Reference in New Issue
Block a user