1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-19 08:31:11 +00:00

Completes documentation and rounds out implementation.

This commit is contained in:
Thomas Harte 2018-11-04 22:17:33 -05:00
parent 1effb97b74
commit 9799aa0975
2 changed files with 64 additions and 55 deletions

View File

@ -16,10 +16,9 @@
using namespace Outputs::CRT; using namespace Outputs::CRT;
void CRT::set_new_timing(int cycles_per_line, int height_of_display, Outputs::Display::ColourSpace colour_space, int colour_cycle_numerator, int colour_cycle_denominator, int vertical_sync_half_lines, bool should_alternate) { void CRT::set_new_timing(int cycles_per_line, int height_of_display, Outputs::Display::ColourSpace colour_space, int colour_cycle_numerator, int colour_cycle_denominator, int vertical_sync_half_lines, bool should_alternate) {
// openGL_output_builder_.set_colour_format(colour_space, colour_cycle_numerator, colour_cycle_denominator);
const int millisecondsHorizontalRetraceTime = 7; // source: Dictionary of Video and Television Technology, p. 234 const int millisecondsHorizontalRetraceTime = 7; // Source: Dictionary of Video and Television Technology, p. 234.
const int scanlinesVerticalRetraceTime = 8; // source: ibid const int scanlinesVerticalRetraceTime = 8; // Source: ibid.
// To quote: // To quote:
// //
@ -37,9 +36,9 @@ void CRT::set_new_timing(int cycles_per_line, int height_of_display, Outputs::Di
cycles_per_line_ = cycles_per_line; cycles_per_line_ = cycles_per_line;
const int multiplied_cycles_per_line = cycles_per_line * time_multiplier_; const int multiplied_cycles_per_line = cycles_per_line * time_multiplier_;
// allow sync to be detected (and acted upon) a line earlier than the specified requirement, // Allow sync to be detected (and acted upon) a line earlier than the specified requirement,
// as a simple way of avoiding not-quite-exact comparison issues while still being true enough to // as a simple way of avoiding not-quite-exact comparison issues while still being true enough to
// the gist for simple debugging // the gist for simple debugging.
sync_capacitor_charge_threshold_ = ((vertical_sync_half_lines - 2) * cycles_per_line) >> 1; sync_capacitor_charge_threshold_ = ((vertical_sync_half_lines - 2) * cycles_per_line) >> 1;
// Create the two flywheels: // Create the two flywheels:
@ -54,10 +53,11 @@ void CRT::set_new_timing(int cycles_per_line, int height_of_display, Outputs::Di
horizontal_flywheel_.reset(new Flywheel(multiplied_cycles_per_line, (millisecondsHorizontalRetraceTime * multiplied_cycles_per_line) >> 6, multiplied_cycles_per_line >> 5)); horizontal_flywheel_.reset(new Flywheel(multiplied_cycles_per_line, (millisecondsHorizontalRetraceTime * multiplied_cycles_per_line) >> 6, multiplied_cycles_per_line >> 5));
vertical_flywheel_.reset(new Flywheel(multiplied_cycles_per_line * height_of_display, scanlinesVerticalRetraceTime * multiplied_cycles_per_line, (multiplied_cycles_per_line * height_of_display) >> 3)); vertical_flywheel_.reset(new Flywheel(multiplied_cycles_per_line * height_of_display, scanlinesVerticalRetraceTime * multiplied_cycles_per_line, (multiplied_cycles_per_line * height_of_display) >> 3));
// figure out the divisor necessary to get the horizontal flywheel into a 16-bit range // Figure out the divisor necessary to get the horizontal flywheel into a 16-bit range.
const int real_clock_scan_period = multiplied_cycles_per_line * height_of_display; const int real_clock_scan_period = multiplied_cycles_per_line * height_of_display;
vertical_flywheel_output_divider_ = (real_clock_scan_period + 65534) / 65535; vertical_flywheel_output_divider_ = (real_clock_scan_period + 65534) / 65535;
// Communicate relevant fields to the scan target.
scan_target_modals_.output_scale.x = uint16_t(time_multiplier_ * cycles_per_line); scan_target_modals_.output_scale.x = uint16_t(time_multiplier_ * cycles_per_line);
scan_target_modals_.output_scale.y = uint16_t((multiplied_cycles_per_line * height_of_display) / vertical_flywheel_output_divider_); scan_target_modals_.output_scale.y = uint16_t((multiplied_cycles_per_line * height_of_display) / vertical_flywheel_output_divider_);
scan_target_modals_.expected_vertical_lines = height_of_display; scan_target_modals_.expected_vertical_lines = height_of_display;
@ -65,16 +65,26 @@ void CRT::set_new_timing(int cycles_per_line, int height_of_display, Outputs::Di
scan_target_->set_modals(scan_target_modals_); scan_target_->set_modals(scan_target_modals_);
} }
void CRT::set_new_data_type(Outputs::Display::ScanTarget::Modals::DataType data_type) {
scan_target_modals_.source_data_type = data_type;
scan_target_->set_modals(scan_target_modals_);
}
void CRT::set_visible_area(Outputs::Display::Rect visible_area) {
scan_target_modals_.visible_area = visible_area;
scan_target_->set_modals(scan_target_modals_);
}
void CRT::set_new_display_type(int cycles_per_line, Outputs::Display::Type displayType) { void CRT::set_new_display_type(int cycles_per_line, Outputs::Display::Type displayType) {
switch(displayType) { switch(displayType) {
case Outputs::Display::Type::PAL50: case Outputs::Display::Type::PAL50:
scan_target_modals_.intended_gamma = 2.8f; scan_target_modals_.intended_gamma = 2.8f;
set_new_timing(cycles_per_line, 312, Outputs::Display::ColourSpace::YUV, 709379, 2500, 5, true); // i.e. 283.7516; 2.5 lines = vertical sync set_new_timing(cycles_per_line, 312, Outputs::Display::ColourSpace::YUV, 709379, 2500, 5, true); // i.e. 283.7516 colour cycles per line; 2.5 lines = vertical sync.
break; break;
case Outputs::Display::Type::NTSC60: case Outputs::Display::Type::NTSC60:
scan_target_modals_.intended_gamma = 2.2f; scan_target_modals_.intended_gamma = 2.2f;
set_new_timing(cycles_per_line, 262, Outputs::Display::ColourSpace::YIQ, 455, 2, 6, false); // i.e. 227.5, 3 lines = vertical sync set_new_timing(cycles_per_line, 262, Outputs::Display::ColourSpace::YIQ, 455, 2, 6, false); // i.e. 227.5 colour cycles per line, 3 lines = vertical sync.
break; break;
} }
} }
@ -155,7 +165,7 @@ void CRT::advance_cycles(int number_of_cycles, bool hsync_requested, bool vsync_
Outputs::Display::ScanTarget::Scan *const next_scan = is_output_segment ? scan_target_->get_scan() : nullptr; Outputs::Display::ScanTarget::Scan *const next_scan = is_output_segment ? scan_target_->get_scan() : nullptr;
did_output |= is_output_segment; did_output |= is_output_segment;
// If outputting, store the start location and // If outputting, store the start location and scan constants.
if(next_scan) { if(next_scan) {
next_scan->end_points[0].x = uint16_t(horizontal_flywheel_->get_current_output_position()); next_scan->end_points[0].x = uint16_t(horizontal_flywheel_->get_current_output_position());
next_scan->end_points[0].y = uint16_t(vertical_flywheel_->get_current_output_position() / vertical_flywheel_output_divider_); next_scan->end_points[0].y = uint16_t(vertical_flywheel_->get_current_output_position() / vertical_flywheel_output_divider_);
@ -212,7 +222,7 @@ void CRT::advance_cycles(int number_of_cycles, bool hsync_requested, bool vsync_
// MARK: - stream feeding methods // MARK: - stream feeding methods
void CRT::output_scan(const Scan *const scan) { void CRT::output_scan(const Scan *const scan) {
// 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) {
// Load phase_numerator_ as a fixed-point quantity in the range [0, 255]. // Load phase_numerator_ as a fixed-point quantity in the range [0, 255].
@ -235,18 +245,18 @@ void CRT::output_scan(const Scan *const scan) {
const bool is_leading_edge = (!is_receiving_sync_ && this_is_sync); const bool is_leading_edge = (!is_receiving_sync_ && this_is_sync);
is_receiving_sync_ = this_is_sync; is_receiving_sync_ = this_is_sync;
// horizontal sync is recognised on any leading edge that is not 'near' the expected vertical sync; // 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 // the second limb is to avoid slightly horizontal sync shifting from the common pattern of
// equalisation pulses as the inverse of ordinary horizontal sync // equalisation pulses as the inverse of ordinary horizontal sync.
bool hsync_requested = is_leading_edge && !vertical_flywheel_->is_near_expected_sync(); bool hsync_requested = is_leading_edge && !vertical_flywheel_->is_near_expected_sync();
if(this_is_sync) { if(this_is_sync) {
// if this is sync then either begin or continue a sync accumulation phase // If this is sync then either begin or continue a sync accumulation phase.
is_accumulating_sync_ = true; is_accumulating_sync_ = true;
cycles_since_sync_ = 0; cycles_since_sync_ = 0;
} else { } else {
// if this is not sync then check how long it has been since sync. If it's more than // 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 // half a line then end sync accumulation and zero out the accumulating count.
cycles_since_sync_ += scan->number_of_cycles; cycles_since_sync_ += scan->number_of_cycles;
if(cycles_since_sync_ > (cycles_per_line_ >> 2)) { if(cycles_since_sync_ > (cycles_per_line_ >> 2)) {
cycles_of_sync_ = 0; cycles_of_sync_ = 0;
@ -258,13 +268,13 @@ void CRT::output_scan(const Scan *const scan) {
int number_of_cycles = scan->number_of_cycles; int number_of_cycles = scan->number_of_cycles;
bool vsync_requested = false; bool vsync_requested = false;
// if sync is being accumulated then accumulate it; if it crosses the vertical sync threshold then // 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 // divide this line at the crossing point and indicate vertical sync there.
if(is_accumulating_sync_ && !is_refusing_sync_) { if(is_accumulating_sync_ && !is_refusing_sync_) {
cycles_of_sync_ += scan->number_of_cycles; cycles_of_sync_ += scan->number_of_cycles;
if(this_is_sync && cycles_of_sync_ >= sync_capacitor_charge_threshold_) { if(this_is_sync && cycles_of_sync_ >= sync_capacitor_charge_threshold_) {
int overshoot = std::min(cycles_of_sync_ - sync_capacitor_charge_threshold_, number_of_cycles); const int overshoot = std::min(cycles_of_sync_ - sync_capacitor_charge_threshold_, number_of_cycles);
if(overshoot) { if(overshoot) {
number_of_cycles -= overshoot; number_of_cycles -= overshoot;
advance_cycles(number_of_cycles, hsync_requested, false, scan->type, 0); advance_cycles(number_of_cycles, hsync_requested, false, scan->type, 0);
@ -342,9 +352,9 @@ Outputs::Display::Rect CRT::get_rect_for_area(int first_line_after_sync, int num
number_of_lines += 4; number_of_lines += 4;
// determine prima facie x extent // determine prima facie x extent
int horizontal_period = horizontal_flywheel_->get_standard_period(); const int horizontal_period = horizontal_flywheel_->get_standard_period();
int horizontal_scan_period = horizontal_flywheel_->get_scan_period(); const int horizontal_scan_period = horizontal_flywheel_->get_scan_period();
int horizontal_retrace_period = horizontal_period - horizontal_scan_period; const int horizontal_retrace_period = horizontal_period - horizontal_scan_period;
// make sure that the requested range is visible // make sure that the requested range is visible
if(static_cast<int>(first_cycle_after_sync) < horizontal_retrace_period) first_cycle_after_sync = static_cast<int>(horizontal_retrace_period); if(static_cast<int>(first_cycle_after_sync) < horizontal_retrace_period) first_cycle_after_sync = static_cast<int>(horizontal_retrace_period);
@ -354,9 +364,9 @@ Outputs::Display::Rect CRT::get_rect_for_area(int first_line_after_sync, int num
float width = static_cast<float>(number_of_cycles) / static_cast<float>(horizontal_scan_period); float width = static_cast<float>(number_of_cycles) / static_cast<float>(horizontal_scan_period);
// determine prima facie y extent // determine prima facie y extent
int vertical_period = vertical_flywheel_->get_standard_period(); const int vertical_period = vertical_flywheel_->get_standard_period();
int vertical_scan_period = vertical_flywheel_->get_scan_period(); const int vertical_scan_period = vertical_flywheel_->get_scan_period();
int vertical_retrace_period = vertical_period - vertical_scan_period; const int vertical_retrace_period = vertical_period - vertical_scan_period;
// make sure that the requested range is visible // make sure that the requested range is visible
// if(static_cast<int>(first_line_after_sync) * horizontal_period < vertical_retrace_period) // if(static_cast<int>(first_line_after_sync) * horizontal_period < vertical_retrace_period)
@ -368,8 +378,8 @@ Outputs::Display::Rect CRT::get_rect_for_area(int first_line_after_sync, int num
float height = static_cast<float>(static_cast<int>(number_of_lines) * horizontal_period) / vertical_scan_period; float height = static_cast<float>(static_cast<int>(number_of_lines) * horizontal_period) / vertical_scan_period;
// adjust to ensure aspect ratio is correct // adjust to ensure aspect ratio is correct
float adjusted_aspect_ratio = (3.0f*aspect_ratio / 4.0f); const float adjusted_aspect_ratio = (3.0f*aspect_ratio / 4.0f);
float ideal_width = height * adjusted_aspect_ratio; const float ideal_width = height * adjusted_aspect_ratio;
if(ideal_width > width) { if(ideal_width > width) {
start_x -= (ideal_width - width) * 0.5f; start_x -= (ideal_width - width) * 0.5f;
width = ideal_width; width = ideal_width;

View File

@ -25,13 +25,20 @@ class Delegate {
virtual void crt_did_end_batch_of_frames(CRT *crt, int number_of_frames, int number_of_unexpected_vertical_syncs) = 0; virtual void crt_did_end_batch_of_frames(CRT *crt, int number_of_frames, int number_of_unexpected_vertical_syncs) = 0;
}; };
/*! Models a class 2d analogue output device, accepting a serial stream of data including syncs
and generating the proper set of output spans. Attempts to act and react exactly as a real
TV would have to things like irregular or off-spec sync, and includes logic properly to track
colour phase for colour composite video.
*/
class CRT { class CRT {
private: private:
// the incoming clock lengths will be multiplied by something to give at least 1000 // The incoming clock lengths will be multiplied by @c time_multiplier_; this increases
// sample points per line // precision across the line.
int time_multiplier_ = 1; int time_multiplier_ = 1;
// the two flywheels regulating scanning // Two flywheels regulate scanning; the vertical will have a range much greater than the horizontal;
// the output divider is what that'll need to be divided by to reduce it into a 16-bit range as
// posted on to the scan target.
std::unique_ptr<Flywheel> horizontal_flywheel_, vertical_flywheel_; std::unique_ptr<Flywheel> horizontal_flywheel_, vertical_flywheel_;
int vertical_flywheel_output_divider_ = 1; int vertical_flywheel_output_divider_ = 1;
@ -58,25 +65,19 @@ class CRT {
int64_t colour_cycle_numerator_ = 1; int64_t colour_cycle_numerator_ = 1;
bool is_alernate_line_ = false, phase_alternates_ = false; bool is_alernate_line_ = false, phase_alternates_ = false;
// the outer entry point for dispatching output_sync, output_blank, output_level and output_data
void advance_cycles(int number_of_cycles, bool hsync_requested, bool vsync_requested, const Scan::Type type, int number_of_samples); void advance_cycles(int number_of_cycles, bool hsync_requested, bool vsync_requested, const Scan::Type type, int number_of_samples);
// the inner entry point that determines whether and when the next sync event will occur within
// the current output window
Flywheel::SyncEvent get_next_vertical_sync_event(bool vsync_is_requested, int cycles_to_run_for, int *cycles_advanced); Flywheel::SyncEvent get_next_vertical_sync_event(bool vsync_is_requested, int cycles_to_run_for, int *cycles_advanced);
Flywheel::SyncEvent get_next_horizontal_sync_event(bool hsync_is_requested, int cycles_to_run_for, int *cycles_advanced); Flywheel::SyncEvent get_next_horizontal_sync_event(bool hsync_is_requested, int cycles_to_run_for, int *cycles_advanced);
// the delegate
Delegate *delegate_ = nullptr; Delegate *delegate_ = nullptr;
int frames_since_last_delegate_call_ = 0; int frames_since_last_delegate_call_ = 0;
// sync counter, for determining vertical sync bool is_receiving_sync_ = false; // @c true if the CRT is currently receiving sync (i.e. this is for edge triggering of horizontal sync); @c false otherwise.
bool is_receiving_sync_ = false; // true if the CRT is currently receiving sync (i.e. this is for edge triggering of horizontal sync) bool is_accumulating_sync_ = false; // @c true if a sync level has triggered the suspicion that a vertical sync might be in progress; @c false otherwise.
bool is_accumulating_sync_ = false; // true if a sync level has triggered the suspicion that a vertical sync might be in progress bool is_refusing_sync_ = false; // @c true once a vertical sync has been detected, until a prolonged period of non-sync has ended suspicion of an ongoing vertical sync.
bool is_refusing_sync_ = false; // true once a vertical sync has been detected, until a prolonged period of non-sync has ended suspicion of an ongoing vertical sync int sync_capacitor_charge_threshold_ = 0; // Charges up during times of sync and depletes otherwise; needs to hit a required threshold to trigger a vertical sync.
int sync_capacitor_charge_threshold_ = 0; // this charges up during times of sync and depletes otherwise; needs to hit a required threshold to trigger a vertical sync int cycles_of_sync_ = 0; // The number of cycles since the potential vertical sync began.
int cycles_of_sync_ = 0; // the number of cycles since the potential vertical sync began int cycles_since_sync_ = 0; // The number of cycles since last in sync, for defeating the possibility of this being a vertical sync.
int cycles_since_sync_ = 0; // the number of cycles since last in sync, for defeating the possibility of this being a vertical sync
int cycles_per_line_ = 1; int cycles_per_line_ = 1;
@ -91,7 +92,8 @@ class CRT {
@param cycles_per_line The clock rate at which this CRT will be driven, specified as the number @param cycles_per_line The clock rate at which this CRT will be driven, specified as the number
of cycles expected to take up one whole scanline of the display. of cycles expected to take up one whole scanline of the display.
@param minimum_cycles_per_pixel TODO. @param clocks_per_pixel_greatest_common_divisor The GCD of all potential lengths of a pixel
in terms of the clock rate given as @c cycles_per_line.
@param height_of_display The number of lines that nominally form one field of the display, rounded @param height_of_display The number of lines that nominally form one field of the display, rounded
up to the next whole integer. up to the next whole integer.
@ -104,14 +106,12 @@ class CRT {
@param vertical_sync_half_lines The expected length of vertical synchronisation (equalisation pulses aside), @param vertical_sync_half_lines The expected length of vertical synchronisation (equalisation pulses aside),
in multiples of half a line. in multiples of half a line.
@param data_type TODO. @param data_type The format that the caller will use for input data.
@param scan_target TODO. @param scan_target The destination for generated scans.
@see @c set_rgb_sampling_function , @c set_composite_sampling_function
*/ */
CRT(int cycles_per_line, CRT(int cycles_per_line,
int minimum_cycles_per_pixel, int clocks_per_pixel_greatest_common_divisor,
int height_of_display, int height_of_display,
Outputs::Display::ColourSpace colour_space, Outputs::Display::ColourSpace colour_space,
int colour_cycle_numerator, int colour_cycle_numerator,
@ -121,11 +121,7 @@ class CRT {
Outputs::Display::ScanTarget::Modals::DataType data_type, Outputs::Display::ScanTarget::Modals::DataType data_type,
Outputs::Display::ScanTarget *scan_target); Outputs::Display::ScanTarget *scan_target);
/*! Constructs the CRT with the specified clock rate, with the display height and colour /*! Exactly identical to calling the designated constructor with colour subcarrier information
subcarrier frequency dictated by a standard display type and with the requested number of
buffers, each with the requested number of bytes per pixel.
Exactly identical to calling the designated constructor with colour subcarrier information
looked up by display type. looked up by display type.
*/ */
CRT(int cycles_per_line, CRT(int cycles_per_line,
@ -151,7 +147,8 @@ class CRT {
int cycles_per_line, int cycles_per_line,
Outputs::Display::Type display_type); Outputs::Display::Type display_type);
// TODO. /*! Changes the type of data being supplied as input.
*/
void set_new_data_type(Outputs::Display::ScanTarget::Modals::DataType data_type); void set_new_data_type(Outputs::Display::ScanTarget::Modals::DataType data_type);
/*! Output at the sync level. /*! Output at the sync level.
@ -252,9 +249,10 @@ class CRT {
*/ */
void set_composite_function_type(CompositeSourceType type, float offset_of_first_sample = 0.0f); void set_composite_function_type(CompositeSourceType type, float offset_of_first_sample = 0.0f);
inline void set_visible_area(Outputs::Display::Rect visible_area) { /*! Nominates a section of the display to crop to for output. */
} void set_visible_area(Outputs::Display::Rect visible_area);
/*! @returns The rectangle describing a subset of the display, allowing for sync periods. */
Outputs::Display::Rect get_rect_for_area( Outputs::Display::Rect get_rect_for_area(
int first_line_after_sync, int first_line_after_sync,
int number_of_lines, int number_of_lines,
@ -262,6 +260,7 @@ class CRT {
int number_of_cycles, int number_of_cycles,
float aspect_ratio); float aspect_ratio);
/*! Sets the CRT delegate; set to @c nullptr if no delegate is desired. */
inline void set_delegate(Delegate *delegate) { inline void set_delegate(Delegate *delegate) {
delegate_ = delegate; delegate_ = delegate;
} }