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>
static const int bufferWidth = 512 ;
static const int bufferHeight = 512 ;
2015-07-19 17:36:27 +00:00
2015-07-20 03:43:22 +00:00
static const int syncCapacityLineChargeThreshold = 3 ;
static const int millisecondsHorizontalRetraceTime = 16 ;
2015-07-21 01:43:00 +00:00
static const int scanlinesVerticalRetraceTime = 26 ;
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-21 20:37:39 +00:00
// store fundamental display configuration properties
2015-07-21 03:18:56 +00:00
_height_of_display = height_of_display ;
_cycles_per_line = cycles_per_line ;
2015-07-21 20:37:39 +00:00
// generate timing values implied by the given arbuments
_hsync_error_window = cycles_per_line > > 5 ;
2015-07-20 01:21:34 +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-20 01:21:34 +00:00
_numberOfBuffers = number_of_buffers ;
_bufferSizes = new int [ _numberOfBuffers ] ;
_buffers = new uint8_t * [ _numberOfBuffers ] ;
va_list va ;
va_start ( va , number_of_buffers ) ;
for ( int c = 0 ; c < _numberOfBuffers ; c + + )
{
_bufferSizes [ c ] = va_arg ( va , int ) ;
_buffers [ c ] = new uint8_t [ bufferHeight * bufferWidth * _bufferSizes [ c ] ] ;
}
va_end ( va ) ;
2015-07-21 20:37:39 +00:00
// reset pointer into output buffers
2015-07-20 01:21:34 +00:00
_write_allocation_pointer = 0 ;
2015-07-21 20:37:39 +00:00
// reset the run buffer pointer
_run_pointer = 0 ;
// reset raster position
_horizontalOffset = 0.0f ;
_verticalOffset = 0.0f ;
// reset flywheel sync
2015-07-20 03:43:22 +00:00
_expected_next_hsync = cycles_per_line ;
_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-20 01:21:34 +00:00
}
CRT : : ~ CRT ( )
{
delete [ ] _bufferSizes ;
for ( int c = 0 ; c < _numberOfBuffers ; c + + )
{
delete [ ] _buffers [ c ] ;
}
delete [ ] _buffers ;
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-21 01:43:00 +00:00
CRT : : SyncEvent CRT : : advance_to_next_sync_event ( bool hsync_is_requested , 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
// do we recognise this hsync, thereby adjusting time expectations?
2015-07-21 01:58:32 +00:00
if ( ( _horizontal_counter < _hsync_error_window | | _horizontal_counter > = _expected_next_hsync - _hsync_error_window ) & & hsync_is_requested ) {
2015-07-21 01:43:00 +00:00
_did_detect_hsync = true ;
2015-07-21 01:58:32 +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 ;
// printf("to %d for %d\n", _expected_next_hsync, time_now);
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?
if ( _hsync_counter > _height_of_display + 10 ) {
* cycles_advanced = 0 ;
return SyncEvent : : StartHSync ;
}
2015-07-21 01:43:00 +00:00
// will we end an ongoing hsync?
const int endOfHSyncTime = ( millisecondsHorizontalRetraceTime * _cycles_per_line ) > > 6 ;
if ( _horizontal_counter < endOfHSyncTime & & _horizontal_counter + proposedSyncTime > = endOfHSyncTime ) {
proposedSyncTime = endOfHSyncTime - _horizontal_counter ;
proposedEvent = SyncEvent : : EndHSync ;
2015-07-20 03:43:22 +00:00
}
2015-07-21 01:43:00 +00:00
// will we start an hsync?
if ( _horizontal_counter + proposedSyncTime > = _expected_next_hsync ) {
proposedSyncTime = _expected_next_hsync - _horizontal_counter ;
proposedEvent = SyncEvent : : StartHSync ;
2015-07-20 03:43:22 +00:00
}
2015-07-21 01:43:00 +00:00
// will an acceptable vertical sync be triggered?
if ( vsync_is_charging & & ! _vretrace_counter ) {
const int startOfVSyncTime = syncCapacityLineChargeThreshold * _cycles_per_line ;
if ( _sync_capacitor_charge_level < startOfVSyncTime & & _sync_capacitor_charge_level + proposedSyncTime > = startOfVSyncTime ) {
proposedSyncTime = startOfVSyncTime - _sync_capacitor_charge_level ;
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-21 03:18:56 +00:00
void CRT : : advance_cycles ( int number_of_cycles , bool hsync_requested , const bool vsync_charging , const CRTRun : : Type type , const char * data_type )
2015-07-20 03:43:22 +00:00
{
2015-07-21 20:37:39 +00:00
// this is safe to keep locally because it accumulates over this run of cycles only
2015-07-21 03:18:56 +00:00
int buffer_offset = 0 ;
2015-07-21 01:43:00 +00:00
while ( number_of_cycles ) {
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-21 01:43:00 +00:00
int next_run_length ;
SyncEvent next_event = advance_to_next_sync_event ( hsync_requested , vsync_charging , number_of_cycles , & next_run_length ) ;
2015-07-21 20:37:39 +00:00
hsync_requested = false ;
2015-07-20 03:43:22 +00:00
2015-07-21 20:37:39 +00:00
// get a run from the allocated list, allocating more if we're about to overrun
2015-07-21 03:18:56 +00:00
if ( _run_pointer > = _all_runs . size ( ) )
{
_all_runs . resize ( ( _all_runs . size ( ) * 2 ) + 1 ) ;
}
CRTRun * nextRun = & _all_runs [ _run_pointer ] ;
_run_pointer + + ;
2015-07-21 20:37:39 +00:00
// set the type, initial raster position and type of this run
2015-07-21 03:18:56 +00:00
nextRun - > type = type ;
nextRun - > start_point . dst_x = _horizontalOffset ;
nextRun - > start_point . dst_y = _verticalOffset ;
2015-07-21 20:37:39 +00:00
nextRun - > data_type = data_type ;
2015-07-21 03:18:56 +00:00
2015-07-21 20:37:39 +00:00
// if this is a data or level run then store a starting data position
2015-07-21 03:18:56 +00:00
if ( type = = CRTRun : : Type : : Data | | type = = CRTRun : : Type : : Level )
{
nextRun - > start_point . src_x = ( _write_target_pointer + buffer_offset ) & ( bufferWidth - 1 ) ;
nextRun - > start_point . dst_x = ( _write_target_pointer + buffer_offset ) / bufferWidth ;
}
2015-07-21 20:37:39 +00:00
// advance the raster position as dictated by current sync status
2015-07-21 03:18:56 +00:00
if ( _vretrace_counter > 0 )
{
_verticalOffset = std : : max ( 0.0f , _verticalOffset - ( float ) number_of_cycles / ( float ) ( scanlinesVerticalRetraceTime * _cycles_per_line ) ) ;
}
else
2015-07-20 03:43:22 +00:00
{
2015-07-21 03:18:56 +00:00
_verticalOffset = std : : min ( 1.0f , _verticalOffset + ( float ) number_of_cycles / ( float ) ( _height_of_display * _cycles_per_line ) ) ;
}
if ( _is_in_hsync )
{
_horizontalOffset = std : : max ( 0.0f , _horizontalOffset - ( float ) ( ( ( millisecondsHorizontalRetraceTime * _cycles_per_line ) > > 6 ) * number_of_cycles ) / ( float ) _cycles_per_line ) ;
}
else
{
_horizontalOffset = std : : min ( 1.0f , _horizontalOffset + ( float ) ( ( ( ( 64 - millisecondsHorizontalRetraceTime ) * _cycles_per_line ) > > 6 ) * number_of_cycles ) / ( float ) _cycles_per_line ) ;
}
2015-07-21 20:37:39 +00:00
// store the final raster position
2015-07-21 03:18:56 +00:00
nextRun - > end_point . dst_x = _horizontalOffset ;
nextRun - > end_point . dst_y = _verticalOffset ;
2015-07-21 20:37:39 +00:00
// if this is a data run then advance the buffer pointer
2015-07-21 03:18:56 +00:00
if ( type = = CRTRun : : Type : : Data )
{
buffer_offset + = next_run_length ;
}
2015-07-21 20:37:39 +00:00
// if this is a data or level run then store the end point
2015-07-21 03:18:56 +00:00
if ( type = = CRTRun : : Type : : Data | | type = = CRTRun : : Type : : Level )
{
nextRun - > end_point . src_x = ( _write_target_pointer + buffer_offset ) & ( bufferWidth - 1 ) ;
nextRun - > end_point . dst_x = ( _write_target_pointer + buffer_offset ) / bufferWidth ;
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-21 01:43:00 +00:00
switch ( next_event ) {
2015-07-21 20:37:39 +00:00
// start of hsync: zero the scanline counter, note that we're now in
// horizontal sync, increment the lines-in-this-frame counter
2015-07-21 01:43:00 +00:00
case SyncEvent : : StartHSync :
_horizontal_counter = 0 ;
2015-07-21 03:18:56 +00:00
_is_in_hsync = true ;
_hsync_counter + + ;
2015-07-21 01:58:32 +00:00
break ;
2015-07-21 20:37:39 +00:00
// end of horizontal sync: update the flywheel's velocity, note that we're no longer
// in horizontal sync
2015-07-21 01:58:32 +00:00
case SyncEvent : : EndHSync :
2015-07-21 01:43:00 +00:00
if ( ! _did_detect_hsync ) {
2015-07-21 01:58:32 +00:00
_expected_next_hsync = ( _expected_next_hsync + ( _hsync_error_window > > 1 ) + _cycles_per_line ) > > 1 ;
2015-07-21 01:43:00 +00:00
}
_did_detect_hsync = false ;
2015-07-21 03:18:56 +00:00
_is_in_hsync = false ;
2015-07-21 01:43:00 +00:00
break ;
2015-07-21 20:37:39 +00:00
// 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
2015-07-21 01:43:00 +00:00
case SyncEvent : : StartVSync :
_vretrace_counter = scanlinesVerticalRetraceTime * _cycles_per_line ;
2015-07-21 03:18:56 +00:00
_hsync_counter = 0 ;
break ;
2015-07-21 20:37:39 +00:00
// end of vertical sync: tell the delegate that we finished vertical sync,
// releasing all runs back into the common pool
2015-07-21 03:18:56 +00:00
case SyncEvent : : EndVSync :
if ( _delegate ! = nullptr )
_delegate - > crt_did_start_vertical_retrace_with_runs ( & _all_runs [ 0 ] , _run_pointer ) ;
_run_pointer = 0 ;
2015-07-21 01:43:00 +00:00
break ;
2015-07-21 20:37:39 +00:00
default : break ;
2015-07-20 03:43:22 +00:00
}
}
}
2015-07-21 03:18:56 +00:00
# pragma mark - delegate
void CRT : : set_crt_delegate ( CRTDelegate * delegate )
{
_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 ;
advance_cycles ( number_of_cycles , _hsync_requested , true , CRTRun : : 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 ;
advance_cycles ( number_of_cycles , false , false , CRTRun : : 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 ;
advance_cycles ( number_of_cycles , false , false , CRTRun : : 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 ;
advance_cycles ( number_of_cycles , false , false , CRTRun : : 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 )
{
int xPos = _write_allocation_pointer & ( bufferWidth - 1 ) ;
if ( xPos + required_length > bufferWidth )
{
_write_allocation_pointer & = ~ ( bufferWidth - 1 ) ;
_write_allocation_pointer = ( _write_allocation_pointer + bufferWidth ) & ( ( bufferHeight - 1 ) * bufferWidth ) ;
}
_write_target_pointer = _write_allocation_pointer ;
_write_allocation_pointer + = required_length ;
}
uint8_t * CRT : : get_write_target_for_buffer ( int buffer )
{
return & _buffers [ buffer ] [ _write_target_pointer * _bufferSizes [ buffer ] ] ;
}