2015-07-19 17:36:27 +00:00
//
// CRT.cpp
// Clock Signal
//
// Created by Thomas Harte on 19/07/2015.
// Copyright © 2015 Thomas Harte. All rights reserved.
//
# include "CRT.hpp"
2015-07-20 01:21:34 +00:00
# include <stdarg.h>
2015-07-19 17:36:27 +00:00
using namespace Outputs ;
2015-07-21 03:18:56 +00:00
CRT : : CRT ( int cycles_per_line , int height_of_display , int number_of_buffers , . . . )
2015-07-19 17:36:27 +00:00
{
2015-07-25 03:56:25 +00:00
const int syncCapacityLineChargeThreshold = 5 ;
2015-07-23 23:24:25 +00:00
const int millisecondsHorizontalRetraceTime = 7 ; // source: Dictionary of Video and Television Technology, p. 234
const int scanlinesVerticalRetraceTime = 10 ; // source: ibid
2015-07-22 22:15:18 +00:00
2015-07-25 03:36:44 +00:00
_time_multiplier = ( 1000 + cycles_per_line - 1 ) / cycles_per_line ;
2015-07-21 20:37:39 +00:00
// store fundamental display configuration properties
2015-07-25 03:56:25 +00:00
_height_of_display = height_of_display ; // + (height_of_display / 10);
2015-07-25 03:36:44 +00:00
_cycles_per_line = cycles_per_line * _time_multiplier ;
2015-07-21 03:18:56 +00:00
2015-07-21 20:37:39 +00:00
// generate timing values implied by the given arbuments
2015-07-25 03:36:44 +00:00
_hsync_error_window = _cycles_per_line > > 5 ;
2015-07-20 01:21:34 +00:00
2015-07-25 03:56:25 +00:00
_sync_capacitor_charge_threshold = ( syncCapacityLineChargeThreshold * _cycles_per_line ) > > 1 ;
2015-07-25 03:36:44 +00:00
_horizontal_retrace_time = ( millisecondsHorizontalRetraceTime * _cycles_per_line ) > > 6 ;
_vertical_retrace_time = scanlinesVerticalRetraceTime * _cycles_per_line ;
2015-07-22 22:15:18 +00:00
2015-07-25 03:36:44 +00:00
_scanSpeed . x = UINT32_MAX / _cycles_per_line ;
2015-07-25 03:56:25 +00:00
_scanSpeed . y = UINT32_MAX / ( _height_of_display * _cycles_per_line ) ;
2015-07-25 03:29:45 +00:00
_retraceSpeed . x = UINT32_MAX / _horizontal_retrace_time ;
_retraceSpeed . y = UINT32_MAX / _vertical_retrace_time ;
2015-07-22 22:15:18 +00:00
2015-07-21 20:37:39 +00:00
// generate buffers for signal storage as requested — format is
// number of buffers, size of buffer 1, size of buffer 2...
2015-07-23 00:33:20 +00:00
const int bufferWidth = 512 ;
const int bufferHeight = 512 ;
for ( int frame = 0 ; frame < 3 ; frame + + )
2015-07-20 01:21:34 +00:00
{
2015-07-23 00:33:20 +00:00
va_list va ;
va_start ( va , number_of_buffers ) ;
2015-07-25 03:29:45 +00:00
_frame_builders [ frame ] = new CRTFrameBuilder ( bufferWidth , bufferHeight , number_of_buffers , va ) ;
2015-07-23 00:33:20 +00:00
va_end ( va ) ;
2015-07-20 01:21:34 +00:00
}
2015-07-23 00:33:20 +00:00
_frames_with_delegate = 0 ;
_frame_read_pointer = 0 ;
2015-07-25 03:29:45 +00:00
_current_frame_builder = _frame_builders [ 0 ] ;
2015-07-21 20:37:39 +00:00
// reset raster position
2015-07-25 03:29:45 +00:00
_rasterPosition . x = _rasterPosition . y = 0 ;
2015-07-21 20:37:39 +00:00
// reset flywheel sync
2015-07-25 03:36:44 +00:00
_expected_next_hsync = _cycles_per_line ;
2015-07-20 03:43:22 +00:00
_horizontal_counter = 0 ;
2015-07-21 20:37:39 +00:00
// reset the vertical charge capacitor
2015-07-20 03:43:22 +00:00
_sync_capacitor_charge_level = 0 ;
2015-07-21 20:37:39 +00:00
// start off not in horizontal sync, not receiving a sync signal
2015-07-21 03:18:56 +00:00
_is_receiving_sync = false ;
_is_in_hsync = false ;
2015-07-22 22:15:18 +00:00
_vretrace_counter = 0 ;
2015-07-20 01:21:34 +00:00
}
CRT : : ~ CRT ( )
{
2015-07-23 00:33:20 +00:00
for ( int frame = 0 ; frame < 3 ; frame + + )
2015-07-20 01:21:34 +00:00
{
2015-07-25 03:29:45 +00:00
delete _frame_builders [ frame ] ;
2015-07-20 01:21:34 +00:00
}
2015-07-19 17:36:27 +00:00
}
2015-07-21 01:43:00 +00:00
# pragma mark - Sync loop
2015-07-20 03:43:22 +00:00
2015-07-23 22:53:18 +00:00
CRT : : SyncEvent CRT : : next_vertical_sync_event ( bool vsync_is_charging , int cycles_to_run_for , int * cycles_advanced )
2015-07-20 03:43:22 +00:00
{
2015-07-21 01:43:00 +00:00
SyncEvent proposedEvent = SyncEvent : : None ;
int proposedSyncTime = cycles_to_run_for ;
2015-07-21 03:18:56 +00:00
// have we overrun the maximum permitted number of horizontal syncs for this frame?
2015-07-25 03:29:45 +00:00
// if (!_vretrace_counter)
// {
2015-07-25 03:56:25 +00:00
// float raster_distance = _scanSpeed.y * proposedSyncTime;
2015-07-25 03:29:45 +00:00
// if(_rasterPosition.y < 1.02f && _rasterPosition.y + raster_distance >= 1.02f) {
// proposedSyncTime = (int)(1.02f - _rasterPosition.y) / _scanSpeed.y;
// proposedEvent = SyncEvent::StartVSync;
// }
// }
2015-07-21 03:18:56 +00:00
2015-07-21 01:43:00 +00:00
// will an acceptable vertical sync be triggered?
if ( vsync_is_charging & & ! _vretrace_counter ) {
2015-07-22 22:15:18 +00:00
if ( _sync_capacitor_charge_level < _sync_capacitor_charge_threshold & & _sync_capacitor_charge_level + proposedSyncTime > = _sync_capacitor_charge_threshold ) {
proposedSyncTime = _sync_capacitor_charge_threshold - _sync_capacitor_charge_level ;
2015-07-21 01:43:00 +00:00
proposedEvent = SyncEvent : : StartVSync ;
}
2015-07-20 03:43:22 +00:00
}
2015-07-21 01:43:00 +00:00
// will an ongoing vertical sync end?
if ( _vretrace_counter > 0 ) {
if ( _vretrace_counter < proposedSyncTime ) {
proposedSyncTime = _vretrace_counter ;
proposedEvent = SyncEvent : : EndVSync ;
}
2015-07-20 03:43:22 +00:00
}
2015-07-21 01:43:00 +00:00
* cycles_advanced = proposedSyncTime ;
return proposedEvent ;
2015-07-20 03:43:22 +00:00
}
2015-07-23 22:53:18 +00:00
CRT : : SyncEvent CRT : : next_horizontal_sync_event ( bool hsync_is_requested , int cycles_to_run_for , int * cycles_advanced )
{
// do we recognise this hsync, thereby adjusting future time expectations?
2015-07-23 23:24:25 +00:00
if ( hsync_is_requested ) {
if ( _horizontal_counter < _hsync_error_window | | _horizontal_counter > = _expected_next_hsync - _hsync_error_window ) {
_did_detect_hsync = true ;
2015-07-23 22:53:18 +00:00
2015-07-23 23:24:25 +00:00
int time_now = ( _horizontal_counter < _hsync_error_window ) ? _expected_next_hsync + _horizontal_counter : _horizontal_counter ;
_expected_next_hsync = ( _expected_next_hsync + time_now ) > > 1 ;
}
2015-07-23 22:53:18 +00:00
}
SyncEvent proposedEvent = SyncEvent : : None ;
int proposedSyncTime = cycles_to_run_for ;
// will we end an ongoing hsync?
if ( _horizontal_counter < _horizontal_retrace_time & & _horizontal_counter + proposedSyncTime > = _horizontal_retrace_time ) {
proposedSyncTime = _horizontal_retrace_time - _horizontal_counter ;
proposedEvent = SyncEvent : : EndHSync ;
}
// will we start an hsync?
if ( _horizontal_counter + proposedSyncTime > = _expected_next_hsync ) {
proposedSyncTime = _expected_next_hsync - _horizontal_counter ;
proposedEvent = SyncEvent : : StartHSync ;
}
* cycles_advanced = proposedSyncTime ;
return proposedEvent ;
}
2015-07-25 03:29:45 +00:00
void CRT : : advance_cycles ( int number_of_cycles , bool hsync_requested , const bool vsync_charging , const Type type , const char * data_type )
2015-07-20 03:43:22 +00:00
{
2015-07-25 03:36:44 +00:00
number_of_cycles * = _time_multiplier ;
2015-07-25 03:29:45 +00:00
const bool is_output_run = ( ( type = = Type : : Level ) | | ( type = = Type : : Data ) ) ;
uint16_t tex_x = 0 ;
uint16_t tex_y = 0 ;
if ( is_output_run & & _current_frame_builder ) {
tex_x = _current_frame_builder - > _write_x_position ;
tex_y = _current_frame_builder - > _write_y_position ;
}
2015-07-21 03:18:56 +00:00
2015-07-21 01:43:00 +00:00
while ( number_of_cycles ) {
2015-07-21 20:37:39 +00:00
2015-07-23 22:53:18 +00:00
int time_until_vertical_sync_event , time_until_horizontal_sync_event ;
SyncEvent next_vertical_sync_event = this - > next_vertical_sync_event ( vsync_charging , number_of_cycles , & time_until_vertical_sync_event ) ;
SyncEvent next_horizontal_sync_event = this - > next_horizontal_sync_event ( hsync_requested , time_until_vertical_sync_event , & time_until_horizontal_sync_event ) ;
hsync_requested = false ;
2015-07-21 20:37:39 +00:00
// get the next sync event and its timing; hsync request is instantaneous (being edge triggered) so
// set it to false for the next run through this loop (if any)
2015-07-23 22:53:18 +00:00
int next_run_length = std : : min ( time_until_vertical_sync_event , time_until_horizontal_sync_event ) ;
2015-07-20 03:43:22 +00:00
2015-07-25 03:29:45 +00:00
uint16_t * next_run = ( is_output_run & & _current_frame_builder & & next_run_length ) ? _current_frame_builder - > get_next_run ( ) : nullptr ;
2015-07-23 00:33:20 +00:00
2015-07-25 03:29:45 +00:00
if ( next_run )
2015-07-23 22:53:18 +00:00
{
2015-07-23 00:33:20 +00:00
// set the type, initial raster position and type of this run
2015-07-25 03:29:45 +00:00
next_run [ 0 ] = _rasterPosition . x > > 16 ;
next_run [ 1 ] = _rasterPosition . y > > 16 ;
next_run [ 2 ] = tex_x ;
next_run [ 3 ] = tex_y ;
}
2015-07-23 00:33:20 +00:00
2015-07-25 03:29:45 +00:00
// advance the raster position as dictated by current sync status
if ( _is_in_hsync )
_rasterPosition . x = ( uint32_t ) std : : max ( ( int64_t ) 0 , ( int64_t ) _rasterPosition . x - number_of_cycles * ( int64_t ) _retraceSpeed . x ) ;
else
_rasterPosition . x = ( uint32_t ) std : : min ( ( int64_t ) UINT32_MAX , ( int64_t ) _rasterPosition . x + number_of_cycles * ( int64_t ) _scanSpeed . x ) ;
2015-07-23 00:33:20 +00:00
2015-07-25 03:29:45 +00:00
if ( _vretrace_counter > 0 )
_rasterPosition . y = ( uint32_t ) std : : max ( ( int64_t ) 0 , ( int64_t ) _rasterPosition . y - number_of_cycles * ( int64_t ) _retraceSpeed . y ) ;
else
_rasterPosition . y = ( uint32_t ) std : : min ( ( int64_t ) UINT32_MAX , ( int64_t ) _rasterPosition . y + number_of_cycles * ( int64_t ) _scanSpeed . y ) ;
2015-07-23 00:33:20 +00:00
2015-07-25 03:29:45 +00:00
if ( next_run )
{
2015-07-23 00:33:20 +00:00
// store the final raster position
2015-07-25 03:29:45 +00:00
next_run [ 4 ] = _rasterPosition . x > > 16 ;
next_run [ 5 ] = _rasterPosition . y > > 16 ;
2015-07-23 00:33:20 +00:00
// if this is a data run then advance the buffer pointer
2015-07-25 03:29:45 +00:00
if ( type = = Type : : Data ) tex_x + = next_run_length ;
2015-07-23 00:33:20 +00:00
// if this is a data or level run then store the end point
2015-07-25 03:29:45 +00:00
next_run [ 6 ] = tex_x ;
next_run [ 7 ] = tex_y ;
2015-07-20 03:43:22 +00:00
}
2015-07-21 01:43:00 +00:00
2015-07-21 20:37:39 +00:00
// decrement the number of cycles left to run for and increment the
// horizontal counter appropriately
2015-07-21 01:43:00 +00:00
number_of_cycles - = next_run_length ;
_horizontal_counter + = next_run_length ;
2015-07-21 20:37:39 +00:00
// either charge or deplete the vertical retrace capacitor (making sure it stops at 0)
2015-07-21 01:43:00 +00:00
if ( vsync_charging )
_sync_capacitor_charge_level + = next_run_length ;
2015-07-20 03:43:22 +00:00
else
2015-07-21 01:43:00 +00:00
_sync_capacitor_charge_level = std : : max ( _sync_capacitor_charge_level - next_run_length , 0 ) ;
2015-07-21 20:37:39 +00:00
// decrement the vertical retrace counter, making sure it stops at 0
2015-07-21 01:43:00 +00:00
_vretrace_counter = std : : max ( _vretrace_counter - next_run_length , 0 ) ;
2015-07-21 20:37:39 +00:00
// react to the incoming event...
2015-07-23 22:53:18 +00:00
if ( next_run_length = = time_until_horizontal_sync_event )
{
switch ( next_horizontal_sync_event )
{
// start of hsync: zero the scanline counter, note that we're now in
// horizontal sync, increment the lines-in-this-frame counter
case SyncEvent : : StartHSync :
_horizontal_counter = 0 ;
_is_in_hsync = true ;
break ;
// end of horizontal sync: update the flywheel's velocity, note that we're no longer
// in horizontal sync
case SyncEvent : : EndHSync :
if ( ! _did_detect_hsync ) {
_expected_next_hsync = ( _expected_next_hsync + ( _hsync_error_window > > 1 ) + _cycles_per_line ) > > 1 ;
}
_did_detect_hsync = false ;
_is_in_hsync = false ;
break ;
default : break ;
}
}
if ( next_run_length = = time_until_vertical_sync_event )
{
switch ( next_vertical_sync_event )
{
// start of vertical sync: reset the lines-in-this-frame counter,
// load the retrace counter with the amount of time it'll take to retrace
case SyncEvent : : StartVSync :
_vretrace_counter = _vertical_retrace_time ;
break ;
// end of vertical sync: tell the delegate that we finished vertical sync,
// releasing all runs back into the common pool
case SyncEvent : : EndVSync :
2015-07-25 03:29:45 +00:00
if ( _delegate & & _current_frame_builder )
2015-07-23 22:53:18 +00:00
{
2015-07-25 03:29:45 +00:00
_current_frame_builder - > complete ( ) ;
2015-07-23 22:53:18 +00:00
_frames_with_delegate + + ;
2015-07-25 03:29:45 +00:00
_delegate - > crt_did_end_frame ( this , & _current_frame_builder - > frame ) ;
2015-07-23 22:53:18 +00:00
}
if ( _frames_with_delegate < kCRTNumberOfFrames )
{
_frame_read_pointer = ( _frame_read_pointer + 1 ) % kCRTNumberOfFrames ;
2015-07-25 03:29:45 +00:00
_current_frame_builder = _frame_builders [ _frame_read_pointer ] ;
_current_frame_builder - > reset ( ) ;
2015-07-23 22:53:18 +00:00
}
else
2015-07-25 03:29:45 +00:00
_current_frame_builder = nullptr ;
2015-07-23 22:53:18 +00:00
break ;
default : break ;
}
2015-07-20 03:43:22 +00:00
}
}
}
2015-07-23 00:33:20 +00:00
void CRT : : return_frame ( )
{
_frames_with_delegate - - ;
}
2015-07-21 03:18:56 +00:00
# pragma mark - delegate
2015-07-22 22:15:18 +00:00
void CRT : : set_delegate ( CRTDelegate * delegate )
2015-07-21 03:18:56 +00:00
{
_delegate = delegate ;
}
2015-07-20 03:43:22 +00:00
# pragma mark - stream feeding methods
2015-07-21 20:37:39 +00:00
/*
These all merely channel into advance_cycles , supplying appropriate arguments
*/
2015-07-19 17:36:27 +00:00
void CRT : : output_sync ( int number_of_cycles )
{
2015-07-21 20:37:39 +00:00
bool _hsync_requested = ! _is_receiving_sync ; // ensure this really is edge triggered; someone calling output_sync twice in succession shouldn't trigger it twice
2015-07-21 03:18:56 +00:00
_is_receiving_sync = true ;
2015-07-25 03:29:45 +00:00
advance_cycles ( number_of_cycles , _hsync_requested , true , Type : : Sync , nullptr ) ;
2015-07-19 17:36:27 +00:00
}
2015-07-21 03:18:56 +00:00
void CRT : : output_blank ( int number_of_cycles )
2015-07-19 17:36:27 +00:00
{
2015-07-21 03:18:56 +00:00
_is_receiving_sync = false ;
2015-07-25 03:29:45 +00:00
advance_cycles ( number_of_cycles , false , false , Type : : Blank , nullptr ) ;
2015-07-19 17:36:27 +00:00
}
2015-07-21 03:18:56 +00:00
void CRT : : output_level ( int number_of_cycles , const char * type )
2015-07-19 17:36:27 +00:00
{
2015-07-21 03:18:56 +00:00
_is_receiving_sync = false ;
2015-07-25 03:29:45 +00:00
advance_cycles ( number_of_cycles , false , false , Type : : Level , type ) ;
2015-07-21 01:43:00 +00:00
}
2015-07-21 03:18:56 +00:00
void CRT : : output_data ( int number_of_cycles , const char * type )
2015-07-21 01:43:00 +00:00
{
2015-07-21 03:18:56 +00:00
_is_receiving_sync = false ;
2015-07-25 03:29:45 +00:00
advance_cycles ( number_of_cycles , false , false , Type : : Data , type ) ;
2015-07-19 17:36:27 +00:00
}
2015-07-20 01:21:34 +00:00
2015-07-20 03:43:22 +00:00
# pragma mark - Buffer supply
2015-07-20 01:21:34 +00:00
void CRT : : allocate_write_area ( int required_length )
{
2015-07-25 03:29:45 +00:00
if ( _current_frame_builder ) _current_frame_builder - > allocate_write_area ( required_length ) ;
2015-07-23 00:33:20 +00:00
}
uint8_t * CRT : : get_write_target_for_buffer ( int buffer )
{
2015-07-25 03:29:45 +00:00
if ( ! _current_frame_builder ) return nullptr ;
return _current_frame_builder - > get_write_target_for_buffer ( buffer ) ;
2015-07-23 00:33:20 +00:00
}
# pragma mark - CRTFrame
2015-07-25 03:29:45 +00:00
CRTFrameBuilder : : CRTFrameBuilder ( int width , int height , int number_of_buffers , va_list buffer_sizes )
2015-07-23 00:33:20 +00:00
{
2015-07-25 03:29:45 +00:00
frame . size . width = width ;
frame . size . height = height ;
frame . number_of_buffers = number_of_buffers ;
frame . buffers = new CRTBuffer [ number_of_buffers ] ;
2015-07-23 00:33:20 +00:00
for ( int buffer = 0 ; buffer < number_of_buffers ; buffer + + )
{
2015-07-25 03:29:45 +00:00
frame . buffers [ buffer ] . depth = va_arg ( buffer_sizes , int ) ;
frame . buffers [ buffer ] . data = new uint8_t [ width * height * frame . buffers [ buffer ] . depth ] ;
2015-07-23 00:33:20 +00:00
}
reset ( ) ;
}
2015-07-25 03:29:45 +00:00
CRTFrameBuilder : : ~ CRTFrameBuilder ( )
2015-07-23 00:33:20 +00:00
{
2015-07-25 03:29:45 +00:00
for ( int buffer = 0 ; buffer < frame . number_of_buffers ; buffer + + )
delete [ ] frame . buffers [ buffer ] . data ;
delete frame . buffers ;
2015-07-23 00:33:20 +00:00
}
2015-07-25 03:29:45 +00:00
void CRTFrameBuilder : : reset ( )
2015-07-23 00:33:20 +00:00
{
2015-07-25 03:29:45 +00:00
frame . number_of_runs = 0 ;
_write_x_position = _write_y_position = 0 ;
frame . dirty_size . width = frame . dirty_size . height = 0 ;
2015-07-23 00:33:20 +00:00
}
2015-07-25 03:29:45 +00:00
void CRTFrameBuilder : : complete ( )
2015-07-23 00:33:20 +00:00
{
2015-07-25 03:29:45 +00:00
frame . runs = & _all_runs [ 0 ] ;
2015-07-23 00:33:20 +00:00
}
2015-07-25 03:29:45 +00:00
uint16_t * CRTFrameBuilder : : get_next_run ( )
2015-07-23 00:33:20 +00:00
{
// get a run from the allocated list, allocating more if we're about to overrun
2015-07-25 03:29:45 +00:00
if ( frame . number_of_runs * 8 > = _all_runs . size ( ) )
2015-07-23 00:33:20 +00:00
{
2015-07-25 03:29:45 +00:00
_all_runs . resize ( _all_runs . size ( ) + 4096 ) ;
2015-07-23 00:33:20 +00:00
}
2015-07-25 03:29:45 +00:00
uint16_t * next_run = & _all_runs [ frame . number_of_runs * 8 ] ;
frame . number_of_runs + + ;
2015-07-23 00:33:20 +00:00
2015-07-25 03:29:45 +00:00
return next_run ;
2015-07-23 00:33:20 +00:00
}
2015-07-25 03:29:45 +00:00
void CRTFrameBuilder : : allocate_write_area ( int required_length )
2015-07-23 00:33:20 +00:00
{
2015-07-25 03:29:45 +00:00
if ( _write_x_position + required_length > frame . size . width )
2015-07-20 01:21:34 +00:00
{
2015-07-25 03:29:45 +00:00
_write_x_position = 0 ;
_write_y_position + + ;
frame . dirty_size . height + + ;
2015-07-20 01:21:34 +00:00
}
2015-07-25 03:29:45 +00:00
_write_target_pointer = ( _write_y_position * frame . size . width ) + _write_x_position ;
_write_x_position + = required_length ;
frame . dirty_size . width = std : : max ( frame . dirty_size . width , _write_x_position ) ;
2015-07-20 01:21:34 +00:00
}
2015-07-25 03:29:45 +00:00
uint8_t * CRTFrameBuilder : : get_write_target_for_buffer ( int buffer )
2015-07-20 01:21:34 +00:00
{
2015-07-25 03:29:45 +00:00
return & frame . buffers [ buffer ] . data [ _write_target_pointer * frame . buffers [ buffer ] . depth ] ;
2015-07-20 01:21:34 +00:00
}