diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index ca2fdabaf..5b2fa6348 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -303,11 +303,12 @@ 4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BB73EAA1B587A5100552FC2 /* MainMenu.xib */; }; 4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */; }; 4BB73EC21B587A5100552FC2 /* Clock_SignalUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EC11B587A5100552FC2 /* Clock_SignalUITests.swift */; }; + 4BBB14311CD2CECE00BDB55C /* IntermediateShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */; }; 4BBF99141C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99081C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp */; }; 4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */; }; - 4BBF99161C8FBA6F0075DAFB /* CRTRunBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF990C1C8FBA6F0075DAFB /* CRTRunBuilder.cpp */; }; - 4BBF99171C8FBA6F0075DAFB /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99101C8FBA6F0075DAFB /* Shader.cpp */; }; 4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */; }; + 4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B74D1CD194CC00F86E85 /* Shader.cpp */; }; + 4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B7501CD1956900F86E85 /* OutputShader.cpp */; }; 4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; }; 4BC76E6B1C98F43700E6EF73 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */; }; 4BCB70B41C947DDC005B1712 /* plus1.rom in Resources */ = {isa = PBXBuildFile; fileRef = 4BCB70B31C947DDC005B1712 /* plus1.rom */; }; @@ -656,19 +657,21 @@ 4BB73EC11B587A5100552FC2 /* Clock_SignalUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Clock_SignalUITests.swift; sourceTree = ""; }; 4BB73EC31B587A5100552FC2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4BB73ECF1B587A6700552FC2 /* Clock Signal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = "Clock Signal.entitlements"; sourceTree = ""; }; + 4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IntermediateShader.cpp; sourceTree = ""; }; + 4BBB14301CD2CECE00BDB55C /* IntermediateShader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IntermediateShader.hpp; sourceTree = ""; }; 4BBF99081C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRTInputBufferBuilder.cpp; sourceTree = ""; }; 4BBF99091C8FBA6F0075DAFB /* CRTInputBufferBuilder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTInputBufferBuilder.hpp; sourceTree = ""; }; 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRTOpenGL.cpp; sourceTree = ""; }; 4BBF990B1C8FBA6F0075DAFB /* CRTOpenGL.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTOpenGL.hpp; sourceTree = ""; }; - 4BBF990C1C8FBA6F0075DAFB /* CRTRunBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRTRunBuilder.cpp; sourceTree = ""; }; - 4BBF990D1C8FBA6F0075DAFB /* CRTRunBuilder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTRunBuilder.hpp; sourceTree = ""; }; 4BBF990E1C8FBA6F0075DAFB /* Flywheel.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Flywheel.hpp; sourceTree = ""; }; 4BBF990F1C8FBA6F0075DAFB /* OpenGL.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OpenGL.hpp; sourceTree = ""; }; - 4BBF99101C8FBA6F0075DAFB /* Shader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Shader.cpp; sourceTree = ""; }; - 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 = ""; }; + 4BC3B74D1CD194CC00F86E85 /* Shader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Shader.cpp; sourceTree = ""; }; + 4BC3B74E1CD194CC00F86E85 /* Shader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Shader.hpp; sourceTree = ""; }; + 4BC3B7501CD1956900F86E85 /* OutputShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OutputShader.cpp; sourceTree = ""; }; + 4BC3B7511CD1956900F86E85 /* OutputShader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OutputShader.hpp; sourceTree = ""; }; 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FIRFilter.cpp; sourceTree = ""; }; 4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FIRFilter.hpp; sourceTree = ""; }; 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; }; @@ -1206,16 +1209,13 @@ 4BBF99071C8FBA6F0075DAFB /* Internals */ = { isa = PBXGroup; children = ( + 4BC3B74C1CD194CC00F86E85 /* Shaders */, 4BBF99081C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp */, 4BBF99091C8FBA6F0075DAFB /* CRTInputBufferBuilder.hpp */, 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */, 4BBF990B1C8FBA6F0075DAFB /* CRTOpenGL.hpp */, - 4BBF990C1C8FBA6F0075DAFB /* CRTRunBuilder.cpp */, - 4BBF990D1C8FBA6F0075DAFB /* CRTRunBuilder.hpp */, 4BBF990E1C8FBA6F0075DAFB /* Flywheel.hpp */, 4BBF990F1C8FBA6F0075DAFB /* OpenGL.hpp */, - 4BBF99101C8FBA6F0075DAFB /* Shader.cpp */, - 4BBF99111C8FBA6F0075DAFB /* Shader.hpp */, 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */, 4BBF99131C8FBA6F0075DAFB /* TextureTarget.hpp */, 4B0B6E121C9DBD5D00FFB60D /* CRTConstants.hpp */, @@ -1223,6 +1223,19 @@ path = Internals; sourceTree = ""; }; + 4BC3B74C1CD194CC00F86E85 /* Shaders */ = { + isa = PBXGroup; + children = ( + 4BC3B74D1CD194CC00F86E85 /* Shader.cpp */, + 4BC3B74E1CD194CC00F86E85 /* Shader.hpp */, + 4BC3B7501CD1956900F86E85 /* OutputShader.cpp */, + 4BC3B7511CD1956900F86E85 /* OutputShader.hpp */, + 4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */, + 4BBB14301CD2CECE00BDB55C /* IntermediateShader.hpp */, + ); + path = Shaders; + sourceTree = ""; + }; 4BE5F85A1C3E1C2500C43F01 /* Resources */ = { isa = PBXGroup; children = ( @@ -1652,10 +1665,11 @@ 4B55CE541C3B7ABF0093A61B /* CSElectron.mm in Sources */, 4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */, 4B55CE591C3B7D360093A61B /* ElectronDocument.swift in Sources */, + 4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */, 4B55CE4B1C3B3B0C0093A61B /* CSAtari2600.mm in Sources */, - 4BBF99161C8FBA6F0075DAFB /* CRTRunBuilder.cpp in Sources */, 4B55CE581C3B7D360093A61B /* Atari2600Document.swift in Sources */, 4B0EBFB81C487F2F00A11F35 /* AudioQueue.m in Sources */, + 4BBB14311CD2CECE00BDB55C /* IntermediateShader.cpp in Sources */, 4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */, 4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */, 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */, @@ -1664,10 +1678,10 @@ 4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */, 4B55CE4E1C3B3BDA0093A61B /* CSMachine.mm in Sources */, 4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */, - 4BBF99171C8FBA6F0075DAFB /* Shader.cpp in Sources */, 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */, 4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */, 4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */, + 4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */, 4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */, 4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */, ); diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index aee6483f5..bb8ec8c12 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -66,12 +66,15 @@ } - (void)clearAllKeys { - @synchronized(self) { - _electron.clear_all_keys(); - } +// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ + @synchronized(self) { + _electron.clear_all_keys(); + } +// }); } - (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed { +// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ @synchronized(self) { switch(key) { @@ -149,13 +152,16 @@ break; } } +// }); } - (void)setUseFastLoadingHack:(BOOL)useFastLoadingHack { - @synchronized(self) { - _useFastLoadingHack = useFastLoadingHack; - _electron.set_use_fast_tape_hack(useFastLoadingHack ? true : false); - } +// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ + @synchronized(self) { + _useFastLoadingHack = useFastLoadingHack; + _electron.set_use_fast_tape_hack(useFastLoadingHack ? true : false); + } +// }); } - (void)setUseTelevisionOutput:(BOOL)useTelevisionOutput { diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index c01587273..1b6c32084 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -10,6 +10,7 @@ #include "CRTOpenGL.hpp" #include #include +#include using namespace Outputs::CRT; @@ -27,7 +28,7 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di // a TV picture tube or camera tube to the starting point of a line or field. It is about 7 µs // for horizontal retrace and 500 to 750 µs for vertical retrace in NTSC and PAL TV." - _time_multiplier = (2000 + cycles_per_line - 1) / cycles_per_line; + _time_multiplier = IntermediateBufferWidth / cycles_per_line; // store fundamental display configuration properties _height_of_display = height_of_display; @@ -44,7 +45,7 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di 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); + _openGL_output_builder->set_timing(cycles_per_line, _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) @@ -99,8 +100,6 @@ Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, #define output_tex_x(v) (*(uint16_t *)&next_run[OutputVertexSize*v + OutputVertexOffsetOfTexCoord + 0]) #define output_tex_y(v) (*(uint16_t *)&next_run[OutputVertexSize*v + OutputVertexOffsetOfTexCoord + 2]) #define output_lateral(v) next_run[OutputVertexSize*v + OutputVertexOffsetOfLateral] -#define output_frame_id(v) next_run[OutputVertexSize*v + OutputVertexOffsetOfFrameID] -#define output_timestamp(v) (*(uint32_t *)&next_run[OutputVertexSize*v + OutputVertexOffsetOfTimestamp]) #define source_input_position_x(v) (*(uint16_t *)&next_run[SourceVertexSize*v + SourceVertexOffsetOfInputPosition + 0]) #define source_input_position_y(v) (*(uint16_t *)&next_run[SourceVertexSize*v + SourceVertexOffsetOfInputPosition + 2]) @@ -134,7 +133,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi uint8_t *next_run = nullptr; if(is_output_segment) { - next_run = (_openGL_output_builder->get_output_device() == Monitor) ? _openGL_output_builder->get_next_output_run() : _openGL_output_builder->get_next_source_run(); + next_run = _openGL_output_builder->get_next_source_run(); } // Vertex output is arranged for triangle strips, as: @@ -144,38 +143,20 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi // [0/1] 3 if(next_run) { - 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) = _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 - output_tex_y(0) = output_tex_y(1) = output_tex_y(2) = output_tex_y(3) = output_tex_y(4) = output_tex_y(5) = tex_y; - output_lateral(0) = output_lateral(1) = output_lateral(3) = 0; - output_lateral(2) = output_lateral(4) = output_lateral(5) = 1; - output_frame_id(0) = output_frame_id(1) = output_frame_id(2) = output_frame_id(3) = output_frame_id(4) = output_frame_id(5) = (uint8_t)_openGL_output_builder->get_current_field(); - } - else - { - source_input_position_x(0) = tex_x; - source_input_position_y(0) = source_input_position_y(1) = tex_y; - source_output_position_x(0) = (uint16_t)_horizontal_flywheel->get_current_output_position(); - source_output_position_y(0) = source_output_position_y(1) = _openGL_output_builder->get_composite_output_y(); - source_phase(0) = source_phase(1) = _colour_burst_phase; - source_amplitude(0) = source_amplitude(1) = _colour_burst_amplitude; - source_phase_time(0) = source_phase_time(1) = _colour_burst_time; - source_offset(0) = 0; - source_offset(1) = 255; - } + source_input_position_x(0) = tex_x; + source_input_position_y(0) = source_input_position_y(1) = tex_y; + source_output_position_x(0) = (uint16_t)_horizontal_flywheel->get_current_output_position(); + source_output_position_y(0) = source_output_position_y(1) = _openGL_output_builder->get_composite_output_y(); + source_phase(0) = source_phase(1) = _colour_burst_phase; + source_amplitude(0) = source_amplitude(1) = _colour_burst_amplitude; + source_phase_time(0) = source_phase_time(1) = _colour_burst_time; + source_offset(0) = 0; + source_offset(1) = 255; } // decrement the number of cycles left to run for and increment the // horizontal counter appropriately number_of_cycles -= 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) @@ -192,57 +173,39 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi // if this is a data run then advance the buffer pointer if(type == Scan::Type::Data && source_divider) tex_x += next_run_length / (_time_multiplier * source_divider); - 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) = _openGL_output_builder->get_current_field_time(); - output_tex_x(3) = output_tex_x(4) = output_tex_x(5) = tex_x; + source_input_position_x(1) = tex_x; + source_output_position_x(1) = (uint16_t)_horizontal_flywheel->get_current_output_position(); - _openGL_output_builder->complete_output_run(6); - } - else - { - source_input_position_x(1) = tex_x; - source_output_position_x(1) = (uint16_t)_horizontal_flywheel->get_current_output_position(); - - _openGL_output_builder->complete_source_run(); - } + _openGL_output_builder->complete_source_run(); } // if this is horizontal retrace then advance the output line counter and bookend an output run - 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; + if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event != Flywheel::SyncEvent::None) honoured_event = next_horizontal_sync_event; + bool needs_endpoint = + (honoured_event == Flywheel::SyncEvent::StartRetrace && _is_writing_composite_run) || + (honoured_event == Flywheel::SyncEvent::EndRetrace && !_horizontal_flywheel->is_in_retrace() && !_vertical_flywheel->is_in_retrace()); + + if(needs_endpoint) { - 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; - if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event != Flywheel::SyncEvent::None) honoured_event = next_horizontal_sync_event; - bool needs_endpoint = - (honoured_event == Flywheel::SyncEvent::StartRetrace && _is_writing_composite_run) || - (honoured_event == Flywheel::SyncEvent::EndRetrace && !_horizontal_flywheel->is_in_retrace() && !_vertical_flywheel->is_in_retrace()); + uint8_t *next_run = _openGL_output_builder->get_next_output_run(); - if(needs_endpoint) - { - 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_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) = _openGL_output_builder->get_composite_output_y(); + output_lateral(0) = 0; + output_lateral(1) = _is_writing_composite_run ? 1 : 0; + output_lateral(2) = 1; - 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) = _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) = _openGL_output_builder->get_composite_output_y(); - output_lateral(0) = 0; - output_lateral(1) = _is_writing_composite_run ? 1 : 0; - output_lateral(2) = 1; - output_frame_id(0) = output_frame_id(1) = output_frame_id(2) = (uint8_t)_openGL_output_builder->get_current_field(); + _openGL_output_builder->complete_output_run(3); + _is_writing_composite_run ^= true; + } - _openGL_output_builder->complete_output_run(3); - _is_writing_composite_run ^= true; - } - - if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace) - { - _openGL_output_builder->increment_composite_output_y(); - } + if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace) + { + _openGL_output_builder->increment_composite_output_y(); } // if this is vertical retrace then adcance a field @@ -257,8 +220,6 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi _frames_since_last_delegate_call = 0; } } - - _openGL_output_builder->increment_field(); } } } @@ -268,7 +229,6 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi #undef output_tex_x #undef output_tex_y #undef output_lateral -#undef output_timestamp #undef input_input_position_x #undef input_input_position_y diff --git a/Outputs/CRT/Internals/CRTConstants.hpp b/Outputs/CRT/Internals/CRTConstants.hpp index 857a3eef9..63cfe99ed 100644 --- a/Outputs/CRT/Internals/CRTConstants.hpp +++ b/Outputs/CRT/Internals/CRTConstants.hpp @@ -19,11 +19,9 @@ namespace CRT { // or is one of the intermediate buffers that we've used to convert from composite towards RGB. const GLsizei OutputVertexOffsetOfPosition = 0; const GLsizei OutputVertexOffsetOfTexCoord = 4; -const GLsizei OutputVertexOffsetOfTimestamp = 8; -const GLsizei OutputVertexOffsetOfLateral = 12; -const GLsizei OutputVertexOffsetOfFrameID = 13; +const GLsizei OutputVertexOffsetOfLateral = 8; -const GLsizei OutputVertexSize = 16; +const GLsizei OutputVertexSize = 12; // Input vertices, used only in composite mode, map from the input buffer to temporary buffer locations; such // remapping occurs to ensure a continous stream of data for each scan, giving correct out-of-bounds behaviour @@ -43,16 +41,9 @@ const GLsizei IntermediateBufferWidth = 2048; const GLsizei IntermediateBufferHeight = 2048; // Some internal buffer sizes -const GLsizeiptr OutputVertexBufferDataSize = 262080; // a multiple of 6 * OutputVertexSize +const GLsizeiptr OutputVertexBufferDataSize = 89856; // a multiple of 6 * OutputVertexSize const GLsizeiptr SourceVertexBufferDataSize = 87360; // a multiple of 2 * SourceVertexSize - -// Runs are divided discretely by vertical syncs in order to put a usable bounds on the uniform used to track -// run age; that therefore creates a discrete number of fields that are stored. This number should be the -// number of historic fields that are required fully to complete a frame. It should be at least two and not -// more than four. -const int NumberOfFields = 4; - } } diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 5ebbc25d5..79f8b3916 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -11,6 +11,7 @@ #include "CRTOpenGL.hpp" #include "../../../SignalProcessing/FIRFilter.hpp" +#include "Shaders/OutputShader.hpp" static const GLint internalFormatForDepth(size_t depth) { @@ -36,8 +37,14 @@ static const GLenum formatForDepth(size_t depth) } } -static int getCircularRanges(GLsizei start, GLsizei end, GLsizei buffer_length, GLsizei *ranges) +static int getCircularRanges(GLsizei start, GLsizei end, GLsizei buffer_length, GLsizei granularity, GLsizei *ranges) { + GLsizei startOffset = start%granularity; + if(startOffset) + { + start -= startOffset; + } + GLsizei length = end - start; if(!length) return 0; if(length > buffer_length) @@ -71,10 +78,10 @@ namespace { static const GLenum filtered_y_texture_unit = GL_TEXTURE1; static const GLenum filtered_texture_unit = GL_TEXTURE2; static const GLenum source_data_texture_unit = GL_TEXTURE3; + static const GLenum pixel_accumulation_texture_unit = GL_TEXTURE4; } OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : - _run_write_pointer(0), _output_mutex(new std::mutex), _visible_area(Rect(0, 0, 1, 1)), _composite_src_output_y(0), @@ -85,29 +92,21 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : _source_buffer_data(nullptr), _input_texture_data(nullptr), _output_buffer_data_pointer(0), + _drawn_output_buffer_data_pointer(0), _source_buffer_data_pointer(0), - _drawn_source_buffer_data_pointer(0) + _drawn_source_buffer_data_pointer(0), + _last_output_width(0), + _last_output_height(0) { - _run_builders = new CRTRunBuilder *[NumberOfFields]; - for(int builder = 0; builder < NumberOfFields; builder++) - { - _run_builders[builder] = new CRTRunBuilder(); - } _buffer_builder = std::unique_ptr(new CRTInputBufferBuilder(buffer_depth)); - glBlendFunc(GL_SRC_ALPHA, GL_CONSTANT_ALPHA); - glBlendColor(1.0f, 1.0f, 1.0f, 0.33f); + glBlendFunc(GL_SRC_ALPHA, GL_CONSTANT_COLOR); + glBlendColor(0.6f, 0.6f, 0.6f, 1.0f); // Create intermediate textures and bind to slots 0, 1 and 2 - glActiveTexture(composite_texture_unit); - compositeTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); - compositeTexture->bind_texture(); - glActiveTexture(filtered_y_texture_unit); - filteredYTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); - filteredYTexture->bind_texture(); - glActiveTexture(filtered_texture_unit); - filteredTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); - filteredTexture->bind_texture(); + compositeTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, composite_texture_unit)); + filteredYTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, filtered_y_texture_unit)); + filteredTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, filtered_texture_unit)); // create the surce texture glGenTextures(1, &textureName); @@ -126,7 +125,7 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : glBufferData(GL_PIXEL_UNPACK_BUFFER, _input_texture_array_size, NULL, GL_STREAM_DRAW); // map the buffer for clients - _input_texture_data = (uint8_t *)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, _input_texture_array_size, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); + _input_texture_data = (uint8_t *)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, _input_texture_array_size, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_FLUSH_EXPLICIT_BIT); // create the output vertex array glGenVertexArrays(1, &output_vertex_array); @@ -137,7 +136,7 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : glBufferData(GL_ARRAY_BUFFER, OutputVertexBufferDataSize, NULL, GL_STREAM_DRAW); // map that buffer too, for any CRT activity that may occur before the first draw - _output_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, OutputVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); + _output_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, OutputVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_FLUSH_EXPLICIT_BIT); // create the source vertex array glGenVertexArrays(1, &source_vertex_array); @@ -148,20 +147,11 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : glBufferData(GL_ARRAY_BUFFER, SourceVertexBufferDataSize, NULL, GL_STREAM_DRAW); // map that buffer too, for any CRT activity that may occur before the first draw - _source_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, SourceVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); - - // map back the default framebuffer - glBindFramebuffer(GL_FRAMEBUFFER, 0); + _source_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, SourceVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_FLUSH_EXPLICIT_BIT); } OpenGLOutputBuilder::~OpenGLOutputBuilder() { - for(int builder = 0; builder < NumberOfFields; builder++) - { - delete _run_builders[builder]; - } - delete[] _run_builders; - glUnmapBuffer(GL_ARRAY_BUFFER); glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); glDeleteTextures(1, &textureName); @@ -177,13 +167,13 @@ OpenGLOutputBuilder::~OpenGLOutputBuilder() void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty) { // establish essentials - if(!composite_input_shader_program && !rgb_shader_program) + if(!output_shader_program) { - prepare_composite_input_shader(); + prepare_composite_input_shaders(); + prepare_rgb_input_shaders(); prepare_source_vertex_array(); - prepare_composite_output_shader(); - prepare_rgb_output_shader(); + prepare_output_shader(); prepare_output_vertex_array(); set_timing_uniforms(); @@ -203,18 +193,35 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // release the mapping, giving up on trying to draw if data has been lost glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); - if(glUnmapBuffer(GL_ARRAY_BUFFER) == GL_FALSE) - { - for(int c = 0; c < NumberOfFields; c++) - _run_builders[c]->reset(); - } + glUnmapBuffer(GL_ARRAY_BUFFER); glBindBuffer(GL_ARRAY_BUFFER, source_array_buffer); glUnmapBuffer(GL_ARRAY_BUFFER); glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); + // make sure there's a target to draw to + if(!framebuffer || framebuffer->get_height() != output_height || framebuffer->get_width() != output_width) + { + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + std::unique_ptr new_framebuffer = std::unique_ptr(new OpenGL::TextureTarget((GLsizei)output_width, (GLsizei)output_height, pixel_accumulation_texture_unit)); + if(framebuffer) + { + new_framebuffer->bind_framebuffer(); + glClear(GL_COLOR_BUFFER_BIT); + + glActiveTexture(pixel_accumulation_texture_unit); + framebuffer->bind_texture(); + framebuffer->draw((float)output_width / (float)output_height); + + new_framebuffer->bind_texture(); + } + framebuffer = std::move(new_framebuffer); + glActiveTexture(source_data_texture_unit); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, _input_texture_array); + } + // upload more source pixel data if any; we'll always resubmit the last line submitted last // time as it may have had extra data appended to it - if(_buffer_builder->_next_write_y_position < _buffer_builder->last_uploaded_line) + if(_buffer_builder->_write_y_position < _buffer_builder->last_uploaded_line) { glTexSubImage2D( GL_TEXTURE_2D, 0, 0, (GLint)_buffer_builder->last_uploaded_line, @@ -224,7 +231,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out _buffer_builder->last_uploaded_line = 0; } - if(_buffer_builder->_next_write_y_position > _buffer_builder->last_uploaded_line) + if(_buffer_builder->_write_y_position > _buffer_builder->last_uploaded_line) { glTexSubImage2D( GL_TEXTURE_2D, 0, 0, (GLint)_buffer_builder->last_uploaded_line, @@ -234,138 +241,146 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out _buffer_builder->last_uploaded_line = _buffer_builder->_next_write_y_position; } - // for television, update intermediate buffers and then draw; for a monitor, just draw - if(_output_device == Television || !rgb_shader_program) + struct RenderStage { + OpenGL::TextureTarget *const target; + OpenGL::Shader *const shader; + float clear_colour[3]; + }; + + RenderStage composite_render_stages[] = { - // decide how much to draw - if(_drawn_source_buffer_data_pointer != _source_buffer_data_pointer) + {compositeTexture.get(), composite_input_shader_program.get(), {0.0, 0.0, 0.0}}, + {filteredYTexture.get(), composite_y_filter_shader_program.get(), {0.0, 0.5, 0.5}}, + {filteredTexture.get(), composite_chrominance_filter_shader_program.get(), {0.0, 0.0, 0.0}}, + {nullptr} + }; + + RenderStage rgb_render_stages[] = + { + {compositeTexture.get(), rgb_input_shader_program.get(), {0.0, 0.0, 0.0}}, + {filteredTexture.get(), rgb_filter_shader_program.get(), {0.0, 0.0, 0.0}}, + {nullptr} + }; + + RenderStage *active_pipeline = (_output_device == Television || !rgb_input_shader_program) ? composite_render_stages : rgb_render_stages; + + // for television, update intermediate buffers and then draw; for a monitor, just draw + if(_drawn_source_buffer_data_pointer != _source_buffer_data_pointer) + { + // determine how many lines are newly reclaimed; they'll need to be cleared + GLsizei clearing_zones[4], drawing_zones[4]; + int number_of_clearing_zones = getCircularRanges(_cleared_composite_output_y+1, _composite_src_output_y+1, IntermediateBufferHeight, 1, clearing_zones); + int number_of_drawing_zones = getCircularRanges(_drawn_source_buffer_data_pointer, _source_buffer_data_pointer, SourceVertexBufferDataSize, 2*SourceVertexSize, drawing_zones); + + _composite_src_output_y %= IntermediateBufferHeight; + _cleared_composite_output_y = _composite_src_output_y; + _source_buffer_data_pointer %= SourceVertexBufferDataSize; + _drawn_source_buffer_data_pointer = _source_buffer_data_pointer; + + // all drawing will be from the source vertex array and without blending + glBindVertexArray(source_vertex_array); + glDisable(GL_BLEND); + + // flush the source data + glBindBuffer(GL_ARRAY_BUFFER, source_array_buffer); + for(int c = 0; c < number_of_drawing_zones; c++) { - // determine how many lines are newly reclaimed; they'll need to be cleared - GLsizei clearing_zones[4], drawing_zones[4]; - int number_of_clearing_zones = getCircularRanges(_cleared_composite_output_y+1, _composite_src_output_y+1, IntermediateBufferHeight, clearing_zones); - int number_of_drawing_zones = getCircularRanges(_drawn_source_buffer_data_pointer, _source_buffer_data_pointer, SourceVertexBufferDataSize, drawing_zones); - - _composite_src_output_y %= IntermediateBufferHeight; - _cleared_composite_output_y = _composite_src_output_y; - _source_buffer_data_pointer %= SourceVertexBufferDataSize; - _drawn_source_buffer_data_pointer = _source_buffer_data_pointer; - - // all drawing will be from the source vertex array and without blending - glBindVertexArray(source_vertex_array); - glDisable(GL_BLEND); - - OpenGL::TextureTarget *targets[] = { - compositeTexture.get(), - filteredYTexture.get(), - filteredTexture.get() - }; - OpenGL::Shader *shaders[] = { - composite_input_shader_program.get(), - composite_y_filter_shader_program.get(), - composite_chrominance_filter_shader_program.get() - }; - float clear_colours[][3] = { - {0.0, 0.0, 0.0}, - {0.0, 0.5, 0.5}, - {0.0, 0.0, 0.0} - }; - for(int stage = 0; stage < 3; stage++) - { - // switch to the initial texture - targets[stage]->bind_framebuffer(); - shaders[stage]->bind(); - - // clear as desired - if(number_of_clearing_zones) - { - glEnable(GL_SCISSOR_TEST); - glClearColor(clear_colours[stage][0], clear_colours[stage][1], clear_colours[stage][2], 1.0); - for(int c = 0; c < number_of_clearing_zones; c++) - { - glScissor(0, clearing_zones[c*2], IntermediateBufferWidth, clearing_zones[c*2 + 1]); - glClear(GL_COLOR_BUFFER_BIT); - } - glDisable(GL_SCISSOR_TEST); - } - - // draw as desired - for(int c = 0; c < number_of_drawing_zones; c++) - { - glDrawArrays(GL_LINES, drawing_zones[c*2] / SourceVertexSize, drawing_zones[c*2 + 1] / SourceVertexSize); - } - } - - // switch back to screen output - glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); - glViewport(0, 0, (GLsizei)output_width, (GLsizei)output_height); - glClearColor(0.0, 0.0, 0.0, 1.0); + glFlushMappedBufferRange(GL_ARRAY_BUFFER, drawing_zones[c*2] / SourceVertexSize, drawing_zones[c*2 + 1] / SourceVertexSize); } - // transfer to screen - perform_output_stage(output_width, output_height, composite_output_shader_program.get()); + while(active_pipeline->target) + { + // switch to the initial texture + active_pipeline->target->bind_framebuffer(); + active_pipeline->shader->bind(); + + // clear as desired + if(number_of_clearing_zones) + { + glEnable(GL_SCISSOR_TEST); + glClearColor(active_pipeline->clear_colour[0], active_pipeline->clear_colour[1], active_pipeline->clear_colour[2], 1.0); + for(int c = 0; c < number_of_clearing_zones; c++) + { + glScissor(0, clearing_zones[c*2], IntermediateBufferWidth, clearing_zones[c*2 + 1]); + glClear(GL_COLOR_BUFFER_BIT); + } + glDisable(GL_SCISSOR_TEST); + } + + // draw as desired + for(int c = 0; c < number_of_drawing_zones; c++) + { + glDrawArrays(GL_LINES, drawing_zones[c*2] / SourceVertexSize, drawing_zones[c*2 + 1] / SourceVertexSize); + } + + active_pipeline++; + } } - else - perform_output_stage(output_width, output_height, rgb_shader_program.get()); + + // transfer to framebuffer + framebuffer->bind_framebuffer(); + + // draw all pending lines + GLsizei drawing_zones[4]; + int number_of_drawing_zones = getCircularRanges(_drawn_output_buffer_data_pointer, _output_buffer_data_pointer, OutputVertexBufferDataSize, 6*OutputVertexSize, drawing_zones); + + // flush the buffer data + glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); + for(int c = 0; c < number_of_drawing_zones; c++) + { + glFlushMappedBufferRange(GL_ARRAY_BUFFER, drawing_zones[c*2] / OutputVertexSize, drawing_zones[c*2 + 1] / OutputVertexSize); + } + + _output_buffer_data_pointer %= SourceVertexBufferDataSize; + _output_buffer_data_pointer -= (_output_buffer_data_pointer%(6*OutputVertexSize)); + _drawn_output_buffer_data_pointer = _output_buffer_data_pointer; + + if(number_of_drawing_zones > 0) + { + glEnable(GL_BLEND); + + // Ensure we're back on the output framebuffer, drawing from the output array buffer + glBindVertexArray(output_vertex_array); + + // update uniforms (implicitly binding the shader) + if(_last_output_width != output_width || _last_output_height != output_height) + { + output_shader_program->set_output_size(output_width, output_height, _visible_area); + _last_output_width = output_width; + _last_output_height = output_height; + } + output_shader_program->bind(); + + // draw + for(int c = 0; c < number_of_drawing_zones; c++) + { + glDrawArrays(GL_TRIANGLE_STRIP, drawing_zones[c*2] / OutputVertexSize, drawing_zones[c*2 + 1] / OutputVertexSize); + } + } + + // copy framebuffer to the intended place + glDisable(GL_BLEND); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glViewport(0, 0, (GLsizei)output_width, (GLsizei)output_height); + glClear(GL_COLOR_BUFFER_BIT); + framebuffer->draw((float)output_width / (float)output_height); // drawing commands having been issued, reclaim the array buffer pointer glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); - _output_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, OutputVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); + _output_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, OutputVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_FLUSH_EXPLICIT_BIT); glBindBuffer(GL_ARRAY_BUFFER, source_array_buffer); - _source_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, SourceVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); + _source_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, SourceVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_FLUSH_EXPLICIT_BIT); - _input_texture_data = (uint8_t *)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, _input_texture_array_size, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); + _input_texture_data = (uint8_t *)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, _input_texture_array_size, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_FLUSH_EXPLICIT_BIT); _output_mutex->unlock(); } -void OpenGLOutputBuilder::perform_output_stage(unsigned int output_width, unsigned int output_height, OpenGL::Shader *const shader) +void OpenGLOutputBuilder::perform_output_stage(unsigned int output_width, unsigned int output_height, OpenGL::OutputShader *const shader) { if(shader) { - // clear the buffer - glClear(GL_COLOR_BUFFER_BIT); - - // draw all sitting frames - unsigned int run = (unsigned int)_run_write_pointer; - GLint total_age = 0; - float timestampBases[4]; - size_t start = 0, count = 0; - for(int c = 0; c < NumberOfFields; c++) - { - total_age += _run_builders[run]->duration; - timestampBases[run] = (float)total_age; - count += _run_builders[run]->amount_of_data; - start = _run_builders[run]->start; - run = (run - 1 + NumberOfFields) % NumberOfFields; - } - - if(count > 0) - { - glEnable(GL_BLEND); - - // Ensure we're back on the output framebuffer, drawing from the output array buffer - glBindVertexArray(output_vertex_array); - shader->bind(); - - // update uniforms - push_size_uniforms(output_width, output_height); - - // draw - glUniform4fv(timestampBaseUniform, 1, timestampBases); - - GLsizei primitive_count = (GLsizei)(count / OutputVertexSize); - GLsizei max_count = (GLsizei)((OutputVertexBufferDataSize - start) / OutputVertexSize); - if(primitive_count < max_count) - { - glDrawArrays(GL_TRIANGLE_STRIP, (GLint)(start / OutputVertexSize), primitive_count); - } - else - { - glDrawArrays(GL_TRIANGLE_STRIP, (GLint)(start / OutputVertexSize), max_count); - glDrawArrays(GL_TRIANGLE_STRIP, 0, primitive_count - max_count); - } - } } } @@ -373,28 +388,6 @@ void OpenGLOutputBuilder::set_openGL_context_will_change(bool should_delete_reso { } -void OpenGLOutputBuilder::push_size_uniforms(unsigned int output_width, unsigned int output_height) -{ - if(windowSizeUniform >= 0) - { - glUniform2f(windowSizeUniform, output_width, output_height); - } - - GLfloat outputAspectRatioMultiplier = ((float)output_width / (float)output_height) / (4.0f / 3.0f); - - Rect _aspect_ratio_corrected_bounds = _visible_area; - - GLfloat bonusWidth = (outputAspectRatioMultiplier - 1.0f) * _visible_area.size.width; - _aspect_ratio_corrected_bounds.origin.x -= bonusWidth * 0.5f * _aspect_ratio_corrected_bounds.size.width; - _aspect_ratio_corrected_bounds.size.width *= outputAspectRatioMultiplier; - - if(boundsOriginUniform >= 0) - glUniform2f(boundsOriginUniform, (GLfloat)_aspect_ratio_corrected_bounds.origin.x, (GLfloat)_aspect_ratio_corrected_bounds.origin.y); - - if(boundsSizeUniform >= 0) - glUniform2f(boundsSizeUniform, (GLfloat)_aspect_ratio_corrected_bounds.size.width, (GLfloat)_aspect_ratio_corrected_bounds.size.height); -} - void OpenGLOutputBuilder::set_composite_sampling_function(const char *shader) { _composite_shader = strdup(shader); @@ -405,384 +398,35 @@ 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 *OpenGLOutputBuilder::get_input_vertex_shader(const char *input_position, const char *header) -{ - char *result; - asprintf(&result, - "#version 150\n" - - "in vec2 inputPosition;" - "in vec2 outputPosition;" - "in vec3 phaseAmplitudeAndOffset;" - "in float phaseTime;" - - "uniform float phaseCyclesPerTick;" - "uniform ivec2 outputTextureSize;" - "uniform float extension;" - - "\n%s\n" - - "out vec2 inputPositionVarying;" - "out vec2 iInputPositionVarying;" - "out float phaseVarying;" - "out float amplitudeVarying;" - "out vec2 inputPositionsVarying[11];" - - "void main(void)" - "{" - "vec2 extensionVector = vec2(extension, 0.0) * 2.0 * (phaseAmplitudeAndOffset.z - 0.5);" - "vec2 extendedInputPosition = %s + extensionVector;" - "vec2 extendedOutputPosition = outputPosition + extensionVector;" - - "vec2 textureSize = vec2(textureSize(texID, 0));" - "iInputPositionVarying = extendedInputPosition;" - "inputPositionVarying = (extendedInputPosition + vec2(0.0, 0.5)) / textureSize;" - - "textureSize = textureSize * vec2(1.0);" - "inputPositionsVarying[0] = inputPositionVarying - (vec2(10.0, 0.0) / textureSize);" - "inputPositionsVarying[1] = inputPositionVarying - (vec2(8.0, 0.0) / textureSize);" - "inputPositionsVarying[2] = inputPositionVarying - (vec2(6.0, 0.0) / textureSize);" - "inputPositionsVarying[3] = inputPositionVarying - (vec2(4.0, 0.0) / textureSize);" - "inputPositionsVarying[4] = inputPositionVarying - (vec2(2.0, 0.0) / textureSize);" - - "inputPositionsVarying[5] = inputPositionVarying;" - - "inputPositionsVarying[6] = inputPositionVarying + (vec2(2.0, 0.0) / textureSize);" - "inputPositionsVarying[7] = inputPositionVarying + (vec2(4.0, 0.0) / textureSize);" - "inputPositionsVarying[8] = inputPositionVarying + (vec2(6.0, 0.0) / textureSize);" - "inputPositionsVarying[9] = inputPositionVarying + (vec2(8.0, 0.0) / textureSize);" - "inputPositionsVarying[10] = inputPositionVarying + (vec2(10.0, 0.0) / textureSize);" - - "phaseVarying = (phaseCyclesPerTick * (extendedOutputPosition.x - phaseTime) + phaseAmplitudeAndOffset.x) * 2.0 * 3.141592654;" - "amplitudeVarying = 0.33;" // phaseAmplitudeAndOffset.y - - "vec2 eyePosition = 2.0*(extendedOutputPosition / outputTextureSize) - vec2(1.0) + vec2(0.5)/textureSize;" - "gl_Position = vec4(eyePosition, 0.0, 1.0);" - "}", header, input_position); - return result; -} - -char *OpenGLOutputBuilder::get_input_fragment_shader() -{ - char *composite_shader = _composite_shader; - if(!composite_shader) - { - asprintf(&composite_shader, - "%s\n" - "uniform mat3 rgbToLumaChroma;" - "float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" - "{" - "vec3 rgbColour = clamp(rgb_sample(texID, coordinate, iCoordinate), vec3(0.0), vec3(1.0));" - "vec3 lumaChromaColour = rgbToLumaChroma * rgbColour;" - "vec2 quadrature = vec2(cos(phase), -sin(phase)) * amplitude;" - "return dot(lumaChromaColour, vec3(1.0 - amplitude, quadrature));" - "}", - _rgb_shader); - // TODO: use YIQ if this is NTSC - } - - char *result; - asprintf(&result, - "#version 150\n" - - "in vec2 inputPositionVarying;" - "in vec2 iInputPositionVarying;" - "in float phaseVarying;" - "in float amplitudeVarying;" - - "out vec4 fragColour;" - - "uniform usampler2D texID;" - - "\n%s\n" - - "void main(void)" - "{" - "fragColour = vec4(composite_sample(texID, inputPositionVarying, iInputPositionVarying, phaseVarying, amplitudeVarying));" - "}" - , composite_shader); - - if(!_composite_shader) free(composite_shader); - - return result; -} - -char *OpenGLOutputBuilder::get_y_filter_fragment_shader() -{ - return strdup( - "#version 150\n" - - "in float phaseVarying;" - "in float amplitudeVarying;" - - "in vec2 inputPositionsVarying[11];" - "uniform vec4 weights[3];" - - "out vec3 fragColour;" - - "uniform sampler2D texID;" - - "void main(void)" - "{" - "vec4 samples[3] = vec4[](" - "vec4(" - "texture(texID, inputPositionsVarying[0]).r," - "texture(texID, inputPositionsVarying[1]).r," - "texture(texID, inputPositionsVarying[2]).r," - "texture(texID, inputPositionsVarying[3]).r" - ")," - "vec4(" - "texture(texID, inputPositionsVarying[4]).r," - "texture(texID, inputPositionsVarying[5]).r," - "texture(texID, inputPositionsVarying[6]).r," - "texture(texID, inputPositionsVarying[7]).r" - ")," - "vec4(" - "texture(texID, inputPositionsVarying[8]).r," - "texture(texID, inputPositionsVarying[9]).r," - "texture(texID, inputPositionsVarying[10]).r," - "0.0" - ")" - ");" - - "float luminance = " - "dot(vec3(" - "dot(samples[0], weights[0])," - "dot(samples[1], weights[1])," - "dot(samples[2], weights[2])" - "), vec3(1.0)) / (1.0 - amplitudeVarying);" - - "float chrominance = 0.5 * (samples[1].y - luminance) / amplitudeVarying;" - "vec2 quadrature = vec2(cos(phaseVarying), -sin(phaseVarying));" - - "fragColour = vec3(luminance, vec2(0.5) + (chrominance * quadrature));" - "}"); -} - -char *OpenGLOutputBuilder::get_chrominance_filter_fragment_shader() -{ - return strdup( - "#version 150\n" - - "in float phaseVarying;" - "in float amplitudeVarying;" - - "in vec2 inputPositionsVarying[11];" - "uniform vec4 weights[3];" - - "out vec3 fragColour;" - - "uniform sampler2D texID;" - "uniform mat3 lumaChromaToRGB;" - - "void main(void)" - "{" - "vec3 centreSample = texture(texID, inputPositionsVarying[5]).rgb;" - "vec2 samples[] = vec2[](" - "texture(texID, inputPositionsVarying[0]).gb - vec2(0.5)," - "texture(texID, inputPositionsVarying[1]).gb - vec2(0.5)," - "texture(texID, inputPositionsVarying[2]).gb - vec2(0.5)," - "texture(texID, inputPositionsVarying[3]).gb - vec2(0.5)," - "texture(texID, inputPositionsVarying[4]).gb - vec2(0.5)," - "centreSample.gb - vec2(0.5)," - "texture(texID, inputPositionsVarying[6]).gb - vec2(0.5)," - "texture(texID, inputPositionsVarying[7]).gb - vec2(0.5)," - "texture(texID, inputPositionsVarying[8]).gb - vec2(0.5)," - "texture(texID, inputPositionsVarying[9]).gb - vec2(0.5)," - "texture(texID, inputPositionsVarying[10]).gb - vec2(0.5)" - ");" - - "vec4 channel1[] = vec4[](" - "vec4(samples[0].r, samples[1].r, samples[2].r, samples[3].r)," - "vec4(samples[4].r, samples[5].r, samples[6].r, samples[7].r)," - "vec4(samples[8].r, samples[9].r, samples[10].r, 0.0)" - ");" - "vec4 channel2[] = vec4[](" - "vec4(samples[0].g, samples[1].g, samples[2].g, samples[3].g)," - "vec4(samples[4].g, samples[5].g, samples[6].g, samples[7].g)," - "vec4(samples[8].g, samples[9].g, samples[10].g, 0.0)" - ");" - - "vec3 lumaChromaColour = vec3(centreSample.r," - "dot(vec3(" - "dot(channel1[0], weights[0])," - "dot(channel1[1], weights[1])," - "dot(channel1[2], weights[2])" - "), vec3(1.0)) + 0.5," - "dot(vec3(" - "dot(channel2[0], weights[0])," - "dot(channel2[1], weights[1])," - "dot(channel2[2], weights[2])" - "), vec3(1.0)) + 0.5" - ");" - - "vec3 lumaChromaColourInRange = (lumaChromaColour - vec3(0.0, 0.5, 0.5)) * vec3(1.0, 2.0, 2.0);" - "fragColour = lumaChromaToRGB * lumaChromaColourInRange;" - "}"); -} - - -#pragma mark - Intermediate vertex shaders (i.e. from intermediate line layout to intermediate line layout) - -#pragma mark - Output vertex shader - -char *OpenGLOutputBuilder::get_output_vertex_shader(const char *header) -{ - // 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 - // from integral to floating point. - - char *result; - asprintf(&result, - "#version 150\n" - - "in vec2 position;" - "in vec2 srcCoordinates;" - "in vec2 lateralAndTimestampBaseOffset;" - "in float timestamp;" - - "uniform vec2 boundsOrigin;" - "uniform vec2 boundsSize;" - - "out float lateralVarying;" -// "out vec2 shadowMaskCoordinates;" - "out float alpha;" - - "uniform vec4 timestampBase;" - "uniform float ticksPerFrame;" - "uniform vec2 positionConversion;" - "uniform vec2 scanNormal;" - - "\n%s\n" -// "uniform sampler2D shadowMaskTexID;" - -// "const float shadowMaskMultiple = 600;" - - "out vec2 srcCoordinatesVarying;" - "out vec2 iSrcCoordinatesVarying;" - - "void main(void)" - "{" - "lateralVarying = lateralAndTimestampBaseOffset.x + 1.0707963267949;" - -// "shadowMaskCoordinates = position * vec2(shadowMaskMultiple, shadowMaskMultiple * 0.85057471264368);" - - "ivec2 textureSize = textureSize(texID, 0);" - "iSrcCoordinatesVarying = srcCoordinates;" - "srcCoordinatesVarying = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);" - "float age = (timestampBase[int(lateralAndTimestampBaseOffset.y)] - timestamp) / ticksPerFrame;" - "alpha = 1.0;"//15.0*exp(-age*3.0);" - - "vec2 floatingPosition = (position / positionConversion) + lateralAndTimestampBaseOffset.x * scanNormal;" - "vec2 mappedPosition = (floatingPosition - boundsOrigin) / boundsSize;" - "gl_Position = vec4(mappedPosition.x * 2.0 - 1.0, 1.0 - mappedPosition.y * 2.0, 0.0, 1.0);" - "}", header); - return result; -} - -char *OpenGLOutputBuilder::get_rgb_output_vertex_shader() -{ - return get_output_vertex_shader("uniform usampler2D texID;"); -} - -char *OpenGLOutputBuilder::get_composite_output_vertex_shader() -{ - return get_output_vertex_shader("uniform sampler2D texID;"); -} - -#pragma mark - Output fragment shaders; RGB and from composite - -char *OpenGLOutputBuilder::get_rgb_output_fragment_shader() -{ - const char *rgb_shader = _rgb_shader; - if(!_rgb_shader) - { - rgb_shader = - "vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)" - "{" - "return texture(sampler, coordinate).rgb / vec3(255.0);" - "}"; - } - - char *result = get_output_fragment_shader(rgb_shader, "uniform usampler2D texID;", - "vec3 colour = rgb_sample(texID, srcCoordinatesVarying, iSrcCoordinatesVarying);"); - - return result; -} - -char *OpenGLOutputBuilder::get_composite_output_fragment_shader() -{ - return get_output_fragment_shader("", - "uniform sampler2D texID;", - "vec3 colour = texture(texID, srcCoordinatesVarying).rgb;" - ); -} - -char *OpenGLOutputBuilder::get_output_fragment_shader(const char *sampling_function, const char *header, const char *fragColour_function) -{ - char *result; - asprintf(&result, - "#version 150\n" - - "in float lateralVarying;" - "in float alpha;" -// "in vec2 shadowMaskCoordinates;" - "in vec2 srcCoordinatesVarying;" - "in vec2 iSrcCoordinatesVarying;" - - "out vec4 fragColour;" - -// "uniform sampler2D shadowMaskTexID;", - "%s\n" - "%s\n" - "void main(void)" - "{" - "\n%s\n" - "fragColour = vec4(colour, clamp(alpha, 0.0, 1.0)*sin(lateralVarying));" - "}", - header, sampling_function, fragColour_function); - - return result; -} - #pragma mark - Program compilation -std::unique_ptr OpenGLOutputBuilder::prepare_intermediate_shader(const char *input_position, const char *header, char *fragment_shader, GLenum texture_unit, bool extends) +void OpenGLOutputBuilder::prepare_composite_input_shaders() { - std::unique_ptr shader; - char *vertex_shader = get_input_vertex_shader(input_position, header); - if(vertex_shader && fragment_shader) - { - OpenGL::Shader::AttributeBinding bindings[] = - { - {"inputPosition", 0}, - {"outputPosition", 1}, - {"phaseAmplitudeAndOffset", 2}, - {"phaseTime", 3}, - {nullptr} - }; - shader = std::unique_ptr(new OpenGL::Shader(vertex_shader, fragment_shader, bindings)); + composite_input_shader_program = OpenGL::IntermediateShader::make_source_conversion_shader(_composite_shader, _rgb_shader); + composite_input_shader_program->set_source_texture_unit(source_data_texture_unit); + composite_input_shader_program->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight); - GLint texIDUniform = shader->get_uniform_location("texID"); - GLint outputTextureSizeUniform = shader->get_uniform_location("outputTextureSize"); + composite_y_filter_shader_program = OpenGL::IntermediateShader::make_chroma_luma_separation_shader(); + composite_y_filter_shader_program->set_source_texture_unit(composite_texture_unit); + composite_y_filter_shader_program->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight); - shader->bind(); - glUniform1i(texIDUniform, (GLint)(texture_unit - GL_TEXTURE0)); - glUniform2i(outputTextureSizeUniform, IntermediateBufferWidth, IntermediateBufferHeight); - } - free(vertex_shader); - free(fragment_shader); - - return shader; + composite_chrominance_filter_shader_program = OpenGL::IntermediateShader::make_chroma_filter_shader(); + composite_chrominance_filter_shader_program->set_source_texture_unit(filtered_y_texture_unit); + composite_chrominance_filter_shader_program->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight); } -void OpenGLOutputBuilder::prepare_composite_input_shader() +void OpenGLOutputBuilder::prepare_rgb_input_shaders() { - composite_input_shader_program = prepare_intermediate_shader("inputPosition", "uniform usampler2D texID;", get_input_fragment_shader(), source_data_texture_unit, false); - composite_y_filter_shader_program = prepare_intermediate_shader("outputPosition", "uniform sampler2D texID;", get_y_filter_fragment_shader(), composite_texture_unit, true); - composite_chrominance_filter_shader_program = prepare_intermediate_shader("outputPosition", "uniform sampler2D texID;", get_chrominance_filter_fragment_shader(), filtered_y_texture_unit, true); + if(_rgb_shader) + { + rgb_input_shader_program = OpenGL::IntermediateShader::make_rgb_source_shader(_rgb_shader); + rgb_input_shader_program->set_source_texture_unit(source_data_texture_unit); + rgb_input_shader_program->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight); + + rgb_filter_shader_program = OpenGL::IntermediateShader::make_rgb_filter_shader(); + rgb_filter_shader_program->set_source_texture_unit(composite_texture_unit); + rgb_filter_shader_program->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight); + } } void OpenGLOutputBuilder::prepare_source_vertex_array() @@ -810,70 +454,31 @@ void OpenGLOutputBuilder::prepare_source_vertex_array() } } -std::unique_ptr OpenGLOutputBuilder::prepare_output_shader(char *vertex_shader, char *fragment_shader, GLint source_texture_unit) +void OpenGLOutputBuilder::prepare_output_shader() { - std::unique_ptr shader_program; - - if(vertex_shader && fragment_shader) - { - OpenGL::Shader::AttributeBinding bindings[] = - { - {"position", 0}, - {"srcCoordinates", 1}, - {"lateralAndTimestampBaseOffset", 2}, - {"timestamp", 3}, - {nullptr} - }; - shader_program = std::unique_ptr(new OpenGL::Shader(vertex_shader, fragment_shader, bindings)); - shader_program->bind(); - - windowSizeUniform = shader_program->get_uniform_location("windowSize"); - boundsSizeUniform = shader_program->get_uniform_location("boundsSize"); - boundsOriginUniform = shader_program->get_uniform_location("boundsOrigin"); - timestampBaseUniform = shader_program->get_uniform_location("timestampBase"); - - GLint texIDUniform = shader_program->get_uniform_location("texID"); - glUniform1i(texIDUniform, source_texture_unit - GL_TEXTURE0); - } - - free(vertex_shader); - free(fragment_shader); - - return shader_program; -} - -void OpenGLOutputBuilder::prepare_rgb_output_shader() -{ - rgb_shader_program = prepare_output_shader(get_rgb_output_vertex_shader(), get_rgb_output_fragment_shader(), source_data_texture_unit); -} - -void OpenGLOutputBuilder::prepare_composite_output_shader() -{ - composite_output_shader_program = prepare_output_shader(get_composite_output_vertex_shader(), get_composite_output_fragment_shader(), filtered_texture_unit); + output_shader_program = OpenGL::OutputShader::make_shader("", "texture(texID, srcCoordinatesVarying).rgb", false); + output_shader_program->set_source_texture_unit(filtered_texture_unit); } void OpenGLOutputBuilder::prepare_output_vertex_array() { - if(rgb_shader_program) + if(output_shader_program) { - 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("lateralAndTimestampBaseOffset"); - GLint timestampAttribute = rgb_shader_program->get_attrib_location("timestamp"); + GLint positionAttribute = output_shader_program->get_attrib_location("position"); + GLint textureCoordinatesAttribute = output_shader_program->get_attrib_location("srcCoordinates"); + GLint lateralAttribute = output_shader_program->get_attrib_location("lateral"); glBindVertexArray(output_vertex_array); glEnableVertexAttribArray((GLuint)positionAttribute); glEnableVertexAttribArray((GLuint)textureCoordinatesAttribute); glEnableVertexAttribArray((GLuint)lateralAttribute); - glEnableVertexAttribArray((GLuint)timestampAttribute); const GLsizei vertexStride = OutputVertexSize; glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); glVertexAttribPointer((GLuint)positionAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)OutputVertexOffsetOfPosition); glVertexAttribPointer((GLuint)textureCoordinatesAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)OutputVertexOffsetOfTexCoord); - glVertexAttribPointer((GLuint)timestampAttribute, 4, GL_UNSIGNED_INT, GL_FALSE, vertexStride, (void *)OutputVertexOffsetOfTimestamp); - glVertexAttribPointer((GLuint)lateralAttribute, 2, GL_UNSIGNED_BYTE, GL_FALSE, vertexStride, (void *)OutputVertexOffsetOfLateral); + glVertexAttribPointer((GLuint)lateralAttribute, 1, GL_UNSIGNED_BYTE, GL_FALSE, vertexStride, (void *)OutputVertexOffsetOfLateral); } } @@ -884,18 +489,15 @@ void OpenGLOutputBuilder::set_output_device(OutputDevice output_device) if(_output_device != output_device) { _output_device = output_device; - - for(int builder = 0; builder < NumberOfFields; builder++) - { - _run_builders[builder]->reset(); - } - _composite_src_output_y = 0; + _last_output_width = 0; + _last_output_height = 0; } } -void OpenGLOutputBuilder::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) +void OpenGLOutputBuilder::set_timing(unsigned int input_frequency, 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) { + _input_frequency = input_frequency; _cycles_per_line = cycles_per_line; _height_of_display = height_of_display; _horizontal_scan_period = horizontal_scan_period; @@ -931,98 +533,34 @@ void OpenGLOutputBuilder::set_colour_space_uniforms() break; } - if(composite_input_shader_program) - { - composite_input_shader_program->bind(); - GLint uniform = composite_input_shader_program->get_uniform_location("rgbToLumaChroma"); - if(uniform >= 0) - { - glUniformMatrix3fv(uniform, 1, GL_FALSE, fromRGB); - } - } - - if(composite_chrominance_filter_shader_program) - { - composite_chrominance_filter_shader_program->bind(); - GLint uniform = composite_chrominance_filter_shader_program->get_uniform_location("lumaChromaToRGB"); - if(uniform >= 0) - { - glUniformMatrix3fv(uniform, 1, GL_FALSE, toRGB); - } - } + if(composite_input_shader_program) composite_input_shader_program->set_colour_conversion_matrices(fromRGB, toRGB); + if(composite_chrominance_filter_shader_program) composite_chrominance_filter_shader_program->set_colour_conversion_matrices(fromRGB, toRGB); _output_mutex->unlock(); } void OpenGLOutputBuilder::set_timing_uniforms() { _output_mutex->lock(); - OpenGL::Shader *intermediate_shaders[] = { + + OpenGL::IntermediateShader *intermediate_shaders[] = { composite_input_shader_program.get(), composite_y_filter_shader_program.get(), composite_chrominance_filter_shader_program.get() }; bool extends = false; + float phaseCyclesPerTick = (float)_colour_cycle_numerator / (float)(_colour_cycle_denominator * _cycles_per_line); for(int c = 0; c < 3; c++) { - if(intermediate_shaders[c]) - { - intermediate_shaders[c]->bind(); - GLint phaseCyclesPerTickUniform = intermediate_shaders[c]->get_uniform_location("phaseCyclesPerTick"); - GLint extensionUniform = intermediate_shaders[c]->get_uniform_location("extension"); - - float phaseCyclesPerTick = (float)_colour_cycle_numerator / (float)(_colour_cycle_denominator * _cycles_per_line); - glUniform1f(phaseCyclesPerTickUniform, phaseCyclesPerTick); - glUniform1f(extensionUniform, extends ? ceilf(1.0f / phaseCyclesPerTick) : 0.0f); - } + if(intermediate_shaders[c]) intermediate_shaders[c]->set_phase_cycles_per_sample(phaseCyclesPerTick, extends); extends = true; } - OpenGL::Shader *output_shaders[] = { - rgb_shader_program.get(), - composite_output_shader_program.get() - }; - for(int c = 0; c < 2; c++) - { - if(output_shaders[c]) - { - output_shaders[c]->bind(); - - GLint ticksPerFrameUniform = output_shaders[c]->get_uniform_location("ticksPerFrame"); - GLint scanNormalUniform = output_shaders[c]->get_uniform_location("scanNormal"); - GLint positionConversionUniform = output_shaders[c]->get_uniform_location("positionConversion"); - - glUniform1f(ticksPerFrameUniform, (GLfloat)(_cycles_per_line * _height_of_display)); - float scan_angle = atan2f(1.0f / (float)_height_of_display, 1.0f); - float scan_normal[] = { -sinf(scan_angle), cosf(scan_angle)}; - 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]); - - glUniform2f(positionConversionUniform, _horizontal_scan_period, _vertical_scan_period / (unsigned int)_vertical_period_divider); - } - } + if(output_shader_program) output_shader_program->set_timing(_height_of_display, _cycles_per_line, _horizontal_scan_period, _vertical_scan_period, _vertical_period_divider); float colour_subcarrier_frequency = (float)_colour_cycle_numerator / (float)_colour_cycle_denominator; - GLint weightsUniform; - float weights[12]; + if(composite_y_filter_shader_program) composite_y_filter_shader_program->set_separation_frequency(_cycles_per_line, colour_subcarrier_frequency); + if(composite_chrominance_filter_shader_program) composite_chrominance_filter_shader_program->set_filter_coefficients(_cycles_per_line, colour_subcarrier_frequency * 0.5f); + if(rgb_filter_shader_program) rgb_filter_shader_program->set_filter_coefficients(_cycles_per_line, (float)_input_frequency * 0.5f); - if(composite_y_filter_shader_program) - { - SignalProcessing::FIRFilter luminance_filter(11, _cycles_per_line * 0.5f, 0.0f, colour_subcarrier_frequency * 0.5f, SignalProcessing::FIRFilter::DefaultAttenuation); - composite_y_filter_shader_program->bind(); - weightsUniform = composite_y_filter_shader_program->get_uniform_location("weights"); - luminance_filter.get_coefficients(weights); - glUniform4fv(weightsUniform, 3, weights); - } - - if(composite_chrominance_filter_shader_program) - { - SignalProcessing::FIRFilter chrominance_filter(11, _cycles_per_line * 0.5f, 0.0f, colour_subcarrier_frequency * 0.5f, SignalProcessing::FIRFilter::DefaultAttenuation); - composite_chrominance_filter_shader_program->bind(); - weightsUniform = composite_chrominance_filter_shader_program->get_uniform_location("weights"); - chrominance_filter.get_coefficients(weights); - glUniform4fv(weightsUniform, 3, weights); - } _output_mutex->unlock(); } diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index fc5144770..7014d9c07 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -15,7 +15,9 @@ #include "TextureTarget.hpp" #include "Shader.hpp" #include "CRTInputBufferBuilder.hpp" -#include "CRTRunBuilder.hpp" + +#include "Shaders/OutputShader.hpp" +#include "Shaders/IntermediateShader.hpp" #include @@ -31,6 +33,7 @@ class OpenGLOutputBuilder { OutputDevice _output_device; // timing information to allow reasoning about input information + unsigned int _input_frequency; unsigned int _cycles_per_line; unsigned int _height_of_display; unsigned int _horizontal_scan_period; @@ -45,48 +48,28 @@ class OpenGLOutputBuilder { char *_rgb_shader; // Methods used by the OpenGL code - void prepare_rgb_output_shader(); - void prepare_composite_output_shader(); - std::unique_ptr prepare_output_shader(char *vertex_shader, char *fragment_shader, GLint source_texture_unit); - - void prepare_composite_input_shader(); - std::unique_ptr prepare_intermediate_shader(const char *input_position, const char *header, char *fragment_shader, GLenum texture_unit, bool extends); + void prepare_output_shader(); + void prepare_rgb_input_shaders(); + void prepare_composite_input_shaders(); void prepare_output_vertex_array(); void prepare_source_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 uint16_t _composite_src_output_y, _cleared_composite_output_y; - char *get_output_vertex_shader(const char *header); - char *get_rgb_output_vertex_shader(); - char *get_composite_output_vertex_shader(); - - char *get_output_fragment_shader(const char *sampling_function, const char *header, const char *fragColour_function); - char *get_rgb_output_fragment_shader(); - char *get_composite_output_fragment_shader(); - - char *get_input_vertex_shader(const char *input_position, const char *header); - char *get_input_fragment_shader(); - - char *get_y_filter_fragment_shader(); - char *get_chrominance_filter_fragment_shader(); - - std::unique_ptr rgb_shader_program; - std::unique_ptr composite_input_shader_program, composite_y_filter_shader_program, composite_chrominance_filter_shader_program, composite_output_shader_program; + std::unique_ptr output_shader_program; + std::unique_ptr composite_input_shader_program, composite_y_filter_shader_program, composite_chrominance_filter_shader_program; + std::unique_ptr rgb_input_shader_program, rgb_filter_shader_program; GLuint output_array_buffer, output_vertex_array; GLuint source_array_buffer, source_vertex_array; - GLint windowSizeUniform, timestampBaseUniform; - GLint boundsOriginUniform, boundsSizeUniform; + unsigned int _last_output_width, _last_output_height; GLuint textureName, shadowMaskTextureName; @@ -96,7 +79,9 @@ class OpenGLOutputBuilder { 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 - void perform_output_stage(unsigned int output_width, unsigned int output_height, OpenGL::Shader *const shader); + std::unique_ptr framebuffer; // the current pixel output + + void perform_output_stage(unsigned int output_width, unsigned int output_height, OpenGL::OutputShader *const shader); void set_timing_uniforms(); void set_colour_space_uniforms(); @@ -132,13 +117,12 @@ class OpenGLOutputBuilder { inline uint8_t *get_next_output_run() { _output_mutex->lock(); - return &_output_buffer_data[_output_buffer_data_pointer]; + return &_output_buffer_data[_output_buffer_data_pointer % OutputVertexBufferDataSize]; } inline void complete_output_run(GLsizei vertices_written) { - _run_builders[_run_write_pointer]->amount_of_data += (size_t)(vertices_written * OutputVertexSize); - _output_buffer_data_pointer = (_output_buffer_data_pointer + vertices_written * OutputVertexSize) % OutputVertexBufferDataSize; + _output_buffer_data_pointer += vertices_written * OutputVertexSize; _output_mutex->unlock(); } @@ -147,16 +131,6 @@ class OpenGLOutputBuilder { 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 % IntermediateBufferHeight; @@ -167,20 +141,6 @@ class OpenGLOutputBuilder { _composite_src_output_y++; } - inline void increment_field() - { - _output_mutex->lock(); - _run_write_pointer = (_run_write_pointer + 1)%NumberOfFields; - _run_builders[_run_write_pointer]->start = (size_t)_output_buffer_data_pointer; - _run_builders[_run_write_pointer]->reset(); - _output_mutex->unlock(); - } - - inline int get_current_field() - { - return _run_write_pointer; - } - inline uint8_t *allocate_write_area(size_t required_length) { _output_mutex->lock(); @@ -210,19 +170,20 @@ class OpenGLOutputBuilder { void set_composite_sampling_function(const char *shader); void set_rgb_sampling_function(const char *shader); void set_output_device(OutputDevice output_device); - 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); + void set_timing(unsigned int input_frequency, 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); uint8_t *_input_texture_data; GLuint _input_texture_array; GLsync _input_texture_sync; GLsizeiptr _input_texture_array_size; - uint8_t *_output_buffer_data; - GLsizei _output_buffer_data_pointer; - uint8_t *_source_buffer_data; GLsizei _source_buffer_data_pointer; GLsizei _drawn_source_buffer_data_pointer; + + uint8_t *_output_buffer_data; + GLsizei _output_buffer_data_pointer; + GLsizei _drawn_output_buffer_data_pointer; }; } diff --git a/Outputs/CRT/Internals/CRTRunBuilder.cpp b/Outputs/CRT/Internals/CRTRunBuilder.cpp deleted file mode 100644 index 5191a1c84..000000000 --- a/Outputs/CRT/Internals/CRTRunBuilder.cpp +++ /dev/null @@ -1,12 +0,0 @@ -// -// CRTFrameBuilder.cpp -// Clock Signal -// -// Created by Thomas Harte on 04/02/2016. -// Copyright © 2016 Thomas Harte. All rights reserved. -// - -#include "CRT.hpp" -#include "CRTOpenGL.hpp" - -using namespace Outputs::CRT; diff --git a/Outputs/CRT/Internals/CRTRunBuilder.hpp b/Outputs/CRT/Internals/CRTRunBuilder.hpp deleted file mode 100644 index 137f4ed85..000000000 --- a/Outputs/CRT/Internals/CRTRunBuilder.hpp +++ /dev/null @@ -1,41 +0,0 @@ -// -// CRTRunBuilder.h -// Clock Signal -// -// Created by Thomas Harte on 08/03/2016. -// Copyright © 2016 Thomas Harte. All rights reserved. -// - -#ifndef CRTRunBuilder_h -#define CRTRunBuilder_h - -#import - -namespace Outputs { -namespace CRT { - -struct CRTRunBuilder { - CRTRunBuilder() : start(0) { reset(); } - - // Resets the run builder. - inline void reset() - { - duration = 0; - amount_of_uploaded_data = 0; - amount_of_data = 0; - } - - // Container for total length in cycles of all contained runs. - uint32_t duration; - size_t start; - - // Storage for the length of run data uploaded so far; reset to zero by reset but otherwise - // entrusted to the CRT to update. - size_t amount_of_uploaded_data; - size_t amount_of_data; -}; - -} -} - -#endif /* CRTRunBuilder_h */ diff --git a/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp b/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp new file mode 100644 index 000000000..faaa8a37c --- /dev/null +++ b/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp @@ -0,0 +1,432 @@ +// +// IntermediateShader.cpp +// Clock Signal +// +// Created by Thomas Harte on 28/04/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "IntermediateShader.hpp" + +#include +#include +#include +#include "../../../../SignalProcessing/FIRFilter.hpp" + +using namespace OpenGL; + +namespace { + const OpenGL::Shader::AttributeBinding bindings[] = + { + {"inputPosition", 0}, + {"outputPosition", 1}, + {"phaseAmplitudeAndOffset", 2}, + {"phaseTime", 3}, + {nullptr} + }; +} + +std::unique_ptr IntermediateShader::make_shader(const char *fragment_shader, bool use_usampler, bool input_is_inputPosition) +{ + const char *sampler_type = use_usampler ? "usampler2D" : "sampler2D"; + const char *input_variable = input_is_inputPosition ? "inputPosition" : "outputPosition"; + + char *vertex_shader; + asprintf(&vertex_shader, + "#version 150\n" + + "in vec2 inputPosition;" + "in vec2 outputPosition;" + "in vec3 phaseAmplitudeAndOffset;" + "in float phaseTime;" + + "uniform float phaseCyclesPerTick;" + "uniform ivec2 outputTextureSize;" + "uniform float extension;" + "uniform %s texID;" + "uniform float offsets[5];" + + "out vec2 phaseAndAmplitudeVarying;" + "out vec2 inputPositionsVarying[11];" + "out vec2 iInputPositionVarying;" + "out vec2 delayLinePositionVarying;" + + "void main(void)" + "{" + "vec2 extensionVector = vec2(extension, 0.0) * 2.0 * (phaseAmplitudeAndOffset.z - 0.5);" + "vec2 extendedInputPosition = %s + extensionVector;" + "vec2 extendedOutputPosition = outputPosition + extensionVector;" + + "vec2 textureSize = vec2(textureSize(texID, 0));" + "iInputPositionVarying = extendedInputPosition;" + "vec2 mappedInputPosition = (extendedInputPosition + vec2(0.0, 0.5)) / textureSize;" + + "inputPositionsVarying[0] = mappedInputPosition - (vec2(offsets[0], 0.0) / textureSize);" + "inputPositionsVarying[1] = mappedInputPosition - (vec2(offsets[1], 0.0) / textureSize);" + "inputPositionsVarying[2] = mappedInputPosition - (vec2(offsets[2], 0.0) / textureSize);" + "inputPositionsVarying[3] = mappedInputPosition - (vec2(offsets[3], 0.0) / textureSize);" + "inputPositionsVarying[4] = mappedInputPosition - (vec2(offsets[4], 0.0) / textureSize);" + "inputPositionsVarying[5] = mappedInputPosition;" + "inputPositionsVarying[6] = mappedInputPosition + (vec2(offsets[4], 0.0) / textureSize);" + "inputPositionsVarying[7] = mappedInputPosition + (vec2(offsets[3], 0.0) / textureSize);" + "inputPositionsVarying[8] = mappedInputPosition + (vec2(offsets[2], 0.0) / textureSize);" + "inputPositionsVarying[9] = mappedInputPosition + (vec2(offsets[1], 0.0) / textureSize);" + "inputPositionsVarying[10] = mappedInputPosition + (vec2(offsets[0], 0.0) / textureSize);" + "delayLinePositionVarying = mappedInputPosition - vec2(0.0, 1.0);" + + "phaseAndAmplitudeVarying.x = (phaseCyclesPerTick * (extendedOutputPosition.x - phaseTime) + phaseAmplitudeAndOffset.x) * 2.0 * 3.141592654;" + "phaseAndAmplitudeVarying.y = 0.33;" + + "vec2 eyePosition = 2.0*(extendedOutputPosition / outputTextureSize) - vec2(1.0) + vec2(0.5)/textureSize;" + "gl_Position = vec4(eyePosition, 0.0, 1.0);" + "}", sampler_type, input_variable); + + std::unique_ptr shader = std::unique_ptr(new IntermediateShader(vertex_shader, fragment_shader, bindings)); + free(vertex_shader); + + shader->texIDUniform = shader->get_uniform_location("texID"); + shader->outputTextureSizeUniform = shader->get_uniform_location("outputTextureSize"); + shader->phaseCyclesPerTickUniform = shader->get_uniform_location("phaseCyclesPerTick"); + shader->extensionUniform = shader->get_uniform_location("extension"); + shader->weightsUniform = shader->get_uniform_location("weights"); + shader->rgbToLumaChromaUniform = shader->get_uniform_location("rgbToLumaChroma"); + shader->lumaChromaToRGBUniform = shader->get_uniform_location("lumaChromaToRGB"); + shader->offsetsUniform = shader->get_uniform_location("offsets"); + + return shader; +} + +std::unique_ptr IntermediateShader::make_source_conversion_shader(const char *composite_shader, const char *rgb_shader) +{ + char *composite_sample = (char *)composite_shader; + if(!composite_sample) + { + asprintf(&composite_sample, + "%s\n" + "uniform mat3 rgbToLumaChroma;" + "float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" + "{" + "vec3 rgbColour = clamp(rgb_sample(texID, coordinate, iCoordinate), vec3(0.0), vec3(1.0));" + "vec3 lumaChromaColour = rgbToLumaChroma * rgbColour;" + "vec2 quadrature = vec2(cos(phase), -sin(phase)) * amplitude;" + "return dot(lumaChromaColour, vec3(1.0 - amplitude, quadrature));" + "}", + rgb_shader); + } + + char *fragment_shader; + asprintf(&fragment_shader, + "#version 150\n" + + "in vec2 inputPositionsVarying[11];" + "in vec2 iInputPositionVarying;" + "in vec2 phaseAndAmplitudeVarying;" + + "out vec4 fragColour;" + + "uniform usampler2D texID;" + + "\n%s\n" + + "void main(void)" + "{" + "fragColour = vec4(composite_sample(texID, inputPositionsVarying[5], iInputPositionVarying, phaseAndAmplitudeVarying.x, phaseAndAmplitudeVarying.y));" + "}" + , composite_sample); + if(!composite_shader) free(composite_sample); + + std::unique_ptr shader = make_shader(fragment_shader, true, true); + free(fragment_shader); + + return shader; +} + +std::unique_ptr IntermediateShader::make_rgb_source_shader(const char *rgb_shader) +{ + char *fragment_shader; + asprintf(&fragment_shader, + "#version 150\n" + + "in vec2 inputPositionsVarying[11];" + "in vec2 iInputPositionVarying;" + "in vec2 phaseAndAmplitudeVarying;" + + "out vec3 fragColour;" + + "uniform usampler2D texID;" + + "\n%s\n" + + "void main(void)" + "{" + "fragColour = rgb_sample(texID, inputPositionsVarying[5], iInputPositionVarying);" + "}" + , rgb_shader); + + std::unique_ptr shader = make_shader(fragment_shader, true, true); + free(fragment_shader); + + return shader; +} + +std::unique_ptr IntermediateShader::make_chroma_luma_separation_shader() +{ + return make_shader( + "#version 150\n" + + "in vec2 phaseAndAmplitudeVarying;" + "in vec2 inputPositionsVarying[11];" + "uniform vec4 weights[3];" + + "out vec3 fragColour;" + + "uniform sampler2D texID;" + + "void main(void)" + "{" + "vec4 samples[3] = vec4[](" + "vec4(" + "texture(texID, inputPositionsVarying[0]).r," + "texture(texID, inputPositionsVarying[1]).r," + "texture(texID, inputPositionsVarying[2]).r," + "texture(texID, inputPositionsVarying[3]).r" + ")," + "vec4(" + "texture(texID, inputPositionsVarying[4]).r," + "texture(texID, inputPositionsVarying[5]).r," + "texture(texID, inputPositionsVarying[6]).r," + "texture(texID, inputPositionsVarying[7]).r" + ")," + "vec4(" + "texture(texID, inputPositionsVarying[8]).r," + "texture(texID, inputPositionsVarying[9]).r," + "texture(texID, inputPositionsVarying[10]).r," + "0.0" + ")" + ");" + + "float luminance = " + "dot(vec3(" + "dot(samples[0], weights[0])," + "dot(samples[1], weights[1])," + "dot(samples[2], weights[2])" + "), vec3(1.0)) / (1.0 - phaseAndAmplitudeVarying.y);" + + "float chrominance = 0.5 * (samples[1].y - luminance) / phaseAndAmplitudeVarying.y;" + "vec2 quadrature = vec2(cos(phaseAndAmplitudeVarying.x), -sin(phaseAndAmplitudeVarying.x));" + + "fragColour = vec3(luminance, vec2(0.5) + (chrominance * quadrature));" + "}",false, false); +} + +std::unique_ptr IntermediateShader::make_chroma_filter_shader() +{ + return make_shader( + "#version 150\n" + + "in vec2 inputPositionsVarying[11];" + "uniform vec4 weights[3];" + + "out vec3 fragColour;" + + "uniform sampler2D texID;" + "uniform mat3 lumaChromaToRGB;" + + "void main(void)" + "{" + "vec3 centreSample = texture(texID, inputPositionsVarying[5]).rgb;" + "vec2 samples[] = vec2[](" + "texture(texID, inputPositionsVarying[0]).gb - vec2(0.5)," + "texture(texID, inputPositionsVarying[1]).gb - vec2(0.5)," + "texture(texID, inputPositionsVarying[2]).gb - vec2(0.5)," + "texture(texID, inputPositionsVarying[3]).gb - vec2(0.5)," + "texture(texID, inputPositionsVarying[4]).gb - vec2(0.5)," + "centreSample.gb - vec2(0.5)," + "texture(texID, inputPositionsVarying[6]).gb - vec2(0.5)," + "texture(texID, inputPositionsVarying[7]).gb - vec2(0.5)," + "texture(texID, inputPositionsVarying[8]).gb - vec2(0.5)," + "texture(texID, inputPositionsVarying[9]).gb - vec2(0.5)," + "texture(texID, inputPositionsVarying[10]).gb - vec2(0.5)" + ");" + + "vec4 channel1[] = vec4[](" + "vec4(samples[0].r, samples[1].r, samples[2].r, samples[3].r)," + "vec4(samples[4].r, samples[5].r, samples[6].r, samples[7].r)," + "vec4(samples[8].r, samples[9].r, samples[10].r, 0.0)" + ");" + "vec4 channel2[] = vec4[](" + "vec4(samples[0].g, samples[1].g, samples[2].g, samples[3].g)," + "vec4(samples[4].g, samples[5].g, samples[6].g, samples[7].g)," + "vec4(samples[8].g, samples[9].g, samples[10].g, 0.0)" + ");" + + "vec3 lumaChromaColour = vec3(centreSample.r," + "dot(vec3(" + "dot(channel1[0], weights[0])," + "dot(channel1[1], weights[1])," + "dot(channel1[2], weights[2])" + "), vec3(1.0)) + 0.5," + "dot(vec3(" + "dot(channel2[0], weights[0])," + "dot(channel2[1], weights[1])," + "dot(channel2[2], weights[2])" + "), vec3(1.0)) + 0.5" + ");" + + "vec3 lumaChromaColourInRange = (lumaChromaColour - vec3(0.0, 0.5, 0.5)) * vec3(1.0, 2.0, 2.0);" + "fragColour = lumaChromaToRGB * lumaChromaColourInRange;" + "}", false, false); +} + +std::unique_ptr IntermediateShader::make_rgb_filter_shader() +{ + return make_shader( + "#version 150\n" + + "in vec2 inputPositionsVarying[11];" + "uniform vec4 weights[3];" + + "out vec3 fragColour;" + + "uniform sampler2D texID;" + + "void main(void)" + "{" + "vec3 samples[] = vec3[](" + "texture(texID, inputPositionsVarying[0]).rgb," + "texture(texID, inputPositionsVarying[1]).rgb," + "texture(texID, inputPositionsVarying[2]).rgb," + "texture(texID, inputPositionsVarying[3]).rgb," + "texture(texID, inputPositionsVarying[4]).rgb," + "texture(texID, inputPositionsVarying[5]).rgb," + "texture(texID, inputPositionsVarying[6]).rgb," + "texture(texID, inputPositionsVarying[7]).rgb," + "texture(texID, inputPositionsVarying[8]).rgb," + "texture(texID, inputPositionsVarying[9]).rgb," + "texture(texID, inputPositionsVarying[10]).rgb" + ");" + + "vec4 channel1[] = vec4[](" + "vec4(samples[0].r, samples[1].r, samples[2].r, samples[3].r)," + "vec4(samples[4].r, samples[5].r, samples[6].r, samples[7].r)," + "vec4(samples[8].r, samples[9].r, samples[10].r, 0.0)" + ");" + "vec4 channel2[] = vec4[](" + "vec4(samples[0].g, samples[1].g, samples[2].g, samples[3].g)," + "vec4(samples[4].g, samples[5].g, samples[6].g, samples[7].g)," + "vec4(samples[8].g, samples[9].g, samples[10].g, 0.0)" + ");" + "vec4 channel3[] = vec4[](" + "vec4(samples[0].b, samples[1].b, samples[2].b, samples[3].b)," + "vec4(samples[4].b, samples[5].b, samples[6].b, samples[7].b)," + "vec4(samples[8].b, samples[9].b, samples[10].b, 0.0)" + ");" + + "fragColour = vec3(" + "dot(vec3(" + "dot(channel1[0], weights[0])," + "dot(channel1[1], weights[1])," + "dot(channel1[2], weights[2])" + "), vec3(1.0))," + "dot(vec3(" + "dot(channel2[0], weights[0])," + "dot(channel2[1], weights[1])," + "dot(channel2[2], weights[2])" + "), vec3(1.0))," + "dot(vec3(" + "dot(channel3[0], weights[0])," + "dot(channel3[1], weights[1])," + "dot(channel3[2], weights[2])" + "), vec3(1.0))" + ");" + "}", false, false); +} + +void IntermediateShader::set_output_size(unsigned int output_width, unsigned int output_height) +{ + bind(); + glUniform2i(outputTextureSizeUniform, (GLint)output_width, (GLint)output_height); +} + +void IntermediateShader::set_source_texture_unit(GLenum unit) +{ + bind(); + glUniform1i(texIDUniform, (GLint)(unit - GL_TEXTURE0)); +} + +void IntermediateShader::set_filter_coefficients(float sampling_rate, float cutoff_frequency) +{ + bind(); + + // The process below: the source texture will have bilinear filtering enabled; so by + // sampling at non-integral offsets from the centre the shader can get a weighted sum + // of two source pixels, then scale that once, to do two taps per sample. However + // that works only if the two coefficients being joined have the same sign. So the + // number of usable taps is between 11 and 21 depending on the values that come out. + // Perform a linear search for the highest number of taps we can use with 11 samples. + float weights[12]; + float offsets[5]; + unsigned int taps = 21; + while(1) + { + float coefficients[21]; + SignalProcessing::FIRFilter luminance_filter(taps, sampling_rate, 0.0f, cutoff_frequency, SignalProcessing::FIRFilter::DefaultAttenuation); + luminance_filter.get_coefficients(coefficients); + + int sample = 0; + int c = 0; + memset(weights, 0, sizeof(float)*12); + memset(offsets, 0, sizeof(float)*5); + + int halfSize = (taps >> 1); + while(c < halfSize && sample < 5) + { + offsets[sample] = (float)(halfSize - c); + if((coefficients[c] < 0.0f) == (coefficients[c+1] < 0.0f) && c+1 < (taps >> 1)) + { + weights[sample] = coefficients[c] + coefficients[c+1]; + offsets[sample] -= (coefficients[c+1] / weights[sample]); + c += 2; + } + else + { + weights[sample] = coefficients[c]; + c++; + } + sample ++; + } + if(c == halfSize) // i.e. we finished combining inputs before we ran out of space + { + weights[sample] = coefficients[c]; + for(int c = 0; c < sample; c++) + { + weights[sample+c+1] = weights[sample-c-1]; + } + break; + } + taps -= 2; + } + + glUniform4fv(weightsUniform, 3, weights); + glUniform1fv(offsetsUniform, 5, offsets); +} + +void IntermediateShader::set_separation_frequency(float sampling_rate, float colour_burst_frequency) +{ + // TODO: apply separately-formed filters for luminance and chrominance + set_filter_coefficients(sampling_rate, colour_burst_frequency - 50.0f); +} + +void IntermediateShader::set_phase_cycles_per_sample(float phase_cycles_per_sample, bool extend_runs_to_full_cycle) +{ + bind(); + glUniform1f(phaseCyclesPerTickUniform, phase_cycles_per_sample); + glUniform1f(extensionUniform, extend_runs_to_full_cycle ? ceilf(1.0f / phase_cycles_per_sample) : 0.0f); +} + +void IntermediateShader::set_colour_conversion_matrices(float *fromRGB, float *toRGB) +{ + bind(); + glUniformMatrix3fv(lumaChromaToRGBUniform, 1, GL_FALSE, toRGB); + glUniformMatrix3fv(rgbToLumaChromaUniform, 1, GL_FALSE, fromRGB); +} diff --git a/Outputs/CRT/Internals/Shaders/IntermediateShader.hpp b/Outputs/CRT/Internals/Shaders/IntermediateShader.hpp new file mode 100644 index 000000000..db4f76199 --- /dev/null +++ b/Outputs/CRT/Internals/Shaders/IntermediateShader.hpp @@ -0,0 +1,99 @@ +// +// IntermediateShader.hpp +// Clock Signal +// +// Created by Thomas Harte on 28/04/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef IntermediateShader_hpp +#define IntermediateShader_hpp + +#include + +#include "Shader.hpp" +#include + +namespace OpenGL { + +class IntermediateShader: public Shader { +public: + using Shader::Shader; + + /*! + Constructs and returns an intermediate shader that will take runs from the inputPositions, + converting them to single-channel composite values using @c composite_shader if supplied + or @c rgb_shader and a reference composite conversion if @c composite_shader is @c nullptr. + */ + static std::unique_ptr make_source_conversion_shader(const char *composite_shader, const char *rgb_shader); + + /*! + Constructs and returns an intermediate shader that will take runs from the inputPositions, + converting them to RGB values using @c rgb_shader. + */ + static std::unique_ptr make_rgb_source_shader(const char *rgb_shader); + + /*! + Constructs and returns an intermediate shader that will read composite samples from the R channel, + filter then to obtain luminance, stored to R, and to separate out unfiltered chrominance, store to G and B. + */ + static std::unique_ptr make_chroma_luma_separation_shader(); + + /*! + Constructs and returns an intermediate shader that will pass R through unchanged while filtering G and B. + */ + static std::unique_ptr make_chroma_filter_shader(); + + /*! + Constructs and returns an intermediate shader that will filter R, G and B. + */ + static std::unique_ptr make_rgb_filter_shader(); + + /*! + Binds this shader and configures it for output to an area of `output_width` and `output_height` pixels. + */ + void set_output_size(unsigned int output_width, unsigned int output_height); + + /*! + Binds this shader and sets the texture unit (as an enum, e.g. `GL_TEXTURE0`) to sample as source data. + */ + void set_source_texture_unit(GLenum unit); + + /*! + Binds this shader and sets filtering coefficients for a lowpass filter based on the cutoff. + */ + void set_filter_coefficients(float sampling_rate, float cutoff_frequency); + + /*! + Binds this shader and configures filtering to separate luminance and chrominance based on a colour + subcarrier of the given frequency. + */ + void set_separation_frequency(float sampling_rate, float colour_burst_frequency); + + /*! + Binds this shader and sets the number of colour phase cycles per sample, indicating whether output + geometry should be extended so that a complete colour cycle is included at both the beginning and end. + */ + void set_phase_cycles_per_sample(float phase_cycles_per_sample, bool extend_runs_to_full_cycle); + + /*! + Binds this shader and sets the matrices that convert between RGB and chrominance/luminance. + */ + void set_colour_conversion_matrices(float *fromRGB, float *toRGB); + +private: + static std::unique_ptr make_shader(const char *fragment_shader, bool use_usampler, bool input_is_inputPosition); + + GLint texIDUniform; + GLint outputTextureSizeUniform; + GLint weightsUniform; + GLint phaseCyclesPerTickUniform; + GLint extensionUniform; + GLint rgbToLumaChromaUniform; + GLint lumaChromaToRGBUniform; + GLint offsetsUniform; +}; + +} + +#endif /* IntermediateShader_hpp */ diff --git a/Outputs/CRT/Internals/Shaders/OutputShader.cpp b/Outputs/CRT/Internals/Shaders/OutputShader.cpp new file mode 100644 index 000000000..7deb4c508 --- /dev/null +++ b/Outputs/CRT/Internals/Shaders/OutputShader.cpp @@ -0,0 +1,126 @@ +// +// OutputShader.cpp +// Clock Signal +// +// Created by Thomas Harte on 27/04/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "OutputShader.hpp" + +#include +#include + +using namespace OpenGL; + +namespace { + const OpenGL::Shader::AttributeBinding bindings[] = + { + {"position", 0}, + {"srcCoordinates", 1}, + {"lateral", 2}, + {nullptr} + }; +} + +std::unique_ptr OutputShader::make_shader(const char *fragment_methods, const char *colour_expression, bool use_usampler) +{ + const char *sampler_type = use_usampler ? "usampler2D" : "sampler2D"; + + char *vertex_shader; + asprintf(&vertex_shader, + "#version 150\n" + + "in vec2 position;" + "in vec2 srcCoordinates;" + "in float lateral;" + + "uniform vec2 boundsOrigin;" + "uniform vec2 boundsSize;" + "uniform vec2 positionConversion;" + "uniform vec2 scanNormal;" + "uniform %s texID;" + + "out float lateralVarying;" + "out vec2 srcCoordinatesVarying;" + "out vec2 iSrcCoordinatesVarying;" + + "void main(void)" + "{" + "lateralVarying = lateral - 0.5;" + + "ivec2 textureSize = textureSize(texID, 0);" + "iSrcCoordinatesVarying = srcCoordinates;" + "srcCoordinatesVarying = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);" + + "vec2 floatingPosition = (position / positionConversion) + lateral * scanNormal;" + "vec2 mappedPosition = (floatingPosition - boundsOrigin) / boundsSize;" + "gl_Position = vec4(mappedPosition.x * 2.0 - 1.0, 1.0 - mappedPosition.y * 2.0, 0.0, 1.0);" + "}", sampler_type); + + char *fragment_shader; + asprintf(&fragment_shader, + "#version 150\n" + + "in float lateralVarying;" + "in vec2 srcCoordinatesVarying;" + "in vec2 iSrcCoordinatesVarying;" + + "out vec4 fragColour;" + + "uniform %s texID;" + + "\n%s\n" + + "void main(void)" + "{" + "fragColour = vec4(%s, 0.5*cos(lateralVarying));" + "}", + sampler_type, fragment_methods, colour_expression); + + std::unique_ptr result = std::unique_ptr(new OutputShader(vertex_shader, fragment_shader, bindings)); + free(vertex_shader); + free(fragment_shader); + + result->boundsSizeUniform = result->get_uniform_location("boundsSize"); + result->boundsOriginUniform = result->get_uniform_location("boundsOrigin"); + result->texIDUniform = result->get_uniform_location("texID"); + result->scanNormalUniform = result->get_uniform_location("scanNormal"); + result->positionConversionUniform = result->get_uniform_location("positionConversion"); + + return result; +} + +void OutputShader::set_output_size(unsigned int output_width, unsigned int output_height, Outputs::CRT::Rect visible_area) +{ + bind(); + + GLfloat outputAspectRatioMultiplier = ((float)output_width / (float)output_height) / (4.0f / 3.0f); + + GLfloat bonusWidth = (outputAspectRatioMultiplier - 1.0f) * visible_area.size.width; + visible_area.origin.x -= bonusWidth * 0.5f * visible_area.size.width; + visible_area.size.width *= outputAspectRatioMultiplier; + + glUniform2f(boundsOriginUniform, (GLfloat)visible_area.origin.x, (GLfloat)visible_area.origin.y); + glUniform2f(boundsSizeUniform, (GLfloat)visible_area.size.width, (GLfloat)visible_area.size.height); +} + +void OutputShader::set_source_texture_unit(GLenum unit) +{ + bind(); + glUniform1i(texIDUniform, (GLint)(unit - GL_TEXTURE0)); +} + +void OutputShader::set_timing(unsigned int height_of_display, unsigned int cycles_per_line, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider) +{ + bind(); + + float scan_angle = atan2f(1.0f / (float)height_of_display, 1.0f); + float scan_normal[] = { -sinf(scan_angle), cosf(scan_angle)}; + 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]); + glUniform2f(positionConversionUniform, horizontal_scan_period, vertical_scan_period / (unsigned int)vertical_period_divider); +} diff --git a/Outputs/CRT/Internals/Shaders/OutputShader.hpp b/Outputs/CRT/Internals/Shaders/OutputShader.hpp new file mode 100644 index 000000000..855069e2c --- /dev/null +++ b/Outputs/CRT/Internals/Shaders/OutputShader.hpp @@ -0,0 +1,66 @@ +// +// OutputShader.hpp +// Clock Signal +// +// Created by Thomas Harte on 27/04/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef OutputShader_hpp +#define OutputShader_hpp + +#include "Shader.hpp" +#include "../../CRTTypes.hpp" +#include + +namespace OpenGL { + +class OutputShader: public Shader { +public: + /*! + Constructs and returns an instance of OutputShader. OutputShaders are intended to read source data + from a texture and draw a single raster scan containing that data as output. + + Does not catch any of the exceptions potentially thrown by `Shader::Shader`. + + All instances of OutputShader are guaranteed to use the same attribute locations for their inputs. + + @param fragment_methods A block of code that will appear within the global area of the fragment shader. + + @param colour_expression An expression that should evaluate to a `vec3` indicating the colour at the current location. The + decision should be a function of the uniform `texID`, which will be either a `usampler2D` or a `sampler2D` as per the + `use_usampler` parameter, and the inputs `srcCoordinatesVarying` which is a location within the texture from which to + take the source value, and `iSrcCoordinatesVarying` which is a value proportional to `srcCoordinatesVarying` but scaled + so that one unit equals one source sample. + + @param use_usampler Dictates the type of the `texID` uniform; will be a `usampler2D` if this parameter is `true`, a + `sampler2D` otherwise. + + @returns an instance of OutputShader. + */ + static std::unique_ptr make_shader(const char *fragment_methods, const char *colour_expression, bool use_usampler); + using Shader::Shader; + + /*! + Binds this shader and configures it for output to an area of `output_width` and `output_height` pixels, ensuring + the largest possible drawing size that allows everything within `visible_area` to be visible. + */ + void set_output_size(unsigned int output_width, unsigned int output_height, Outputs::CRT::Rect visible_area); + + /*! + Binds this shader and sets the texture unit (as an enum, e.g. `GL_TEXTURE0`) to sample as source data. + */ + void set_source_texture_unit(GLenum unit); + + /*! + Binds this shader and configures its understanding of how to map from the source vertex stream to screen coordinates. + */ + void set_timing(unsigned int height_of_display, unsigned int cycles_per_line, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider); + + private: + GLint boundsOriginUniform, boundsSizeUniform, texIDUniform, scanNormalUniform, positionConversionUniform; +}; + +} + +#endif /* OutputShader_hpp */ diff --git a/Outputs/CRT/Internals/Shader.cpp b/Outputs/CRT/Internals/Shaders/Shader.cpp similarity index 90% rename from Outputs/CRT/Internals/Shader.cpp rename to Outputs/CRT/Internals/Shaders/Shader.cpp index 8d8a01677..cfb69a48d 100644 --- a/Outputs/CRT/Internals/Shader.cpp +++ b/Outputs/CRT/Internals/Shaders/Shader.cpp @@ -13,6 +13,10 @@ using namespace OpenGL; +namespace { + Shader *bound_shader = nullptr; +} + GLuint Shader::compile_shader(const char *source, GLenum type) { GLuint shader = glCreateShader(type); @@ -80,12 +84,23 @@ Shader::Shader(const char *vertex_shader, const char *fragment_shader, const Att Shader::~Shader() { + if(bound_shader == this) Shader::unbind(); glDeleteProgram(_shader_program); } void Shader::bind() { - glUseProgram(_shader_program); + if(bound_shader != this) + { + glUseProgram(_shader_program); + bound_shader = this; + } +} + +void Shader::unbind() +{ + bound_shader = nullptr; + glUseProgram(0); } GLint Shader::get_attrib_location(const GLchar *name) diff --git a/Outputs/CRT/Internals/Shader.hpp b/Outputs/CRT/Internals/Shaders/Shader.hpp similarity index 86% rename from Outputs/CRT/Internals/Shader.hpp rename to Outputs/CRT/Internals/Shaders/Shader.hpp index 5a4dc4117..42ca1a099 100644 --- a/Outputs/CRT/Internals/Shader.hpp +++ b/Outputs/CRT/Internals/Shaders/Shader.hpp @@ -41,10 +41,17 @@ public: ~Shader(); /*! - Performs an @c glUseProgram to make this the active shader. + Performs an @c glUseProgram to make this the active shader unless: + (i) it was the previous shader bound; and + (ii) no calls have been received to unbind in the interim. */ void bind(); + /*! + Unbinds the current instance of Shader, if one is bound. + */ + static void unbind(); + /*! Performs a @c glGetAttribLocation call. @param name The name of the attribute to locate. diff --git a/Outputs/CRT/Internals/TextureTarget.cpp b/Outputs/CRT/Internals/TextureTarget.cpp index a57eaa26c..2ea700a6f 100644 --- a/Outputs/CRT/Internals/TextureTarget.cpp +++ b/Outputs/CRT/Internals/TextureTarget.cpp @@ -7,19 +7,31 @@ // #include "TextureTarget.hpp" +#include using namespace OpenGL; -TextureTarget::TextureTarget(GLsizei width, GLsizei height) : _width(width), _height(height) +TextureTarget::TextureTarget(GLsizei width, GLsizei height, GLenum texture_unit) : + _width(width), + _height(height), + _pixel_shader(nullptr), + _drawing_vertex_array(0), + _drawing_array_buffer(0), + _set_aspect_ratio(0.0f), + _texture_unit(texture_unit) { glGenFramebuffers(1, &_framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer); + _expanded_width = 1 << (GLsizei)ceil(log2(width)); + _expanded_height = 1 << (GLsizei)ceil(log2(height)); + glGenTextures(1, &_texture); + glActiveTexture(texture_unit); glBindTexture(GL_TEXTURE_2D, _texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)width, (GLsizei)height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)_expanded_width, (GLsizei)_expanded_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0); @@ -31,6 +43,8 @@ TextureTarget::~TextureTarget() { glDeleteFramebuffers(1, &_framebuffer); glDeleteTextures(1, &_texture); + if(_drawing_vertex_array) glDeleteVertexArrays(1, &_drawing_vertex_array); + if(_drawing_array_buffer) glDeleteBuffers(1, &_drawing_array_buffer); } void TextureTarget::bind_framebuffer() @@ -43,3 +57,88 @@ void TextureTarget::bind_texture() { glBindTexture(GL_TEXTURE_2D, _texture); } + +void TextureTarget::draw(float aspect_ratio) +{ + if(!_pixel_shader) + { + const char *vertex_shader = + "#version 150\n" + + "in vec2 texCoord;" + "in vec2 position;" + + "out vec2 texCoordVarying;" + + "void main(void)" + "{" + "texCoordVarying = texCoord;" + "gl_Position = vec4(position, 0.0, 1.0);" + "}"; + const char *fragment_shader = + "#version 150\n" + + "in vec2 texCoordVarying;" + "uniform sampler2D texID;" + "out vec4 fragColour;" + + "void main(void)" + "{" + "fragColour = texture(texID, texCoordVarying);" + "}"; + _pixel_shader = std::unique_ptr(new Shader(vertex_shader, fragment_shader, nullptr)); + _pixel_shader->bind(); + + glGenVertexArrays(1, &_drawing_vertex_array); + glGenBuffers(1, &_drawing_array_buffer); + + glBindVertexArray(_drawing_vertex_array); + glBindBuffer(GL_ARRAY_BUFFER, _drawing_array_buffer); + + GLint positionAttribute = _pixel_shader->get_attrib_location("position"); + GLint texCoordAttribute = _pixel_shader->get_attrib_location("texCoord"); + + glEnableVertexAttribArray((GLuint)positionAttribute); + glEnableVertexAttribArray((GLuint)texCoordAttribute); + + const GLsizei vertexStride = 4 * sizeof(GLfloat); + glVertexAttribPointer((GLuint)positionAttribute, 2, GL_FLOAT, GL_FALSE, vertexStride, (void *)0); + glVertexAttribPointer((GLuint)texCoordAttribute, 2, GL_FLOAT, GL_FALSE, vertexStride, (void *)(2 * sizeof(GLfloat))); + + GLint texIDUniform = _pixel_shader->get_uniform_location("texID"); + glUniform1i(texIDUniform, (GLint)(_texture_unit - GL_TEXTURE0)); + } + + if(_set_aspect_ratio != aspect_ratio) + { + _set_aspect_ratio = aspect_ratio; + float buffer[4*4]; + + // establish texture coordinates + buffer[2] = 0.0f; + buffer[3] = 0.0f; + buffer[6] = 0.0f; + buffer[7] = (float)_height / (float)_expanded_height; + buffer[10] = (float)_width / (float)_expanded_width; + buffer[11] = 0; + buffer[14] = buffer[10]; + buffer[15] = buffer[7]; + + // determine positions; rule is to keep the same height and centre + float internal_aspect_ratio = (float)_width / (float)_height; + float aspect_ratio_ratio = internal_aspect_ratio / aspect_ratio; + + buffer[0] = -aspect_ratio_ratio; buffer[1] = -1.0f; + buffer[4] = -aspect_ratio_ratio; buffer[5] = 1.0f; + buffer[8] = aspect_ratio_ratio; buffer[9] = -1.0f; + buffer[12] = aspect_ratio_ratio; buffer[13] = 1.0f; + + // upload buffer + glBindBuffer(GL_ARRAY_BUFFER, _drawing_array_buffer); + glBufferData(GL_ARRAY_BUFFER, sizeof(buffer), buffer, GL_STATIC_DRAW); + } + + _pixel_shader->bind(); + glBindVertexArray(_drawing_vertex_array); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); +} diff --git a/Outputs/CRT/Internals/TextureTarget.hpp b/Outputs/CRT/Internals/TextureTarget.hpp index 471caae4b..7e4b9dfef 100644 --- a/Outputs/CRT/Internals/TextureTarget.hpp +++ b/Outputs/CRT/Internals/TextureTarget.hpp @@ -10,6 +10,8 @@ #define TextureTarget_hpp #include "OpenGL.hpp" +#include "Shaders/Shader.hpp" +#include namespace OpenGL { @@ -22,12 +24,13 @@ class TextureTarget { /*! Creates a new texture target. - Throws ErrorFramebufferIncomplete if creation fails. + Throws ErrorFramebufferIncomplete if creation fails. Leaves both the generated texture and framebuffer bound. @param width The width of target to create. @param height The height of target to create. + @param texture_unit A texture unit on which to bind the texture. */ - TextureTarget(GLsizei width, GLsizei height); + TextureTarget(GLsizei width, GLsizei height, GLenum texture_unit); ~TextureTarget(); /*! @@ -40,6 +43,27 @@ class TextureTarget { */ void bind_texture(); + /*! + @returns the width of the texture target. + */ + GLsizei get_width() + { + return _width; + } + + /*! + @returns the height of the texture target. + */ + GLsizei get_height() + { + return _height; + } + + /*! + + */ + void draw(float aspect_ratio); + enum { ErrorFramebufferIncomplete }; @@ -47,6 +71,12 @@ class TextureTarget { private: GLuint _framebuffer, _texture; GLsizei _width, _height; + GLsizei _expanded_width, _expanded_height; + GLenum _texture_unit; + + std::unique_ptr _pixel_shader; + GLuint _drawing_vertex_array, _drawing_array_buffer; + float _set_aspect_ratio; }; }