2015-07-19 13:36:27 -04:00
//
// CRT.hpp
// Clock Signal
//
// Created by Thomas Harte on 19/07/2015.
// Copyright © 2015 Thomas Harte. All rights reserved.
//
# ifndef CRT_cpp
# define CRT_cpp
# include <stdint.h>
2015-07-22 20:33:20 -04:00
# include <stdarg.h>
2015-07-19 13:36:27 -04:00
# include <string>
2015-07-20 23:18:56 -04:00
# include <vector>
2016-02-04 22:28:50 -05:00
# include <mutex>
2015-07-19 13:36:27 -04:00
2016-02-11 22:13:38 -05:00
# include "Flywheel.hpp"
2015-07-22 20:33:20 -04:00
2015-07-24 23:29:45 -04:00
namespace Outputs {
2015-07-22 20:33:20 -04:00
2016-02-07 22:18:55 -05:00
struct Rect {
struct {
float x , y ;
} origin ;
struct {
float width , height ;
} size ;
Rect ( ) { }
Rect ( float x , float y , float width , float height ) :
origin ( { . x = x , . y = y } ) , size ( { . width = width , . height = height } ) { }
} ;
2016-02-02 22:41:33 -05:00
class CRT {
public :
~ CRT ( ) ;
2015-07-22 20:33:20 -04:00
2016-02-02 22:41:33 -05:00
enum DisplayType {
PAL50 ,
NTSC60
} ;
2015-07-22 20:33:20 -04:00
2016-02-07 17:32:38 -05:00
enum ColourSpace {
YIQ ,
YUV
} ;
enum OutputDevice {
Monitor ,
Television
} ;
2016-02-02 22:41:33 -05:00
/*! Constructs the CRT with a specified clock rate, height and colour subcarrier frequency.
The requested number of buffers , each with the requested number of bytes per pixel ,
is created for the machine to write raw pixel data to .
2015-07-22 20:33:20 -04:00
2016-02-02 22:41:33 -05:00
@ 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 .
2015-07-22 20:33:20 -04:00
2016-02-02 22:41:33 -05:00
@ param height_of_dispaly The number of lines that nominally form one field of the display , rounded
up to the next whole integer .
2015-07-22 20:33:20 -04:00
2016-02-02 22:41:33 -05:00
@ param colour_cycle_numerator Specifies the numerator for the per - line frequency of the colour subcarrier .
2015-07-22 20:33:20 -04:00
2016-02-02 22:41:33 -05:00
@ param colour_cycle_denominator Specifies the denominator for the per - line frequency of the colour subcarrier .
The colour subcarrier is taken to have colour_cycle_numerator / colour_cycle_denominator cycles per line .
2015-07-22 20:33:20 -04:00
2016-02-02 22:41:33 -05:00
@ param number_of_buffers The number of source data buffers to create for this machine . Machines
may provide per - clock - cycle data in any form that they consider convenient , supplying a sampling
function to convert between their data format and either a composite or RGB signal , allowing that
work to be offloaded onto the GPU and allowing the output signal to be sampled at a rate appropriate
to the display size .
2015-07-22 20:33:20 -04:00
2016-02-02 22:41:33 -05:00
@ param . . . A list of sizes for source data buffers , provided as the number of bytes per sample .
For compatibility with OpenGL ES , samples should be 1 – 4 bytes in size . If a machine requires more
than 4 bytes / sample then it should use multiple buffers .
2016-01-23 18:53:20 -05:00
2016-02-02 22:41:33 -05:00
@ see @ c set_rgb_sampling_function , @ c set_composite_sampling_function
*/
2016-02-07 17:32:38 -05:00
CRT ( unsigned int cycles_per_line , unsigned int height_of_display , ColourSpace colour_space , unsigned int colour_cycle_numerator , unsigned int colour_cycle_denominator , unsigned int number_of_buffers , . . . ) ;
2016-02-02 22:41:33 -05:00
/*! 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
looked up by display type .
*/
2016-01-23 18:53:20 -05:00
CRT ( unsigned int cycles_per_line , DisplayType displayType , unsigned int number_of_buffers , . . . ) ;
2015-07-19 21:21:34 -04:00
2016-02-02 22:41:33 -05:00
/*! Resets the CRT with new timing information. The CRT then continues as though the new timing had
been provided at construction . */
2016-02-07 17:32:38 -05:00
void set_new_timing ( unsigned int cycles_per_line , unsigned int height_of_display , ColourSpace colour_space , unsigned int colour_cycle_numerator , unsigned int colour_cycle_denominator ) ;
2016-02-02 22:41:33 -05:00
/*! Resets the CRT with new timing information derived from a new display type. The CRT then continues
as though the new timing had been provided at construction . */
2016-01-23 18:53:20 -05:00
void set_new_display_type ( unsigned int cycles_per_line , DisplayType displayType ) ;
2015-07-31 18:04:33 -04:00
2016-02-02 22:41:33 -05:00
/*! Output at the sync level.
2016-01-21 21:17:47 -05:00
@ param number_of_cycles The amount of time to putput sync for .
*/
2015-08-16 16:08:29 -04:00
void output_sync ( unsigned int number_of_cycles ) ;
2016-01-21 21:17:47 -05:00
2016-02-02 22:41:33 -05:00
/*! Output at the blanking level.
2016-01-21 21:17:47 -05:00
@ param number_of_cycles The amount of time to putput the blanking level for .
*/
2015-08-16 16:08:29 -04:00
void output_blank ( unsigned int number_of_cycles ) ;
2016-01-21 21:17:47 -05:00
2016-02-02 22:41:33 -05:00
/*! Outputs the first written to the most-recently created run of data repeatedly for a prolonged period.
2016-01-21 21:17:47 -05:00
@ param number_of_cycles The number of cycles to repeat the output for .
*/
2015-09-05 20:25:30 -04:00
void output_level ( unsigned int number_of_cycles ) ;
2016-01-21 21:17:47 -05:00
2016-02-02 22:41:33 -05:00
/*! Declares that the caller has created a run of data via @c allocate_write_area and @c get_write_target_for_buffer
2016-01-21 21:17:47 -05:00
that is at least @ c number_of_cycles long , and that the first @ c number_of_cycles / source_divider should be spread
over that amount of time .
@ param number_of_cycles The amount of data to output .
@ param source_divider A divider for source data ; if the divider is 1 then one source pixel is output every cycle ,
if it is 2 then one source pixel covers two cycles ; if it is n then one source pixel covers n cycles .
2016-02-02 22:41:33 -05:00
@ see @ c allocate_write_area , @ c get_write_target_for_buffer
2016-01-21 21:17:47 -05:00
*/
void output_data ( unsigned int number_of_cycles , unsigned int source_divider ) ;
2016-02-02 22:41:33 -05:00
/*! Outputs a colour burst.
2016-01-21 21:17:47 -05:00
@ param number_of_cycles The length of the colour burst .
@ param phase The initial phase of the colour burst in a measuring system with 256 units
per circle , e . g . 0 = 0 degrees , 128 = 180 degrees , 256 = 360 degree .
@ param magnitude The magnitude of the colour burst in 1 / 256 ths of the magnitude of the
positive portion of the wave .
*/
void output_colour_burst ( unsigned int number_of_cycles , uint8_t phase , uint8_t magnitude ) ;
2015-07-19 18:32:42 -04:00
2016-02-02 22:41:33 -05:00
/*! Ensures that the given number of output samples are allocated for writing.
Following this call , the caller should call @ c get_write_target_for_buffer for each
buffer they requested to get the location of the allocated memory .
The beginning of the most recently allocated area is used as the start
of data written by a call to @ c output_data ; it is acceptable to write and to
output less data than the amount requested but that may be less efficient .
@ param required_length The number of samples to allocate .
*/
2016-02-12 19:53:49 -05:00
void allocate_write_area ( size_t required_length ) ;
2016-02-02 22:41:33 -05:00
/*! Gets a pointer for writing to the area created by the most recent call to @c allocate_write_area
for the nominated buffer .
@ param buffer The buffer to get a write target for .
*/
uint8_t * get_write_target_for_buffer ( int buffer ) ;
2016-02-03 19:11:18 -05:00
/*! Causes appropriate OpenGL or OpenGL ES calls to be issued in order to draw the current CRT state.
The caller is responsible for ensuring that a valid OpenGL context exists for the duration of this call .
2016-01-23 19:06:32 -05:00
*/
2016-02-10 23:11:25 -05:00
void draw_frame ( unsigned int output_width , unsigned int output_height , bool only_if_dirty ) ;
2016-01-23 19:06:32 -05:00
2016-02-02 22:41:33 -05:00
/*! Tells the CRT that the next call to draw_frame will occur on a different OpenGL context than
the previous .
2016-01-23 19:06:32 -05:00
2016-02-02 22:41:33 -05:00
@ param should_delete_resources If @ c true then all resources — textures , vertex arrays , etc —
currently held by the CRT will be deleted now via calls to glDeleteTexture and equivalent . If
@ c false then the references are simply marked as invalid .
2016-01-23 19:06:32 -05:00
*/
2016-02-02 22:41:33 -05:00
void set_openGL_context_will_change ( bool should_delete_resources ) ;
2016-01-23 19:06:32 -05:00
2016-02-02 22:41:33 -05:00
/*! Sets a function that will map from whatever data the machine provided to a composite signal.
2016-01-23 19:06:32 -05:00
2016-02-02 22:41:33 -05:00
@ param shader A GLSL fragment including a function with the signature
` float composite_sample ( vec2 coordinate , float phase ) ` that evaluates to the composite signal
level as a function of a source buffer sampling location and the provided colour carrier phase .
The shader may assume a uniform array of sampler2Ds named ` buffers ` provides access to all input data .
*/
void set_composite_sampling_function ( const char * shader ) ;
/*! Sets a function that will map from whatever data the machine provided to an RGB signal.
If the output mode is composite then a default mapping from RGB to the display ' s composite
format will be applied .
@ param shader A GLSL fragent including a function with the signature
2016-01-27 21:35:57 -05:00
` vec3 rgb_sample ( vec2 coordinate ) ` that evaluates to an RGB colour as a function of
2016-01-23 19:06:32 -05:00
the source buffer sampling location .
2016-02-02 22:41:33 -05:00
The shader may assume a uniform array of sampler2Ds named ` buffers ` provides access to all input data .
*/
void set_rgb_sampling_function ( const char * shader ) ;
2016-01-23 19:06:32 -05:00
2016-02-02 22:41:33 -05:00
/*! Optionally sets a function that will map from an input cycle count to a colour carrier phase.
If this function is not supplied then the colour phase is determined from
the input clock rate and the the colour cycle clock rate . Machines whose per - line clock rate
is not intended exactly to match the normal line time may prefer to supply a custom function .
@ param A GLSL fragent including a function with the signature
` float phase_for_clock_cycle ( int cycle ) ` that returns the colour phase at the beginning of
the supplied cycle .
2016-01-23 19:06:32 -05:00
*/
2016-02-07 17:32:38 -05:00
// void set_phase_function(const char *shader);
void set_output_device ( OutputDevice output_device ) ;
2016-02-07 22:18:55 -05:00
void set_visible_area ( Rect visible_area )
{
_visible_area = visible_area ;
}
2016-01-23 19:06:32 -05:00
2016-02-19 23:41:09 -05:00
# ifdef DEBUG
inline uint32_t get_field_cycle ( )
{
return _run_builders [ _run_write_pointer ] - > duration / _time_multiplier ;
}
2016-02-20 17:32:51 -05:00
inline uint32_t get_line_cycle ( )
{
return _horizontal_flywheel - > get_current_time ( ) / _time_multiplier ;
}
inline float get_raster_x ( )
{
return ( float ) _horizontal_flywheel - > get_current_output_position ( ) / ( float ) _horizontal_flywheel - > get_scan_period ( ) ;
}
2016-02-19 23:41:09 -05:00
# endif
2015-07-19 18:32:42 -04:00
private :
2016-01-23 18:53:20 -05:00
CRT ( ) ;
void allocate_buffers ( unsigned int number , va_list sizes ) ;
2015-07-24 23:36:44 -04:00
// the incoming clock lengths will be multiplied by something to give at least 1000
// sample points per line
2015-08-16 16:08:29 -04:00
unsigned int _time_multiplier ;
2015-07-24 23:36:44 -04:00
2015-07-21 16:37:39 -04:00
// fundamental creator-specified properties
2015-08-16 16:08:29 -04:00
unsigned int _cycles_per_line ;
unsigned int _height_of_display ;
2015-07-21 16:37:39 -04:00
2016-02-07 17:32:38 -05:00
// colour invormation
ColourSpace _colour_space ;
unsigned int _colour_cycle_numerator ;
unsigned int _colour_cycle_denominator ;
2016-02-07 19:21:22 -05:00
OutputDevice _output_device ;
2016-02-07 17:32:38 -05:00
2016-02-13 20:52:23 -05:00
// The user-supplied visible area
Rect _visible_area ;
2016-02-11 23:43:08 -05:00
// the current scanning position (TODO: can I eliminate this in favour of just using the flywheels?)
2015-07-22 18:15:18 -04:00
struct Vector {
2015-07-24 23:29:45 -04:00
uint32_t x , y ;
2015-08-02 19:30:45 -04:00
} _rasterPosition , _scanSpeed [ 4 ] , _beamWidth [ 4 ] ;
2015-07-26 23:50:43 -04:00
2016-02-11 23:43:08 -05:00
// the two flywheels regulating scanning
std : : unique_ptr < Outputs : : Flywheel > _horizontal_flywheel , _vertical_flywheel ;
// elements of sync separation
2015-07-22 18:15:18 -04:00
bool _is_receiving_sync ; // true if the CRT is currently receiving sync (i.e. this is for edge triggering of horizontal sync)
int _sync_capacitor_charge_level ; // this 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 ; // this charges up during times of sync and depletes otherwise; needs to hit a required threshold to trigger a vertical sync
2016-02-20 20:29:40 -05:00
unsigned int _sync_period ;
2015-07-21 16:37:39 -04:00
// the outer entry point for dispatching output_sync, output_blank, output_level and output_data
2015-07-24 23:29:45 -04:00
enum Type {
2016-01-23 17:44:34 -05:00
Sync , Level , Data , Blank , ColourBurst
2015-07-24 23:29:45 -04:00
} type ;
2016-01-23 17:49:25 -05:00
void advance_cycles ( unsigned int number_of_cycles , unsigned int source_divider , bool hsync_requested , bool vsync_requested , const bool vsync_charging , const Type type , uint16_t tex_x , uint16_t tex_y ) ;
2015-07-20 21:43:00 -04:00
2015-07-21 16:37:39 -04:00
// the inner entry point that determines whether and when the next sync event will occur within
// the current output window
2016-02-11 23:43:08 -05:00
Flywheel : : SyncEvent get_next_vertical_sync_event ( bool vsync_is_requested , unsigned int cycles_to_run_for , unsigned int * cycles_advanced ) ;
Flywheel : : SyncEvent get_next_horizontal_sync_event ( bool hsync_is_requested , unsigned int cycles_to_run_for , unsigned int * cycles_advanced ) ;
2016-01-23 17:44:34 -05:00
// each call to output_* generates a scan. A two-slot queue for scans allows edge extensions.
struct Scan {
Type type ;
unsigned int number_of_cycles ;
union {
struct {
unsigned int source_divider ;
uint16_t tex_x , tex_y ;
} ;
struct {
uint8_t phase , magnitude ;
} ;
} ;
} _scans [ 2 ] ;
int _next_scan ;
2016-01-23 17:49:25 -05:00
void output_scan ( ) ;
2016-02-02 22:41:33 -05:00
2016-02-13 20:52:23 -05:00
struct CRTRunBuilder {
// Resets the run builder.
void reset ( ) ;
2016-02-02 22:41:33 -05:00
2016-02-13 20:52:23 -05:00
// Getter for new storage plus backing storage; in RGB mode input runs will map directly
// from the input buffer to the screen. In composite mode input runs will map from the
// input buffer to the processing buffer, and output runs will map from the processing
// buffer to the screen.
uint8_t * get_next_input_run ( ) ;
std : : vector < uint8_t > _input_runs ;
2016-02-02 22:41:33 -05:00
2016-02-13 20:52:23 -05:00
uint8_t * get_next_output_run ( ) ;
std : : vector < uint8_t > _output_runs ;
2016-02-02 22:41:33 -05:00
2016-02-13 20:52:23 -05:00
// Container for total length in cycles of all contained runs.
uint32_t duration ;
2016-02-02 22:41:33 -05:00
2016-02-13 20:52:23 -05:00
// Storage for the length of run data uploaded so far; reset to zero by reset but otherwise
// entrusted to the CRT to update.
2016-02-13 23:50:18 -05:00
size_t uploaded_vertices ;
2016-02-13 20:52:23 -05:00
size_t number_of_vertices ;
} ;
2016-02-02 22:41:33 -05:00
2016-02-13 20:52:23 -05:00
struct CRTInputBufferBuilder {
CRTInputBufferBuilder ( unsigned int number_of_buffers , va_list buffer_sizes ) ;
~ CRTInputBufferBuilder ( ) ;
2016-02-02 22:41:33 -05:00
2016-02-07 20:29:32 -05:00
void allocate_write_area ( size_t required_length ) ;
void reduce_previous_allocation_to ( size_t actual_length ) ;
2016-02-06 16:07:23 -05:00
uint8_t * get_write_target_for_buffer ( int buffer ) ;
2016-02-02 22:41:33 -05:00
2016-02-06 16:07:23 -05:00
// a pointer to the section of content buffer currently being
// returned and to where the next section will begin
uint16_t _next_write_x_position , _next_write_y_position ;
uint16_t _write_x_position , _write_y_position ;
size_t _write_target_pointer ;
2016-02-07 20:29:32 -05:00
size_t _last_allocation_amount ;
2016-02-13 20:52:23 -05:00
struct Buffer {
uint8_t * data ;
size_t bytes_per_pixel ;
} * buffers ;
unsigned int number_of_buffers ;
// Storage for the amount of buffer uploaded so far; initialised correctly by the buffer
// builder but otherwise entrusted to the CRT to update.
unsigned int last_uploaded_line ;
2016-02-02 22:41:33 -05:00
} ;
2016-02-13 20:52:23 -05:00
// the run and input data buffers
std : : unique_ptr < CRTInputBufferBuilder > _buffer_builder ;
2016-02-13 23:50:18 -05:00
CRTRunBuilder * * _run_builders ;
2016-02-13 20:52:23 -05:00
int _run_write_pointer ;
std : : shared_ptr < std : : mutex > _output_mutex ;
2016-02-02 22:41:33 -05:00
2016-02-13 20:52:23 -05:00
// OpenGL state, kept behind an opaque pointer to avoid inclusion of the GL headers here.
2016-02-04 22:57:46 -05:00
struct OpenGLState ;
OpenGLState * _openGL_state ;
2016-02-13 20:52:23 -05:00
// Other things the caller may have provided.
2016-02-04 22:57:46 -05:00
char * _composite_shader ;
char * _rgb_shader ;
2016-02-13 20:52:23 -05:00
// Setup and teardown for the OpenGL code
2016-02-04 22:57:46 -05:00
void construct_openGL ( ) ;
void destruct_openGL ( ) ;
2016-02-13 20:52:23 -05:00
// Methods used by the OpenGL code
2016-02-05 21:35:39 -05:00
void prepare_shader ( ) ;
2016-02-13 23:50:18 -05:00
void prepare_vertex_array ( ) ;
2016-02-05 21:35:39 -05:00
void push_size_uniforms ( unsigned int output_width , unsigned int output_height ) ;
2016-02-04 22:57:46 -05:00
char * get_vertex_shader ( ) ;
char * get_fragment_shader ( ) ;
2016-02-05 21:35:39 -05:00
char * get_compound_shader ( const char * base , const char * insert ) ;
2015-07-19 13:36:27 -04:00
} ;
}
# endif /* CRT_cpp */