From 416c944f0209052511fc238fb084ea3a196df7f0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 26 Apr 2016 21:41:39 -0400 Subject: [PATCH 01/24] Experimental: let's try accumulating history directly in the framebuffer. --- .../Clock Signal.xcodeproj/project.pbxproj | 6 -- Outputs/CRT/CRT.cpp | 15 ++-- Outputs/CRT/Internals/CRTOpenGL.cpp | 78 ++++++------------- Outputs/CRT/Internals/CRTOpenGL.hpp | 39 ++-------- Outputs/CRT/Internals/CRTRunBuilder.cpp | 12 --- Outputs/CRT/Internals/CRTRunBuilder.hpp | 41 ---------- 6 files changed, 38 insertions(+), 153 deletions(-) delete mode 100644 Outputs/CRT/Internals/CRTRunBuilder.cpp delete mode 100644 Outputs/CRT/Internals/CRTRunBuilder.hpp diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index ca2fdabaf..a66cae767 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -305,7 +305,6 @@ 4BB73EC21B587A5100552FC2 /* Clock_SignalUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EC11B587A5100552FC2 /* Clock_SignalUITests.swift */; }; 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 */; }; 4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; }; @@ -660,8 +659,6 @@ 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 = ""; }; @@ -1210,8 +1207,6 @@ 4BBF99091C8FBA6F0075DAFB /* CRTInputBufferBuilder.hpp */, 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */, 4BBF990B1C8FBA6F0075DAFB /* CRTOpenGL.hpp */, - 4BBF990C1C8FBA6F0075DAFB /* CRTRunBuilder.cpp */, - 4BBF990D1C8FBA6F0075DAFB /* CRTRunBuilder.hpp */, 4BBF990E1C8FBA6F0075DAFB /* Flywheel.hpp */, 4BBF990F1C8FBA6F0075DAFB /* OpenGL.hpp */, 4BBF99101C8FBA6F0075DAFB /* Shader.cpp */, @@ -1653,7 +1648,6 @@ 4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */, 4B55CE591C3B7D360093A61B /* ElectronDocument.swift in Sources */, 4B55CE4B1C3B3B0C0093A61B /* CSAtari2600.mm in Sources */, - 4BBF99161C8FBA6F0075DAFB /* CRTRunBuilder.cpp in Sources */, 4B55CE581C3B7D360093A61B /* Atari2600Document.swift in Sources */, 4B0EBFB81C487F2F00A11F35 /* AudioQueue.m in Sources */, 4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */, diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index c01587273..db6e745f7 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; @@ -149,14 +150,14 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi // 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_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(); +// 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 { @@ -175,7 +176,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi // decrement the number of cycles left to run for and increment the // horizontal counter appropriately number_of_cycles -= next_run_length; - _openGL_output_builder->add_to_field_time(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) @@ -197,7 +198,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi // 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_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; _openGL_output_builder->complete_output_run(6); @@ -227,13 +228,13 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi 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_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(); +// 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; @@ -258,7 +259,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi } } - _openGL_output_builder->increment_field(); +// _openGL_output_builder->increment_field(); } } } diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 5ebbc25d5..0400f8da6 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -36,8 +36,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) @@ -74,7 +80,6 @@ namespace { } 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,18 +90,14 @@ 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) { - _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); + glBlendColor(1.0f, 1.0f, 1.0f, 0.5f); // Create intermediate textures and bind to slots 0, 1 and 2 glActiveTexture(composite_texture_unit); @@ -156,12 +157,6 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : 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); @@ -203,11 +198,7 @@ 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); @@ -242,8 +233,8 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out { // 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); + 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; @@ -324,23 +315,17 @@ void OpenGLOutputBuilder::perform_output_stage(unsigned int output_width, unsign if(shader) { // clear the buffer - glClear(GL_COLOR_BUFFER_BIT); +// 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; - } + // 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); - if(count > 0) + _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); @@ -352,18 +337,9 @@ void OpenGLOutputBuilder::perform_output_stage(unsigned int output_width, unsign 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) + for(int c = 0; c < number_of_drawing_zones; c++) { - 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); + glDrawArrays(GL_TRIANGLE_STRIP, drawing_zones[c*2] / OutputVertexSize, drawing_zones[c*2 + 1] / OutputVertexSize); } } } @@ -673,7 +649,7 @@ char *OpenGLOutputBuilder::get_output_vertex_shader(const char *header) "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);" + "alpha = 0.5;"//15.0*exp(-age*3.0);" "vec2 floatingPosition = (position / positionConversion) + lateralAndTimestampBaseOffset.x * scanNormal;" "vec2 mappedPosition = (floatingPosition - boundsOrigin) / boundsSize;" @@ -884,12 +860,6 @@ 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; } } diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index fc5144770..8d8d00ee8 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -15,7 +15,6 @@ #include "TextureTarget.hpp" #include "Shader.hpp" #include "CRTInputBufferBuilder.hpp" -#include "CRTRunBuilder.hpp" #include @@ -58,8 +57,6 @@ class OpenGLOutputBuilder { // 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 @@ -132,13 +129,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 +143,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 +153,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(); @@ -217,12 +189,13 @@ class OpenGLOutputBuilder { 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 */ From f6d58f1ce7fe13bc2728bae0012ec6b884cac5cd Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 26 Apr 2016 22:14:12 -0400 Subject: [PATCH 02/24] Played in the margins with image retention. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 0400f8da6..fc9b8d98f 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -96,8 +96,8 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : { _buffer_builder = std::unique_ptr(new CRTInputBufferBuilder(buffer_depth)); - glBlendFunc(GL_SRC_ALPHA, GL_CONSTANT_ALPHA); - glBlendColor(1.0f, 1.0f, 1.0f, 0.5f); + glBlendFunc(GL_SRC_ALPHA, GL_CONSTANT_COLOR); + glBlendColor(0.4f, 0.4f, 0.4f, 0.5f); // Create intermediate textures and bind to slots 0, 1 and 2 glActiveTexture(composite_texture_unit); @@ -205,7 +205,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // 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, @@ -215,7 +215,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, @@ -649,7 +649,7 @@ char *OpenGLOutputBuilder::get_output_vertex_shader(const char *header) "iSrcCoordinatesVarying = srcCoordinates;" "srcCoordinatesVarying = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);" "float age = (timestampBase[int(lateralAndTimestampBaseOffset.y)] - timestamp) / ticksPerFrame;" - "alpha = 0.5;"//15.0*exp(-age*3.0);" + "alpha = 0.6;"//15.0*exp(-age*3.0);" "vec2 floatingPosition = (position / positionConversion) + lateralAndTimestampBaseOffset.x * scanNormal;" "vec2 mappedPosition = (floatingPosition - boundsOrigin) / boundsSize;" From 496d979aca5839947590015c6170b6d3b62f7d18 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 27 Apr 2016 22:29:54 -0400 Subject: [PATCH 03/24] Made a decision to clean up by creating per-shader classes. Well, more or less. First thing to be factored out: the output shader. --- .../Clock Signal.xcodeproj/project.pbxproj | 26 ++- Outputs/CRT/CRT.cpp | 3 - Outputs/CRT/Internals/CRTConstants.hpp | 8 +- Outputs/CRT/Internals/CRTOpenGL.cpp | 160 ++---------------- Outputs/CRT/Internals/CRTOpenGL.hpp | 11 +- .../CRT/Internals/Shaders/OutputShader.cpp | 103 +++++++++++ .../CRT/Internals/Shaders/OutputShader.hpp | 59 +++++++ .../CRT/Internals/{ => Shaders}/Shader.cpp | 0 .../CRT/Internals/{ => Shaders}/Shader.hpp | 0 9 files changed, 206 insertions(+), 164 deletions(-) create mode 100644 Outputs/CRT/Internals/Shaders/OutputShader.cpp create mode 100644 Outputs/CRT/Internals/Shaders/OutputShader.hpp rename Outputs/CRT/Internals/{ => Shaders}/Shader.cpp (100%) rename Outputs/CRT/Internals/{ => Shaders}/Shader.hpp (100%) diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index a66cae767..6285d78f8 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -305,8 +305,9 @@ 4BB73EC21B587A5100552FC2 /* Clock_SignalUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EC11B587A5100552FC2 /* Clock_SignalUITests.swift */; }; 4BBF99141C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99081C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp */; }; 4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.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 */; }; @@ -661,11 +662,13 @@ 4BBF990B1C8FBA6F0075DAFB /* CRTOpenGL.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTOpenGL.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; }; @@ -1203,14 +1206,13 @@ 4BBF99071C8FBA6F0075DAFB /* Internals */ = { isa = PBXGroup; children = ( + 4BC3B74C1CD194CC00F86E85 /* Shaders */, 4BBF99081C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp */, 4BBF99091C8FBA6F0075DAFB /* CRTInputBufferBuilder.hpp */, 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */, 4BBF990B1C8FBA6F0075DAFB /* CRTOpenGL.hpp */, 4BBF990E1C8FBA6F0075DAFB /* Flywheel.hpp */, 4BBF990F1C8FBA6F0075DAFB /* OpenGL.hpp */, - 4BBF99101C8FBA6F0075DAFB /* Shader.cpp */, - 4BBF99111C8FBA6F0075DAFB /* Shader.hpp */, 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */, 4BBF99131C8FBA6F0075DAFB /* TextureTarget.hpp */, 4B0B6E121C9DBD5D00FFB60D /* CRTConstants.hpp */, @@ -1218,6 +1220,17 @@ path = Internals; sourceTree = ""; }; + 4BC3B74C1CD194CC00F86E85 /* Shaders */ = { + isa = PBXGroup; + children = ( + 4BC3B74D1CD194CC00F86E85 /* Shader.cpp */, + 4BC3B74E1CD194CC00F86E85 /* Shader.hpp */, + 4BC3B7501CD1956900F86E85 /* OutputShader.cpp */, + 4BC3B7511CD1956900F86E85 /* OutputShader.hpp */, + ); + path = Shaders; + sourceTree = ""; + }; 4BE5F85A1C3E1C2500C43F01 /* Resources */ = { isa = PBXGroup; children = ( @@ -1647,6 +1660,7 @@ 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 */, 4B55CE581C3B7D360093A61B /* Atari2600Document.swift in Sources */, 4B0EBFB81C487F2F00A11F35 /* AudioQueue.m in Sources */, @@ -1658,10 +1672,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/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index db6e745f7..fc1e6ccd3 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -100,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]) @@ -269,7 +267,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..7df8f20d6 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,7 +41,7 @@ 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 diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index fc9b8d98f..795e662a3 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) { @@ -310,7 +311,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out _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) { @@ -331,10 +332,9 @@ void OpenGLOutputBuilder::perform_output_stage(unsigned int output_width, unsign // 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); + // update uniforms (implicitly binding the shader) + shader->set_output_size(output_width, output_height, _visible_area); // draw for(int c = 0; c < number_of_drawing_zones; c++) @@ -349,28 +349,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); @@ -602,72 +580,6 @@ char *OpenGLOutputBuilder::get_chrominance_filter_fragment_shader() #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 = 0.6;"//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() @@ -703,20 +615,17 @@ char *OpenGLOutputBuilder::get_output_fragment_shader(const char *sampling_funct "#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));" + "fragColour = vec4(colour, 0.5*cos(lateralVarying));" "}", header, sampling_function, fragColour_function); @@ -786,46 +695,24 @@ void OpenGLOutputBuilder::prepare_source_vertex_array() } } -std::unique_ptr OpenGLOutputBuilder::prepare_output_shader(char *vertex_shader, char *fragment_shader, GLint source_texture_unit) +std::unique_ptr OpenGLOutputBuilder::prepare_output_shader(char *fragment_shader, bool use_usampler, GLenum source_texture_unit) { - std::unique_ptr shader_program; + 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); + shader_program = OpenGL::OutputShader::make_shader(fragment_shader, use_usampler); + shader_program->set_source_texture_unit(source_texture_unit); 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); + rgb_shader_program = prepare_output_shader(get_rgb_output_fragment_shader(), true, 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); + composite_output_shader_program = prepare_output_shader(get_composite_output_fragment_shader(), false, filtered_texture_unit); } void OpenGLOutputBuilder::prepare_output_vertex_array() @@ -834,22 +721,19 @@ void OpenGLOutputBuilder::prepare_output_vertex_array() { 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 lateralAttribute = rgb_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); } } @@ -947,7 +831,7 @@ void OpenGLOutputBuilder::set_timing_uniforms() extends = true; } - OpenGL::Shader *output_shaders[] = { + OpenGL::OutputShader *output_shaders[] = { rgb_shader_program.get(), composite_output_shader_program.get() }; @@ -955,21 +839,7 @@ void OpenGLOutputBuilder::set_timing_uniforms() { 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); + output_shaders[c]->set_timing(_height_of_display, _cycles_per_line, _horizontal_scan_period, _vertical_scan_period, _vertical_period_divider); } } diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 8d8d00ee8..d5de3fd4a 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -16,6 +16,8 @@ #include "Shader.hpp" #include "CRTInputBufferBuilder.hpp" +#include "Shaders/OutputShader.hpp" + #include namespace Outputs { @@ -46,14 +48,13 @@ class OpenGLOutputBuilder { // 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); + std::unique_ptr prepare_output_shader(char *fragment_shader, bool use_usampler, GLenum 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_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; @@ -76,8 +77,8 @@ class OpenGLOutputBuilder { 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 rgb_shader_program, composite_output_shader_program; + std::unique_ptr composite_input_shader_program, composite_y_filter_shader_program, composite_chrominance_filter_shader_program; GLuint output_array_buffer, output_vertex_array; GLuint source_array_buffer, source_vertex_array; @@ -93,7 +94,7 @@ 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); + 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(); diff --git a/Outputs/CRT/Internals/Shaders/OutputShader.cpp b/Outputs/CRT/Internals/Shaders/OutputShader.cpp new file mode 100644 index 000000000..0d5ac8ad9 --- /dev/null +++ b/Outputs/CRT/Internals/Shaders/OutputShader.cpp @@ -0,0 +1,103 @@ +// +// 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_shader, bool use_usampler) +{ + 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);" + "}", use_usampler ? "usampler2D" : "sampler2D"); + + std::unique_ptr result = std::unique_ptr(new OutputShader(vertex_shader, fragment_shader, bindings)); + free(vertex_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..95c17cd08 --- /dev/null +++ b/Outputs/CRT/Internals/Shaders/OutputShader.hpp @@ -0,0 +1,59 @@ +// +// 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. + + The fragment shader should expect to receive the inputs: + + in float lateralVarying; + in vec2 srcCoordinatesVarying; + in vec2 iSrcCoordinatesVarying; + + If `use_usampler` is `true` then a `uniform usampler2D texID` will be used as the source texture. + Otherwise it'll be a `sampler2D`. + + `lateralVarying` is a value in radians that is equal to 0 at the centre of the scan and ± a suitable + angle at the top and bottom extremes. + + `srcCoordinatesVarying` is a value representing the coordinates for source data in the texture + attached to the sampler `texID`. + + `iSrcCoordinatesVarying` is a value corresponding to `srcCoordinatesVarying` but scaled up so that + one unit is the width of one source sample. + + Does not catch any exceptions raised by `Shader::Shader`. + + @returns an instance of OutputShader. + */ + static std::unique_ptr make_shader(const char *fragment_shader, bool use_usampler); + using Shader::Shader; + + void set_output_size(unsigned int output_width, unsigned int output_height, Outputs::CRT::Rect visible_area); + void set_source_texture_unit(GLenum unit); + 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 100% rename from Outputs/CRT/Internals/Shader.cpp rename to Outputs/CRT/Internals/Shaders/Shader.cpp diff --git a/Outputs/CRT/Internals/Shader.hpp b/Outputs/CRT/Internals/Shaders/Shader.hpp similarity index 100% rename from Outputs/CRT/Internals/Shader.hpp rename to Outputs/CRT/Internals/Shaders/Shader.hpp From 8849dc3265e95c2181a7b06a55576861754bbd51 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 27 Apr 2016 22:41:32 -0400 Subject: [PATCH 04/24] Finished consolidation into OutputShader. With its documentation yet to update. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 78 ++++--------------- Outputs/CRT/Internals/CRTOpenGL.hpp | 8 -- .../CRT/Internals/Shaders/OutputShader.cpp | 27 ++++++- .../CRT/Internals/Shaders/OutputShader.hpp | 2 +- 4 files changed, 40 insertions(+), 75 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 795e662a3..ecd37b814 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -580,58 +580,6 @@ char *OpenGLOutputBuilder::get_chrominance_filter_fragment_shader() #pragma mark - Intermediate vertex shaders (i.e. from intermediate line layout to intermediate line layout) -#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 vec2 srcCoordinatesVarying;" - "in vec2 iSrcCoordinatesVarying;" - - "out vec4 fragColour;" - - "%s\n" - "%s\n" - "void main(void)" - "{" - "\n%s\n" - "fragColour = vec4(colour, 0.5*cos(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) @@ -695,24 +643,26 @@ void OpenGLOutputBuilder::prepare_source_vertex_array() } } -std::unique_ptr OpenGLOutputBuilder::prepare_output_shader(char *fragment_shader, bool use_usampler, GLenum source_texture_unit) -{ - std::unique_ptr shader_program; - - shader_program = OpenGL::OutputShader::make_shader(fragment_shader, use_usampler); - shader_program->set_source_texture_unit(source_texture_unit); - - return shader_program; -} - void OpenGLOutputBuilder::prepare_rgb_output_shader() { - rgb_shader_program = prepare_output_shader(get_rgb_output_fragment_shader(), true, source_data_texture_unit); + 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);" + "}"; + } + + rgb_shader_program = OpenGL::OutputShader::make_shader(rgb_shader, "rgb_sample(texID, srcCoordinatesVarying, iSrcCoordinatesVarying)", true); + rgb_shader_program->set_source_texture_unit(source_data_texture_unit); } void OpenGLOutputBuilder::prepare_composite_output_shader() { - composite_output_shader_program = prepare_output_shader(get_composite_output_fragment_shader(), false, filtered_texture_unit); + composite_output_shader_program = OpenGL::OutputShader::make_shader("", "texture(texID, srcCoordinatesVarying).rgb", false); + composite_output_shader_program->set_source_texture_unit(filtered_texture_unit); } void OpenGLOutputBuilder::prepare_output_vertex_array() diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index d5de3fd4a..870595d31 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -63,14 +63,6 @@ class OpenGLOutputBuilder { // 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(); diff --git a/Outputs/CRT/Internals/Shaders/OutputShader.cpp b/Outputs/CRT/Internals/Shaders/OutputShader.cpp index 0d5ac8ad9..399ec934f 100644 --- a/Outputs/CRT/Internals/Shaders/OutputShader.cpp +++ b/Outputs/CRT/Internals/Shaders/OutputShader.cpp @@ -23,8 +23,10 @@ namespace { }; } -std::unique_ptr OutputShader::make_shader(const char *fragment_shader, bool use_usampler) +std::unique_ptr OutputShader::make_shader(const char *fragment_methods, const char *output_colour, bool use_usampler) { + const char *sampler_type = use_usampler ? "usampler2D" : "sampler2D"; + char *vertex_shader; asprintf(&vertex_shader, "#version 150\n" @@ -54,10 +56,31 @@ std::unique_ptr OutputShader::make_shader(const char *fragment_sha "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);" - "}", use_usampler ? "usampler2D" : "sampler2D"); + "}", 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.6*cos(lateralVarying));" + "}", + sampler_type, fragment_methods, output_colour); 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"); diff --git a/Outputs/CRT/Internals/Shaders/OutputShader.hpp b/Outputs/CRT/Internals/Shaders/OutputShader.hpp index 95c17cd08..2498aaa78 100644 --- a/Outputs/CRT/Internals/Shaders/OutputShader.hpp +++ b/Outputs/CRT/Internals/Shaders/OutputShader.hpp @@ -43,7 +43,7 @@ public: @returns an instance of OutputShader. */ - static std::unique_ptr make_shader(const char *fragment_shader, bool use_usampler); + static std::unique_ptr make_shader(const char *fragment_methods, const char *output_colour, bool use_usampler); using Shader::Shader; void set_output_size(unsigned int output_width, unsigned int output_height, Outputs::CRT::Rect visible_area); From 7c20fce467a350895f6bb0324b561d28f331d6d8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 27 Apr 2016 22:48:37 -0400 Subject: [PATCH 05/24] I guess NumberOfFields is done for. --- Outputs/CRT/Internals/CRTConstants.hpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Outputs/CRT/Internals/CRTConstants.hpp b/Outputs/CRT/Internals/CRTConstants.hpp index 7df8f20d6..63cfe99ed 100644 --- a/Outputs/CRT/Internals/CRTConstants.hpp +++ b/Outputs/CRT/Internals/CRTConstants.hpp @@ -44,13 +44,6 @@ const GLsizei IntermediateBufferHeight = 2048; 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; - } } From 494ef6a324f08011f9ca0d95bffd787fa445b42a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 27 Apr 2016 22:57:40 -0400 Subject: [PATCH 06/24] Updated documentation, taking the opportunity to improve a parameter name. --- .../CRT/Internals/Shaders/OutputShader.cpp | 2 +- .../CRT/Internals/Shaders/OutputShader.hpp | 39 +++++++++++-------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/Outputs/CRT/Internals/Shaders/OutputShader.cpp b/Outputs/CRT/Internals/Shaders/OutputShader.cpp index 399ec934f..b382f0d5d 100644 --- a/Outputs/CRT/Internals/Shaders/OutputShader.cpp +++ b/Outputs/CRT/Internals/Shaders/OutputShader.cpp @@ -23,7 +23,7 @@ namespace { }; } -std::unique_ptr OutputShader::make_shader(const char *fragment_methods, const char *output_colour, bool use_usampler) +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"; diff --git a/Outputs/CRT/Internals/Shaders/OutputShader.hpp b/Outputs/CRT/Internals/Shaders/OutputShader.hpp index 2498aaa78..855069e2c 100644 --- a/Outputs/CRT/Internals/Shaders/OutputShader.hpp +++ b/Outputs/CRT/Internals/Shaders/OutputShader.hpp @@ -21,33 +21,40 @@ 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. - The fragment shader should expect to receive the inputs: + Does not catch any of the exceptions potentially thrown by `Shader::Shader`. - in float lateralVarying; - in vec2 srcCoordinatesVarying; - in vec2 iSrcCoordinatesVarying; + All instances of OutputShader are guaranteed to use the same attribute locations for their inputs. - If `use_usampler` is `true` then a `uniform usampler2D texID` will be used as the source texture. - Otherwise it'll be a `sampler2D`. + @param fragment_methods A block of code that will appear within the global area of the fragment shader. - `lateralVarying` is a value in radians that is equal to 0 at the centre of the scan and ± a suitable - angle at the top and bottom extremes. + @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. - `srcCoordinatesVarying` is a value representing the coordinates for source data in the texture - attached to the sampler `texID`. - - `iSrcCoordinatesVarying` is a value corresponding to `srcCoordinatesVarying` but scaled up so that - one unit is the width of one source sample. - - Does not catch any exceptions raised by `Shader::Shader`. + @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 *output_colour, bool use_usampler); + 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: From 87c778ebb7b4375821ed3eb8ebba382f8a491538 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 27 Apr 2016 22:58:53 -0400 Subject: [PATCH 07/24] Fixed code use of parameter name. --- Outputs/CRT/Internals/Shaders/OutputShader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Outputs/CRT/Internals/Shaders/OutputShader.cpp b/Outputs/CRT/Internals/Shaders/OutputShader.cpp index b382f0d5d..ae77e0853 100644 --- a/Outputs/CRT/Internals/Shaders/OutputShader.cpp +++ b/Outputs/CRT/Internals/Shaders/OutputShader.cpp @@ -76,7 +76,7 @@ std::unique_ptr OutputShader::make_shader(const char *fragment_met "{" "fragColour = vec4(%s, 0.6*cos(lateralVarying));" "}", - sampler_type, fragment_methods, output_colour); + sampler_type, fragment_methods, colour_expression); std::unique_ptr result = std::unique_ptr(new OutputShader(vertex_shader, fragment_shader, bindings)); free(vertex_shader); From ef3647f6cfe84e8b8da75fe614bcbf366891d93b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 27 Apr 2016 23:00:43 -0400 Subject: [PATCH 08/24] Being bold: per-scan timing isn't coming back, I think, so started completely removing it. --- Outputs/CRT/CRT.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index fc1e6ccd3..269b1c023 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -148,14 +148,12 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi // 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 { @@ -174,7 +172,6 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi // decrement the number of cycles left to run for and increment the // horizontal counter appropriately number_of_cycles -= next_run_length; -// _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) @@ -196,7 +193,6 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi // 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; _openGL_output_builder->complete_output_run(6); @@ -226,13 +222,11 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi 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; @@ -256,8 +250,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(); } } } From 8538b62ea2c26efcca6357cb6bce256ba281d64a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 28 Apr 2016 21:04:59 -0400 Subject: [PATCH 09/24] Made an attempt to extract and consolidate the logic behind the intermediate shaders. --- .../Clock Signal.xcodeproj/project.pbxproj | 6 + .../Internals/Shaders/IntermediateShader.cpp | 285 ++++++++++++++++++ .../Internals/Shaders/IntermediateShader.hpp | 58 ++++ 3 files changed, 349 insertions(+) create mode 100644 Outputs/CRT/Internals/Shaders/IntermediateShader.cpp create mode 100644 Outputs/CRT/Internals/Shaders/IntermediateShader.hpp diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 6285d78f8..5b2fa6348 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -303,6 +303,7 @@ 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 */; }; 4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */; }; @@ -656,6 +657,8 @@ 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 = ""; }; @@ -1227,6 +1230,8 @@ 4BC3B74E1CD194CC00F86E85 /* Shader.hpp */, 4BC3B7501CD1956900F86E85 /* OutputShader.cpp */, 4BC3B7511CD1956900F86E85 /* OutputShader.hpp */, + 4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */, + 4BBB14301CD2CECE00BDB55C /* IntermediateShader.hpp */, ); path = Shaders; sourceTree = ""; @@ -1664,6 +1669,7 @@ 4B55CE4B1C3B3B0C0093A61B /* CSAtari2600.mm 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 */, diff --git a/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp b/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp new file mode 100644 index 000000000..0bab2a785 --- /dev/null +++ b/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp @@ -0,0 +1,285 @@ +// +// 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;" + + "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(10.0, 0.0) / textureSize);" + "inputPositionsVarying[1] = mappedInputPosition - (vec2(8.0, 0.0) / textureSize);" + "inputPositionsVarying[2] = mappedInputPosition - (vec2(6.0, 0.0) / textureSize);" + "inputPositionsVarying[3] = mappedInputPosition - (vec2(4.0, 0.0) / textureSize);" + "inputPositionsVarying[4] = mappedInputPosition - (vec2(2.0, 0.0) / textureSize);" + "inputPositionsVarying[5] = mappedInputPosition;" + "inputPositionsVarying[6] = mappedInputPosition + (vec2(2.0, 0.0) / textureSize);" + "inputPositionsVarying[7] = mappedInputPosition + (vec2(4.0, 0.0) / textureSize);" + "inputPositionsVarying[8] = mappedInputPosition + (vec2(6.0, 0.0) / textureSize);" + "inputPositionsVarying[9] = mappedInputPosition + (vec2(8.0, 0.0) / textureSize);" + "inputPositionsVarying[10] = mappedInputPosition + (vec2(10.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"); + + 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_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); +} + +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(); + + float weights[12]; + SignalProcessing::FIRFilter luminance_filter(11, sampling_rate, 0.0f, cutoff_frequency, SignalProcessing::FIRFilter::DefaultAttenuation); + luminance_filter.get_coefficients(weights); + glUniform4fv(weightsUniform, 3, weights); +} + +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..732207905 --- /dev/null +++ b/Outputs/CRT/Internals/Shaders/IntermediateShader.hpp @@ -0,0 +1,58 @@ +// +// 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; + + static std::unique_ptr make_source_conversion_shader(const char *composite_shader, const char *rgb_shader); + static std::unique_ptr make_chroma_luma_separation_shader(); + static std::unique_ptr make_chroma_filter_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); + + /*! + 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); + + void set_filter_coefficients(float sampling_rate, float cutoff_frequency); + + void set_phase_cycles_per_sample(float phase_cycles_per_sample, bool extend_runs_to_full_cycle); + + 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; +}; + +} + +#endif /* IntermediateShader_hpp */ From e6c3b148acb35bb4720fb5c2a365bb3ffbb3bafd Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 28 Apr 2016 21:45:44 -0400 Subject: [PATCH 10/24] Switched wholeheartedly to the new shader. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 326 +----------------- Outputs/CRT/Internals/CRTOpenGL.hpp | 6 +- .../Internals/Shaders/IntermediateShader.cpp | 4 + .../Internals/Shaders/IntermediateShader.hpp | 13 +- 4 files changed, 36 insertions(+), 313 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index ecd37b814..cd436368d 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -359,263 +359,21 @@ 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 - Program compilation -std::unique_ptr OpenGLOutputBuilder::prepare_intermediate_shader(const char *input_position, const char *header, char *fragment_shader, GLenum texture_unit, bool extends) -{ - 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)); - - GLint texIDUniform = shader->get_uniform_location("texID"); - GLint outputTextureSizeUniform = shader->get_uniform_location("outputTextureSize"); - - shader->bind(); - glUniform1i(texIDUniform, (GLint)(texture_unit - GL_TEXTURE0)); - glUniform2i(outputTextureSizeUniform, IntermediateBufferWidth, IntermediateBufferHeight); - } - free(vertex_shader); - free(fragment_shader); - - return shader; -} - void OpenGLOutputBuilder::prepare_composite_input_shader() { - 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); + 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); + + 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); + + 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_source_vertex_array() @@ -735,49 +493,24 @@ 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; } @@ -787,32 +520,11 @@ void OpenGLOutputBuilder::set_timing_uniforms() }; for(int c = 0; c < 2; c++) { - if(output_shaders[c]) - { - output_shaders[c]->set_timing(_height_of_display, _cycles_per_line, _horizontal_scan_period, _vertical_scan_period, _vertical_period_divider); - } + if(output_shaders[c]) output_shaders[c]->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) - { - 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); - } + if(composite_y_filter_shader_program) composite_y_filter_shader_program->set_filter_coefficients(_cycles_per_line, colour_subcarrier_frequency * 0.5f); + if(composite_chrominance_filter_shader_program) composite_chrominance_filter_shader_program->set_filter_coefficients(_cycles_per_line, colour_subcarrier_frequency * 0.5f); _output_mutex->unlock(); } diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 870595d31..3ad4b59fa 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -17,6 +17,7 @@ #include "CRTInputBufferBuilder.hpp" #include "Shaders/OutputShader.hpp" +#include "Shaders/IntermediateShader.hpp" #include @@ -70,14 +71,11 @@ class OpenGLOutputBuilder { char *get_chrominance_filter_fragment_shader(); std::unique_ptr rgb_shader_program, composite_output_shader_program; - std::unique_ptr composite_input_shader_program, composite_y_filter_shader_program, composite_chrominance_filter_shader_program; + std::unique_ptr composite_input_shader_program, composite_y_filter_shader_program, composite_chrominance_filter_shader_program; GLuint output_array_buffer, output_vertex_array; GLuint source_array_buffer, source_vertex_array; - GLint windowSizeUniform, timestampBaseUniform; - GLint boundsOriginUniform, boundsSizeUniform; - GLuint textureName, shadowMaskTextureName; GLuint defaultFramebuffer; diff --git a/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp b/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp index 0bab2a785..e7e4153f2 100644 --- a/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp +++ b/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp @@ -264,7 +264,11 @@ void IntermediateShader::set_filter_coefficients(float sampling_rate, float cuto { bind(); + sampling_rate *= 0.5f; + cutoff_frequency *= 0.5f; + float weights[12]; + weights[11] = 0.0f; SignalProcessing::FIRFilter luminance_filter(11, sampling_rate, 0.0f, cutoff_frequency, SignalProcessing::FIRFilter::DefaultAttenuation); luminance_filter.get_coefficients(weights); glUniform4fv(weightsUniform, 3, weights); diff --git a/Outputs/CRT/Internals/Shaders/IntermediateShader.hpp b/Outputs/CRT/Internals/Shaders/IntermediateShader.hpp index 732207905..dd959f590 100644 --- a/Outputs/CRT/Internals/Shaders/IntermediateShader.hpp +++ b/Outputs/CRT/Internals/Shaders/IntermediateShader.hpp @@ -25,8 +25,7 @@ public: static std::unique_ptr make_chroma_filter_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. + 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); @@ -35,10 +34,20 @@ public: */ 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 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: From 3638414b37b4ee65a1fcb2295f8be9eed6729c13 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 28 Apr 2016 21:55:14 -0400 Subject: [PATCH 11/24] Added additional comments. --- .../CRT/Internals/Shaders/IntermediateShader.hpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Outputs/CRT/Internals/Shaders/IntermediateShader.hpp b/Outputs/CRT/Internals/Shaders/IntermediateShader.hpp index dd959f590..a955045ae 100644 --- a/Outputs/CRT/Internals/Shaders/IntermediateShader.hpp +++ b/Outputs/CRT/Internals/Shaders/IntermediateShader.hpp @@ -20,8 +20,22 @@ 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 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(); /*! From 23ea832f41e11e47c58c6392936e0deef7618775 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 28 Apr 2016 22:04:47 -0400 Subject: [PATCH 12/24] Increased brightness in television mode, added a clear if screen resolution changes. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 19 ++++++++++++++----- Outputs/CRT/Internals/CRTOpenGL.hpp | 11 ++--------- .../CRT/Internals/Shaders/OutputShader.cpp | 2 +- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index cd436368d..be274da01 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -93,7 +93,9 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : _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) { _buffer_builder = std::unique_ptr(new CRTInputBufferBuilder(buffer_depth)); @@ -315,9 +317,6 @@ void OpenGLOutputBuilder::perform_output_stage(unsigned int output_width, unsign { if(shader) { - // clear the buffer -// glClear(GL_COLOR_BUFFER_BIT); - // 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); @@ -334,7 +333,15 @@ void OpenGLOutputBuilder::perform_output_stage(unsigned int output_width, unsign glBindVertexArray(output_vertex_array); // update uniforms (implicitly binding the shader) - shader->set_output_size(output_width, output_height, _visible_area); + if(_last_output_width != output_width || _last_output_height != output_height) + { + glClear(GL_COLOR_BUFFER_BIT); + shader->set_output_size(output_width, output_height, _visible_area); + _last_output_width = output_width; + _last_output_height = output_height; + } + else + shader->bind(); // draw for(int c = 0; c < number_of_drawing_zones; c++) @@ -453,6 +460,8 @@ void OpenGLOutputBuilder::set_output_device(OutputDevice output_device) { _output_device = output_device; _composite_src_output_y = 0; + _last_output_width = 0; + _last_output_height = 0; } } diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 3ad4b59fa..5cdbfec77 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -49,10 +49,7 @@ class OpenGLOutputBuilder { // Methods used by the OpenGL code void prepare_rgb_output_shader(); void prepare_composite_output_shader(); - std::unique_ptr prepare_output_shader(char *fragment_shader, bool use_usampler, GLenum 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_vertex_array(); void prepare_source_vertex_array(); @@ -64,18 +61,14 @@ class OpenGLOutputBuilder { // transient buffers indicating composite data not yet decoded uint16_t _composite_src_output_y, _cleared_composite_output_y; - 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, composite_output_shader_program; std::unique_ptr composite_input_shader_program, composite_y_filter_shader_program, composite_chrominance_filter_shader_program; GLuint output_array_buffer, output_vertex_array; GLuint source_array_buffer, source_vertex_array; + unsigned int _last_output_width, _last_output_height; + GLuint textureName, shadowMaskTextureName; GLuint defaultFramebuffer; diff --git a/Outputs/CRT/Internals/Shaders/OutputShader.cpp b/Outputs/CRT/Internals/Shaders/OutputShader.cpp index ae77e0853..149701e9e 100644 --- a/Outputs/CRT/Internals/Shaders/OutputShader.cpp +++ b/Outputs/CRT/Internals/Shaders/OutputShader.cpp @@ -74,7 +74,7 @@ std::unique_ptr OutputShader::make_shader(const char *fragment_met "void main(void)" "{" - "fragColour = vec4(%s, 0.6*cos(lateralVarying));" + "fragColour = vec4(%s, 0.7*cos(lateralVarying));" "}", sampler_type, fragment_methods, colour_expression); From 501ab74eafa0df9d040fb02aa4a5db5696c13bad Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 28 Apr 2016 22:12:59 -0400 Subject: [PATCH 13/24] Fixed time multiplier calculation. --- Outputs/CRT/CRT.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 269b1c023..40047e56f 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -28,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; From 16ef09fc2bc5c204def3e685eb0b1458d1168e89 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 29 Apr 2016 18:37:35 -0400 Subject: [PATCH 14/24] Made an attempt intelligently to use bilinear filtering to increase filtering quality with the same number of samples. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 2 +- .../Internals/Shaders/IntermediateShader.cpp | 74 +++++++++++++++---- .../Internals/Shaders/IntermediateShader.hpp | 1 + Outputs/CRT/Internals/TextureTarget.cpp | 4 +- 4 files changed, 62 insertions(+), 19 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index be274da01..260a1d0c5 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -533,7 +533,7 @@ void OpenGLOutputBuilder::set_timing_uniforms() } float colour_subcarrier_frequency = (float)_colour_cycle_numerator / (float)_colour_cycle_denominator; - if(composite_y_filter_shader_program) composite_y_filter_shader_program->set_filter_coefficients(_cycles_per_line, colour_subcarrier_frequency * 0.5f); + if(composite_y_filter_shader_program) composite_y_filter_shader_program->set_filter_coefficients(_cycles_per_line, colour_subcarrier_frequency - 90.0f); if(composite_chrominance_filter_shader_program) composite_chrominance_filter_shader_program->set_filter_coefficients(_cycles_per_line, colour_subcarrier_frequency * 0.5f); _output_mutex->unlock(); } diff --git a/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp b/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp index e7e4153f2..006ca60be 100644 --- a/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp +++ b/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp @@ -44,6 +44,7 @@ std::unique_ptr IntermediateShader::make_shader(const char * "uniform ivec2 outputTextureSize;" "uniform float extension;" "uniform %s texID;" + "uniform float offsets[5];" "out vec2 phaseAndAmplitudeVarying;" "out vec2 inputPositionsVarying[11];" @@ -60,17 +61,17 @@ std::unique_ptr IntermediateShader::make_shader(const char * "iInputPositionVarying = extendedInputPosition;" "vec2 mappedInputPosition = (extendedInputPosition + vec2(0.0, 0.5)) / textureSize;" - "inputPositionsVarying[0] = mappedInputPosition - (vec2(10.0, 0.0) / textureSize);" - "inputPositionsVarying[1] = mappedInputPosition - (vec2(8.0, 0.0) / textureSize);" - "inputPositionsVarying[2] = mappedInputPosition - (vec2(6.0, 0.0) / textureSize);" - "inputPositionsVarying[3] = mappedInputPosition - (vec2(4.0, 0.0) / textureSize);" - "inputPositionsVarying[4] = mappedInputPosition - (vec2(2.0, 0.0) / textureSize);" + "inputPositionsVarying[0] = mappedInputPosition - (vec2(offsets[4], 0.0) / textureSize);" + "inputPositionsVarying[1] = mappedInputPosition - (vec2(offsets[3], 0.0) / textureSize);" + "inputPositionsVarying[2] = mappedInputPosition - (vec2(offsets[2], 0.0) / textureSize);" + "inputPositionsVarying[3] = mappedInputPosition - (vec2(offsets[1], 0.0) / textureSize);" + "inputPositionsVarying[4] = mappedInputPosition - (vec2(offsets[0], 0.0) / textureSize);" "inputPositionsVarying[5] = mappedInputPosition;" - "inputPositionsVarying[6] = mappedInputPosition + (vec2(2.0, 0.0) / textureSize);" - "inputPositionsVarying[7] = mappedInputPosition + (vec2(4.0, 0.0) / textureSize);" - "inputPositionsVarying[8] = mappedInputPosition + (vec2(6.0, 0.0) / textureSize);" - "inputPositionsVarying[9] = mappedInputPosition + (vec2(8.0, 0.0) / textureSize);" - "inputPositionsVarying[10] = mappedInputPosition + (vec2(10.0, 0.0) / textureSize);" + "inputPositionsVarying[6] = mappedInputPosition + (vec2(offsets[0], 0.0) / textureSize);" + "inputPositionsVarying[7] = mappedInputPosition + (vec2(offsets[1], 0.0) / textureSize);" + "inputPositionsVarying[8] = mappedInputPosition + (vec2(offsets[2], 0.0) / textureSize);" + "inputPositionsVarying[9] = mappedInputPosition + (vec2(offsets[3], 0.0) / textureSize);" + "inputPositionsVarying[10] = mappedInputPosition + (vec2(offsets[4], 0.0) / textureSize);" "delayLinePositionVarying = mappedInputPosition - vec2(0.0, 1.0);" "phaseAndAmplitudeVarying.x = (phaseCyclesPerTick * (extendedOutputPosition.x - phaseTime) + phaseAmplitudeAndOffset.x) * 2.0 * 3.141592654;" @@ -90,6 +91,7 @@ std::unique_ptr IntermediateShader::make_shader(const char * 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; } @@ -264,14 +266,54 @@ void IntermediateShader::set_filter_coefficients(float sampling_rate, float cuto { bind(); - sampling_rate *= 0.5f; - cutoff_frequency *= 0.5f; - + // 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]; - weights[11] = 0.0f; - SignalProcessing::FIRFilter luminance_filter(11, sampling_rate, 0.0f, cutoff_frequency, SignalProcessing::FIRFilter::DefaultAttenuation); - luminance_filter.get_coefficients(weights); + 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); + while(c < (taps >> 1) && sample < 5) + { + if((coefficients[c] < 0.0f) == (coefficients[c+1] < 0.0f) && c+1 < (taps >> 1)) + { + weights[sample] = coefficients[c] + coefficients[c+1]; + offsets[sample] = (float)c + 1.0f + (coefficients[c+1] / weights[sample]); + c += 2; + } + else + { + offsets[sample] = (float)c + 1.0f; + weights[sample] = coefficients[c]; + c++; + } + sample ++; + } + if(c == (taps >> 1)) + { + 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_phase_cycles_per_sample(float phase_cycles_per_sample, bool extend_runs_to_full_cycle) diff --git a/Outputs/CRT/Internals/Shaders/IntermediateShader.hpp b/Outputs/CRT/Internals/Shaders/IntermediateShader.hpp index a955045ae..8b75a51df 100644 --- a/Outputs/CRT/Internals/Shaders/IntermediateShader.hpp +++ b/Outputs/CRT/Internals/Shaders/IntermediateShader.hpp @@ -74,6 +74,7 @@ private: GLint extensionUniform; GLint rgbToLumaChromaUniform; GLint lumaChromaToRGBUniform; + GLint offsetsUniform; }; } diff --git a/Outputs/CRT/Internals/TextureTarget.cpp b/Outputs/CRT/Internals/TextureTarget.cpp index a57eaa26c..f575b3d23 100644 --- a/Outputs/CRT/Internals/TextureTarget.cpp +++ b/Outputs/CRT/Internals/TextureTarget.cpp @@ -18,8 +18,8 @@ TextureTarget::TextureTarget(GLsizei width, GLsizei height) : _width(width), _he glGenTextures(1, &_texture); 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); + 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); From fe2abbd4edf2a1db8b30a7983729d74bef466452 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 30 Apr 2016 20:48:09 -0400 Subject: [PATCH 15/24] Minor adjustments to ensure sampling occurs as intended. --- .../Internals/Shaders/IntermediateShader.cpp | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp b/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp index 006ca60be..51deed3e1 100644 --- a/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp +++ b/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp @@ -61,17 +61,17 @@ std::unique_ptr IntermediateShader::make_shader(const char * "iInputPositionVarying = extendedInputPosition;" "vec2 mappedInputPosition = (extendedInputPosition + vec2(0.0, 0.5)) / textureSize;" - "inputPositionsVarying[0] = mappedInputPosition - (vec2(offsets[4], 0.0) / textureSize);" - "inputPositionsVarying[1] = mappedInputPosition - (vec2(offsets[3], 0.0) / 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[1], 0.0) / textureSize);" - "inputPositionsVarying[4] = mappedInputPosition - (vec2(offsets[0], 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[0], 0.0) / textureSize);" - "inputPositionsVarying[7] = mappedInputPosition + (vec2(offsets[1], 0.0) / textureSize);" + "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[3], 0.0) / textureSize);" - "inputPositionsVarying[10] = mappedInputPosition + (vec2(offsets[4], 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;" @@ -284,23 +284,26 @@ void IntermediateShader::set_filter_coefficients(float sampling_rate, float cuto int sample = 0; int c = 0; memset(weights, 0, sizeof(float)*12); - while(c < (taps >> 1) && sample < 5) + 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] = (float)c + 1.0f + (coefficients[c+1] / weights[sample]); + offsets[sample] -= (coefficients[c+1] / weights[sample]); c += 2; } else { - offsets[sample] = (float)c + 1.0f; weights[sample] = coefficients[c]; c++; } sample ++; } - if(c == (taps >> 1)) + 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++) From 2616d748feaa12bfd37706d6aa7d69343e0630e0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 1 May 2016 11:07:49 -0400 Subject: [PATCH 16/24] Introduced bind-if-necessary/unbind semantics for shaders. --- Outputs/CRT/Internals/Shaders/Shader.cpp | 18 +++++++++++++++++- Outputs/CRT/Internals/Shaders/Shader.hpp | 9 ++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/Outputs/CRT/Internals/Shaders/Shader.cpp b/Outputs/CRT/Internals/Shaders/Shader.cpp index 8d8a01677..4b76d3627 100644 --- a/Outputs/CRT/Internals/Shaders/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,24 @@ 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; + } + else printf("-"); +} + +void Shader::unbind() +{ + bound_shader = nullptr; + glUseProgram(0); } GLint Shader::get_attrib_location(const GLchar *name) diff --git a/Outputs/CRT/Internals/Shaders/Shader.hpp b/Outputs/CRT/Internals/Shaders/Shader.hpp index 5a4dc4117..42ca1a099 100644 --- a/Outputs/CRT/Internals/Shaders/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. From 4ec4cceb84ac7a25248ba17c778a08c9e4dc1203 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 1 May 2016 13:52:35 -0400 Subject: [PATCH 17/24] Made a first attempt at adding draw-as-a-quad functionality to texture targets. --- Outputs/CRT/Internals/TextureTarget.cpp | 99 ++++++++++++++++++++++++- Outputs/CRT/Internals/TextureTarget.hpp | 11 +++ 2 files changed, 109 insertions(+), 1 deletion(-) diff --git a/Outputs/CRT/Internals/TextureTarget.cpp b/Outputs/CRT/Internals/TextureTarget.cpp index f575b3d23..f63dbbbec 100644 --- a/Outputs/CRT/Internals/TextureTarget.cpp +++ b/Outputs/CRT/Internals/TextureTarget.cpp @@ -10,7 +10,13 @@ using namespace OpenGL; -TextureTarget::TextureTarget(GLsizei width, GLsizei height) : _width(width), _height(height) +TextureTarget::TextureTarget(GLsizei width, GLsizei height) : + _width(width), + _height(height), + _pixel_shader(nullptr), + _drawing_vertex_array(0), + _drawing_array_buffer(0), + _set_aspect_ratio(0.0f) { glGenFramebuffers(1, &_framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer); @@ -31,6 +37,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 +51,92 @@ void TextureTarget::bind_texture() { glBindTexture(GL_TEXTURE_2D, _texture); } + +void TextureTarget::draw(float aspect_ratio, GLenum texture_unit) +{ + 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)); + + glGenVertexArrays(1, &_drawing_vertex_array); + glGenBuffers(1, &_drawing_array_buffer); + + glBindVertexArray(_drawing_array_buffer); + glBindBuffer(GL_ARRAY_BUFFER, _drawing_vertex_array); + + 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 = 12; + glVertexAttribPointer((GLuint)positionAttribute, 2, GL_FLOAT, GL_FALSE, vertexStride, (void *)0); + glVertexAttribPointer((GLuint)texCoordAttribute, 2, GL_BYTE, GL_FALSE, vertexStride, (void *)(2 * sizeof(GLfloat))); + } + + if(_set_aspect_ratio != aspect_ratio) + { + aspect_ratio = _set_aspect_ratio; + int8_t buffer[12*4]; + + // establish texture coordinates + buffer[8] = 0; buffer[9] = 0; + buffer[20] = 0; buffer[21] = 1; + buffer[32] = 1; buffer[33] = 0; + buffer[44] = 1; buffer[45] = 1; + + // determine positions + float internal_aspect_ratio = (float)_width / (float)_height; + float aspect_ratio_ratio = internal_aspect_ratio / aspect_ratio; + float *fl_buffer = (float *)buffer; + if(aspect_ratio_ratio >= 1.0f) + { + // output is thinner than we are; letterbox + fl_buffer[0] = -1.0f; fl_buffer[1] = 1.0f / aspect_ratio_ratio; + fl_buffer[3] = -1.0f; fl_buffer[4] = -1.0f / aspect_ratio_ratio; + fl_buffer[6] = 1.0f; fl_buffer[7] = 1.0f / aspect_ratio_ratio; + fl_buffer[9] = 1.0f; fl_buffer[10] = -1.0f / aspect_ratio_ratio; + } + else + { + // output is wider than we are; pillarbox + fl_buffer[0] = -aspect_ratio_ratio; fl_buffer[1] = 1.0f; + fl_buffer[3] = -aspect_ratio_ratio; fl_buffer[4] = -1.0f; + fl_buffer[6] = aspect_ratio_ratio; fl_buffer[7] = 1.0f; + fl_buffer[9] = aspect_ratio_ratio; fl_buffer[10] = -1.0f; + } + + // upload buffer + glBindBuffer(GL_ARRAY_BUFFER, _drawing_vertex_array); + glBufferData(GL_ARRAY_BUFFER, sizeof(buffer), buffer, GL_STATIC_DRAW); + } + + 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..d00d878e9 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 { @@ -40,6 +42,11 @@ class TextureTarget { */ void bind_texture(); + /*! + + */ + void draw(float aspect_ratio, GLenum texture_unit); + enum { ErrorFramebufferIncomplete }; @@ -47,6 +54,10 @@ class TextureTarget { private: GLuint _framebuffer, _texture; GLsizei _width, _height; + + std::unique_ptr _pixel_shader; + GLuint _drawing_vertex_array, _drawing_array_buffer; + float _set_aspect_ratio; }; } From 6982e945fba8e7fe4fb77812c12e49d96be813aa Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 1 May 2016 16:17:52 -0400 Subject: [PATCH 18/24] Fixed those issues breaking GL state, I hope. --- .../Mac/Clock Signal/Wrappers/CSElectron.mm | 20 ++++++--- Outputs/CRT/Internals/CRTOpenGL.cpp | 44 ++++++++++++------- Outputs/CRT/Internals/CRTOpenGL.hpp | 2 + Outputs/CRT/Internals/Shaders/Shader.cpp | 1 - Outputs/CRT/Internals/TextureTarget.cpp | 23 +++++++--- Outputs/CRT/Internals/TextureTarget.hpp | 25 +++++++++-- 6 files changed, 80 insertions(+), 35 deletions(-) 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/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 260a1d0c5..ddefdcfa2 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -78,6 +78,7 @@ 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) : @@ -103,15 +104,9 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : glBlendColor(0.4f, 0.4f, 0.4f, 0.5f); // 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); @@ -153,9 +148,6 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : // 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); } OpenGLOutputBuilder::~OpenGLOutputBuilder() @@ -196,6 +188,21 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out defaultFramebuffer = 0; } + // 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(); + framebuffer->draw((float)output_width / (float)output_height); + } + framebuffer = std::move(new_framebuffer); + glActiveTexture(source_data_texture_unit); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, _input_texture_array); + } + // lock down any further work on the current frame _output_mutex->lock(); @@ -288,11 +295,6 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out 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); } // transfer to screen @@ -317,6 +319,9 @@ void OpenGLOutputBuilder::perform_output_stage(unsigned int output_width, unsign { if(shader) { + // bind the target + 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); @@ -349,6 +354,11 @@ void OpenGLOutputBuilder::perform_output_stage(unsigned int output_width, unsign glDrawArrays(GL_TRIANGLE_STRIP, drawing_zones[c*2] / OutputVertexSize, drawing_zones[c*2 + 1] / OutputVertexSize); } } + + // copy to the intended place + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glClear(GL_COLOR_BUFFER_BIT); + framebuffer->draw((float)output_width / (float)output_height); } } diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 5cdbfec77..b3ae9939b 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -77,6 +77,8 @@ 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 + 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(); diff --git a/Outputs/CRT/Internals/Shaders/Shader.cpp b/Outputs/CRT/Internals/Shaders/Shader.cpp index 4b76d3627..cfb69a48d 100644 --- a/Outputs/CRT/Internals/Shaders/Shader.cpp +++ b/Outputs/CRT/Internals/Shaders/Shader.cpp @@ -95,7 +95,6 @@ void Shader::bind() glUseProgram(_shader_program); bound_shader = this; } - else printf("-"); } void Shader::unbind() diff --git a/Outputs/CRT/Internals/TextureTarget.cpp b/Outputs/CRT/Internals/TextureTarget.cpp index f63dbbbec..603bc9821 100644 --- a/Outputs/CRT/Internals/TextureTarget.cpp +++ b/Outputs/CRT/Internals/TextureTarget.cpp @@ -7,23 +7,29 @@ // #include "TextureTarget.hpp" +#include using namespace OpenGL; -TextureTarget::TextureTarget(GLsizei width, GLsizei 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) + _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); + 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); @@ -52,7 +58,7 @@ void TextureTarget::bind_texture() glBindTexture(GL_TEXTURE_2D, _texture); } -void TextureTarget::draw(float aspect_ratio, GLenum texture_unit) +void TextureTarget::draw(float aspect_ratio) { if(!_pixel_shader) { @@ -86,8 +92,8 @@ void TextureTarget::draw(float aspect_ratio, GLenum texture_unit) glGenVertexArrays(1, &_drawing_vertex_array); glGenBuffers(1, &_drawing_array_buffer); - glBindVertexArray(_drawing_array_buffer); - glBindBuffer(GL_ARRAY_BUFFER, _drawing_vertex_array); + 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"); @@ -98,6 +104,9 @@ void TextureTarget::draw(float aspect_ratio, GLenum texture_unit) const GLsizei vertexStride = 12; glVertexAttribPointer((GLuint)positionAttribute, 2, GL_FLOAT, GL_FALSE, vertexStride, (void *)0); glVertexAttribPointer((GLuint)texCoordAttribute, 2, GL_BYTE, 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) @@ -133,7 +142,7 @@ void TextureTarget::draw(float aspect_ratio, GLenum texture_unit) } // upload buffer - glBindBuffer(GL_ARRAY_BUFFER, _drawing_vertex_array); + glBindBuffer(GL_ARRAY_BUFFER, _drawing_array_buffer); glBufferData(GL_ARRAY_BUFFER, sizeof(buffer), buffer, GL_STATIC_DRAW); } diff --git a/Outputs/CRT/Internals/TextureTarget.hpp b/Outputs/CRT/Internals/TextureTarget.hpp index d00d878e9..7e4b9dfef 100644 --- a/Outputs/CRT/Internals/TextureTarget.hpp +++ b/Outputs/CRT/Internals/TextureTarget.hpp @@ -24,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(); /*! @@ -42,10 +43,26 @@ 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, GLenum texture_unit); + void draw(float aspect_ratio); enum { ErrorFramebufferIncomplete @@ -54,6 +71,8 @@ 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; From 69984b54e5d3776c3f2ca1b20ab038fd6561399f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 1 May 2016 16:49:44 -0400 Subject: [PATCH 19/24] Fixed aspect ratio storage. Adjusted indentation slightly. --- Outputs/CRT/Internals/TextureTarget.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Outputs/CRT/Internals/TextureTarget.cpp b/Outputs/CRT/Internals/TextureTarget.cpp index 603bc9821..9391dbfc1 100644 --- a/Outputs/CRT/Internals/TextureTarget.cpp +++ b/Outputs/CRT/Internals/TextureTarget.cpp @@ -85,7 +85,7 @@ void TextureTarget::draw(float aspect_ratio) "void main(void)" "{" - "fragColour = texture(texID, texCoordVarying);" + "fragColour = vec4(0.5);"//texture(texID, texCoordVarying);" "}"; _pixel_shader = std::unique_ptr(new Shader(vertex_shader, fragment_shader, nullptr)); @@ -111,7 +111,7 @@ void TextureTarget::draw(float aspect_ratio) if(_set_aspect_ratio != aspect_ratio) { - aspect_ratio = _set_aspect_ratio; + _set_aspect_ratio = aspect_ratio; int8_t buffer[12*4]; // establish texture coordinates @@ -127,18 +127,18 @@ void TextureTarget::draw(float aspect_ratio) if(aspect_ratio_ratio >= 1.0f) { // output is thinner than we are; letterbox - fl_buffer[0] = -1.0f; fl_buffer[1] = 1.0f / aspect_ratio_ratio; - fl_buffer[3] = -1.0f; fl_buffer[4] = -1.0f / aspect_ratio_ratio; - fl_buffer[6] = 1.0f; fl_buffer[7] = 1.0f / aspect_ratio_ratio; - fl_buffer[9] = 1.0f; fl_buffer[10] = -1.0f / aspect_ratio_ratio; + fl_buffer[0] = -1.0f; fl_buffer[1] = 1.0f / aspect_ratio_ratio; + fl_buffer[3] = -1.0f; fl_buffer[4] = -1.0f / aspect_ratio_ratio; + fl_buffer[6] = 1.0f; fl_buffer[7] = 1.0f / aspect_ratio_ratio; + fl_buffer[9] = 1.0f; fl_buffer[10] = -1.0f / aspect_ratio_ratio; } else { // output is wider than we are; pillarbox - fl_buffer[0] = -aspect_ratio_ratio; fl_buffer[1] = 1.0f; - fl_buffer[3] = -aspect_ratio_ratio; fl_buffer[4] = -1.0f; - fl_buffer[6] = aspect_ratio_ratio; fl_buffer[7] = 1.0f; - fl_buffer[9] = aspect_ratio_ratio; fl_buffer[10] = -1.0f; + fl_buffer[0] = -aspect_ratio_ratio; fl_buffer[1] = 1.0f; + fl_buffer[3] = -aspect_ratio_ratio; fl_buffer[4] = -1.0f; + fl_buffer[6] = aspect_ratio_ratio; fl_buffer[7] = 1.0f; + fl_buffer[9] = aspect_ratio_ratio; fl_buffer[10] = -1.0f; } // upload buffer From 1b6754c5f8424edef3cb73c6f5fdd406a20eceee Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 1 May 2016 19:22:24 -0400 Subject: [PATCH 20/24] Shuffled code, realised I wasn't actually binding my shader. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 20 ++++++++++---------- Outputs/CRT/Internals/TextureTarget.cpp | 20 ++++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index ddefdcfa2..baf99f97b 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -188,6 +188,16 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out defaultFramebuffer = 0; } + // lock down any further work on the current frame + _output_mutex->lock(); + + // release the mapping, giving up on trying to draw if data has been lost + glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); + 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) { @@ -203,16 +213,6 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out glBindBuffer(GL_PIXEL_UNPACK_BUFFER, _input_texture_array); } - // lock down any further work on the current frame - _output_mutex->lock(); - - // release the mapping, giving up on trying to draw if data has been lost - glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); - glUnmapBuffer(GL_ARRAY_BUFFER); - glBindBuffer(GL_ARRAY_BUFFER, source_array_buffer); - glUnmapBuffer(GL_ARRAY_BUFFER); - glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); - // 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->_write_y_position < _buffer_builder->last_uploaded_line) diff --git a/Outputs/CRT/Internals/TextureTarget.cpp b/Outputs/CRT/Internals/TextureTarget.cpp index 9391dbfc1..de1348b32 100644 --- a/Outputs/CRT/Internals/TextureTarget.cpp +++ b/Outputs/CRT/Internals/TextureTarget.cpp @@ -75,7 +75,6 @@ void TextureTarget::draw(float aspect_ratio) "texCoordVarying = texCoord;" "gl_Position = vec4(position, 0.0, 1.0);" "}"; - const char *fragment_shader = "#version 150\n" @@ -85,7 +84,7 @@ void TextureTarget::draw(float aspect_ratio) "void main(void)" "{" - "fragColour = vec4(0.5);"//texture(texID, texCoordVarying);" + "fragColour = texture(texID, texCoordVarying);" "}"; _pixel_shader = std::unique_ptr(new Shader(vertex_shader, fragment_shader, nullptr)); @@ -127,18 +126,18 @@ void TextureTarget::draw(float aspect_ratio) if(aspect_ratio_ratio >= 1.0f) { // output is thinner than we are; letterbox - fl_buffer[0] = -1.0f; fl_buffer[1] = 1.0f / aspect_ratio_ratio; - fl_buffer[3] = -1.0f; fl_buffer[4] = -1.0f / aspect_ratio_ratio; - fl_buffer[6] = 1.0f; fl_buffer[7] = 1.0f / aspect_ratio_ratio; - fl_buffer[9] = 1.0f; fl_buffer[10] = -1.0f / aspect_ratio_ratio; + fl_buffer[0] = -1.0f; fl_buffer[1] = -1.0f / aspect_ratio_ratio; + fl_buffer[3] = 1.0f; fl_buffer[4] = -1.0f / aspect_ratio_ratio; + fl_buffer[6] = -1.0f; fl_buffer[7] = 1.0f / aspect_ratio_ratio; + fl_buffer[9] = 1.0f; fl_buffer[10] = 1.0f / aspect_ratio_ratio; } else { // output is wider than we are; pillarbox - fl_buffer[0] = -aspect_ratio_ratio; fl_buffer[1] = 1.0f; - fl_buffer[3] = -aspect_ratio_ratio; fl_buffer[4] = -1.0f; - fl_buffer[6] = aspect_ratio_ratio; fl_buffer[7] = 1.0f; - fl_buffer[9] = aspect_ratio_ratio; fl_buffer[10] = -1.0f; + fl_buffer[0] = -aspect_ratio_ratio; fl_buffer[1] = -1.0f; + fl_buffer[3] = aspect_ratio_ratio; fl_buffer[4] = -1.0f; + fl_buffer[6] = -aspect_ratio_ratio; fl_buffer[7] = 1.0f; + fl_buffer[9] = aspect_ratio_ratio; fl_buffer[10] = 1.0f; } // upload buffer @@ -146,6 +145,7 @@ void TextureTarget::draw(float aspect_ratio) glBufferData(GL_ARRAY_BUFFER, sizeof(buffer), buffer, GL_STATIC_DRAW); } + _pixel_shader->bind(); glBindVertexArray(_drawing_vertex_array); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } From 09ed8eb65483212e2a52ca8286f731a9824394c3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 1 May 2016 21:07:27 -0400 Subject: [PATCH 21/24] Quelle surprise, failure to find strikes again. Fixed. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 3 +-- Outputs/CRT/Internals/TextureTarget.cpp | 36 ++++++++++++++----------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index baf99f97b..8a882e427 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -345,8 +345,7 @@ void OpenGLOutputBuilder::perform_output_stage(unsigned int output_width, unsign _last_output_width = output_width; _last_output_height = output_height; } - else - shader->bind(); + shader->bind(); // draw for(int c = 0; c < number_of_drawing_zones; c++) diff --git a/Outputs/CRT/Internals/TextureTarget.cpp b/Outputs/CRT/Internals/TextureTarget.cpp index de1348b32..cc73afb24 100644 --- a/Outputs/CRT/Internals/TextureTarget.cpp +++ b/Outputs/CRT/Internals/TextureTarget.cpp @@ -87,6 +87,7 @@ void TextureTarget::draw(float aspect_ratio) "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); @@ -100,9 +101,9 @@ void TextureTarget::draw(float aspect_ratio) glEnableVertexAttribArray((GLuint)positionAttribute); glEnableVertexAttribArray((GLuint)texCoordAttribute); - const GLsizei vertexStride = 12; + const GLsizei vertexStride = 4 * sizeof(GLfloat); glVertexAttribPointer((GLuint)positionAttribute, 2, GL_FLOAT, GL_FALSE, vertexStride, (void *)0); - glVertexAttribPointer((GLuint)texCoordAttribute, 2, GL_BYTE, GL_FALSE, vertexStride, (void *)(2 * sizeof(GLfloat))); + 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)); @@ -111,33 +112,36 @@ void TextureTarget::draw(float aspect_ratio) if(_set_aspect_ratio != aspect_ratio) { _set_aspect_ratio = aspect_ratio; - int8_t buffer[12*4]; + float buffer[4*4]; // establish texture coordinates - buffer[8] = 0; buffer[9] = 0; - buffer[20] = 0; buffer[21] = 1; - buffer[32] = 1; buffer[33] = 0; - buffer[44] = 1; buffer[45] = 1; + 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 float internal_aspect_ratio = (float)_width / (float)_height; float aspect_ratio_ratio = internal_aspect_ratio / aspect_ratio; - float *fl_buffer = (float *)buffer; if(aspect_ratio_ratio >= 1.0f) { // output is thinner than we are; letterbox - fl_buffer[0] = -1.0f; fl_buffer[1] = -1.0f / aspect_ratio_ratio; - fl_buffer[3] = 1.0f; fl_buffer[4] = -1.0f / aspect_ratio_ratio; - fl_buffer[6] = -1.0f; fl_buffer[7] = 1.0f / aspect_ratio_ratio; - fl_buffer[9] = 1.0f; fl_buffer[10] = 1.0f / aspect_ratio_ratio; + buffer[0] = -1.0f; buffer[1] = -1.0f / aspect_ratio_ratio; + buffer[4] = -1.0f; buffer[5] = 1.0f / aspect_ratio_ratio; + buffer[8] = 1.0f; buffer[9] = -1.0f / aspect_ratio_ratio; + buffer[12] = 1.0f; buffer[13] = 1.0f / aspect_ratio_ratio; } else { // output is wider than we are; pillarbox - fl_buffer[0] = -aspect_ratio_ratio; fl_buffer[1] = -1.0f; - fl_buffer[3] = aspect_ratio_ratio; fl_buffer[4] = -1.0f; - fl_buffer[6] = -aspect_ratio_ratio; fl_buffer[7] = 1.0f; - fl_buffer[9] = aspect_ratio_ratio; fl_buffer[10] = 1.0f; + 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 From c93bcf9f28ba25e179ed722d70013acac0418861 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 1 May 2016 22:28:33 -0400 Subject: [PATCH 22/24] Discovered cause of clears: stale code. Tweaked persistence, again. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 21 +++++++++++------ .../CRT/Internals/Shaders/OutputShader.cpp | 2 +- Outputs/CRT/Internals/TextureTarget.cpp | 23 +++++-------------- 3 files changed, 21 insertions(+), 25 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 8a882e427..a5ab2689e 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -101,7 +101,7 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : _buffer_builder = std::unique_ptr(new CRTInputBufferBuilder(buffer_depth)); glBlendFunc(GL_SRC_ALPHA, GL_CONSTANT_COLOR); - glBlendColor(0.4f, 0.4f, 0.4f, 0.5f); + glBlendColor(0.6f, 0.6f, 0.6f, 1.0f); // Create intermediate textures and bind to slots 0, 1 and 2 compositeTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, composite_texture_unit)); @@ -206,7 +206,13 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out 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); @@ -303,6 +309,13 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out else perform_output_stage(output_width, output_height, rgb_shader_program.get()); + // 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); @@ -340,7 +353,6 @@ void OpenGLOutputBuilder::perform_output_stage(unsigned int output_width, unsign // update uniforms (implicitly binding the shader) if(_last_output_width != output_width || _last_output_height != output_height) { - glClear(GL_COLOR_BUFFER_BIT); shader->set_output_size(output_width, output_height, _visible_area); _last_output_width = output_width; _last_output_height = output_height; @@ -353,11 +365,6 @@ void OpenGLOutputBuilder::perform_output_stage(unsigned int output_width, unsign glDrawArrays(GL_TRIANGLE_STRIP, drawing_zones[c*2] / OutputVertexSize, drawing_zones[c*2 + 1] / OutputVertexSize); } } - - // copy to the intended place - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glClear(GL_COLOR_BUFFER_BIT); - framebuffer->draw((float)output_width / (float)output_height); } } diff --git a/Outputs/CRT/Internals/Shaders/OutputShader.cpp b/Outputs/CRT/Internals/Shaders/OutputShader.cpp index 149701e9e..7deb4c508 100644 --- a/Outputs/CRT/Internals/Shaders/OutputShader.cpp +++ b/Outputs/CRT/Internals/Shaders/OutputShader.cpp @@ -74,7 +74,7 @@ std::unique_ptr OutputShader::make_shader(const char *fragment_met "void main(void)" "{" - "fragColour = vec4(%s, 0.7*cos(lateralVarying));" + "fragColour = vec4(%s, 0.5*cos(lateralVarying));" "}", sampler_type, fragment_methods, colour_expression); diff --git a/Outputs/CRT/Internals/TextureTarget.cpp b/Outputs/CRT/Internals/TextureTarget.cpp index cc73afb24..2ea700a6f 100644 --- a/Outputs/CRT/Internals/TextureTarget.cpp +++ b/Outputs/CRT/Internals/TextureTarget.cpp @@ -124,25 +124,14 @@ void TextureTarget::draw(float aspect_ratio) buffer[14] = buffer[10]; buffer[15] = buffer[7]; - // determine positions + // 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; - if(aspect_ratio_ratio >= 1.0f) - { - // output is thinner than we are; letterbox - buffer[0] = -1.0f; buffer[1] = -1.0f / aspect_ratio_ratio; - buffer[4] = -1.0f; buffer[5] = 1.0f / aspect_ratio_ratio; - buffer[8] = 1.0f; buffer[9] = -1.0f / aspect_ratio_ratio; - buffer[12] = 1.0f; buffer[13] = 1.0f / aspect_ratio_ratio; - } - else - { - // output is wider than we are; pillarbox - 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; - } + + 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); From 4a0e1dc789791bd59c0ce720ea5c5b51ddb07768 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 1 May 2016 22:33:00 -0400 Subject: [PATCH 23/24] Switched to explicit flushing of data. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index a5ab2689e..1616a4d8b 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -125,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); @@ -136,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); @@ -147,7 +147,7 @@ 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); + _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() @@ -261,6 +261,13 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out 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++) + { + glFlushMappedBufferRange(GL_ARRAY_BUFFER, drawing_zones[c*2] / SourceVertexSize, drawing_zones[c*2 + 1] / SourceVertexSize); + } + OpenGL::TextureTarget *targets[] = { compositeTexture.get(), filteredYTexture.get(), @@ -318,12 +325,12 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // 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(); } @@ -339,6 +346,13 @@ void OpenGLOutputBuilder::perform_output_stage(unsigned int output_width, unsign 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; From 29e7c96e4c2eb75c4bffc488a4535526be46c66c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 2 May 2016 21:05:58 -0400 Subject: [PATCH 24/24] Brought monitor mode inside the pipeline and added an intermediate filter. --- Outputs/CRT/CRT.cpp | 102 +++--- Outputs/CRT/Internals/CRTOpenGL.cpp | 293 +++++++++--------- Outputs/CRT/Internals/CRTOpenGL.hpp | 12 +- .../Internals/Shaders/IntermediateShader.cpp | 98 ++++++ .../Internals/Shaders/IntermediateShader.hpp | 17 + 5 files changed, 303 insertions(+), 219 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 40047e56f..1b6c32084 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -45,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) @@ -133,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: @@ -143,30 +143,15 @@ 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_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; - } - 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 @@ -188,54 +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_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_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; + _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 diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 1616a4d8b..79f8b3916 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -167,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(); @@ -241,80 +241,122 @@ 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, 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++) - { - glFlushMappedBufferRange(GL_ARRAY_BUFFER, drawing_zones[c*2] / SourceVertexSize, drawing_zones[c*2 + 1] / SourceVertexSize); - } - - 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); - } - } + 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++; + } + } + + // 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); + } } - else - perform_output_stage(output_width, output_height, rgb_shader_program.get()); // copy framebuffer to the intended place glDisable(GL_BLEND); @@ -339,46 +381,6 @@ void OpenGLOutputBuilder::perform_output_stage(unsigned int output_width, unsign { if(shader) { - // bind the target - 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) - { - shader->set_output_size(output_width, output_height, _visible_area); - _last_output_width = output_width; - _last_output_height = output_height; - } - shader->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); - } - } } } @@ -398,7 +400,7 @@ void OpenGLOutputBuilder::set_rgb_sampling_function(const char *shader) #pragma mark - Program compilation -void OpenGLOutputBuilder::prepare_composite_input_shader() +void OpenGLOutputBuilder::prepare_composite_input_shaders() { 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); @@ -413,6 +415,20 @@ void OpenGLOutputBuilder::prepare_composite_input_shader() composite_chrominance_filter_shader_program->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight); } +void OpenGLOutputBuilder::prepare_rgb_input_shaders() +{ + 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() { if(composite_input_shader_program) @@ -438,35 +454,19 @@ void OpenGLOutputBuilder::prepare_source_vertex_array() } } -void OpenGLOutputBuilder::prepare_rgb_output_shader() +void OpenGLOutputBuilder::prepare_output_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);" - "}"; - } - - rgb_shader_program = OpenGL::OutputShader::make_shader(rgb_shader, "rgb_sample(texID, srcCoordinatesVarying, iSrcCoordinatesVarying)", true); - rgb_shader_program->set_source_texture_unit(source_data_texture_unit); -} - -void OpenGLOutputBuilder::prepare_composite_output_shader() -{ - composite_output_shader_program = OpenGL::OutputShader::make_shader("", "texture(texID, srcCoordinatesVarying).rgb", false); - composite_output_shader_program->set_source_texture_unit(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("lateral"); + 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); @@ -495,8 +495,9 @@ void OpenGLOutputBuilder::set_output_device(OutputDevice output_device) } } -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; @@ -540,6 +541,7 @@ void OpenGLOutputBuilder::set_colour_space_uniforms() void OpenGLOutputBuilder::set_timing_uniforms() { _output_mutex->lock(); + OpenGL::IntermediateShader *intermediate_shaders[] = { composite_input_shader_program.get(), composite_y_filter_shader_program.get(), @@ -553,17 +555,12 @@ void OpenGLOutputBuilder::set_timing_uniforms() extends = true; } - OpenGL::OutputShader *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]->set_timing(_height_of_display, _cycles_per_line, _horizontal_scan_period, _vertical_scan_period, _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; - if(composite_y_filter_shader_program) composite_y_filter_shader_program->set_filter_coefficients(_cycles_per_line, colour_subcarrier_frequency - 90.0f); - if(composite_chrominance_filter_shader_program) composite_chrominance_filter_shader_program->set_filter_coefficients(_cycles_per_line, colour_subcarrier_frequency * 0.5f); + 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); + _output_mutex->unlock(); } diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index b3ae9939b..7014d9c07 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -33,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; @@ -47,9 +48,9 @@ class OpenGLOutputBuilder { char *_rgb_shader; // Methods used by the OpenGL code - void prepare_rgb_output_shader(); - void prepare_composite_output_shader(); - void prepare_composite_input_shader(); + void prepare_output_shader(); + void prepare_rgb_input_shaders(); + void prepare_composite_input_shaders(); void prepare_output_vertex_array(); void prepare_source_vertex_array(); @@ -61,8 +62,9 @@ class OpenGLOutputBuilder { // transient buffers indicating composite data not yet decoded uint16_t _composite_src_output_y, _cleared_composite_output_y; - std::unique_ptr rgb_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; @@ -168,7 +170,7 @@ 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; diff --git a/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp b/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp index 51deed3e1..faaa8a37c 100644 --- a/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp +++ b/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp @@ -141,6 +141,34 @@ std::unique_ptr IntermediateShader::make_source_conversion_s 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( @@ -250,6 +278,70 @@ std::unique_ptr IntermediateShader::make_chroma_filter_shade "}", 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(); @@ -319,6 +411,12 @@ void IntermediateShader::set_filter_coefficients(float sampling_rate, float cuto 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(); diff --git a/Outputs/CRT/Internals/Shaders/IntermediateShader.hpp b/Outputs/CRT/Internals/Shaders/IntermediateShader.hpp index 8b75a51df..db4f76199 100644 --- a/Outputs/CRT/Internals/Shaders/IntermediateShader.hpp +++ b/Outputs/CRT/Internals/Shaders/IntermediateShader.hpp @@ -27,6 +27,12 @@ public: */ 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. @@ -38,6 +44,11 @@ public: */ 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. */ @@ -53,6 +64,12 @@ public: */ 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.