diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 049ef77ea..7a8130733 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -664,6 +664,7 @@ 4BBF99111C8FBA6F0075DAFB /* Shader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Shader.hpp; sourceTree = ""; }; 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextureTarget.cpp; sourceTree = ""; }; 4BBF99131C8FBA6F0075DAFB /* TextureTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TextureTarget.hpp; sourceTree = ""; }; + 4BBF99191C8FC2750075DAFB /* CRTTypes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTTypes.hpp; sourceTree = ""; }; 4BE5F85C1C3E1C2500C43F01 /* basic.rom */ = {isa = PBXFileReference; lastKnownFileType = file; path = basic.rom; sourceTree = ""; }; 4BE5F85D1C3E1C2500C43F01 /* os.rom */ = {isa = PBXFileReference; lastKnownFileType = file; path = os.rom; sourceTree = ""; }; /* End PBXFileReference section */ @@ -700,6 +701,7 @@ 4BBF99071C8FBA6F0075DAFB /* Internals */, 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */, 4B0CCC431C62D0B3001CAC5F /* CRT.hpp */, + 4BBF99191C8FC2750075DAFB /* CRTTypes.hpp */, ); name = CRT; path = ../../Outputs/CRT; diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 733f1da82..fd139386e 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -15,9 +15,7 @@ using namespace Outputs::CRT; void CRT::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) { - _colour_space = colour_space; - _colour_cycle_numerator = colour_cycle_numerator; - _colour_cycle_denominator = colour_cycle_denominator; + _openGL_output_builder->set_colour_format(colour_space, colour_cycle_numerator, colour_cycle_denominator); const unsigned int syncCapacityLineChargeThreshold = 3; const unsigned int millisecondsHorizontalRetraceTime = 7; // source: Dictionary of Video and Television Technology, p. 234 @@ -45,6 +43,8 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di // figure out the divisor necessary to get the horizontal flywheel into a 16-bit range unsigned int real_clock_scan_period = (_cycles_per_line * height_of_display) / (_time_multiplier * _common_output_divisor); _vertical_flywheel_output_divider = (uint16_t)(ceilf(real_clock_scan_period / 65536.0f) * (_time_multiplier * _common_output_divisor)); + + _openGL_output_builder->set_timing(_cycles_per_line, _height_of_display, _horizontal_flywheel->get_scan_period(), _vertical_flywheel->get_scan_period(), _vertical_flywheel_output_divider); } void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType displayType) @@ -61,63 +61,31 @@ void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType display } } -void CRT::allocate_buffers(unsigned int number, va_list sizes) -{ - _run_builders = new CRTRunBuilder *[NumberOfFields]; - for(int builder = 0; builder < NumberOfFields; builder++) - { - _run_builders[builder] = new CRTRunBuilder(OutputVertexSize); - } - _composite_src_runs = std::unique_ptr(new CRTRunBuilder(InputVertexSize)); - - va_list va; - va_copy(va, sizes); - _buffer_builder = std::unique_ptr(new CRTInputBufferBuilder(number, va)); - va_end(va); -} - CRT::CRT(unsigned int common_output_divisor) : - _run_write_pointer(0), _sync_capacitor_charge_level(0), _is_receiving_sync(false), - _output_mutex(new std::mutex), - _visible_area(Rect(0, 0, 1, 1)), _sync_period(0), _common_output_divisor(common_output_divisor), - _composite_src_output_y(0), - _is_writing_composite_run(false) -{ - construct_openGL(); -} - -CRT::~CRT() -{ - for(int builder = 0; builder < NumberOfFields; builder++) - { - delete _run_builders[builder]; - } - delete[] _run_builders; - destruct_openGL(); -} + _is_writing_composite_run(false) {} CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, unsigned int number_of_buffers, ...) : CRT(common_output_divisor) { - set_new_timing(cycles_per_line, height_of_display, colour_space, colour_cycle_numerator, colour_cycle_denominator); - va_list buffer_sizes; va_start(buffer_sizes, number_of_buffers); - allocate_buffers(number_of_buffers, buffer_sizes); + _openGL_output_builder = std::unique_ptr(new OpenGLOutputBuilder(number_of_buffers, buffer_sizes)); va_end(buffer_sizes); + + set_new_timing(cycles_per_line, height_of_display, colour_space, colour_cycle_numerator, colour_cycle_denominator); } CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, DisplayType displayType, unsigned int number_of_buffers, ...) : CRT(common_output_divisor) { - set_new_display_type(cycles_per_line, displayType); - va_list buffer_sizes; va_start(buffer_sizes, number_of_buffers); - allocate_buffers(number_of_buffers, buffer_sizes); + _openGL_output_builder = std::unique_ptr(new OpenGLOutputBuilder(number_of_buffers, buffer_sizes)); va_end(buffer_sizes); + + set_new_display_type(cycles_per_line, displayType); } #pragma mark - Sync loop @@ -147,11 +115,11 @@ Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, #define input_amplitude(v) next_run[OutputVertexSize*v + InputVertexOffsetOfPhaseAndAmplitude + 1] #define input_phase_time(v) (*(uint16_t *)&next_run[OutputVertexSize*v + InputVertexOffsetOfPhaseTime]) -void CRT::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) +void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divider, bool hsync_requested, bool vsync_requested, const bool vsync_charging, const Scan::Type type, uint16_t tex_x, uint16_t tex_y) { number_of_cycles *= _time_multiplier; - bool is_output_run = ((type == Type::Level) || (type == Type::Data)); + bool is_output_run = ((type == Scan::Type::Level) || (type == Scan::Type::Data)); while(number_of_cycles) { @@ -170,8 +138,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi uint8_t *next_run = nullptr; if(is_output_segment) { - _output_mutex->lock(); - next_run = (_output_device == Monitor) ? _run_builders[_run_write_pointer]->get_next_run(6) : _composite_src_runs->get_next_run(2); + next_run = _openGL_output_builder->get_next_input_run(); } // Vertex output is arranged for triangle strips, as: @@ -181,12 +148,12 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi // [0/1] 3 if(next_run) { - if(_output_device == Monitor) + if(_openGL_output_builder->get_output_device() == Monitor) { // set the type, initial raster position and type of this run output_position_x(0) = output_position_x(1) = output_position_x(2) = (uint16_t)_horizontal_flywheel->get_current_output_position(); output_position_y(0) = output_position_y(1) = output_position_y(2) = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider); - output_timestamp(0) = output_timestamp(1) = output_timestamp(2) = _run_builders[_run_write_pointer]->duration; + output_timestamp(0) = output_timestamp(1) = output_timestamp(2) = _openGL_output_builder->get_current_field_time(); output_tex_x(0) = output_tex_x(1) = output_tex_x(2) = tex_x; // these things are constants across the line so just throw them out now @@ -199,7 +166,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi input_input_position_x(0) = tex_x; input_input_position_y(0) = input_input_position_y(1) = tex_y; input_output_position_x(0) = (uint16_t)_horizontal_flywheel->get_current_output_position(); - input_output_position_y(0) = input_output_position_y(1) = _composite_src_output_y; + input_output_position_y(0) = input_output_position_y(1) = _openGL_output_builder->get_composite_output_y(); input_phase(0) = input_phase(1) = _colour_burst_phase; input_amplitude(0) = input_amplitude(1) = _colour_burst_amplitude; input_phase_time(0) = input_phase_time(1) = _colour_burst_time; @@ -209,7 +176,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi // decrement the number of cycles left to run for and increment the // horizontal counter appropriately number_of_cycles -= next_run_length; - _run_builders[_run_write_pointer]->duration += next_run_length; + _openGL_output_builder->add_to_field_time(next_run_length); // either charge or deplete the vertical retrace capacitor (making sure it stops at 0) if (vsync_charging && !_vertical_flywheel->is_in_retrace()) @@ -224,14 +191,14 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi if(next_run) { // if this is a data run then advance the buffer pointer - if(type == Type::Data && source_divider) tex_x += next_run_length / (_time_multiplier * source_divider); + if(type == Scan::Type::Data && source_divider) tex_x += next_run_length / (_time_multiplier * source_divider); - if(_output_device == Monitor) + if(_openGL_output_builder->get_output_device() == Monitor) { // store the final raster position output_position_x(3) = output_position_x(4) = output_position_x(5) = (uint16_t)_horizontal_flywheel->get_current_output_position(); output_position_y(3) = output_position_y(4) = output_position_y(5) = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider); - output_timestamp(3) = output_timestamp(4) = output_timestamp(5) = _run_builders[_run_write_pointer]->duration; + output_timestamp(3) = output_timestamp(4) = output_timestamp(5) = _openGL_output_builder->get_current_field_time(); output_tex_x(3) = output_tex_x(4) = output_tex_x(5) = tex_x; } else @@ -243,11 +210,11 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi if(is_output_segment) { - _output_mutex->unlock(); + _openGL_output_builder->complete_input_run(); } // if this is horizontal retrace then advance the output line counter and bookend an output run - if(_output_device == Television) + if(_openGL_output_builder->get_output_device() == Television) { Flywheel::SyncEvent honoured_event = Flywheel::SyncEvent::None; if(next_run_length == time_until_vertical_sync_event && next_vertical_sync_event != Flywheel::SyncEvent::None) honoured_event = next_vertical_sync_event; @@ -258,23 +225,24 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi if(needs_endpoint) { - uint8_t *next_run = _run_builders[_run_write_pointer]->get_next_run(3); + uint8_t *next_run = _openGL_output_builder->get_next_output_run(); output_position_x(0) = output_position_x(1) = output_position_x(2) = (uint16_t)_horizontal_flywheel->get_current_output_position(); output_position_y(0) = output_position_y(1) = output_position_y(2) = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider); - output_timestamp(0) = output_timestamp(1) = output_timestamp(2) = _run_builders[_run_write_pointer]->duration; + output_timestamp(0) = output_timestamp(1) = output_timestamp(2) = _openGL_output_builder->get_current_field_time(); output_tex_x(0) = output_tex_x(1) = output_tex_x(2) = (uint16_t)_horizontal_flywheel->get_current_output_position(); - output_tex_y(0) = output_tex_y(1) = output_tex_y(2) = _composite_src_output_y; + output_tex_y(0) = output_tex_y(1) = output_tex_y(2) = _openGL_output_builder->get_composite_output_y(); output_lateral(0) = 0; output_lateral(1) = _is_writing_composite_run ? 1 : 0; output_lateral(2) = 1; + _openGL_output_builder->complete_output_run(); _is_writing_composite_run ^= true; } if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::EndRetrace) { - _composite_src_output_y = (_composite_src_output_y + 1) % IntermediateBufferHeight; + _openGL_output_builder->increment_composite_output_y(); } } @@ -284,8 +252,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi // TODO: how to communicate did_detect_vsync? Bring the delegate back? // _delegate->crt_did_end_frame(this, &_current_frame_builder->frame, _did_detect_vsync); - _run_write_pointer = (_run_write_pointer + 1)%NumberOfFields; - _run_builders[_run_write_pointer]->reset(); + _openGL_output_builder->increment_field(); } } } @@ -309,14 +276,14 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi void CRT::output_scan(Scan *scan) { - bool this_is_sync = (scan->type == Type::Sync); + bool this_is_sync = (scan->type == Scan::Type::Sync); bool is_trailing_edge = (_is_receiving_sync && !this_is_sync); bool hsync_requested = is_trailing_edge && (_sync_period < (_horizontal_flywheel->get_scan_period() >> 2)); bool vsync_requested = is_trailing_edge && (_sync_capacitor_charge_level >= _sync_capacitor_charge_threshold); _is_receiving_sync = this_is_sync; // simplified colour burst logic: if it's within the back porch we'll take it - if(scan->type == Type::ColourBurst) + if(scan->type == Scan::Type::ColourBurst) { if(_horizontal_flywheel->get_current_time() < (_horizontal_flywheel->get_standard_period() * 12) >> 6) { @@ -338,7 +305,7 @@ void CRT::output_scan(Scan *scan) void CRT::output_sync(unsigned int number_of_cycles) { Scan scan{ - .type = Type::Sync, + .type = Scan::Type::Sync, .number_of_cycles = number_of_cycles }; output_scan(&scan); @@ -347,7 +314,7 @@ void CRT::output_sync(unsigned int number_of_cycles) void CRT::output_blank(unsigned int number_of_cycles) { Scan scan { - .type = Type::Blank, + .type = Scan::Type::Blank, .number_of_cycles = number_of_cycles }; output_scan(&scan); @@ -356,10 +323,10 @@ void CRT::output_blank(unsigned int number_of_cycles) void CRT::output_level(unsigned int number_of_cycles) { Scan scan { - .type = Type::Level, + .type = Scan::Type::Level, .number_of_cycles = number_of_cycles, - .tex_x = _buffer_builder->_write_x_position, - .tex_y = _buffer_builder->_write_y_position + .tex_x = _openGL_output_builder->get_last_write_x_posiiton(), + .tex_y = _openGL_output_builder->get_last_write_y_posiiton() }; output_scan(&scan); } @@ -367,7 +334,7 @@ void CRT::output_level(unsigned int number_of_cycles) void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t amplitude) { Scan scan { - .type = Type::ColourBurst, + .type = Scan::Type::ColourBurst, .number_of_cycles = number_of_cycles, .phase = phase, .amplitude = amplitude @@ -377,27 +344,13 @@ void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider) { - _buffer_builder->reduce_previous_allocation_to(number_of_cycles / source_divider); + _openGL_output_builder->reduce_previous_allocation_to(number_of_cycles / source_divider); Scan scan { - .type = Type::Data, + .type = Scan::Type::Data, .number_of_cycles = number_of_cycles, - .tex_x = _buffer_builder->_write_x_position, - .tex_y = _buffer_builder->_write_y_position, + .tex_x = _openGL_output_builder->get_last_write_x_posiiton(), + .tex_y = _openGL_output_builder->get_last_write_y_posiiton(), .source_divider = source_divider }; output_scan(&scan); } - -#pragma mark - Buffer supply - -void CRT::allocate_write_area(size_t required_length) -{ - _output_mutex->lock(); - _buffer_builder->allocate_write_area(required_length); - _output_mutex->unlock(); -} - -uint8_t *CRT::get_write_target_for_buffer(int buffer) -{ - return _buffer_builder->get_write_target_for_buffer(buffer); -} diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 8a6de7327..3ccc46c9d 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -15,48 +15,70 @@ #include #include +#include "CRTTypes.hpp" #include "Internals/Flywheel.hpp" -#include "Internals/CRTInputBufferBuilder.hpp" -#include "Internals/CRTRunBuilder.hpp" +#include "Internals/CRTOpenGL.hpp" namespace Outputs { namespace CRT { -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}) {} -}; - -enum DisplayType { - PAL50, - NTSC60 -}; - -enum ColourSpace { - YIQ, - YUV -}; - -enum OutputDevice { - Monitor, - Television -}; - -struct OpenGLState; - class CRT { - public: - ~CRT(); + private: + CRT(unsigned int common_output_divisor); + // the incoming clock lengths will be multiplied by something to give at least 1000 + // sample points per line + unsigned int _time_multiplier; + const unsigned int _common_output_divisor; + + // fundamental creator-specified properties + unsigned int _cycles_per_line; + unsigned int _height_of_display; + + // the two flywheels regulating scanning + std::unique_ptr _horizontal_flywheel, _vertical_flywheel; + uint16_t _vertical_flywheel_output_divider; + + // elements of sync separation + 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 + unsigned int _sync_period; + + // each call to output_* generates a scan. A two-slot queue for scans allows edge extensions. + struct Scan { + enum Type { + Sync, Level, Data, Blank, ColourBurst + } type; + unsigned int number_of_cycles; + union { + struct { + unsigned int source_divider; + uint16_t tex_x, tex_y; + }; + struct { + uint8_t phase, amplitude; + }; + }; + }; + void output_scan(Scan *scan); + + uint8_t _colour_burst_phase, _colour_burst_amplitude; + uint16_t _colour_burst_time; + bool _is_writing_composite_run; + + // the outer entry point for dispatching output_sync, output_blank, output_level and output_data + void advance_cycles(unsigned int number_of_cycles, unsigned int source_divider, bool hsync_requested, bool vsync_requested, const bool vsync_charging, const Scan::Type type, uint16_t tex_x, uint16_t tex_y); + + // 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, 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); + + // OpenGL state, kept behind an opaque pointer to avoid inclusion of the GL headers here. + std::unique_ptr _openGL_output_builder; + + public: /*! 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. @@ -162,19 +184,28 @@ class CRT { @param required_length The number of samples to allocate. */ - void allocate_write_area(size_t required_length); + inline void allocate_write_area(size_t required_length) + { + return _openGL_output_builder->allocate_write_area(required_length); + } /*! 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); + inline uint8_t *get_write_target_for_buffer(int buffer) + { + return _openGL_output_builder->get_write_target_for_buffer(buffer); + } /*! 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. */ - void draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty); + inline void draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty) + { + _openGL_output_builder->draw_frame(output_width, output_height, only_if_dirty); + } /*! Tells the CRT that the next call to draw_frame will occur on a different OpenGL context than the previous. @@ -183,7 +214,10 @@ class CRT { 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. */ - void set_openGL_context_will_change(bool should_delete_resources); + inline void set_openGL_context_will_change(bool should_delete_resources) + { + _openGL_output_builder->set_openGL_context_will_change(should_delete_resources); + } /*! Sets a function that will map from whatever data the machine provided to a composite signal. @@ -192,7 +226,10 @@ class CRT { 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); + inline void set_composite_sampling_function(const char *shader) + { + _openGL_output_builder->set_composite_sampling_function(shader); + } /*! Sets a function that will map from whatever data the machine provided to an RGB signal. @@ -204,7 +241,10 @@ class CRT { the source buffer sampling location. 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); + inline void set_rgb_sampling_function(const char *shader) + { + _openGL_output_builder->set_rgb_sampling_function(shader); + } /*! Optionally sets a function that will map from an input cycle count to a colour carrier phase. @@ -218,128 +258,15 @@ class CRT { */ // void set_phase_function(const char *shader); - void set_output_device(OutputDevice output_device); - void set_visible_area(Rect visible_area) + inline void set_output_device(OutputDevice output_device) { - _visible_area = visible_area; + _openGL_output_builder->set_output_device(output_device); } -#ifdef DEBUG - inline uint32_t get_field_cycle() + inline void set_visible_area(Rect visible_area) { - return _run_builders[_run_write_pointer]->duration / _time_multiplier; + _openGL_output_builder->set_visible_area(visible_area); } - - 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(); - } -#endif - - private: - CRT(unsigned int common_output_divisor); - void allocate_buffers(unsigned int number, va_list sizes); - - // the incoming clock lengths will be multiplied by something to give at least 1000 - // sample points per line - unsigned int _time_multiplier; - const unsigned int _common_output_divisor; - - // fundamental creator-specified properties - unsigned int _cycles_per_line; - unsigned int _height_of_display; - - // colour invormation - ColourSpace _colour_space; - unsigned int _colour_cycle_numerator; - unsigned int _colour_cycle_denominator; - OutputDevice _output_device; - - // The user-supplied visible area - Rect _visible_area; - - // the two flywheels regulating scanning - std::unique_ptr _horizontal_flywheel, _vertical_flywheel; - uint16_t _vertical_flywheel_output_divider; - - // elements of sync separation - 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 - unsigned int _sync_period; - - // the outer entry point for dispatching output_sync, output_blank, output_level and output_data - enum Type { - Sync, Level, Data, Blank, ColourBurst - } type; - 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); - - // 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, 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); - - // 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, amplitude; - }; - }; - }; - void output_scan(Scan *scan); - - // the run and input data buffers - std::unique_ptr _buffer_builder; - CRTRunBuilder **_run_builders; - int _run_write_pointer; - std::shared_ptr _output_mutex; - - // transient buffers indicating composite data not yet decoded - std::unique_ptr _composite_src_runs; - uint16_t _composite_src_output_y; - uint8_t _colour_burst_phase, _colour_burst_amplitude; - uint16_t _colour_burst_time; - bool _is_writing_composite_run; - - // OpenGL state, kept behind an opaque pointer to avoid inclusion of the GL headers here. - OpenGLState *_openGL_state; - - // Other things the caller may have provided. - char *_composite_shader; - char *_rgb_shader; - - // Setup and teardown for the OpenGL code - void construct_openGL(); - void destruct_openGL(); - - // Methods used by the OpenGL code - void prepare_rgb_output_shader(); - void prepare_composite_input_shader(); - void prepare_output_vertex_array(); - void push_size_uniforms(unsigned int output_width, unsigned int output_height); - - char *get_output_vertex_shader(); - - char *get_output_fragment_shader(const char *sampling_function); - char *get_rgb_output_fragment_shader(); - char *get_composite_output_fragment_shader(); - - char *get_input_vertex_shader(); - char *get_input_fragment_shader(); - - char *get_compound_shader(const char *base, const char *insert); }; } diff --git a/Outputs/CRT/CRTTypes.hpp b/Outputs/CRT/CRTTypes.hpp new file mode 100644 index 000000000..262f01616 --- /dev/null +++ b/Outputs/CRT/CRTTypes.hpp @@ -0,0 +1,47 @@ +// +// CRTTypes.hpp +// Clock Signal +// +// Created by Thomas Harte on 08/03/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef CRTTypes_h +#define CRTTypes_h + +namespace Outputs { +namespace CRT { + +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}) {} +}; + +enum DisplayType { + PAL50, + NTSC60 +}; + +enum ColourSpace { + YIQ, + YUV +}; + +enum OutputDevice { + Monitor, + Television +}; + +} +} + +#endif /* CRTTypes_h */ diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 0b811a199..3d9ddcddf 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -9,42 +9,47 @@ #include #include -#include "OpenGL.hpp" -#include "TextureTarget.hpp" -#include "Shader.hpp" #include "CRTOpenGL.hpp" -namespace Outputs { -namespace CRT { - -struct OpenGLState { - std::unique_ptr rgb_shader_program; - std::unique_ptr composite_input_shader_program, composite_output_shader_program; - - GLuint output_array_buffer, output_vertex_array; - size_t output_vertices_per_slice; - - GLint windowSizeUniform, timestampBaseUniform; - GLint boundsOriginUniform, boundsSizeUniform; - - GLuint textureName, shadowMaskTextureName; - - GLuint defaultFramebuffer; - - std::unique_ptr compositeTexture; // receives raw composite levels - std::unique_ptr filteredYTexture; // receives filtered Y in the R channel plus unfiltered I/U and Q/V in G and B - std::unique_ptr filteredTexture; // receives filtered YIQ or YUV -}; - -} -} - using namespace Outputs::CRT; namespace { static const GLenum first_supplied_buffer_texture_unit = 3; } +OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int number_of_buffers, va_list sizes) : + _run_write_pointer(0), + _output_mutex(new std::mutex), + _visible_area(Rect(0, 0, 1, 1)), + _composite_src_output_y(0), + _composite_shader(nullptr), + _rgb_shader(nullptr) +{ + _run_builders = new CRTRunBuilder *[NumberOfFields]; + for(int builder = 0; builder < NumberOfFields; builder++) + { + _run_builders[builder] = new CRTRunBuilder(OutputVertexSize); + } + _composite_src_runs = std::unique_ptr(new CRTRunBuilder(InputVertexSize)); + + va_list va; + va_copy(va, sizes); + _buffer_builder = std::unique_ptr(new CRTInputBufferBuilder(number_of_buffers, sizes)); + va_end(va); +} + +OpenGLOutputBuilder::~OpenGLOutputBuilder() +{ + for(int builder = 0; builder < NumberOfFields; builder++) + { + delete _run_builders[builder]; + } + delete[] _run_builders; + + free(_composite_shader); + free(_rgb_shader); +} + static GLenum formatForDepth(size_t depth) { switch(depth) @@ -57,33 +62,17 @@ static GLenum formatForDepth(size_t depth) } } -void CRT::construct_openGL() -{ - _openGL_state = nullptr; - _composite_shader = _rgb_shader = nullptr; -} - -void CRT::destruct_openGL() -{ - delete _openGL_state; - _openGL_state = nullptr; - if(_composite_shader) free(_composite_shader); - if(_rgb_shader) free(_rgb_shader); -} - -void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty) +void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty) { // establish essentials - if(!_openGL_state) + if(!composite_input_shader_program && !rgb_shader_program) { - _openGL_state = new OpenGLState; - // generate and bind textures for every one of the requested buffers for(unsigned int buffer = 0; buffer < _buffer_builder->number_of_buffers; buffer++) { - glGenTextures(1, &_openGL_state->textureName); + glGenTextures(1, &textureName); glActiveTexture(GL_TEXTURE0 + first_supplied_buffer_texture_unit + buffer); - glBindTexture(GL_TEXTURE_2D, _openGL_state->textureName); + glBindTexture(GL_TEXTURE_2D, textureName); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); @@ -93,29 +82,29 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, InputBufferBuilderWidth, InputBufferBuilderHeight, 0, format, GL_UNSIGNED_BYTE, _buffer_builder->buffers[buffer].data); } - glGenVertexArrays(1, &_openGL_state->output_vertex_array); - glGenBuffers(1, &_openGL_state->output_array_buffer); - _openGL_state->output_vertices_per_slice = 0; + glGenVertexArrays(1, &output_vertex_array); + glGenBuffers(1, &output_array_buffer); + output_vertices_per_slice = 0; prepare_composite_input_shader(); prepare_rgb_output_shader(); - glBindBuffer(GL_ARRAY_BUFFER, _openGL_state->output_array_buffer); - glBindVertexArray(_openGL_state->output_vertex_array); + glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); + glBindVertexArray(output_vertex_array); prepare_output_vertex_array(); // This should return either an actual framebuffer number, if this is a target with a framebuffer intended for output, // or 0 if no framebuffer is bound, in which case 0 is also what we want to supply to bind the implied framebuffer. So // it works either way. - glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *)&_openGL_state->defaultFramebuffer); + glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *)&defaultFramebuffer); // Create intermediate textures and bind to slots 0, 1 and 2 glActiveTexture(GL_TEXTURE0); - _openGL_state->compositeTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); + compositeTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); glActiveTexture(GL_TEXTURE1); - _openGL_state->filteredYTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); + filteredYTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); glActiveTexture(GL_TEXTURE2); - _openGL_state->filteredTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); + filteredTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); } // glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *)&_openGL_state->defaultFramebuffer); @@ -155,7 +144,7 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool // check for anything to decode from composite if(_composite_src_runs->number_of_vertices) { - _openGL_state->composite_input_shader_program->bind(); + composite_input_shader_program->bind(); _composite_src_runs->reset(); } @@ -167,35 +156,35 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool // glGetIntegerv(GL_VIEWPORT, results); // ensure array buffer is up to date - glBindBuffer(GL_ARRAY_BUFFER, _openGL_state->output_array_buffer); + glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); size_t max_number_of_vertices = 0; for(int c = 0; c < NumberOfFields; c++) { max_number_of_vertices = std::max(max_number_of_vertices, _run_builders[c]->number_of_vertices); } - if(_openGL_state->output_vertices_per_slice < max_number_of_vertices) + if(output_vertices_per_slice < max_number_of_vertices) { - _openGL_state->output_vertices_per_slice = max_number_of_vertices; + output_vertices_per_slice = max_number_of_vertices; glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(max_number_of_vertices * OutputVertexSize * OutputVertexSize), NULL, GL_STREAM_DRAW); for(unsigned int c = 0; c < NumberOfFields; c++) { uint8_t *data = &_run_builders[c]->_runs[0]; - glBufferSubData(GL_ARRAY_BUFFER, (GLsizeiptr)(c * _openGL_state->output_vertices_per_slice * OutputVertexSize), (GLsizeiptr)(_run_builders[c]->number_of_vertices * OutputVertexSize), data); + glBufferSubData(GL_ARRAY_BUFFER, (GLsizeiptr)(c * output_vertices_per_slice * OutputVertexSize), (GLsizeiptr)(_run_builders[c]->number_of_vertices * OutputVertexSize), data); _run_builders[c]->uploaded_vertices = _run_builders[c]->number_of_vertices; } } // switch to the output shader - if(_openGL_state->rgb_shader_program) + if(rgb_shader_program) { - _openGL_state->rgb_shader_program->bind(); + rgb_shader_program->bind(); // update uniforms push_size_uniforms(output_width, output_height); // Ensure we're back on the output framebuffer - glBindFramebuffer(GL_FRAMEBUFFER, _openGL_state->defaultFramebuffer); + glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); // clear the buffer glClear(GL_COLOR_BUFFER_BIT); @@ -211,19 +200,19 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool if(_run_builders[run]->number_of_vertices > 0) { - glUniform1f(_openGL_state->timestampBaseUniform, (GLfloat)total_age); + glUniform1f(timestampBaseUniform, (GLfloat)total_age); if(_run_builders[run]->uploaded_vertices != _run_builders[run]->number_of_vertices) { uint8_t *data = &_run_builders[run]->_runs[_run_builders[run]->uploaded_vertices * OutputVertexSize]; glBufferSubData(GL_ARRAY_BUFFER, - (GLsizeiptr)(((run * _openGL_state->output_vertices_per_slice) + _run_builders[run]->uploaded_vertices) * OutputVertexSize), + (GLsizeiptr)(((run * output_vertices_per_slice) + _run_builders[run]->uploaded_vertices) * OutputVertexSize), (GLsizeiptr)((_run_builders[run]->number_of_vertices - _run_builders[run]->uploaded_vertices) * OutputVertexSize), data); _run_builders[run]->uploaded_vertices = _run_builders[run]->number_of_vertices; } // draw this frame - glDrawArrays(GL_TRIANGLE_STRIP, (GLint)(run * _openGL_state->output_vertices_per_slice), (GLsizei)_run_builders[run]->number_of_vertices); + glDrawArrays(GL_TRIANGLE_STRIP, (GLint)(run * output_vertices_per_slice), (GLsizei)_run_builders[run]->number_of_vertices); } // advance back in time @@ -234,16 +223,15 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool _output_mutex->unlock(); } -void CRT::set_openGL_context_will_change(bool should_delete_resources) +void OpenGLOutputBuilder::set_openGL_context_will_change(bool should_delete_resources) { - _openGL_state = nullptr; } -void CRT::push_size_uniforms(unsigned int output_width, unsigned int output_height) +void OpenGLOutputBuilder::push_size_uniforms(unsigned int output_width, unsigned int output_height) { - if(_openGL_state->windowSizeUniform >= 0) + if(windowSizeUniform >= 0) { - glUniform2f(_openGL_state->windowSizeUniform, output_width, output_height); + glUniform2f(windowSizeUniform, output_width, output_height); } GLfloat outputAspectRatioMultiplier = ((float)output_width / (float)output_height) / (4.0f / 3.0f); @@ -254,26 +242,26 @@ void CRT::push_size_uniforms(unsigned int output_width, unsigned int output_heig _aspect_ratio_corrected_bounds.origin.x -= bonusWidth * 0.5f * _aspect_ratio_corrected_bounds.size.width; _aspect_ratio_corrected_bounds.size.width *= outputAspectRatioMultiplier; - if(_openGL_state->boundsOriginUniform >= 0) - glUniform2f(_openGL_state->boundsOriginUniform, (GLfloat)_aspect_ratio_corrected_bounds.origin.x, (GLfloat)_aspect_ratio_corrected_bounds.origin.y); + if(boundsOriginUniform >= 0) + glUniform2f(boundsOriginUniform, (GLfloat)_aspect_ratio_corrected_bounds.origin.x, (GLfloat)_aspect_ratio_corrected_bounds.origin.y); - if(_openGL_state->boundsSizeUniform >= 0) - glUniform2f(_openGL_state->boundsSizeUniform, (GLfloat)_aspect_ratio_corrected_bounds.size.width, (GLfloat)_aspect_ratio_corrected_bounds.size.height); + if(boundsSizeUniform >= 0) + glUniform2f(boundsSizeUniform, (GLfloat)_aspect_ratio_corrected_bounds.size.width, (GLfloat)_aspect_ratio_corrected_bounds.size.height); } -void CRT::set_composite_sampling_function(const char *shader) +void OpenGLOutputBuilder::set_composite_sampling_function(const char *shader) { _composite_shader = strdup(shader); } -void CRT::set_rgb_sampling_function(const char *shader) +void OpenGLOutputBuilder::set_rgb_sampling_function(const char *shader) { _rgb_shader = strdup(shader); } #pragma mark - Input vertex shader (i.e. from source data to intermediate line layout) -char *CRT::get_input_vertex_shader() +char *OpenGLOutputBuilder::get_input_vertex_shader() { return strdup( "#version 150\n" @@ -298,7 +286,7 @@ char *CRT::get_input_vertex_shader() "}"); } -char *CRT::get_input_fragment_shader() +char *OpenGLOutputBuilder::get_input_fragment_shader() { const char *composite_shader = _composite_shader; if(!composite_shader) @@ -329,7 +317,7 @@ char *CRT::get_input_fragment_shader() #pragma mark - Output vertex shader -char *CRT::get_output_vertex_shader() +char *OpenGLOutputBuilder::get_output_vertex_shader() { // the main job of the vertex shader is just to map from an input area of [0,1]x[0,1], with the origin in the // top left to OpenGL's [-1,1]x[-1,1] with the origin in the lower left, and to convert input data coordinates @@ -378,12 +366,12 @@ char *CRT::get_output_vertex_shader() #pragma mark - Output fragment shaders; RGB and from composite -char *CRT::get_rgb_output_fragment_shader() +char *OpenGLOutputBuilder::get_rgb_output_fragment_shader() { return get_output_fragment_shader(_rgb_shader); } -char *CRT::get_composite_output_fragment_shader() +char *OpenGLOutputBuilder::get_composite_output_fragment_shader() { return get_output_fragment_shader( "vec4 rgb_sample(vec2 coordinate)" @@ -392,7 +380,7 @@ char *CRT::get_composite_output_fragment_shader() "}"); } -char *CRT::get_output_fragment_shader(const char *sampling_function) +char *OpenGLOutputBuilder::get_output_fragment_shader(const char *sampling_function) { return get_compound_shader( "#version 150\n" @@ -418,7 +406,7 @@ char *CRT::get_output_fragment_shader(const char *sampling_function) #pragma mark - Shader utilities -char *CRT::get_compound_shader(const char *base, const char *insert) +char *OpenGLOutputBuilder::get_compound_shader(const char *base, const char *insert) { if(!base || !insert) return nullptr; size_t totalLength = strlen(base) + strlen(insert) + 1; @@ -429,18 +417,18 @@ char *CRT::get_compound_shader(const char *base, const char *insert) #pragma mark - Program compilation -void CRT::prepare_composite_input_shader() +void OpenGLOutputBuilder::prepare_composite_input_shader() { char *vertex_shader = get_input_vertex_shader(); char *fragment_shader = get_input_fragment_shader(); if(vertex_shader && fragment_shader) { - _openGL_state->composite_input_shader_program = std::unique_ptr(new OpenGL::Shader(vertex_shader, fragment_shader)); + composite_input_shader_program = std::unique_ptr(new OpenGL::Shader(vertex_shader, fragment_shader)); - GLint texIDUniform = _openGL_state->composite_input_shader_program->get_uniform_location("texID"); - GLint inputTextureSizeUniform = _openGL_state->composite_input_shader_program->get_uniform_location("inputTextureSize"); - GLint outputTextureSizeUniform = _openGL_state->composite_input_shader_program->get_uniform_location("outputTextureSize"); - GLint phaseCyclesPerTickUniform = _openGL_state->composite_input_shader_program->get_uniform_location("phaseCyclesPerTick"); + GLint texIDUniform = composite_input_shader_program->get_uniform_location("texID"); + GLint inputTextureSizeUniform = composite_input_shader_program->get_uniform_location("inputTextureSize"); + GLint outputTextureSizeUniform = composite_input_shader_program->get_uniform_location("outputTextureSize"); + GLint phaseCyclesPerTickUniform = composite_input_shader_program->get_uniform_location("phaseCyclesPerTick"); glUniform1i(texIDUniform, first_supplied_buffer_texture_unit); glUniform2f(outputTextureSizeUniform, IntermediateBufferWidth, IntermediateBufferHeight); @@ -451,7 +439,7 @@ void CRT::prepare_composite_input_shader() free(fragment_shader); } -/*void CRT::prepare_output_shader(char *fragment_shader) +/*void OpenGLOutputBuilder::prepare_output_shader(char *fragment_shader) { char *vertex_shader = get_output_vertex_shader(); if(vertex_shader && fragment_shader) @@ -490,38 +478,38 @@ void CRT::prepare_composite_input_shader() free(fragment_shader); }*/ -void CRT::prepare_rgb_output_shader() +void OpenGLOutputBuilder::prepare_rgb_output_shader() { char *vertex_shader = get_output_vertex_shader(); char *fragment_shader = get_rgb_output_fragment_shader(); if(vertex_shader && fragment_shader) { - _openGL_state->rgb_shader_program = std::unique_ptr(new OpenGL::Shader(vertex_shader, fragment_shader)); + rgb_shader_program = std::unique_ptr(new OpenGL::Shader(vertex_shader, fragment_shader)); - _openGL_state->rgb_shader_program->bind(); + rgb_shader_program->bind(); - _openGL_state->windowSizeUniform = _openGL_state->rgb_shader_program->get_uniform_location("windowSize"); - _openGL_state->boundsSizeUniform = _openGL_state->rgb_shader_program->get_uniform_location("boundsSize"); - _openGL_state->boundsOriginUniform = _openGL_state->rgb_shader_program->get_uniform_location("boundsOrigin"); - _openGL_state->timestampBaseUniform = _openGL_state->rgb_shader_program->get_uniform_location("timestampBase"); + windowSizeUniform = rgb_shader_program->get_uniform_location("windowSize"); + boundsSizeUniform = rgb_shader_program->get_uniform_location("boundsSize"); + boundsOriginUniform = rgb_shader_program->get_uniform_location("boundsOrigin"); + timestampBaseUniform = rgb_shader_program->get_uniform_location("timestampBase"); - GLint texIDUniform = _openGL_state->rgb_shader_program->get_uniform_location("texID"); - GLint shadowMaskTexIDUniform = _openGL_state->rgb_shader_program->get_uniform_location("shadowMaskTexID"); - GLint textureSizeUniform = _openGL_state->rgb_shader_program->get_uniform_location("textureSize"); - GLint ticksPerFrameUniform = _openGL_state->rgb_shader_program->get_uniform_location("ticksPerFrame"); - GLint scanNormalUniform = _openGL_state->rgb_shader_program->get_uniform_location("scanNormal"); - GLint positionConversionUniform = _openGL_state->rgb_shader_program->get_uniform_location("positionConversion"); + GLint texIDUniform = rgb_shader_program->get_uniform_location("texID"); + GLint shadowMaskTexIDUniform = rgb_shader_program->get_uniform_location("shadowMaskTexID"); + GLint textureSizeUniform = rgb_shader_program->get_uniform_location("textureSize"); + GLint ticksPerFrameUniform = rgb_shader_program->get_uniform_location("ticksPerFrame"); + GLint scanNormalUniform = rgb_shader_program->get_uniform_location("scanNormal"); + GLint positionConversionUniform = rgb_shader_program->get_uniform_location("positionConversion"); glUniform1i(texIDUniform, first_supplied_buffer_texture_unit); glUniform1i(shadowMaskTexIDUniform, 1); glUniform2f(textureSizeUniform, InputBufferBuilderWidth, InputBufferBuilderHeight); glUniform1f(ticksPerFrameUniform, (GLfloat)(_cycles_per_line * _height_of_display)); - glUniform2f(positionConversionUniform, _horizontal_flywheel->get_scan_period(), _vertical_flywheel->get_scan_period() / (unsigned int)_vertical_flywheel_output_divider); + glUniform2f(positionConversionUniform, _horizontal_scan_period, _vertical_scan_period / (unsigned int)_vertical_period_divider); float scan_angle = atan2f(1.0f / (float)_height_of_display, 1.0f); float scan_normal[] = { -sinf(scan_angle), cosf(scan_angle)}; - float multiplier = (float)_horizontal_flywheel->get_standard_period() / ((float)_height_of_display * (float)_horizontal_flywheel->get_scan_period()); + float multiplier = (float)_cycles_per_line / ((float)_height_of_display * (float)_horizontal_scan_period); scan_normal[0] *= multiplier; scan_normal[1] *= multiplier; glUniform2f(scanNormalUniform, scan_normal[0], scan_normal[1]); @@ -531,14 +519,14 @@ void CRT::prepare_rgb_output_shader() free(fragment_shader); } -void CRT::prepare_output_vertex_array() +void OpenGLOutputBuilder::prepare_output_vertex_array() { - if(_openGL_state->rgb_shader_program) + if(rgb_shader_program) { - GLint positionAttribute = _openGL_state->rgb_shader_program->get_attrib_location("position"); - GLint textureCoordinatesAttribute = _openGL_state->rgb_shader_program->get_attrib_location("srcCoordinates"); - GLint lateralAttribute = _openGL_state->rgb_shader_program->get_attrib_location("lateral"); - GLint timestampAttribute = _openGL_state->rgb_shader_program->get_attrib_location("timestamp"); + GLint positionAttribute = rgb_shader_program->get_attrib_location("position"); + GLint textureCoordinatesAttribute = rgb_shader_program->get_attrib_location("srcCoordinates"); + GLint lateralAttribute = rgb_shader_program->get_attrib_location("lateral"); + GLint timestampAttribute = rgb_shader_program->get_attrib_location("timestamp"); glEnableVertexAttribArray((GLuint)positionAttribute); glEnableVertexAttribArray((GLuint)textureCoordinatesAttribute); @@ -555,7 +543,7 @@ void CRT::prepare_output_vertex_array() #pragma mark - Configuration -void CRT::set_output_device(OutputDevice output_device) +void OpenGLOutputBuilder::set_output_device(OutputDevice output_device) { if (_output_device != output_device) { diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 35e340ca9..0acd9f680 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -9,6 +9,15 @@ #ifndef CRTOpenGL_h #define CRTOpenGL_h +#include "../CRTTypes.hpp" +#include "OpenGL.hpp" +#include "TextureTarget.hpp" +#include "Shader.hpp" +#include "CRTInputBufferBuilder.hpp" +#include "CRTRunBuilder.hpp" + +#include + namespace Outputs { namespace CRT { @@ -43,6 +52,184 @@ const int IntermediateBufferHeight = 2048; // number of historic fields that are required fully to const int NumberOfFields = 3; +class OpenGLOutputBuilder { + private: + // colour information + ColourSpace _colour_space; + unsigned int _colour_cycle_numerator; + unsigned int _colour_cycle_denominator; + OutputDevice _output_device; + + // timing information to allow reasoning about input information + unsigned int _cycles_per_line; + unsigned int _height_of_display; + unsigned int _horizontal_scan_period; + unsigned int _vertical_scan_period; + unsigned int _vertical_period_divider; + + // The user-supplied visible area + Rect _visible_area; + + // Other things the caller may have provided. + char *_composite_shader; + char *_rgb_shader; + + // Methods used by the OpenGL code + void prepare_rgb_output_shader(); + void prepare_composite_input_shader(); + void prepare_output_vertex_array(); + void push_size_uniforms(unsigned int output_width, unsigned int output_height); + + // the run and input data buffers + std::unique_ptr _buffer_builder; + CRTRunBuilder **_run_builders; + int _run_write_pointer; + std::shared_ptr _output_mutex; + + // transient buffers indicating composite data not yet decoded + std::unique_ptr _composite_src_runs; + uint16_t _composite_src_output_y; + + char *get_output_vertex_shader(); + + char *get_output_fragment_shader(const char *sampling_function); + char *get_rgb_output_fragment_shader(); + char *get_composite_output_fragment_shader(); + + char *get_input_vertex_shader(); + char *get_input_fragment_shader(); + + char *get_compound_shader(const char *base, const char *insert); + + std::unique_ptr rgb_shader_program; + std::unique_ptr composite_input_shader_program, composite_output_shader_program; + + GLuint output_array_buffer, output_vertex_array; + size_t output_vertices_per_slice; + + GLint windowSizeUniform, timestampBaseUniform; + GLint boundsOriginUniform, boundsSizeUniform; + + GLuint textureName, shadowMaskTextureName; + + GLuint defaultFramebuffer; + + std::unique_ptr compositeTexture; // receives raw composite levels + std::unique_ptr filteredYTexture; // receives filtered Y in the R channel plus unfiltered I/U and Q/V in G and B + std::unique_ptr filteredTexture; // receives filtered YIQ or YUV + + public: + OpenGLOutputBuilder(unsigned int number_of_buffers, va_list sizes); + ~OpenGLOutputBuilder(); + + inline void set_colour_format(ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator) + { + _colour_space = colour_space; + _colour_cycle_numerator = colour_cycle_numerator; + _colour_cycle_denominator = colour_cycle_denominator; + } + + inline void set_visible_area(Rect visible_area) + { + _visible_area = visible_area; + } + + inline uint8_t *get_next_input_run() + { + _output_mutex->lock(); + return (_output_device == Monitor) ? _run_builders[_run_write_pointer]->get_next_run(6) : _composite_src_runs->get_next_run(2); + } + + inline void complete_input_run() + { + _output_mutex->unlock(); + } + + inline uint8_t *get_next_output_run() + { + _output_mutex->lock(); + return (_output_device == Monitor) ? _run_builders[_run_write_pointer]->get_next_run(6) : _composite_src_runs->get_next_run(2); + } + + inline void complete_output_run() + { + } + + inline OutputDevice get_output_device() + { + return _output_device; + } + + inline uint32_t get_current_field_time() + { + return _run_builders[_run_write_pointer]->duration; + } + + inline void add_to_field_time(uint32_t amount) + { + _run_builders[_run_write_pointer]->duration += amount; + } + + inline uint16_t get_composite_output_y() + { + return _composite_src_output_y; + } + + inline void increment_composite_output_y() + { + _composite_src_output_y = (_composite_src_output_y + 1) % IntermediateBufferHeight; + } + + inline void increment_field() + { + _run_write_pointer = (_run_write_pointer + 1)%NumberOfFields; + _run_builders[_run_write_pointer]->reset(); + } + + inline void allocate_write_area(size_t required_length) + { + _output_mutex->lock(); + _buffer_builder->allocate_write_area(required_length); + _output_mutex->unlock(); + } + + inline void reduce_previous_allocation_to(size_t actual_length) + { + _buffer_builder->reduce_previous_allocation_to(actual_length); + } + + inline uint8_t *get_write_target_for_buffer(int buffer) + { + return _buffer_builder->get_write_target_for_buffer(buffer); + } + + inline uint16_t get_last_write_x_posiiton() + { + return _buffer_builder->_write_x_position; + } + + inline uint16_t get_last_write_y_posiiton() + { + return _buffer_builder->_write_y_position; + } + + void draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty); + void set_openGL_context_will_change(bool should_delete_resources); + void set_composite_sampling_function(const char *shader); + void set_rgb_sampling_function(const char *shader); + void set_output_device(OutputDevice output_device); + inline void set_timing(unsigned int cycles_per_line, unsigned int height_of_display, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider) + { + _cycles_per_line = cycles_per_line; + _height_of_display = height_of_display; + _horizontal_scan_period = horizontal_scan_period; + _vertical_scan_period = vertical_scan_period; + _vertical_period_divider = vertical_period_divider; + + // TODO: update related uniforms + } +}; + } } diff --git a/Outputs/CRT/Internals/CRTRunBuilder.hpp b/Outputs/CRT/Internals/CRTRunBuilder.hpp index ad67b2689..f5969a304 100644 --- a/Outputs/CRT/Internals/CRTRunBuilder.hpp +++ b/Outputs/CRT/Internals/CRTRunBuilder.hpp @@ -9,6 +9,8 @@ #ifndef CRTRunBuilder_h #define CRTRunBuilder_h +#import + namespace Outputs { namespace CRT {