From 9799aa0975715446e87e72bfe8d3558fffa2d27a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 4 Nov 2018 22:17:33 -0500 Subject: [PATCH] Completes documentation and rounds out implementation. --- Outputs/CRT/CRT.cpp | 62 ++++++++++++++++++++++++++------------------- Outputs/CRT/CRT.hpp | 57 ++++++++++++++++++++--------------------- 2 files changed, 64 insertions(+), 55 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 52f677e99..1820a7c94 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -16,10 +16,9 @@ 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) { -// 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 scanlinesVerticalRetraceTime = 8; // source: ibid + const int millisecondsHorizontalRetraceTime = 7; // Source: Dictionary of Video and Television Technology, p. 234. + const int scanlinesVerticalRetraceTime = 8; // Source: ibid. // 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; 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 - // the gist for simple debugging + // the gist for simple debugging. sync_capacitor_charge_threshold_ = ((vertical_sync_half_lines - 2) * cycles_per_line) >> 1; // 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)); 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; 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.y = uint16_t((multiplied_cycles_per_line * height_of_display) / vertical_flywheel_output_divider_); 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_); } +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) { switch(displayType) { case Outputs::Display::Type::PAL50: 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; case Outputs::Display::Type::NTSC60: 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; } } @@ -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; did_output |= is_output_segment; - // If outputting, store the start location and + // If outputting, store the start location and scan constants. if(next_scan) { 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_); @@ -212,7 +222,7 @@ 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) { - // 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(!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]. @@ -235,18 +245,18 @@ void CRT::output_scan(const Scan *const scan) { const bool is_leading_edge = (!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 - // 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(); 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; 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 + // 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; @@ -258,13 +268,13 @@ void CRT::output_scan(const Scan *const scan) { 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 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_) { - 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) { number_of_cycles -= overshoot; 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; // determine prima facie x extent - int horizontal_period = horizontal_flywheel_->get_standard_period(); - int horizontal_scan_period = horizontal_flywheel_->get_scan_period(); - int horizontal_retrace_period = horizontal_period - horizontal_scan_period; + const int horizontal_period = horizontal_flywheel_->get_standard_period(); + const int horizontal_scan_period = horizontal_flywheel_->get_scan_period(); + const int horizontal_retrace_period = horizontal_period - horizontal_scan_period; // make sure that the requested range is visible if(static_cast(first_cycle_after_sync) < horizontal_retrace_period) first_cycle_after_sync = static_cast(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(number_of_cycles) / static_cast(horizontal_scan_period); // determine prima facie y extent - int vertical_period = vertical_flywheel_->get_standard_period(); - int vertical_scan_period = vertical_flywheel_->get_scan_period(); - int vertical_retrace_period = vertical_period - vertical_scan_period; + const int vertical_period = vertical_flywheel_->get_standard_period(); + const int vertical_scan_period = vertical_flywheel_->get_scan_period(); + const int vertical_retrace_period = vertical_period - vertical_scan_period; // make sure that the requested range is visible // if(static_cast(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(static_cast(number_of_lines) * horizontal_period) / vertical_scan_period; // adjust to ensure aspect ratio is correct - float adjusted_aspect_ratio = (3.0f*aspect_ratio / 4.0f); - float ideal_width = height * adjusted_aspect_ratio; + const float adjusted_aspect_ratio = (3.0f*aspect_ratio / 4.0f); + const float ideal_width = height * adjusted_aspect_ratio; if(ideal_width > width) { start_x -= (ideal_width - width) * 0.5f; width = ideal_width; diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 0dccfc5b3..a876c9f93 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -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; }; +/*! 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 { private: - // the incoming clock lengths will be multiplied by something to give at least 1000 - // sample points per line + // The incoming clock lengths will be multiplied by @c time_multiplier_; this increases + // precision across the line. 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 horizontal_flywheel_, vertical_flywheel_; int vertical_flywheel_output_divider_ = 1; @@ -58,25 +65,19 @@ class CRT { int64_t colour_cycle_numerator_ = 1; 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); - - // 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_horizontal_sync_event(bool hsync_is_requested, int cycles_to_run_for, int *cycles_advanced); - // the delegate Delegate *delegate_ = nullptr; int frames_since_last_delegate_call_ = 0; - // sync counter, for determining vertical sync - 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; // true if a sync level has triggered the suspicion that a vertical sync might be in progress - 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; // 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_since_sync_ = 0; // the number of cycles since last in sync, for defeating the possibility of this being a 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_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_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. + 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 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_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 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 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), 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. - - @see @c set_rgb_sampling_function , @c set_composite_sampling_function + @param scan_target The destination for generated scans. */ CRT(int cycles_per_line, - int minimum_cycles_per_pixel, + int clocks_per_pixel_greatest_common_divisor, int height_of_display, Outputs::Display::ColourSpace colour_space, int colour_cycle_numerator, @@ -121,11 +121,7 @@ class CRT { Outputs::Display::ScanTarget::Modals::DataType data_type, Outputs::Display::ScanTarget *scan_target); - /*! 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 - buffers, each with the requested number of bytes per pixel. - - 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. */ CRT(int cycles_per_line, @@ -151,7 +147,8 @@ class CRT { int cycles_per_line, 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); /*! 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); - 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( int first_line_after_sync, int number_of_lines, @@ -262,6 +260,7 @@ class CRT { int number_of_cycles, float aspect_ratio); + /*! Sets the CRT delegate; set to @c nullptr if no delegate is desired. */ inline void set_delegate(Delegate *delegate) { delegate_ = delegate; }