mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-27 01:31:42 +00:00
Completes documentation and rounds out implementation.
This commit is contained in:
parent
1effb97b74
commit
9799aa0975
@ -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<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);
|
||||
|
||||
// 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<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;
|
||||
|
||||
// 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;
|
||||
|
@ -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<Flywheel> 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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user