From fd0ffc70855566be17cf9824194883bcaebebe94 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 1 Jan 2019 21:02:21 -0500 Subject: [PATCH 01/16] Attempts an initial flattening of the pipeline, seemingly losing all output. --- Outputs/OpenGL/ScanTarget.cpp | 112 +------ Outputs/OpenGL/ScanTarget.hpp | 21 +- Outputs/OpenGL/ScanTargetGLSLFragments.cpp | 341 ++++++++++++--------- 3 files changed, 211 insertions(+), 263 deletions(-) diff --git a/Outputs/OpenGL/ScanTarget.cpp b/Outputs/OpenGL/ScanTarget.cpp index 1a9ce475e..a7805df1a 100644 --- a/Outputs/OpenGL/ScanTarget.cpp +++ b/Outputs/OpenGL/ScanTarget.cpp @@ -13,22 +13,14 @@ using namespace Outputs::Display::OpenGL; namespace { -/// The texture unit from which to source 1bpp input data. -constexpr GLenum SourceData1BppTextureUnit = GL_TEXTURE0; -/// The texture unit from which to source 2bpp input data. -//constexpr GLenum SourceData2BppTextureUnit = GL_TEXTURE1; -/// The texture unit from which to source 4bpp input data. -//constexpr GLenum SourceData4BppTextureUnit = GL_TEXTURE2; +/// The texture unit from which to source input data. +constexpr GLenum SourceDataTextureUnit = GL_TEXTURE0; /// The texture unit which contains raw line-by-line composite, S-Video or RGB data. -constexpr GLenum UnprocessedLineBufferTextureUnit = GL_TEXTURE3; -/// The texture unit which contains line-by-line records of luminance and two channels of chrominance, straight after multiplication by the quadrature vector, not yet filtered. -constexpr GLenum SVideoLineBufferTextureUnit = GL_TEXTURE4; -/// The texture unit which contains line-by-line records of RGB. -constexpr GLenum RGBLineBufferTextureUnit = GL_TEXTURE5; +constexpr GLenum UnprocessedLineBufferTextureUnit = GL_TEXTURE1; /// The texture unit that contains the current display. -constexpr GLenum AccumulationTextureUnit = GL_TEXTURE6; +constexpr GLenum AccumulationTextureUnit = GL_TEXTURE2; #define TextureAddress(x, y) (((y) << 11) | (x)) #define TextureAddressGetY(v) uint16_t((v) >> 11) @@ -292,14 +284,9 @@ void ScanTarget::setup_pipeline() { write_pointers_.write_area = 0; } - // Pick a processing width; this will be at least four times the - // colour subcarrier, and an integer multiple of the pixel clock and - // at most 2048. - const int colour_cycle_width = (modals_.colour_cycle_numerator * 4 + modals_.colour_cycle_denominator - 1) / modals_.colour_cycle_denominator; - const int dot_clock = modals_.cycles_per_line / modals_.clocks_per_pixel_greatest_common_divisor; - const int overflow = colour_cycle_width % dot_clock; - processing_width_ = colour_cycle_width + (overflow ? dot_clock - overflow : 0); - processing_width_ = std::min(processing_width_, 2048); + // Pick a processing width; this will be the minimum necessary not to + // lose any detail when combining the input. + processing_width_ = modals_.cycles_per_line / modals_.clocks_per_pixel_greatest_common_divisor; // Establish an output shader. TODO: add gamma correction here. output_shader_.reset(new Shader( @@ -323,63 +310,15 @@ void ScanTarget::setup_pipeline() { set_uniforms(ShaderType::Line, *output_shader_); output_shader_->set_uniform("origin", modals_.visible_area.origin.x, modals_.visible_area.origin.y); output_shader_->set_uniform("size", modals_.visible_area.size.width, modals_.visible_area.size.height); - - // Establish such intermediary shaders as are required. - pipeline_stages_.clear(); - if(modals_.display_type == DisplayType::CompositeColour) { - pipeline_stages_.emplace_back( - composite_to_svideo_shader(modals_.colour_cycle_numerator, modals_.colour_cycle_denominator, processing_width_).release(), - SVideoLineBufferTextureUnit, - GL_NEAREST); - } - if(modals_.display_type == DisplayType::SVideo || modals_.display_type == DisplayType::CompositeColour) { - pipeline_stages_.emplace_back( - svideo_to_rgb_shader(modals_.colour_cycle_numerator, modals_.colour_cycle_denominator, processing_width_).release(), - (modals_.display_type == DisplayType::CompositeColour) ? RGBLineBufferTextureUnit : SVideoLineBufferTextureUnit, - GL_NEAREST); - } - - glBindVertexArray(scan_vertex_array_); - glBindBuffer(GL_ARRAY_BUFFER, scan_buffer_name_); + output_shader_->set_uniform("textureName", GLint(UnprocessedLineBufferTextureUnit - GL_TEXTURE0)); // Establish an input shader. - input_shader_ = input_shader(modals_.input_data_type, modals_.display_type); + input_shader_ = composition_shader(); + glBindVertexArray(scan_vertex_array_); + glBindBuffer(GL_ARRAY_BUFFER, scan_buffer_name_); enable_vertex_attributes(ShaderType::InputScan, *input_shader_); set_uniforms(ShaderType::InputScan, *input_shader_); - input_shader_->set_uniform("textureName", GLint(SourceData1BppTextureUnit - GL_TEXTURE0)); - - // Cascade the texture units in use as per the pipeline stages. - std::vector input_shaders = {input_shader_.get()}; - GLint texture_unit = GLint(UnprocessedLineBufferTextureUnit - GL_TEXTURE0); - for(const auto &stage: pipeline_stages_) { - input_shaders.push_back(stage.shader.get()); - - stage.shader->set_uniform("textureName", texture_unit); - set_uniforms(ShaderType::ProcessedScan, *stage.shader); - enable_vertex_attributes(ShaderType::ProcessedScan, *stage.shader); - - ++texture_unit; - } - output_shader_->set_uniform("textureName", texture_unit); - - // Ensure that all shaders involved in the input pipeline have the proper colour space knowledged. - for(auto shader: input_shaders) { - switch(modals_.composite_colour_space) { - case ColourSpace::YIQ: { - const GLfloat rgbToYIQ[] = {0.299f, 0.596f, 0.211f, 0.587f, -0.274f, -0.523f, 0.114f, -0.322f, 0.312f}; - const GLfloat yiqToRGB[] = {1.0f, 1.0f, 1.0f, 0.956f, -0.272f, -1.106f, 0.621f, -0.647f, 1.703f}; - shader->set_uniform_matrix("lumaChromaToRGB", 3, false, yiqToRGB); - shader->set_uniform_matrix("rgbToLumaChroma", 3, false, rgbToYIQ); - } break; - - case ColourSpace::YUV: { - const GLfloat rgbToYUV[] = {0.299f, -0.14713f, 0.615f, 0.587f, -0.28886f, -0.51499f, 0.114f, 0.436f, -0.10001f}; - const GLfloat yuvToRGB[] = {1.0f, 1.0f, 1.0f, 0.0f, -0.39465f, 2.03211f, 1.13983f, -0.58060f, 0.0f}; - shader->set_uniform_matrix("lumaChromaToRGB", 3, false, yuvToRGB); - shader->set_uniform_matrix("rgbToLumaChroma", 3, false, rgbToYUV); - } break; - } - } + input_shader_->set_uniform("textureName", GLint(SourceDataTextureUnit - GL_TEXTURE0)); } void ScanTarget::draw(bool synchronous, int output_width, int output_height) { @@ -431,7 +370,7 @@ void ScanTarget::draw(bool synchronous, int output_width, int output_height) { // Submit texture. if(submit_pointers.write_area != read_pointers.write_area) { - glActiveTexture(SourceData1BppTextureUnit); + glActiveTexture(SourceDataTextureUnit); glBindTexture(GL_TEXTURE_2D, write_area_texture_name_); // Create storage for the texture if it doesn't yet exist; this was deferred until here @@ -487,7 +426,6 @@ void ScanTarget::draw(bool synchronous, int output_width, int output_height) { // Push new input to the unprocessed line buffer. if(new_scans) { - glDisable(GL_BLEND); unprocessed_line_texture_.bind_framebuffer(); // Clear newly-touched lines; that is everything from (read+1) to submit. @@ -499,26 +437,11 @@ void ScanTarget::draw(bool synchronous, int output_width, int output_height) { if(first_line_to_clear < final_line_to_clear) { glScissor(0, first_line_to_clear, unprocessed_line_texture_.get_width(), final_line_to_clear - first_line_to_clear); glClear(GL_COLOR_BUFFER_BIT); - - if(pipeline_stages_.size()) { - pipeline_stages_.back().target.bind_framebuffer(); - glClear(GL_COLOR_BUFFER_BIT); - unprocessed_line_texture_.bind_framebuffer(); - } } else { glScissor(0, 0, unprocessed_line_texture_.get_width(), final_line_to_clear); glClear(GL_COLOR_BUFFER_BIT); glScissor(0, first_line_to_clear, unprocessed_line_texture_.get_width(), unprocessed_line_texture_.get_height() - first_line_to_clear); glClear(GL_COLOR_BUFFER_BIT); - - if(pipeline_stages_.size()) { - pipeline_stages_.back().target.bind_framebuffer(); - glScissor(0, 0, unprocessed_line_texture_.get_width(), final_line_to_clear); - glClear(GL_COLOR_BUFFER_BIT); - glScissor(0, first_line_to_clear, unprocessed_line_texture_.get_width(), unprocessed_line_texture_.get_height() - first_line_to_clear); - glClear(GL_COLOR_BUFFER_BIT); - unprocessed_line_texture_.bind_framebuffer(); - } } glDisable(GL_SCISSOR_TEST); @@ -528,13 +451,6 @@ void ScanTarget::draw(bool synchronous, int output_width, int output_height) { glBindVertexArray(scan_vertex_array_); input_shader_->bind(); glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, GLsizei(new_scans)); - - // If there are any further pipeline stages, apply them. - for(auto &stage: pipeline_stages_) { - stage.target.bind_framebuffer(); - stage.shader->bind(); - glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, GLsizei(new_scans)); - } } // Ensure the accumulation buffer is properly sized. @@ -545,7 +461,7 @@ void ScanTarget::draw(bool synchronous, int output_width, int output_height) { GLsizei(proportional_width), GLsizei(output_height), AccumulationTextureUnit, - GL_LINEAR, + GL_NEAREST, true)); if(accumulation_texture_) { new_framebuffer->bind_framebuffer(); diff --git a/Outputs/OpenGL/ScanTarget.hpp b/Outputs/OpenGL/ScanTarget.hpp index 78cc6035f..53fe8a5b8 100644 --- a/Outputs/OpenGL/ScanTarget.hpp +++ b/Outputs/OpenGL/ScanTarget.hpp @@ -105,8 +105,7 @@ class ScanTarget: public Outputs::Display::ScanTarget { // Contains the first composition of scans into lines; // they're accumulated prior to output to allow for continuous - // application of any necessary conversions — e.g. composite processing — - // which happen progressively from here to the RGB texture. + // application of any necessary conversions — e.g. composite processing. TextureTarget unprocessed_line_texture_; // Scans are accumulated to the accumulation texture; the full-display @@ -180,22 +179,8 @@ class ScanTarget: public Outputs::Display::ScanTarget { std::unique_ptr input_shader_; std::unique_ptr output_shader_; - static std::unique_ptr input_shader(InputDataType input_data_type, DisplayType display_type); - static std::unique_ptr composite_to_svideo_shader(int colour_cycle_numerator, int colour_cycle_denominator, int processing_width); - static std::unique_ptr svideo_to_rgb_shader(int colour_cycle_numerator, int colour_cycle_denominator, int processing_width); - static SignalProcessing::FIRFilter colour_filter(int colour_cycle_numerator, int colour_cycle_denominator, int processing_width, float low_cutoff, float high_cutoff); - - struct PipelineStage { - PipelineStage(Shader *shader, GLenum texture_unit, GLint magnification_filter) : - shader(shader), - target(LineBufferWidth, LineBufferHeight, texture_unit, magnification_filter, false) {} - - std::unique_ptr shader; - TextureTarget target; - }; - - // A list is used here to avoid requiring a copy constructor on PipelineStage. - std::list pipeline_stages_; + static std::unique_ptr composition_shader(); + static std::unique_ptr conversion_shader(InputDataType input_data_type, DisplayType display_type, int colour_cycle_numerator, int colour_cycle_denominator, int processing_width); }; } diff --git a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp index 261b6d94f..fe4abbf1e 100644 --- a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp +++ b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp @@ -90,12 +90,14 @@ std::string ScanTarget::glsl_default_vertex_shader(ShaderType type) { if(type == ShaderType::InputScan) { result += "out vec2 textureCoordinate;" - "uniform usampler2D textureName;"; + "uniform sampler2D textureName;"; } else { result += "out vec2 textureCoordinates[15];" + "out vec2 chromaCoordinates[2];" "uniform sampler2D textureName;" + "uniform float chromaOffset;" "uniform float edgeExpansion;"; } @@ -120,9 +122,9 @@ std::string ScanTarget::glsl_default_vertex_shader(ShaderType type) { "vec2 eyePosition = (sourcePosition + vec2(0.0, longitudinal - 0.5)) / vec2(scale.x, 2048.0);" "sourcePosition /= vec2(scale.x, 2048.0);" - "vec2 expansion = vec2(2.0*lateral*edgeExpansion - edgeExpansion, 0.0) / textureSize(textureName, 0);" - "eyePosition = eyePosition + expansion;" - "sourcePosition = sourcePosition + expansion;" +// "vec2 expansion = vec2(edgeExpansion, 0.0) / textureSize(textureName, 0);" +// "eyePosition = eyePosition + expansion;" +// "sourcePosition = sourcePosition + expansion;" "textureCoordinates[0] = sourcePosition + vec2(-7.0, 0.0) / textureSize(textureName, 0);" "textureCoordinates[1] = sourcePosition + vec2(-6.0, 0.0) / textureSize(textureName, 0);" @@ -140,6 +142,9 @@ std::string ScanTarget::glsl_default_vertex_shader(ShaderType type) { "textureCoordinates[13] = sourcePosition + vec2(6.0, 0.0) / textureSize(textureName, 0);" "textureCoordinates[14] = sourcePosition + vec2(7.0, 0.0) / textureSize(textureName, 0);" + "chromaCoordinates[0] = sourcePosition + vec2(chromaOffset, 0.0);" + "chromaCoordinates[1] = sourcePosition - vec2(chromaOffset, 0.0);" + "eyePosition = eyePosition;"; } @@ -235,8 +240,8 @@ void ScanTarget::enable_vertex_attributes(ShaderType type, Shader &target) { } } -std::unique_ptr ScanTarget::input_shader(InputDataType input_data_type, DisplayType display_type) { - std::string fragment_shader = +std::unique_ptr ScanTarget::composition_shader() { +/* std::string fragment_shader = "#version 150\n" "out vec3 fragColour;" @@ -313,161 +318,203 @@ std::unique_ptr ScanTarget::input_shader(InputDataType input_data_type, computed_display_type = DisplayType::RGB; fragment_shader += "fragColour = texture(textureName, textureCoordinate).rgb / vec3(255.0);"; break; - } + }*/ // If the input type is RGB but the output type isn't then // there'll definitely be an RGB to SVideo step. - if(computed_display_type == DisplayType::RGB && display_type != DisplayType::RGB) { - fragment_shader += - "vec3 composite_colour = rgbToLumaChroma * fragColour;" - "vec2 quadrature = vec2(cos(compositeAngle), sin(compositeAngle));" - "fragColour = vec3(composite_colour.r, 0.5 + dot(quadrature, composite_colour.gb)*0.5, 0.0);"; - } +// if(computed_display_type == DisplayType::RGB && display_type != DisplayType::RGB) { +// fragment_shader += +// "vec3 composite_colour = rgbToLumaChroma * fragColour;" +// "vec2 quadrature = vec2(cos(compositeAngle), sin(compositeAngle));" +// "fragColour = vec3(composite_colour.r, 0.5 + dot(quadrature, composite_colour.gb)*0.5, 0.0);"; +// } // If the output type is SVideo, throw in an attempt to separate the two chrominance // channels here. - if(display_type == DisplayType::SVideo) { - if(computed_display_type != DisplayType::RGB) { - fragment_shader += - "vec2 quadrature = vec2(cos(compositeAngle), sin(compositeAngle));"; - } - fragment_shader += - "vec2 chroma = (((fragColour.y - 0.5)*2.0) * quadrature)*0.5 + vec2(0.5);" - "fragColour = vec3(fragColour.x, chroma);"; - } +// if(display_type == DisplayType::SVideo) { +// if(computed_display_type != DisplayType::RGB) { +// fragment_shader += +// "vec2 quadrature = vec2(cos(compositeAngle), sin(compositeAngle));"; +// } +// fragment_shader += +// "vec2 chroma = (((fragColour.y - 0.5)*2.0) * quadrature)*0.5 + vec2(0.5);" +// "fragColour = vec3(fragColour.x, chroma);"; +// } // Add an SVideo to composite step if necessary. - if( - (display_type == DisplayType::CompositeMonochrome || display_type == DisplayType::CompositeColour) && - computed_display_type != DisplayType::CompositeMonochrome - ) { - fragment_shader += "fragColour = vec3(mix(fragColour.r, 2.0*(fragColour.g - 0.5), 1.0 / oneOverCompositeAmplitude));"; - } +// if( +// (display_type == DisplayType::CompositeMonochrome || display_type == DisplayType::CompositeColour) && +// computed_display_type != DisplayType::CompositeMonochrome +// ) { +// fragment_shader += "fragColour = vec3(mix(fragColour.r, 2.0*(fragColour.g - 0.5), 1.0 / oneOverCompositeAmplitude));"; +// } + + + const std::string fragment_shader = + "#version 150\n" + + "in vec2 textureCoordinate;" + "out vec4 fragColour;" + + "uniform sampler2D textureName;" + + "void main(void) {" + "fragColour = vec4(1.0) - texture(textureName, textureCoordinate);" + "}"; return std::unique_ptr(new Shader( glsl_globals(ShaderType::InputScan) + glsl_default_vertex_shader(ShaderType::InputScan), - fragment_shader + "}", + fragment_shader, attribute_bindings(ShaderType::InputScan) )); } -SignalProcessing::FIRFilter ScanTarget::colour_filter(int colour_cycle_numerator, int colour_cycle_denominator, int processing_width, float low_cutoff, float high_cutoff) { - const float cycles_per_expanded_line = (float(colour_cycle_numerator) / float(colour_cycle_denominator)) / (float(processing_width) / float(LineBufferWidth)); - return SignalProcessing::FIRFilter(15, float(LineBufferWidth), cycles_per_expanded_line * low_cutoff, cycles_per_expanded_line * high_cutoff); -} - -std::unique_ptr ScanTarget::svideo_to_rgb_shader(int colour_cycle_numerator, int colour_cycle_denominator, int processing_width) { - /* - Composite to S-Video conversion is achieved by filtering the input signal to obtain luminance, and then subtracting that - from the original to get chrominance. - - (Colour cycle numerator)/(Colour cycle denominator) gives the number of colour cycles in (processing_width / LineBufferWidth), - there'll be at least four samples per colour clock and in practice at most just a shade more than 9. - */ - auto shader = std::unique_ptr(new Shader( - glsl_globals(ShaderType::ProcessedScan) + glsl_default_vertex_shader(ShaderType::ProcessedScan), - "#version 150\n" - - "in vec2 textureCoordinates[15];" - "uniform vec4 chromaWeights[4];" - "uniform vec4 lumaWeights[4];" - "uniform sampler2D textureName;" - "uniform mat3 lumaChromaToRGB;" - - "out vec3 fragColour;" - "void main() {" - "vec3 samples[15] = vec3[15](" - "texture(textureName, textureCoordinates[0]).rgb," - "texture(textureName, textureCoordinates[1]).rgb," - "texture(textureName, textureCoordinates[2]).rgb," - "texture(textureName, textureCoordinates[3]).rgb," - "texture(textureName, textureCoordinates[4]).rgb," - "texture(textureName, textureCoordinates[5]).rgb," - "texture(textureName, textureCoordinates[6]).rgb," - "texture(textureName, textureCoordinates[7]).rgb," - "texture(textureName, textureCoordinates[8]).rgb," - "texture(textureName, textureCoordinates[9]).rgb," - "texture(textureName, textureCoordinates[10]).rgb," - "texture(textureName, textureCoordinates[11]).rgb," - "texture(textureName, textureCoordinates[12]).rgb," - "texture(textureName, textureCoordinates[13]).rgb," - "texture(textureName, textureCoordinates[14]).rgb" - ");" - "vec4 samples0[4] = vec4[4](" - "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, samples[11].r)," - "vec4(samples[12].r, samples[13].r, samples[14].r, 0.0)" - ");" - "vec4 samples1[4] = vec4[4](" - "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, samples[11].g)," - "vec4(samples[12].g, samples[13].g, samples[14].g, 0.0)" - ");" - "vec4 samples2[4] = vec4[4](" - "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, samples[11].b)," - "vec4(samples[12].b, samples[13].b, samples[14].b, 0.0)" - ");" - "float channel0 = dot(lumaWeights[0], samples0[0]) + dot(lumaWeights[1], samples0[1]) + dot(lumaWeights[2], samples0[2]) + dot(lumaWeights[3], samples0[3]);" - "float channel1 = dot(chromaWeights[0], samples1[0]) + dot(chromaWeights[1], samples1[1]) + dot(chromaWeights[2], samples1[2]) + dot(chromaWeights[3], samples1[3]);" - "float channel2 = dot(chromaWeights[0], samples2[0]) + dot(chromaWeights[1], samples2[1]) + dot(chromaWeights[2], samples2[2]) + dot(chromaWeights[3], samples2[3]);" - "vec2 chroma = vec2(channel1, channel2)*2.0 - vec2(1.0);" - "fragColour = lumaChromaToRGB * vec3(channel0, chroma);" - "}", - attribute_bindings(ShaderType::ProcessedScan) - )); - - auto chroma_coefficients = colour_filter(colour_cycle_numerator, colour_cycle_denominator, processing_width, 0.0f, 0.25f).get_coefficients(); - chroma_coefficients.push_back(0.0f); - shader->set_uniform("chromaWeights", 4, 4, chroma_coefficients.data()); - - auto luma_coefficients = colour_filter(colour_cycle_numerator, colour_cycle_denominator, processing_width, 0.0f, 0.15f).get_coefficients(); - luma_coefficients.push_back(0.0f); - shader->set_uniform("lumaWeights", 4, 4, luma_coefficients.data()); - - shader->set_uniform("edgeExpansion", 0); - - return shader; -} - -std::unique_ptr ScanTarget::composite_to_svideo_shader(int colour_cycle_numerator, int colour_cycle_denominator, int processing_width) { - auto shader = std::unique_ptr(new Shader( - glsl_globals(ShaderType::ProcessedScan) + glsl_default_vertex_shader(ShaderType::ProcessedScan), - "#version 150\n" - - "in vec2 textureCoordinates[15];" - "in float compositeAngle;" - "in float oneOverCompositeAmplitude;" - - "uniform vec4 lumaWeights[4];" - "uniform sampler2D textureName;" - - "out vec3 fragColour;" - "void main() {" - "vec4 samples[4] = vec4[4](" - "vec4(texture(textureName, textureCoordinates[0]).r, texture(textureName, textureCoordinates[1]).r, texture(textureName, textureCoordinates[2]).r, texture(textureName, textureCoordinates[3]).r)," - "vec4(texture(textureName, textureCoordinates[4]).r, texture(textureName, textureCoordinates[5]).r, texture(textureName, textureCoordinates[6]).r, texture(textureName, textureCoordinates[7]).r)," - "vec4(texture(textureName, textureCoordinates[8]).r, texture(textureName, textureCoordinates[9]).r, texture(textureName, textureCoordinates[10]).r, texture(textureName, textureCoordinates[11]).r)," - "vec4(texture(textureName, textureCoordinates[12]).r, texture(textureName, textureCoordinates[13]).r, texture(textureName, textureCoordinates[14]).r, 0.0)" - ");" - "float luma = dot(lumaWeights[0], samples[0]) + dot(lumaWeights[1], samples[1]) + dot(lumaWeights[2], samples[2]) + dot(lumaWeights[3], samples[3]);" - "vec2 quadrature = vec2(cos(compositeAngle), sin(compositeAngle));" - "vec2 chroma = ((samples[1].a - luma) * oneOverCompositeAmplitude)*quadrature;" - "fragColour = vec3(samples[1].a, chroma*0.5 + vec2(0.5));" - "}", - attribute_bindings(ShaderType::ProcessedScan) - )); - - auto luma_low = colour_filter(colour_cycle_numerator, colour_cycle_denominator, processing_width, 0.0f, 0.9f); - auto luma_coefficients = luma_low.get_coefficients(); - luma_coefficients.push_back(0.0f); - shader->set_uniform("lumaWeights", 4, 4, luma_coefficients.data()); - - shader->set_uniform("edgeExpansion", 0); - - return shader; +std::unique_ptr ScanTarget::conversion_shader(InputDataType input_data_type, DisplayType display_type, int colour_cycle_numerator, int colour_cycle_denominator, int processing_width) { + return nullptr; } +// +//SignalProcessing::FIRFilter ScanTarget::colour_filter(int colour_cycle_numerator, int colour_cycle_denominator, int processing_width, float low_cutoff, float high_cutoff) { +// const float cycles_per_expanded_line = (float(colour_cycle_numerator) / float(colour_cycle_denominator)) / (float(processing_width) / float(LineBufferWidth)); +// return SignalProcessing::FIRFilter(15, float(LineBufferWidth), cycles_per_expanded_line * low_cutoff, cycles_per_expanded_line * high_cutoff); +//} +// +//std::unique_ptr ScanTarget::svideo_to_rgb_shader(int colour_cycle_numerator, int colour_cycle_denominator, int processing_width) { +// /* +// Composite to S-Video conversion is achieved by filtering the input signal to obtain luminance, and then subtracting that +// from the original to get chrominance. +// +// (Colour cycle numerator)/(Colour cycle denominator) gives the number of colour cycles in (processing_width / LineBufferWidth), +// there'll be at least four samples per colour clock and in practice at most just a shade more than 9. +// */ +// auto shader = std::unique_ptr(new Shader( +// glsl_globals(ShaderType::ProcessedScan) + glsl_default_vertex_shader(ShaderType::ProcessedScan), +// "#version 150\n" +// +// "in vec2 textureCoordinates[15];" +// "in vec2 chromaCoordinates[2];" +// "in float compositeAngle;" +// +//// "uniform vec4 chromaWeights[4];" +//// "uniform vec4 lumaWeights[4];" +// "uniform sampler2D textureName;" +// "uniform mat3 lumaChromaToRGB;" +// +// "out vec3 fragColour;" +// "void main() {" +// "vec2 angles = vec2(compositeAngle - 1.570795827, compositeAngle + 1.570795827);" +// +// "vec2 sines = sin(angles) * vec2(0.5) + vec2(0.5);" +// "vec2 coses = cos(angles);" +// "float denominator = sines.y * coses.x - sines.x * coses.y;" +// +// "vec2 samples = vec2(texture(textureName, chromaCoordinates[0]).g, texture(textureName, chromaCoordinates[1]).g);" +// +// "float channel1 = (samples.x * sines.x - samples.y * sines.y) / denominator;" +// "float channel2 = (samples.x * coses.x - samples.y * coses.y) / denominator;" +// +//// "fragColour = lumaChromaToRGB * vec3(texture(textureName, textureCoordinates[7]).r, channel1, channel2);" +// "fragColour = vec3(sines.x + sines.y, 0.0, 0.0);" +// //, 0.0);" +// +//// "fragColour = lumaChromaToRGB * vec3(texture(textureName, textureCoordinates[7]).g, 0.0, 0.0);" +//// "fragColour = vec3(0.5);" +///* "vec3 samples[15] = vec3[15](" +// "texture(textureName, textureCoordinates[0]).rgb," +// "texture(textureName, textureCoordinates[1]).rgb," +// "texture(textureName, textureCoordinates[2]).rgb," +// "texture(textureName, textureCoordinates[3]).rgb," +// "texture(textureName, textureCoordinates[4]).rgb," +// "texture(textureName, textureCoordinates[5]).rgb," +// "texture(textureName, textureCoordinates[6]).rgb," +// "texture(textureName, textureCoordinates[7]).rgb," +// "texture(textureName, textureCoordinates[8]).rgb," +// "texture(textureName, textureCoordinates[9]).rgb," +// "texture(textureName, textureCoordinates[10]).rgb," +// "texture(textureName, textureCoordinates[11]).rgb," +// "texture(textureName, textureCoordinates[12]).rgb," +// "texture(textureName, textureCoordinates[13]).rgb," +// "texture(textureName, textureCoordinates[14]).rgb" +// ");" +// "vec4 samples0[4] = vec4[4](" +// "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, samples[11].r)," +// "vec4(samples[12].r, samples[13].r, samples[14].r, 0.0)" +// ");" +// "vec4 samples1[4] = vec4[4](" +// "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, samples[11].g)," +// "vec4(samples[12].g, samples[13].g, samples[14].g, 0.0)" +// ");" +// "vec4 samples2[4] = vec4[4](" +// "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, samples[11].b)," +// "vec4(samples[12].b, samples[13].b, samples[14].b, 0.0)" +// ");" +// "float channel0 = dot(lumaWeights[0], samples0[0]) + dot(lumaWeights[1], samples0[1]) + dot(lumaWeights[2], samples0[2]) + dot(lumaWeights[3], samples0[3]);" +// "float channel1 = dot(chromaWeights[0], samples1[0]) + dot(chromaWeights[1], samples1[1]) + dot(chromaWeights[2], samples1[2]) + dot(chromaWeights[3], samples1[3]);" +// "float channel2 = dot(chromaWeights[0], samples2[0]) + dot(chromaWeights[1], samples2[1]) + dot(chromaWeights[2], samples2[2]) + dot(chromaWeights[3], samples2[3]);" +// "vec2 chroma = vec2(channel1, channel2)*2.0 - vec2(1.0);" +// "fragColour = lumaChromaToRGB * vec3(channel0, chroma);"*/ +// "}", +// attribute_bindings(ShaderType::ProcessedScan) +// )); +// +// const float cycles_per_expanded_line = (float(colour_cycle_numerator) / float(colour_cycle_denominator)) / (float(processing_width) / float(LineBufferWidth)); +// const float chroma_offset = 0.25f / cycles_per_expanded_line; +// shader->set_uniform("chromaOffset", chroma_offset); +// +//// auto chroma_coefficients = colour_filter(colour_cycle_numerator, colour_cycle_denominator, processing_width, 0.0f, 0.25f).get_coefficients(); +//// chroma_coefficients.push_back(0.0f); +//// shader->set_uniform("chromaWeights", 4, 4, chroma_coefficients.data()); +//// +//// auto luma_coefficients = colour_filter(colour_cycle_numerator, colour_cycle_denominator, processing_width, 0.0f, 0.15f).get_coefficients(); +//// luma_coefficients.push_back(0.0f); +//// shader->set_uniform("lumaWeights", 4, 4, luma_coefficients.data()); +// +// shader->set_uniform("edgeExpansion", 20); +// +// return shader; +//} +// +//std::unique_ptr ScanTarget::composite_to_svideo_shader(int colour_cycle_numerator, int colour_cycle_denominator, int processing_width) { +// auto shader = std::unique_ptr(new Shader( +// glsl_globals(ShaderType::ProcessedScan) + glsl_default_vertex_shader(ShaderType::ProcessedScan), +// "#version 150\n" +// +// "in vec2 textureCoordinates[15];" +// "in float compositeAngle;" +// "in float oneOverCompositeAmplitude;" +// +// "uniform vec4 lumaWeights[4];" +// "uniform sampler2D textureName;" +// +// "out vec3 fragColour;" +// "void main() {" +// "vec4 samples[4] = vec4[4](" +// "vec4(texture(textureName, textureCoordinates[0]).r, texture(textureName, textureCoordinates[1]).r, texture(textureName, textureCoordinates[2]).r, texture(textureName, textureCoordinates[3]).r)," +// "vec4(texture(textureName, textureCoordinates[4]).r, texture(textureName, textureCoordinates[5]).r, texture(textureName, textureCoordinates[6]).r, texture(textureName, textureCoordinates[7]).r)," +// "vec4(texture(textureName, textureCoordinates[8]).r, texture(textureName, textureCoordinates[9]).r, texture(textureName, textureCoordinates[10]).r, texture(textureName, textureCoordinates[11]).r)," +// "vec4(texture(textureName, textureCoordinates[12]).r, texture(textureName, textureCoordinates[13]).r, texture(textureName, textureCoordinates[14]).r, 0.0)" +// ");" +// "float luma = dot(lumaWeights[0], samples[0]) + dot(lumaWeights[1], samples[1]) + dot(lumaWeights[2], samples[2]) + dot(lumaWeights[3], samples[3]);" +// "vec2 quadrature = vec2(cos(compositeAngle), sin(compositeAngle));" +// "vec2 chroma = ((samples[1].a - luma) * oneOverCompositeAmplitude)*quadrature;" +// "fragColour = vec3(samples[1].a, chroma*0.5 + vec2(0.5));" +// "}", +// attribute_bindings(ShaderType::ProcessedScan) +// )); +// +// auto luma_low = colour_filter(colour_cycle_numerator, colour_cycle_denominator, processing_width, 0.0f, 0.9f); +// auto luma_coefficients = luma_low.get_coefficients(); +// luma_coefficients.push_back(0.0f); +// shader->set_uniform("lumaWeights", 4, 4, luma_coefficients.data()); +// +// shader->set_uniform("edgeExpansion", 10); +// +// return shader; +//} +// From 46d756d2980df77c1b37e403ebcce204c466e844 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 5 Jan 2019 18:11:39 -0500 Subject: [PATCH 02/16] Starts towards a flattening of the intermediate video processing. Immediate issue: using x position to index into a bitmap sampled at the input data rate doesn't allow for the disconnection between input rate and output speed provided by the flywheels. --- Outputs/OpenGL/Primitives/Shader.cpp | 21 +++++----- Outputs/OpenGL/ScanTarget.cpp | 26 +++++++++--- Outputs/OpenGL/ScanTarget.hpp | 2 +- Outputs/OpenGL/ScanTargetGLSLFragments.cpp | 47 +++++++++++++++++----- 4 files changed, 71 insertions(+), 25 deletions(-) diff --git a/Outputs/OpenGL/Primitives/Shader.cpp b/Outputs/OpenGL/Primitives/Shader.cpp index 6b08fcc11..7290cfe39 100644 --- a/Outputs/OpenGL/Primitives/Shader.cpp +++ b/Outputs/OpenGL/Primitives/Shader.cpp @@ -47,8 +47,8 @@ GLuint Shader::compile_shader(const std::string &source, GLenum type) { Shader::Shader(const std::string &vertex_shader, const std::string &fragment_shader, const std::vector &attribute_bindings) { shader_program_ = glCreateProgram(); - GLuint vertex = compile_shader(vertex_shader, GL_VERTEX_SHADER); - GLuint fragment = compile_shader(fragment_shader, GL_FRAGMENT_SHADER); + const GLuint vertex = compile_shader(vertex_shader, GL_VERTEX_SHADER); + const GLuint fragment = compile_shader(fragment_shader, GL_FRAGMENT_SHADER); glAttachShader(shader_program_, vertex); glAttachShader(shader_program_, fragment); @@ -60,17 +60,18 @@ Shader::Shader(const std::string &vertex_shader, const std::string &fragment_sha glLinkProgram(shader_program_); #ifdef DEBUG + GLint logLength; + glGetProgramiv(shader_program_, GL_INFO_LOG_LENGTH, &logLength); + if(logLength > 0) { + GLchar *log = new GLchar[static_cast(logLength)]; + glGetProgramInfoLog(shader_program_, logLength, &logLength, log); + printf("Link log:\n%s\n", log); + delete[] log; + } + GLint didLink = 0; glGetProgramiv(shader_program_, GL_LINK_STATUS, &didLink); if(didLink == GL_FALSE) { - GLint logLength; - glGetProgramiv(shader_program_, GL_INFO_LOG_LENGTH, &logLength); - if(logLength > 0) { - GLchar *log = new GLchar[static_cast(logLength)]; - glGetProgramInfoLog(shader_program_, logLength, &logLength, log); - printf("Link log:\n%s\n", log); - delete[] log; - } throw ProgramLinkageError; } #endif diff --git a/Outputs/OpenGL/ScanTarget.cpp b/Outputs/OpenGL/ScanTarget.cpp index a7805df1a..787b471c5 100644 --- a/Outputs/OpenGL/ScanTarget.cpp +++ b/Outputs/OpenGL/ScanTarget.cpp @@ -14,10 +14,10 @@ using namespace Outputs::Display::OpenGL; namespace { /// The texture unit from which to source input data. -constexpr GLenum SourceDataTextureUnit = GL_TEXTURE0; +constexpr GLenum SourceDataTextureUnit = GL_TEXTURE1; /// The texture unit which contains raw line-by-line composite, S-Video or RGB data. -constexpr GLenum UnprocessedLineBufferTextureUnit = GL_TEXTURE1; +constexpr GLenum UnprocessedLineBufferTextureUnit = GL_TEXTURE3; /// The texture unit that contains the current display. constexpr GLenum AccumulationTextureUnit = GL_TEXTURE2; @@ -288,7 +288,7 @@ void ScanTarget::setup_pipeline() { // lose any detail when combining the input. processing_width_ = modals_.cycles_per_line / modals_.clocks_per_pixel_greatest_common_divisor; - // Establish an output shader. TODO: add gamma correction here. + // Establish an output shader. TODO: add proper decoding and gamma correction here. output_shader_.reset(new Shader( glsl_globals(ShaderType::Line) + glsl_default_vertex_shader(ShaderType::Line), "#version 150\n" @@ -312,8 +312,24 @@ void ScanTarget::setup_pipeline() { output_shader_->set_uniform("size", modals_.visible_area.size.width, modals_.visible_area.size.height); output_shader_->set_uniform("textureName", GLint(UnprocessedLineBufferTextureUnit - GL_TEXTURE0)); +// switch(modals_.composite_colour_space) { +// case ColourSpace::YIQ: { +// const GLfloat rgbToYIQ[] = {0.299f, 0.596f, 0.211f, 0.587f, -0.274f, -0.523f, 0.114f, -0.322f, 0.312f}; +// const GLfloat yiqToRGB[] = {1.0f, 1.0f, 1.0f, 0.956f, -0.272f, -1.106f, 0.621f, -0.647f, 1.703f}; +// shader->set_uniform_matrix("lumaChromaToRGB", 3, false, yiqToRGB); +// shader->set_uniform_matrix("rgbToLumaChroma", 3, false, rgbToYIQ); +// } break; +// +// case ColourSpace::YUV: { +// const GLfloat rgbToYUV[] = {0.299f, -0.14713f, 0.615f, 0.587f, -0.28886f, -0.51499f, 0.114f, 0.436f, -0.10001f}; +// const GLfloat yuvToRGB[] = {1.0f, 1.0f, 1.0f, 0.0f, -0.39465f, 2.03211f, 1.13983f, -0.58060f, 0.0f}; +// shader->set_uniform_matrix("lumaChromaToRGB", 3, false, yuvToRGB); +// shader->set_uniform_matrix("rgbToLumaChroma", 3, false, rgbToYUV); +// } break; +// } + // Establish an input shader. - input_shader_ = composition_shader(); + input_shader_ = composition_shader(modals_.input_data_type); glBindVertexArray(scan_vertex_array_); glBindBuffer(GL_ARRAY_BUFFER, scan_buffer_name_); enable_vertex_attributes(ShaderType::InputScan, *input_shader_); @@ -492,7 +508,7 @@ void ScanTarget::draw(bool synchronous, int output_width, int output_height) { // Enable blending and stenciling, and ensure spans increment the stencil buffer. glEnable(GL_BLEND); glEnable(GL_STENCIL_TEST); - glStencilFunc(GL_EQUAL, 0, GLuint(-1)); + glStencilFunc(GL_EQUAL, 0, GLuint(~0)); glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); // Prepare to output lines. diff --git a/Outputs/OpenGL/ScanTarget.hpp b/Outputs/OpenGL/ScanTarget.hpp index 53fe8a5b8..669239167 100644 --- a/Outputs/OpenGL/ScanTarget.hpp +++ b/Outputs/OpenGL/ScanTarget.hpp @@ -179,7 +179,7 @@ class ScanTarget: public Outputs::Display::ScanTarget { std::unique_ptr input_shader_; std::unique_ptr output_shader_; - static std::unique_ptr composition_shader(); + static std::unique_ptr composition_shader(InputDataType input_data_type); static std::unique_ptr conversion_shader(InputDataType input_data_type, DisplayType display_type, int colour_cycle_numerator, int colour_cycle_denominator, int processing_width); }; diff --git a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp index fe4abbf1e..e33177639 100644 --- a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp +++ b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp @@ -90,7 +90,7 @@ std::string ScanTarget::glsl_default_vertex_shader(ShaderType type) { if(type == ShaderType::InputScan) { result += "out vec2 textureCoordinate;" - "uniform sampler2D textureName;"; + "uniform usampler2D textureName;"; } else { result += "out vec2 textureCoordinates[15];" @@ -240,7 +240,7 @@ void ScanTarget::enable_vertex_attributes(ShaderType type, Shader &target) { } } -std::unique_ptr ScanTarget::composition_shader() { +std::unique_ptr ScanTarget::composition_shader(InputDataType input_data_type) { /* std::string fragment_shader = "#version 150\n" @@ -350,21 +350,50 @@ std::unique_ptr ScanTarget::composition_shader() { // } - const std::string fragment_shader = + std::string fragment_shader = "#version 150\n" - "in vec2 textureCoordinate;" "out vec4 fragColour;" + "in vec2 textureCoordinate;" - "uniform sampler2D textureName;" + "uniform usampler2D textureName;" - "void main(void) {" - "fragColour = vec4(1.0) - texture(textureName, textureCoordinate);" - "}"; + "void main(void) {"; + + switch(input_data_type) { + case InputDataType::Luminance1: + fragment_shader += "fragColour = texture(textureName, textureCoordinate).rrrr;"; + break; + case InputDataType::Luminance8: + fragment_shader += "fragColour = texture(textureName, textureCoordinate).rrrr / vec4(255.0);"; + break; + + case InputDataType::PhaseLinkedLuminance8: + case InputDataType::Luminance8Phase8: + case InputDataType::Red8Green8Blue8: + fragment_shader += "fragColour = texture(textureName, textureCoordinate) / vec4(255.0);"; + break; + + case InputDataType::Red1Green1Blue1: + fragment_shader += "fragColour = vec4(texture(textureName, textureCoordinate).rrr & uvec3(4u, 2u, 1u), 1.0);"; + break; + + case InputDataType::Red2Green2Blue2: + fragment_shader += + "uint textureValue = texture(textureName, textureCoordinate).r;" + "fragColour = vec4(float((textureValue >> 4) & 3u), float((textureValue >> 2) & 3u), float(textureValue & 3u), 3.0) / 3.0;"; + break; + + case InputDataType::Red4Green4Blue4: + fragment_shader += + "uvec2 textureValue = texture(textureName, textureCoordinate).rg;" + "fragColour = vec4(float(textureValue.r) / 15.0, float(textureValue.g & 240u) / 240.0, float(textureValue.g & 15u) / 15.0, 1.0);"; + break; + } return std::unique_ptr(new Shader( glsl_globals(ShaderType::InputScan) + glsl_default_vertex_shader(ShaderType::InputScan), - fragment_shader, + fragment_shader + "}", attribute_bindings(ShaderType::InputScan) )); } From e9d9ff0da0d4d76177c3326b3ef59c29f1ae3c7f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 5 Jan 2019 23:09:17 -0500 Subject: [PATCH 03/16] Enhances ScanTarget to provide additional timing information. --- .../xcschemes/Clock Signal.xcscheme | 2 +- Outputs/CRT/CRT.cpp | 33 ++++++++++++------- Outputs/CRT/CRT.hpp | 2 ++ Outputs/OpenGL/ScanTarget.cpp | 10 +++--- Outputs/OpenGL/ScanTarget.hpp | 2 +- Outputs/ScanTarget.hpp | 13 ++++++-- 6 files changed, 41 insertions(+), 21 deletions(-) diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme index 782b17c09..f9049689f 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme @@ -68,7 +68,7 @@ get_next_event_in_period(hsync_is_requested, cycles_to_run_for, cycles_advanced); } +Outputs::Display::ScanTarget::Scan::EndPoint CRT::end_point(uint16_t data_offset) { + Display::ScanTarget::Scan::EndPoint end_point; + + end_point.x = uint16_t(horizontal_flywheel_->get_current_output_position()); + end_point.y = uint16_t(vertical_flywheel_->get_current_output_position() / vertical_flywheel_output_divider_); + end_point.composite_angle = int16_t((phase_numerator_ << 6) / phase_denominator_) * (is_alernate_line_ ? -1 : 1); + end_point.data_offset = data_offset; + end_point.cycles_since_end_of_horizontal_retrace = uint16_t(cycles_since_horizontal_sync_ / time_multiplier_); + + return end_point; +} + void CRT::advance_cycles(int number_of_cycles, bool hsync_requested, bool vsync_requested, const Scan::Type type, int number_of_samples) { number_of_cycles *= time_multiplier_; @@ -186,16 +198,14 @@ void CRT::advance_cycles(int number_of_cycles, bool hsync_requested, bool vsync_ // If outputting, store the start location and scan constants. if(next_scan) { - next_scan->end_points[0].x = uint16_t(horizontal_flywheel_->get_current_output_position()); - next_scan->end_points[0].y = uint16_t(vertical_flywheel_->get_current_output_position() / vertical_flywheel_output_divider_); - next_scan->end_points[0].composite_angle = int16_t((phase_numerator_ << 6) / phase_denominator_) * (is_alernate_line_ ? -1 : 1); - next_scan->end_points[0].data_offset = uint16_t((total_cycles - number_of_cycles) * number_of_samples / total_cycles); + next_scan->end_points[0] = end_point(uint16_t((total_cycles - number_of_cycles) * number_of_samples / total_cycles)); next_scan->composite_amplitude = colour_burst_amplitude_; } // Advance time: that'll affect both the colour subcarrier position and the number of cycles left to run. phase_numerator_ += next_run_length * colour_cycle_numerator_; number_of_cycles -= next_run_length; + cycles_since_horizontal_sync_ += next_run_length; // React to the incoming event. horizontal_flywheel_->apply_event(next_run_length, (next_run_length == time_until_horizontal_sync_event) ? next_horizontal_sync_event : Flywheel::SyncEvent::None); @@ -203,10 +213,7 @@ void CRT::advance_cycles(int number_of_cycles, bool hsync_requested, bool vsync_ // End the scan if necessary. if(next_scan) { - next_scan->end_points[1].x = uint16_t(horizontal_flywheel_->get_current_output_position()); - next_scan->end_points[1].y = uint16_t(vertical_flywheel_->get_current_output_position() / vertical_flywheel_output_divider_); - next_scan->end_points[1].composite_angle = int16_t((phase_numerator_ << 6) / phase_denominator_) * (is_alernate_line_ ? -1 : 1); - next_scan->end_points[1].data_offset = uint16_t((total_cycles - number_of_cycles) * number_of_samples / total_cycles); + next_scan->end_points[1] = end_point(uint16_t((total_cycles - number_of_cycles) * number_of_samples / total_cycles)); scan_target_->end_scan(); } @@ -217,13 +224,15 @@ void CRT::advance_cycles(int number_of_cycles, bool hsync_requested, bool vsync_ ? Outputs::Display::ScanTarget::Event::BeginHorizontalRetrace : Outputs::Display::ScanTarget::Event::EndHorizontalRetrace; scan_target_->announce( event, - uint16_t(horizontal_flywheel_->get_current_output_position()), - uint16_t(vertical_flywheel_->get_current_output_position() / vertical_flywheel_output_divider_)); + !(horizontal_flywheel_->is_in_retrace() || vertical_flywheel_->is_in_retrace()), + end_point(uint16_t((total_cycles - number_of_cycles) * number_of_samples / total_cycles))); // Prepare for the next line. if(next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace) { is_alernate_line_ ^= phase_alternates_; colour_burst_amplitude_ = 0; + } else { + cycles_since_horizontal_sync_ = 0; } } @@ -234,8 +243,8 @@ void CRT::advance_cycles(int number_of_cycles, bool hsync_requested, bool vsync_ ? Outputs::Display::ScanTarget::Event::BeginVerticalRetrace : Outputs::Display::ScanTarget::Event::EndVerticalRetrace; scan_target_->announce( event, - uint16_t(horizontal_flywheel_->get_current_output_position()), - uint16_t(vertical_flywheel_->get_current_output_position() / vertical_flywheel_output_divider_)); + !(horizontal_flywheel_->is_in_retrace() || vertical_flywheel_->is_in_retrace()), + end_point(uint16_t((total_cycles - number_of_cycles) * number_of_samples / total_cycles))); } // if this is vertical retrace then adcance a field diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index c0ecc6b0d..89b76bd76 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -41,6 +41,8 @@ class CRT { // posted on to the scan target. std::unique_ptr horizontal_flywheel_, vertical_flywheel_; int vertical_flywheel_output_divider_ = 1; + int cycles_since_horizontal_sync_ = 0; + Display::ScanTarget::Scan::EndPoint end_point(uint16_t data_offset); struct Scan { enum Type { diff --git a/Outputs/OpenGL/ScanTarget.cpp b/Outputs/OpenGL/ScanTarget.cpp index 787b471c5..ce9440dbb 100644 --- a/Outputs/OpenGL/ScanTarget.cpp +++ b/Outputs/OpenGL/ScanTarget.cpp @@ -222,13 +222,13 @@ void ScanTarget::submit() { allocation_has_failed_ = false; } -void ScanTarget::announce(Event event, uint16_t x, uint16_t y) { +void ScanTarget::announce(Event event, bool is_visible, const Outputs::Display::ScanTarget::Scan::EndPoint &location) { switch(event) { default: break; case ScanTarget::Event::BeginHorizontalRetrace: if(active_line_) { - active_line_->end_points[1].x = x; - active_line_->end_points[1].y = y; + active_line_->end_points[1].x = location.x; + active_line_->end_points[1].y = location.y; } break; case ScanTarget::Event::EndHorizontalRetrace: { @@ -257,8 +257,8 @@ void ScanTarget::announce(Event event, uint16_t x, uint16_t y) { } if(active_line_) { - active_line_->end_points[0].x = x; - active_line_->end_points[0].y = y; + active_line_->end_points[0].x = location.x; + active_line_->end_points[0].y = location.y; active_line_->line = write_pointers_.line; } } break; diff --git a/Outputs/OpenGL/ScanTarget.hpp b/Outputs/OpenGL/ScanTarget.hpp index 669239167..db549a2d1 100644 --- a/Outputs/OpenGL/ScanTarget.hpp +++ b/Outputs/OpenGL/ScanTarget.hpp @@ -53,7 +53,7 @@ class ScanTarget: public Outputs::Display::ScanTarget { uint8_t *begin_data(size_t required_length, size_t required_alignment) override; void end_data(size_t actual_length) override; void submit() override; - void announce(Event event, uint16_t x, uint16_t y) override; + void announce(Event event, bool is_visible, const Outputs::Display::ScanTarget::Scan::EndPoint &location) override; // Extends the definition of a Scan to include two extra fields, // relevant to the way that this scan target processes video. diff --git a/Outputs/ScanTarget.hpp b/Outputs/ScanTarget.hpp index 28c4c151e..9443efd3c 100644 --- a/Outputs/ScanTarget.hpp +++ b/Outputs/ScanTarget.hpp @@ -228,6 +228,9 @@ struct ScanTarget { /// /// It will produce undefined behaviour if signs differ on a single scan. int16_t composite_angle; + + /// Gives the number of cycles since the most recent horizontal retrace ended. + uint16_t cycles_since_end_of_horizontal_retrace; } end_points[2]; /// For composite video, dictates the amplitude of the colour subcarrier as a proportion of @@ -284,8 +287,14 @@ struct ScanTarget { EndVerticalRetrace, }; - /// Provides a hint that the named event has occurred. - virtual void announce(Event event, uint16_t x, uint16_t y) {} + /*! + Provides a hint that the named event has occurred. + + @param event The event. + @param is_visible @c true if the output stream is visible immediately after this event; @c false otherwise. + @param location The location of the event. + */ + virtual void announce(Event event, bool is_visible, const Scan::EndPoint &location) {} }; /*! From c392c819c118f3e69ed5a79bcf0002966f83333c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 6 Jan 2019 13:37:34 -0500 Subject: [PATCH 04/16] Switches to using the `announce` `is_visible` flag to spot line ends. --- Outputs/OpenGL/ScanTarget.cpp | 86 +++++++++++++++++------------------ Outputs/OpenGL/ScanTarget.hpp | 2 + 2 files changed, 43 insertions(+), 45 deletions(-) diff --git a/Outputs/OpenGL/ScanTarget.cpp b/Outputs/OpenGL/ScanTarget.cpp index ce9440dbb..0eab6bff8 100644 --- a/Outputs/OpenGL/ScanTarget.cpp +++ b/Outputs/OpenGL/ScanTarget.cpp @@ -223,53 +223,49 @@ void ScanTarget::submit() { } void ScanTarget::announce(Event event, bool is_visible, const Outputs::Display::ScanTarget::Scan::EndPoint &location) { - switch(event) { - default: break; - case ScanTarget::Event::BeginHorizontalRetrace: - if(active_line_) { - active_line_->end_points[1].x = location.x; - active_line_->end_points[1].y = location.y; - } - break; - case ScanTarget::Event::EndHorizontalRetrace: { - // Commit the most recent line only if any scans fell on it. - // Otherwise there's no point outputting it, it'll contribute nothing. - if(provided_scans_) { - // Store metadata if concluding a previous line. - if(active_line_) { - line_metadata_buffer_[size_t(write_pointers_.line)].is_first_in_frame = is_first_in_frame_; - line_metadata_buffer_[size_t(write_pointers_.line)].previous_frame_was_complete = frame_was_complete_; - is_first_in_frame_ = false; - } - - const auto read_pointers = read_pointers_.load(); - - // Attempt to allocate a new line; note allocation failure if necessary. - const auto next_line = uint16_t((write_pointers_.line + 1) % LineBufferHeight); - if(next_line == read_pointers.line) { - allocation_has_failed_ = true; - active_line_ = nullptr; - } else { - write_pointers_.line = next_line; - active_line_ = &line_buffer_[size_t(write_pointers_.line)]; - } - provided_scans_ = 0; - } - - if(active_line_) { - active_line_->end_points[0].x = location.x; - active_line_->end_points[0].y = location.y; - active_line_->line = write_pointers_.line; - } - } break; - case ScanTarget::Event::EndVerticalRetrace: - is_first_in_frame_ = true; - frame_was_complete_ = true; - break; + if(event == ScanTarget::Event::EndVerticalRetrace) { + is_first_in_frame_ = true; + frame_was_complete_ = true; } - // TODO: any lines that include any portion of vertical sync should be hidden. - // (maybe set a flag and zero out the line coordinates?) + if(output_is_visible_ == is_visible) return; + if(is_visible) { + // Commit the most recent line only if any scans fell on it. + // Otherwise there's no point outputting it, it'll contribute nothing. + if(provided_scans_) { + // Store metadata if concluding a previous line. + if(active_line_) { + line_metadata_buffer_[size_t(write_pointers_.line)].is_first_in_frame = is_first_in_frame_; + line_metadata_buffer_[size_t(write_pointers_.line)].previous_frame_was_complete = frame_was_complete_; + is_first_in_frame_ = false; + } + + const auto read_pointers = read_pointers_.load(); + + // Attempt to allocate a new line; note allocation failure if necessary. + const auto next_line = uint16_t((write_pointers_.line + 1) % LineBufferHeight); + if(next_line == read_pointers.line) { + allocation_has_failed_ = true; + active_line_ = nullptr; + } else { + write_pointers_.line = next_line; + active_line_ = &line_buffer_[size_t(write_pointers_.line)]; + } + provided_scans_ = 0; + } + + if(active_line_) { + active_line_->end_points[0].x = location.x; + active_line_->end_points[0].y = location.y; + active_line_->line = write_pointers_.line; + } + } else { + if(active_line_) { + active_line_->end_points[1].x = location.x; + active_line_->end_points[1].y = location.y; + } + } + output_is_visible_ = is_visible; } void ScanTarget::setup_pipeline() { diff --git a/Outputs/OpenGL/ScanTarget.hpp b/Outputs/OpenGL/ScanTarget.hpp index db549a2d1..22ad7c390 100644 --- a/Outputs/OpenGL/ScanTarget.hpp +++ b/Outputs/OpenGL/ScanTarget.hpp @@ -55,6 +55,8 @@ class ScanTarget: public Outputs::Display::ScanTarget { void submit() override; void announce(Event event, bool is_visible, const Outputs::Display::ScanTarget::Scan::EndPoint &location) override; + bool output_is_visible_ = false; + // Extends the definition of a Scan to include two extra fields, // relevant to the way that this scan target processes video. struct Scan { From 248a8efd2f12ffc3a60dd7448f61562d926a7113 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 6 Jan 2019 16:32:13 -0500 Subject: [PATCH 05/16] Corrects declared pixel clock GCD. --- Components/6560/6560.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Components/6560/6560.hpp b/Components/6560/6560.hpp index c1dcf85e1..eaa5264c8 100644 --- a/Components/6560/6560.hpp +++ b/Components/6560/6560.hpp @@ -64,7 +64,7 @@ template class MOS6560 { public: MOS6560(BusHandler &bus_handler) : bus_handler_(bus_handler), - crt_(65*4, 4, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Luminance8Phase8), + crt_(65*4, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Luminance8Phase8), audio_generator_(audio_queue_), speaker_(audio_generator_) { From 906a2ff6ebe2407b648c6bfbaa620de572299597 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 6 Jan 2019 18:47:01 -0500 Subject: [PATCH 06/16] Switches to using clock times for buffer merging and output. --- Outputs/CRT/CRT.cpp | 17 ++++----- Outputs/OpenGL/ScanTarget.cpp | 2 ++ Outputs/OpenGL/ScanTarget.hpp | 1 + Outputs/OpenGL/ScanTargetGLSLFragments.cpp | 40 +++++++++++++++++----- 4 files changed, 43 insertions(+), 17 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 48999b4ca..9a19d9369 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -219,14 +219,6 @@ void CRT::advance_cycles(int number_of_cycles, bool hsync_requested, bool vsync_ // Announce horizontal retrace events. if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event != Flywheel::SyncEvent::None) { - const auto event = - (next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace) - ? Outputs::Display::ScanTarget::Event::BeginHorizontalRetrace : Outputs::Display::ScanTarget::Event::EndHorizontalRetrace; - scan_target_->announce( - event, - !(horizontal_flywheel_->is_in_retrace() || vertical_flywheel_->is_in_retrace()), - end_point(uint16_t((total_cycles - number_of_cycles) * number_of_samples / total_cycles))); - // Prepare for the next line. if(next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace) { is_alernate_line_ ^= phase_alternates_; @@ -234,6 +226,15 @@ void CRT::advance_cycles(int number_of_cycles, bool hsync_requested, bool vsync_ } else { cycles_since_horizontal_sync_ = 0; } + + // Announce event. + const auto event = + (next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace) + ? Outputs::Display::ScanTarget::Event::BeginHorizontalRetrace : Outputs::Display::ScanTarget::Event::EndHorizontalRetrace; + scan_target_->announce( + event, + !(horizontal_flywheel_->is_in_retrace() || vertical_flywheel_->is_in_retrace()), + end_point(uint16_t((total_cycles - number_of_cycles) * number_of_samples / total_cycles))); } // Also announce vertical retrace events. diff --git a/Outputs/OpenGL/ScanTarget.cpp b/Outputs/OpenGL/ScanTarget.cpp index 0eab6bff8..932f91f8d 100644 --- a/Outputs/OpenGL/ScanTarget.cpp +++ b/Outputs/OpenGL/ScanTarget.cpp @@ -257,12 +257,14 @@ void ScanTarget::announce(Event event, bool is_visible, const Outputs::Display:: if(active_line_) { active_line_->end_points[0].x = location.x; active_line_->end_points[0].y = location.y; + active_line_->end_points[0].cycles_since_end_of_horizontal_retrace = location.cycles_since_end_of_horizontal_retrace; active_line_->line = write_pointers_.line; } } else { if(active_line_) { active_line_->end_points[1].x = location.x; active_line_->end_points[1].y = location.y; + active_line_->end_points[1].cycles_since_end_of_horizontal_retrace = location.cycles_since_end_of_horizontal_retrace; } } output_is_visible_ = is_visible; diff --git a/Outputs/OpenGL/ScanTarget.hpp b/Outputs/OpenGL/ScanTarget.hpp index 22ad7c390..8e60775a2 100644 --- a/Outputs/OpenGL/ScanTarget.hpp +++ b/Outputs/OpenGL/ScanTarget.hpp @@ -95,6 +95,7 @@ class ScanTarget: public Outputs::Display::ScanTarget { struct Line { struct EndPoint { uint16_t x, y; + uint16_t cycles_since_end_of_horizontal_retrace; } end_points[2]; uint16_t line; }; diff --git a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp index e33177639..09a725471 100644 --- a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp +++ b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp @@ -28,10 +28,12 @@ std::string ScanTarget::glsl_globals(ShaderType type) { "in vec2 startPoint;" "in float startDataX;" "in float startCompositeAngle;" + "in float startClock;" "in vec2 endPoint;" "in float endDataX;" "in float endCompositeAngle;" + "in float endClock;" "in float dataY;" "in float lineY;" @@ -48,6 +50,9 @@ std::string ScanTarget::glsl_globals(ShaderType type) { "in vec2 startPoint;" "in vec2 endPoint;" + "in float startClock;" + "in float endClock;" + "in float lineY;" "uniform sampler2D textureName;" @@ -64,19 +69,23 @@ std::vector ScanTarget::attribute_bindings(ShaderType {"startPoint", 0}, {"startDataX", 1}, {"startCompositeAngle", 2}, - {"endPoint", 3}, - {"endDataX", 4}, - {"endCompositeAngle", 5}, - {"dataY", 6}, - {"lineY", 7}, - {"compositeAmplitude", 8}, + {"startClock", 3}, + {"endPoint", 4}, + {"endDataX", 5}, + {"endCompositeAngle", 6}, + {"endClock", 7}, + {"dataY", 8}, + {"lineY", 9}, + {"compositeAmplitude", 10}, }; case ShaderType::Line: return { {"startPoint", 0}, {"endPoint", 1}, - {"lineY", 2}, + {"startClock", 2}, + {"endClock", 3}, + {"lineY", 4}, }; } } @@ -115,7 +124,7 @@ std::string ScanTarget::glsl_default_vertex_shader(ShaderType type) { if(type == ShaderType::InputScan) { result += "textureCoordinate = vec2(mix(startDataX, endDataX, lateral), dataY + 0.5) / textureSize(textureName, 0);" - "vec2 eyePosition = vec2(mix(startPoint.x, endPoint.x, lateral) * processingWidth, lineY + longitudinal) / vec2(scale.x, 2048.0);"; + "vec2 eyePosition = vec2(mix(startClock, endClock, lateral), lineY + longitudinal) / vec2(2048.0, 2048.0);"; } else { result += "vec2 sourcePosition = vec2(mix(startPoint.x, endPoint.x, lateral) * processingWidth, lineY + 0.5);" @@ -161,7 +170,7 @@ std::string ScanTarget::glsl_default_vertex_shader(ShaderType type) { "float lateral = float(gl_VertexID & 1);" "float longitudinal = float((gl_VertexID & 2) >> 1);" - "textureCoordinate = vec2(lateral * processingWidth, lineY + 0.5) / vec2(1.0, textureSize(textureName, 0).y);" + "textureCoordinate = vec2(mix(startClock, endClock, lateral), lineY + 0.5) / textureSize(textureName, 0);" "vec2 centrePoint = mix(startPoint, endPoint, lateral) / scale;" "vec2 height = normalize(endPoint - startPoint).yx * (longitudinal - 0.5) * rowHeight;" @@ -196,6 +205,12 @@ void ScanTarget::enable_vertex_attributes(ShaderType type, Shader &target) { sizeof(Scan), reinterpret_cast(offsetof(Scan, scan.end_points[c].composite_angle)), 1); + target.enable_vertex_attribute_with_pointer( + prefix + "Clock", + 1, GL_UNSIGNED_SHORT, GL_FALSE, + sizeof(Scan), + reinterpret_cast(offsetof(Scan, scan.end_points[c].cycles_since_end_of_horizontal_retrace)), + 1); } target.enable_vertex_attribute_with_pointer( @@ -228,6 +243,13 @@ void ScanTarget::enable_vertex_attributes(ShaderType type, Shader &target) { sizeof(Line), reinterpret_cast(offsetof(Line, end_points[c].x)), 1); + + target.enable_vertex_attribute_with_pointer( + prefix + "Clock", + 1, GL_UNSIGNED_SHORT, GL_FALSE, + sizeof(Line), + reinterpret_cast(offsetof(Line, end_points[c].cycles_since_end_of_horizontal_retrace)), + 1); } target.enable_vertex_attribute_with_pointer( From 028e530232a2714765079b8e9b9859fdff9bce52 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 6 Jan 2019 22:59:14 -0500 Subject: [PATCH 07/16] Shunts output shader to its proper place. --- Outputs/OpenGL/ScanTarget.cpp | 32 +--------------------- Outputs/OpenGL/ScanTargetGLSLFragments.cpp | 31 ++++++++++++++++++++- 2 files changed, 31 insertions(+), 32 deletions(-) diff --git a/Outputs/OpenGL/ScanTarget.cpp b/Outputs/OpenGL/ScanTarget.cpp index 932f91f8d..ca5aeca37 100644 --- a/Outputs/OpenGL/ScanTarget.cpp +++ b/Outputs/OpenGL/ScanTarget.cpp @@ -287,21 +287,7 @@ void ScanTarget::setup_pipeline() { processing_width_ = modals_.cycles_per_line / modals_.clocks_per_pixel_greatest_common_divisor; // Establish an output shader. TODO: add proper decoding and gamma correction here. - output_shader_.reset(new Shader( - glsl_globals(ShaderType::Line) + glsl_default_vertex_shader(ShaderType::Line), - "#version 150\n" - - "out vec4 fragColour;" - "in vec2 textureCoordinate;" - - "uniform sampler2D textureName;" - - "void main(void) {" - "fragColour = vec4(texture(textureName, textureCoordinate).rgb, 0.64);" - "}", - attribute_bindings(ShaderType::Line) - )); - + output_shader_ = conversion_shader(modals_.input_data_type, modals_.display_type, modals_.colour_cycle_numerator, modals_.colour_cycle_denominator, processing_width_); glBindVertexArray(line_vertex_array_); glBindBuffer(GL_ARRAY_BUFFER, line_buffer_name_); enable_vertex_attributes(ShaderType::Line, *output_shader_); @@ -310,22 +296,6 @@ void ScanTarget::setup_pipeline() { output_shader_->set_uniform("size", modals_.visible_area.size.width, modals_.visible_area.size.height); output_shader_->set_uniform("textureName", GLint(UnprocessedLineBufferTextureUnit - GL_TEXTURE0)); -// switch(modals_.composite_colour_space) { -// case ColourSpace::YIQ: { -// const GLfloat rgbToYIQ[] = {0.299f, 0.596f, 0.211f, 0.587f, -0.274f, -0.523f, 0.114f, -0.322f, 0.312f}; -// const GLfloat yiqToRGB[] = {1.0f, 1.0f, 1.0f, 0.956f, -0.272f, -1.106f, 0.621f, -0.647f, 1.703f}; -// shader->set_uniform_matrix("lumaChromaToRGB", 3, false, yiqToRGB); -// shader->set_uniform_matrix("rgbToLumaChroma", 3, false, rgbToYIQ); -// } break; -// -// case ColourSpace::YUV: { -// const GLfloat rgbToYUV[] = {0.299f, -0.14713f, 0.615f, 0.587f, -0.28886f, -0.51499f, 0.114f, 0.436f, -0.10001f}; -// const GLfloat yuvToRGB[] = {1.0f, 1.0f, 1.0f, 0.0f, -0.39465f, 2.03211f, 1.13983f, -0.58060f, 0.0f}; -// shader->set_uniform_matrix("lumaChromaToRGB", 3, false, yuvToRGB); -// shader->set_uniform_matrix("rgbToLumaChroma", 3, false, rgbToYUV); -// } break; -// } - // Establish an input shader. input_shader_ = composition_shader(modals_.input_data_type); glBindVertexArray(scan_vertex_array_); diff --git a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp index 09a725471..c517d114c 100644 --- a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp +++ b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp @@ -421,7 +421,36 @@ std::unique_ptr ScanTarget::composition_shader(InputDataType input_data_ } std::unique_ptr ScanTarget::conversion_shader(InputDataType input_data_type, DisplayType display_type, int colour_cycle_numerator, int colour_cycle_denominator, int processing_width) { - return nullptr; + return std::unique_ptr(new Shader( + glsl_globals(ShaderType::Line) + glsl_default_vertex_shader(ShaderType::Line), + "#version 150\n" + + "out vec4 fragColour;" + "in vec2 textureCoordinate;" + + "uniform sampler2D textureName;" + + "void main(void) {" + "fragColour = vec4(texture(textureName, textureCoordinate).rgb, 0.64);" + "}", + attribute_bindings(ShaderType::Line) + )); + +// switch(modals_.composite_colour_space) { +// case ColourSpace::YIQ: { +// const GLfloat rgbToYIQ[] = {0.299f, 0.596f, 0.211f, 0.587f, -0.274f, -0.523f, 0.114f, -0.322f, 0.312f}; +// const GLfloat yiqToRGB[] = {1.0f, 1.0f, 1.0f, 0.956f, -0.272f, -1.106f, 0.621f, -0.647f, 1.703f}; +// shader->set_uniform_matrix("lumaChromaToRGB", 3, false, yiqToRGB); +// shader->set_uniform_matrix("rgbToLumaChroma", 3, false, rgbToYIQ); +// } break; +// +// case ColourSpace::YUV: { +// const GLfloat rgbToYUV[] = {0.299f, -0.14713f, 0.615f, 0.587f, -0.28886f, -0.51499f, 0.114f, 0.436f, -0.10001f}; +// const GLfloat yuvToRGB[] = {1.0f, 1.0f, 1.0f, 0.0f, -0.39465f, 2.03211f, 1.13983f, -0.58060f, 0.0f}; +// shader->set_uniform_matrix("lumaChromaToRGB", 3, false, yuvToRGB); +// shader->set_uniform_matrix("rgbToLumaChroma", 3, false, rgbToYUV); +// } break; +// } } // From ccb52fb6250843621aaed7fac5b6681d6e52ba78 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 11 Jan 2019 22:00:44 -0500 Subject: [PATCH 08/16] Ensures no writes to `pixel_pointer_` when allocation has failed. --- Machines/AppleII/Video.hpp | 133 +++++++++++++++++++------------------ 1 file changed, 68 insertions(+), 65 deletions(-) diff --git a/Machines/AppleII/Video.hpp b/Machines/AppleII/Video.hpp index 4c9ffe85b..387c247d0 100644 --- a/Machines/AppleII/Video.hpp +++ b/Machines/AppleII/Video.hpp @@ -394,7 +394,6 @@ template class Video: public VideoBase { static_cast(fetch_end - column_), &base_stream_[static_cast(column_)], &auxiliary_stream_[static_cast(column_)]); - // TODO: should character modes be mapped to character pixel outputs here? } if(row_ < 192) { @@ -413,7 +412,7 @@ template class Video: public VideoBase { const int pixel_row = row_ & 7; const bool is_double = Video::is_double_mode(line_mode); - if(!is_double && was_double_) { + if(!is_double && was_double_ && pixel_pointer_) { pixel_pointer_[pixel_start*14 + 0] = pixel_pointer_[pixel_start*14 + 1] = pixel_pointer_[pixel_start*14 + 2] = @@ -424,79 +423,83 @@ template class Video: public VideoBase { } was_double_ = is_double; - switch(line_mode) { - case GraphicsMode::Text: - output_text( - &pixel_pointer_[pixel_start * 14 + 7], - &base_stream_[static_cast(pixel_start)], - static_cast(pixel_end - pixel_start), - static_cast(pixel_row)); - break; + if(pixel_pointer_) { + switch(line_mode) { + case GraphicsMode::Text: + output_text( + &pixel_pointer_[pixel_start * 14 + 7], + &base_stream_[static_cast(pixel_start)], + static_cast(pixel_end - pixel_start), + static_cast(pixel_row)); + break; - case GraphicsMode::DoubleText: - output_double_text( - &pixel_pointer_[pixel_start * 14], - &base_stream_[static_cast(pixel_start)], - &auxiliary_stream_[static_cast(pixel_start)], - static_cast(pixel_end - pixel_start), - static_cast(pixel_row)); - break; + case GraphicsMode::DoubleText: + output_double_text( + &pixel_pointer_[pixel_start * 14], + &base_stream_[static_cast(pixel_start)], + &auxiliary_stream_[static_cast(pixel_start)], + static_cast(pixel_end - pixel_start), + static_cast(pixel_row)); + break; - case GraphicsMode::LowRes: - output_low_resolution( - &pixel_pointer_[pixel_start * 14 + 7], - &base_stream_[static_cast(pixel_start)], - static_cast(pixel_end - pixel_start), - pixel_start, - pixel_row); - break; + case GraphicsMode::LowRes: + output_low_resolution( + &pixel_pointer_[pixel_start * 14 + 7], + &base_stream_[static_cast(pixel_start)], + static_cast(pixel_end - pixel_start), + pixel_start, + pixel_row); + break; - case GraphicsMode::FatLowRes: - output_fat_low_resolution( - &pixel_pointer_[pixel_start * 14 + 7], - &base_stream_[static_cast(pixel_start)], - static_cast(pixel_end - pixel_start), - pixel_start, - pixel_row); - break; + case GraphicsMode::FatLowRes: + output_fat_low_resolution( + &pixel_pointer_[pixel_start * 14 + 7], + &base_stream_[static_cast(pixel_start)], + static_cast(pixel_end - pixel_start), + pixel_start, + pixel_row); + break; - case GraphicsMode::DoubleLowRes: - output_double_low_resolution( - &pixel_pointer_[pixel_start * 14], - &base_stream_[static_cast(pixel_start)], - &auxiliary_stream_[static_cast(pixel_start)], - static_cast(pixel_end - pixel_start), - pixel_start, - pixel_row); - break; + case GraphicsMode::DoubleLowRes: + output_double_low_resolution( + &pixel_pointer_[pixel_start * 14], + &base_stream_[static_cast(pixel_start)], + &auxiliary_stream_[static_cast(pixel_start)], + static_cast(pixel_end - pixel_start), + pixel_start, + pixel_row); + break; - case GraphicsMode::HighRes: - output_high_resolution( - &pixel_pointer_[pixel_start * 14 + 7], - &base_stream_[static_cast(pixel_start)], - static_cast(pixel_end - pixel_start)); - break; + case GraphicsMode::HighRes: + output_high_resolution( + &pixel_pointer_[pixel_start * 14 + 7], + &base_stream_[static_cast(pixel_start)], + static_cast(pixel_end - pixel_start)); + break; - case GraphicsMode::DoubleHighRes: - output_double_high_resolution( - &pixel_pointer_[pixel_start * 14], - &base_stream_[static_cast(pixel_start)], - &auxiliary_stream_[static_cast(pixel_start)], - static_cast(pixel_end - pixel_start)); - break; + case GraphicsMode::DoubleHighRes: + output_double_high_resolution( + &pixel_pointer_[pixel_start * 14], + &base_stream_[static_cast(pixel_start)], + &auxiliary_stream_[static_cast(pixel_start)], + static_cast(pixel_end - pixel_start)); + break; - default: break; + default: break; + } } if(pixel_end == 40) { - if(was_double_) { - pixel_pointer_[560] = pixel_pointer_[561] = pixel_pointer_[562] = pixel_pointer_[563] = - pixel_pointer_[564] = pixel_pointer_[565] = pixel_pointer_[566] = pixel_pointer_[567] = 0; - } else { - if(line_mode == GraphicsMode::HighRes && base_stream_[39]&0x80) - pixel_pointer_[567] = graphics_carry_; - else - pixel_pointer_[567] = 0; + if(pixel_pointer_) { + if(was_double_) { + pixel_pointer_[560] = pixel_pointer_[561] = pixel_pointer_[562] = pixel_pointer_[563] = + pixel_pointer_[564] = pixel_pointer_[565] = pixel_pointer_[566] = pixel_pointer_[567] = 0; + } else { + if(line_mode == GraphicsMode::HighRes && base_stream_[39]&0x80) + pixel_pointer_[567] = graphics_carry_; + else + pixel_pointer_[567] = 0; + } } crt_.output_data(568, 568); From 5d9521fcb9d40f27b7f11b906db306fba7f98524 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 11 Jan 2019 22:02:15 -0500 Subject: [PATCH 09/16] Advances back to a semi-complete monochrome composite output. i.e. composite phase and amplitude is ostensibly flowing to its new destination. --- Outputs/CRT/CRT.cpp | 19 +- Outputs/OpenGL/ScanTarget.cpp | 7 +- Outputs/OpenGL/ScanTarget.hpp | 6 +- Outputs/OpenGL/ScanTargetGLSLFragments.cpp | 236 ++++++++++++++++++--- Outputs/ScanTarget.hpp | 3 +- 5 files changed, 232 insertions(+), 39 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 9a19d9369..487dc1333 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -219,11 +219,8 @@ void CRT::advance_cycles(int number_of_cycles, bool hsync_requested, bool vsync_ // Announce horizontal retrace events. if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event != Flywheel::SyncEvent::None) { - // Prepare for the next line. - if(next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace) { - is_alernate_line_ ^= phase_alternates_; - colour_burst_amplitude_ = 0; - } else { + // Reset the cycles-since-sync counter if this is the end of retrace. + if(next_horizontal_sync_event == Flywheel::SyncEvent::EndRetrace) { cycles_since_horizontal_sync_ = 0; } @@ -234,7 +231,14 @@ void CRT::advance_cycles(int number_of_cycles, bool hsync_requested, bool vsync_ scan_target_->announce( event, !(horizontal_flywheel_->is_in_retrace() || vertical_flywheel_->is_in_retrace()), - end_point(uint16_t((total_cycles - number_of_cycles) * number_of_samples / total_cycles))); + end_point(uint16_t((total_cycles - number_of_cycles) * number_of_samples / total_cycles)), + colour_burst_amplitude_); + + // If retrace is starting, update phase if required and mark no colour burst spotted yet. + if(next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace) { + is_alernate_line_ ^= phase_alternates_; + colour_burst_amplitude_ = 0; + } } // Also announce vertical retrace events. @@ -245,7 +249,8 @@ void CRT::advance_cycles(int number_of_cycles, bool hsync_requested, bool vsync_ scan_target_->announce( event, !(horizontal_flywheel_->is_in_retrace() || vertical_flywheel_->is_in_retrace()), - end_point(uint16_t((total_cycles - number_of_cycles) * number_of_samples / total_cycles))); + end_point(uint16_t((total_cycles - number_of_cycles) * number_of_samples / total_cycles)), + colour_burst_amplitude_); } // if this is vertical retrace then adcance a field diff --git a/Outputs/OpenGL/ScanTarget.cpp b/Outputs/OpenGL/ScanTarget.cpp index ca5aeca37..f1dcac4e5 100644 --- a/Outputs/OpenGL/ScanTarget.cpp +++ b/Outputs/OpenGL/ScanTarget.cpp @@ -222,7 +222,7 @@ void ScanTarget::submit() { allocation_has_failed_ = false; } -void ScanTarget::announce(Event event, bool is_visible, const Outputs::Display::ScanTarget::Scan::EndPoint &location) { +void ScanTarget::announce(Event event, bool is_visible, const Outputs::Display::ScanTarget::Scan::EndPoint &location, uint8_t composite_amplitude) { if(event == ScanTarget::Event::EndVerticalRetrace) { is_first_in_frame_ = true; frame_was_complete_ = true; @@ -258,13 +258,16 @@ void ScanTarget::announce(Event event, bool is_visible, const Outputs::Display:: active_line_->end_points[0].x = location.x; active_line_->end_points[0].y = location.y; active_line_->end_points[0].cycles_since_end_of_horizontal_retrace = location.cycles_since_end_of_horizontal_retrace; + active_line_->end_points[0].composite_angle = location.composite_angle; active_line_->line = write_pointers_.line; + active_line_->composite_amplitude = composite_amplitude; } } else { if(active_line_) { active_line_->end_points[1].x = location.x; active_line_->end_points[1].y = location.y; active_line_->end_points[1].cycles_since_end_of_horizontal_retrace = location.cycles_since_end_of_horizontal_retrace; + active_line_->end_points[1].composite_angle = location.composite_angle; } } output_is_visible_ = is_visible; @@ -287,7 +290,7 @@ void ScanTarget::setup_pipeline() { processing_width_ = modals_.cycles_per_line / modals_.clocks_per_pixel_greatest_common_divisor; // Establish an output shader. TODO: add proper decoding and gamma correction here. - output_shader_ = conversion_shader(modals_.input_data_type, modals_.display_type, modals_.colour_cycle_numerator, modals_.colour_cycle_denominator, processing_width_); + output_shader_ = conversion_shader(modals_.input_data_type, modals_.display_type, modals_.composite_colour_space); glBindVertexArray(line_vertex_array_); glBindBuffer(GL_ARRAY_BUFFER, line_buffer_name_); enable_vertex_attributes(ShaderType::Line, *output_shader_); diff --git a/Outputs/OpenGL/ScanTarget.hpp b/Outputs/OpenGL/ScanTarget.hpp index 8e60775a2..a5a79639c 100644 --- a/Outputs/OpenGL/ScanTarget.hpp +++ b/Outputs/OpenGL/ScanTarget.hpp @@ -53,7 +53,7 @@ class ScanTarget: public Outputs::Display::ScanTarget { uint8_t *begin_data(size_t required_length, size_t required_alignment) override; void end_data(size_t actual_length) override; void submit() override; - void announce(Event event, bool is_visible, const Outputs::Display::ScanTarget::Scan::EndPoint &location) override; + void announce(Event event, bool is_visible, const Outputs::Display::ScanTarget::Scan::EndPoint &location, uint8_t colour_burst_amplitude) override; bool output_is_visible_ = false; @@ -96,8 +96,10 @@ class ScanTarget: public Outputs::Display::ScanTarget { struct EndPoint { uint16_t x, y; uint16_t cycles_since_end_of_horizontal_retrace; + int16_t composite_angle; } end_points[2]; uint16_t line; + uint8_t composite_amplitude; }; struct LineMetadata { bool is_first_in_frame; @@ -183,7 +185,7 @@ class ScanTarget: public Outputs::Display::ScanTarget { std::unique_ptr output_shader_; static std::unique_ptr composition_shader(InputDataType input_data_type); - static std::unique_ptr conversion_shader(InputDataType input_data_type, DisplayType display_type, int colour_cycle_numerator, int colour_cycle_denominator, int processing_width); + static std::unique_ptr conversion_shader(InputDataType input_data_type, DisplayType display_type, ColourSpace colour_space); }; } diff --git a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp index c517d114c..95d3b8fd3 100644 --- a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp +++ b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp @@ -51,9 +51,12 @@ std::string ScanTarget::glsl_globals(ShaderType type) { "in vec2 endPoint;" "in float startClock;" + "in float startCompositeAngle;" "in float endClock;" + "in float endCompositeAngle;" "in float lineY;" + "in float compositeAmplitude;" "uniform sampler2D textureName;" "uniform vec2 origin;" @@ -86,6 +89,9 @@ std::vector ScanTarget::attribute_bindings(ShaderType {"startClock", 2}, {"endClock", 3}, {"lineY", 4}, + {"compositeAmplitude", 5}, + {"startCompositeAngle", 6}, + {"endCompositeAngle", 7}, }; } } @@ -164,13 +170,19 @@ std::string ScanTarget::glsl_default_vertex_shader(ShaderType type) { case ShaderType::Line: return - "out vec2 textureCoordinate;" + "out vec2 textureCoordinates[4];" + + "out float compositeAngle;" + "out float oneOverCompositeAmplitude;" "void main(void) {" "float lateral = float(gl_VertexID & 1);" "float longitudinal = float((gl_VertexID & 2) >> 1);" - "textureCoordinate = vec2(mix(startClock, endClock, lateral), lineY + 0.5) / textureSize(textureName, 0);" + "textureCoordinates[0] = vec2(mix(startClock, endClock, lateral), lineY + 0.5) / textureSize(textureName, 0);" + + "compositeAngle = (mix(startCompositeAngle, endCompositeAngle, lateral) / 32.0) * 3.141592654;" + "oneOverCompositeAmplitude = mix(0.0, 255.0 / compositeAmplitude, step(0.01, compositeAmplitude));" "vec2 centrePoint = mix(startPoint, endPoint, lateral) / scale;" "vec2 height = normalize(endPoint - startPoint).yx * (longitudinal - 0.5) * rowHeight;" @@ -250,6 +262,13 @@ void ScanTarget::enable_vertex_attributes(ShaderType type, Shader &target) { sizeof(Line), reinterpret_cast(offsetof(Line, end_points[c].cycles_since_end_of_horizontal_retrace)), 1); + + target.enable_vertex_attribute_with_pointer( + prefix + "CompositeAngle", + 1, GL_UNSIGNED_SHORT, GL_FALSE, + sizeof(Line), + reinterpret_cast(offsetof(Line, end_points[c].composite_angle)), + 1); } target.enable_vertex_attribute_with_pointer( @@ -258,6 +277,13 @@ void ScanTarget::enable_vertex_attributes(ShaderType type, Shader &target) { sizeof(Line), reinterpret_cast(offsetof(Line, line)), 1); + + target.enable_vertex_attribute_with_pointer( + "compositeAmplitude", + 1, GL_UNSIGNED_BYTE, GL_FALSE, + sizeof(Line), + reinterpret_cast(offsetof(Line, composite_amplitude)), + 1); break; } } @@ -420,37 +446,193 @@ std::unique_ptr ScanTarget::composition_shader(InputDataType input_data_ )); } -std::unique_ptr ScanTarget::conversion_shader(InputDataType input_data_type, DisplayType display_type, int colour_cycle_numerator, int colour_cycle_denominator, int processing_width) { - return std::unique_ptr(new Shader( - glsl_globals(ShaderType::Line) + glsl_default_vertex_shader(ShaderType::Line), +std::unique_ptr ScanTarget::conversion_shader(InputDataType input_data_type, DisplayType display_type, ColourSpace colour_space) { + display_type = DisplayType::CompositeMonochrome; // Just a test. + + // Compose a vertex shader. If the display type is RGB, generate just the proper + // geometry position, plus a solitary textureCoordinate. + // + // If the display type is anything other than RGB, also produce composite + // angle and 1/composite amplitude as outputs. + // + // If the display type is composite colour, generate four textureCoordinates, + // spanning a range of -135, -45, +45, +135 degrees. + // + // If the display type is S-Video, generate three textureCoordinates, at + // -45, 0, +45. + std::string vertex_shader = glsl_globals(ShaderType::Line); + std::string fragment_shader = "#version 150\n" - "out vec4 fragColour;" - "in vec2 textureCoordinate;" - "uniform sampler2D textureName;" + "out vec4 fragColour;"; + if(display_type != DisplayType::RGB) { + vertex_shader += + "out float compositeAngle;" + "out float oneOverCompositeAmplitude;"; + fragment_shader += + "in float compositeAngle;" + "in float oneOverCompositeAmplitude;"; + } + + switch(display_type){ + case DisplayType::RGB: + case DisplayType::CompositeMonochrome: + vertex_shader += "out vec2 textureCoordinate;"; + fragment_shader += "in vec2 textureCoordinate;"; + break; + + case DisplayType::CompositeColour: + vertex_shader += "out vec2 textureCoordinates[4];"; + fragment_shader += "in vec2 textureCoordinates[4];"; + break; + + case DisplayType::SVideo: + vertex_shader += "out vec2 textureCoordinates[3];"; + fragment_shader += "in vec2 textureCoordinates[3];"; + break; + } + + // Add the code to generate a proper output position; this applies to all display types. + vertex_shader += + "void main(void) {" + "float lateral = float(gl_VertexID & 1);" + "float longitudinal = float((gl_VertexID & 2) >> 1);" + "vec2 centrePoint = mix(startPoint, endPoint, lateral) / scale;" + "vec2 height = normalize(endPoint - startPoint).yx * (longitudinal - 0.5) * rowHeight;" + "vec2 eyePosition = vec2(-1.0, 1.0) + vec2(2.0, -2.0) * (((centrePoint + height) - origin) / size);" + "gl_Position = vec4(eyePosition, 0.0, 1.0);"; + + // For everything other than RGB, calculate the two composite outputs. + if(display_type != DisplayType::RGB) { + vertex_shader += + "compositeAngle = (mix(startCompositeAngle, endCompositeAngle, lateral) / 32.0) * 3.141592654;" + "oneOverCompositeAmplitude = mix(0.0, 255.0 / compositeAmplitude, step(0.01, compositeAmplitude));"; + } + + // For RGB and monochrome composite, generate the single texture coordinate; otherwise generate either three + // or four depending on the type of decoding to apply. + switch(display_type){ + case DisplayType::RGB: + case DisplayType::CompositeMonochrome: + vertex_shader += + "textureCoordinate = vec2(mix(startClock, endClock, lateral), lineY + 0.5) / textureSize(textureName, 0);"; + break; + + case DisplayType::CompositeColour: + // TODO + break; + + case DisplayType::SVideo: + // TODO + break; + } + + vertex_shader += "}"; + + // Compose a fragment shader. + // + // For an RGB display ... [TODO] + + if(display_type != DisplayType::RGB) { + fragment_shader += + "uniform mat3 lumaChromaToRGB;" + "uniform mat3 rgbToLumaChroma;"; + } + + fragment_shader += "void main(void) {" - "fragColour = vec4(texture(textureName, textureCoordinate).rgb, 0.64);" - "}", - attribute_bindings(ShaderType::Line) - )); + "vec3 fragColour3;"; -// switch(modals_.composite_colour_space) { -// case ColourSpace::YIQ: { -// const GLfloat rgbToYIQ[] = {0.299f, 0.596f, 0.211f, 0.587f, -0.274f, -0.523f, 0.114f, -0.322f, 0.312f}; -// const GLfloat yiqToRGB[] = {1.0f, 1.0f, 1.0f, 0.956f, -0.272f, -1.106f, 0.621f, -0.647f, 1.703f}; -// shader->set_uniform_matrix("lumaChromaToRGB", 3, false, yiqToRGB); -// shader->set_uniform_matrix("rgbToLumaChroma", 3, false, rgbToYIQ); -// } break; -// -// case ColourSpace::YUV: { -// const GLfloat rgbToYUV[] = {0.299f, -0.14713f, 0.615f, 0.587f, -0.28886f, -0.51499f, 0.114f, 0.436f, -0.10001f}; -// const GLfloat yuvToRGB[] = {1.0f, 1.0f, 1.0f, 0.0f, -0.39465f, 2.03211f, 1.13983f, -0.58060f, 0.0f}; -// shader->set_uniform_matrix("lumaChromaToRGB", 3, false, yuvToRGB); -// shader->set_uniform_matrix("rgbToLumaChroma", 3, false, rgbToYUV); -// } break; -// } + switch(display_type) { + case DisplayType::RGB: + // Easy, just copy across. + fragment_shader += "fragColour3 = texture(textureName, textureCoordinate).rgb;"; + break; + + case DisplayType::SVideo: + // TODO + break; + + case DisplayType::CompositeColour: + // TODO + break; + + case DisplayType::CompositeMonochrome: { + switch(input_data_type) { + case InputDataType::Luminance1: + case InputDataType::Luminance8: + // Easy, just copy across. + fragment_shader += "fragColour3 = texture(textureName, textureCoordinate).rgb;"; + break; + + case InputDataType::PhaseLinkedLuminance8: + fragment_shader += + "uint iPhase = uint((compositeAngle * 2.0 / 3.141592654) ) & 3u;" // + phaseOffset*4.0 + "fragColour3 = vec3(texture(textureName, textureCoordinate)[iPhase] / 255.0);"; + break; + + case InputDataType::Luminance8Phase8: + fragment_shader += + "vec2 yc = texture(textureName, textureCoordinate).rg / vec2(255.0);" + + "float phaseOffset = 3.141592654 * 2.0 * 2.0 * yc.y;" + "float rawChroma = step(yc.y, 0.75) * cos(compositeAngle + phaseOffset);" + "float level = mix(yc.x, yc.y * rawChroma, 1.0 / oneOverCompositeAmplitude);" // TODO: no divide by zero. + "fragColour3 = vec3(level);"; +// "fragColour3 = vec3(yc.x, 0.5 + rawChroma*0.5, 0.0);"; + break; + + case InputDataType::Red1Green1Blue1: + case InputDataType::Red2Green2Blue2: + case InputDataType::Red4Green4Blue4: + case InputDataType::Red8Green8Blue8: + fragment_shader += + "vec3 colour = rgbToLumaChroma * texture(textureName, textureCoordinate).rgb;" + "vec2 quadrature = vec2(cos(compositeAngle), sin(compositeAngle));" + "float level = mix(colour.r, dot(quadrature, colour.gb), 1.0 / oneOverCompositeAmplitude);" // TODO: no divide by zero. + "fragColour3 = vec3(level);"; + break; + + default: break; + } + // TODO + } break; + } + + fragment_shader += + "fragColour = vec4(fragColour3, 0.64);" + "}"; + + // TODO gamma and range corrections. + + const auto shader = new Shader( + vertex_shader, + fragment_shader, + attribute_bindings(ShaderType::Line) + ); + + // If this isn't an RGB or composite colour shader, set the proper colour space. + if(display_type != DisplayType::RGB) { + switch(colour_space) { + case ColourSpace::YIQ: { + const GLfloat rgbToYIQ[] = {0.299f, 0.596f, 0.211f, 0.587f, -0.274f, -0.523f, 0.114f, -0.322f, 0.312f}; + const GLfloat yiqToRGB[] = {1.0f, 1.0f, 1.0f, 0.956f, -0.272f, -1.106f, 0.621f, -0.647f, 1.703f}; + shader->set_uniform_matrix("lumaChromaToRGB", 3, false, yiqToRGB); + shader->set_uniform_matrix("rgbToLumaChroma", 3, false, rgbToYIQ); + } break; + + case ColourSpace::YUV: { + const GLfloat rgbToYUV[] = {0.299f, -0.14713f, 0.615f, 0.587f, -0.28886f, -0.51499f, 0.114f, 0.436f, -0.10001f}; + const GLfloat yuvToRGB[] = {1.0f, 1.0f, 1.0f, 0.0f, -0.39465f, 2.03211f, 1.13983f, -0.58060f, 0.0f}; + shader->set_uniform_matrix("lumaChromaToRGB", 3, false, yuvToRGB); + shader->set_uniform_matrix("rgbToLumaChroma", 3, false, rgbToYUV); + } break; + } + } + + return std::unique_ptr(shader); } // diff --git a/Outputs/ScanTarget.hpp b/Outputs/ScanTarget.hpp index 9443efd3c..e2ea8edc4 100644 --- a/Outputs/ScanTarget.hpp +++ b/Outputs/ScanTarget.hpp @@ -293,8 +293,9 @@ struct ScanTarget { @param event The event. @param is_visible @c true if the output stream is visible immediately after this event; @c false otherwise. @param location The location of the event. + @param composite_amplitude The amplitude of the colour burst on this line (0, if no colour burst was found). */ - virtual void announce(Event event, bool is_visible, const Scan::EndPoint &location) {} + virtual void announce(Event event, bool is_visible, const Scan::EndPoint &location, uint8_t composite_amplitude) {} }; /*! From 27541196cca89928bc99ddc2692d6224dea9ec4a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 11 Jan 2019 22:46:50 -0500 Subject: [PATCH 10/16] Corrects Luminance8Phase8 and PhaseLinkedLuminance8 composite encodings. --- Outputs/OpenGL/ScanTargetGLSLFragments.cpp | 67 +++++++++++----------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp index 95d3b8fd3..758cf0e0f 100644 --- a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp +++ b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp @@ -40,27 +40,7 @@ std::string ScanTarget::glsl_globals(ShaderType type) { "in float compositeAmplitude;"; case ShaderType::Line: - return - "#version 150\n" - - "uniform vec2 scale;" - "uniform float rowHeight;" - "uniform float processingWidth;" - - "in vec2 startPoint;" - "in vec2 endPoint;" - - "in float startClock;" - "in float startCompositeAngle;" - "in float endClock;" - "in float endCompositeAngle;" - - "in float lineY;" - "in float compositeAmplitude;" - - "uniform sampler2D textureName;" - "uniform vec2 origin;" - "uniform vec2 size;"; + return ""; } } @@ -89,7 +69,7 @@ std::vector ScanTarget::attribute_bindings(ShaderType {"startClock", 2}, {"endClock", 3}, {"lineY", 4}, - {"compositeAmplitude", 5}, + {"lineCompositeAmplitude", 5}, {"startCompositeAngle", 6}, {"endCompositeAngle", 7}, }; @@ -279,7 +259,7 @@ void ScanTarget::enable_vertex_attributes(ShaderType type, Shader &target) { 1); target.enable_vertex_attribute_with_pointer( - "compositeAmplitude", + "lineCompositeAmplitude", 1, GL_UNSIGNED_BYTE, GL_FALSE, sizeof(Line), reinterpret_cast(offsetof(Line, composite_amplitude)), @@ -412,6 +392,7 @@ std::unique_ptr ScanTarget::composition_shader(InputDataType input_data_ case InputDataType::Luminance1: fragment_shader += "fragColour = texture(textureName, textureCoordinate).rrrr;"; break; + case InputDataType::Luminance8: fragment_shader += "fragColour = texture(textureName, textureCoordinate).rrrr / vec4(255.0);"; break; @@ -460,7 +441,28 @@ std::unique_ptr ScanTarget::conversion_shader(InputDataType input_data_t // // If the display type is S-Video, generate three textureCoordinates, at // -45, 0, +45. - std::string vertex_shader = glsl_globals(ShaderType::Line); + std::string vertex_shader = + "#version 150\n" + + "uniform vec2 scale;" + "uniform float rowHeight;" + "uniform float processingWidth;" + + "in vec2 startPoint;" + "in vec2 endPoint;" + + "in float startClock;" + "in float startCompositeAngle;" + "in float endClock;" + "in float endCompositeAngle;" + + "in float lineY;" + "in float lineCompositeAmplitude;" + + "uniform sampler2D textureName;" + "uniform vec2 origin;" + "uniform vec2 size;"; + std::string fragment_shader = "#version 150\n" @@ -470,9 +472,11 @@ std::unique_ptr ScanTarget::conversion_shader(InputDataType input_data_t if(display_type != DisplayType::RGB) { vertex_shader += "out float compositeAngle;" + "out float compositeAmplitude;" "out float oneOverCompositeAmplitude;"; fragment_shader += "in float compositeAngle;" + "in float compositeAmplitude;" "in float oneOverCompositeAmplitude;"; } @@ -508,7 +512,8 @@ std::unique_ptr ScanTarget::conversion_shader(InputDataType input_data_t if(display_type != DisplayType::RGB) { vertex_shader += "compositeAngle = (mix(startCompositeAngle, endCompositeAngle, lateral) / 32.0) * 3.141592654;" - "oneOverCompositeAmplitude = mix(0.0, 255.0 / compositeAmplitude, step(0.01, compositeAmplitude));"; + "compositeAmplitude = lineCompositeAmplitude / 255.0;" + "oneOverCompositeAmplitude = mix(0.0, 255.0 / lineCompositeAmplitude, step(0.01, lineCompositeAmplitude));"; } // For RGB and monochrome composite, generate the single texture coordinate; otherwise generate either three @@ -570,18 +575,17 @@ std::unique_ptr ScanTarget::conversion_shader(InputDataType input_data_t case InputDataType::PhaseLinkedLuminance8: fragment_shader += "uint iPhase = uint((compositeAngle * 2.0 / 3.141592654) ) & 3u;" // + phaseOffset*4.0 - "fragColour3 = vec3(texture(textureName, textureCoordinate)[iPhase] / 255.0);"; + "fragColour3 = vec3(texture(textureName, textureCoordinate)[iPhase]);"; break; case InputDataType::Luminance8Phase8: fragment_shader += - "vec2 yc = texture(textureName, textureCoordinate).rg / vec2(255.0);" + "vec2 yc = texture(textureName, textureCoordinate).rg;" "float phaseOffset = 3.141592654 * 2.0 * 2.0 * yc.y;" "float rawChroma = step(yc.y, 0.75) * cos(compositeAngle + phaseOffset);" - "float level = mix(yc.x, yc.y * rawChroma, 1.0 / oneOverCompositeAmplitude);" // TODO: no divide by zero. + "float level = mix(yc.x, yc.y * rawChroma, compositeAmplitude);" "fragColour3 = vec3(level);"; -// "fragColour3 = vec3(yc.x, 0.5 + rawChroma*0.5, 0.0);"; break; case InputDataType::Red1Green1Blue1: @@ -591,13 +595,10 @@ std::unique_ptr ScanTarget::conversion_shader(InputDataType input_data_t fragment_shader += "vec3 colour = rgbToLumaChroma * texture(textureName, textureCoordinate).rgb;" "vec2 quadrature = vec2(cos(compositeAngle), sin(compositeAngle));" - "float level = mix(colour.r, dot(quadrature, colour.gb), 1.0 / oneOverCompositeAmplitude);" // TODO: no divide by zero. + "float level = mix(colour.r, dot(quadrature, colour.gb), compositeAmplitude);" "fragColour3 = vec3(level);"; break; - - default: break; } - // TODO } break; } From 25a1f23fc070007208808f2c72078da07f3cb36c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 12 Jan 2019 17:59:24 -0500 Subject: [PATCH 11/16] Takes a first shot at re[re,re]-implementing composite colour decoding. --- Outputs/OpenGL/ScanTargetGLSLFragments.cpp | 124 ++++++++++++++------- 1 file changed, 82 insertions(+), 42 deletions(-) diff --git a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp index 758cf0e0f..f76575f7c 100644 --- a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp +++ b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp @@ -428,7 +428,7 @@ std::unique_ptr ScanTarget::composition_shader(InputDataType input_data_ } std::unique_ptr ScanTarget::conversion_shader(InputDataType input_data_type, DisplayType display_type, ColourSpace colour_space) { - display_type = DisplayType::CompositeMonochrome; // Just a test. + display_type = DisplayType::CompositeColour; // Just a test. // Compose a vertex shader. If the display type is RGB, generate just the proper // geometry position, plus a solitary textureCoordinate. @@ -526,7 +526,13 @@ std::unique_ptr ScanTarget::conversion_shader(InputDataType input_data_t break; case DisplayType::CompositeColour: - // TODO + vertex_shader += + "float centreCoordinate = mix(startClock, endClock, lateral);" + "float samplesPerAngle = (endClock - startClock) / (abs(endCompositeAngle - startCompositeAngle) / 32.0);" + "textureCoordinates[0] = vec2(centreCoordinate - 0.375*samplesPerAngle, lineY + 0.5) / textureSize(textureName, 0);" + "textureCoordinates[1] = vec2(centreCoordinate - 0.125*samplesPerAngle, lineY + 0.5) / textureSize(textureName, 0);" + "textureCoordinates[2] = vec2(centreCoordinate + 0.125*samplesPerAngle, lineY + 0.5) / textureSize(textureName, 0);" + "textureCoordinates[3] = vec2(centreCoordinate + 0.375*samplesPerAngle, lineY + 0.5) / textureSize(textureName, 0);"; break; case DisplayType::SVideo: @@ -546,13 +552,52 @@ std::unique_ptr ScanTarget::conversion_shader(InputDataType input_data_t "uniform mat3 rgbToLumaChroma;"; } + if(display_type == DisplayType::CompositeMonochrome || display_type == DisplayType::CompositeColour) { + fragment_shader += + "float composite_sample(vec2 coordinate, float angle) {"; + + switch(input_data_type) { + case InputDataType::Luminance1: + case InputDataType::Luminance8: + // Easy, just copy across. + fragment_shader += "return texture(textureName, coordinate).r;"; + break; + + case InputDataType::PhaseLinkedLuminance8: + fragment_shader += + "uint iPhase = uint((angle * 2.0 / 3.141592654) ) & 3u;" // + phaseOffset*4.0 + "return texture(textureName, coordinate)[iPhase];"; + break; + + case InputDataType::Luminance8Phase8: + fragment_shader += + "vec2 yc = texture(textureName, coordinate).rg;" + + "float phaseOffset = 3.141592654 * 2.0 * 2.0 * yc.y;" + "float rawChroma = step(yc.y, 0.75) * cos(angle + phaseOffset);" + "return mix(yc.x, yc.y * rawChroma, compositeAmplitude);"; + break; + + case InputDataType::Red1Green1Blue1: + case InputDataType::Red2Green2Blue2: + case InputDataType::Red4Green4Blue4: + case InputDataType::Red8Green8Blue8: + fragment_shader += + "vec3 colour = rgbToLumaChroma * texture(textureName, coordinate).rgb;" + "vec2 quadrature = vec2(cos(angle), sin(angle));" + "return mix(colour.r, dot(quadrature, colour.gb), compositeAmplitude);"; + break; + } + + fragment_shader += "}"; + } + fragment_shader += "void main(void) {" "vec3 fragColour3;"; switch(display_type) { case DisplayType::RGB: - // Easy, just copy across. fragment_shader += "fragColour3 = texture(textureName, textureCoordinate).rgb;"; break; @@ -561,53 +606,48 @@ std::unique_ptr ScanTarget::conversion_shader(InputDataType input_data_t break; case DisplayType::CompositeColour: - // TODO + fragment_shader += + // Figure out the four composite angles. TODO: make these an input? + "vec4 angles = vec4(" + "compositeAngle - 2.356194490192345," + "compositeAngle - 0.785398163397448," + "compositeAngle + 0.785398163397448," + "compositeAngle + 2.356194490192345" + ");" + + // Sample four times over, at proper angle offsets. + "vec4 samples = vec4(" + "composite_sample(textureCoordinates[0], angles[0])," + "composite_sample(textureCoordinates[1], angles[1])," + "composite_sample(textureCoordinates[2], angles[2])," + "composite_sample(textureCoordinates[3], angles[3])" + ");" + + // Take the average to calculate luminance, then subtract that from all four samples to + // give chrominance. + "float luminance = dot(samples, vec4(0.25));" + "samples -= vec4(luminance);" + + // Split and average chrominance. + "vec2 channels = vec2(" + "dot(cos(angles), samples)," + "dot(sin(angles), samples)" + ") / vec2(2.0);" + + // Apply a colour space conversion to get RGB. + "fragColour3 = lumaChromaToRGB * vec3(luminance, channels);"; break; - case DisplayType::CompositeMonochrome: { - switch(input_data_type) { - case InputDataType::Luminance1: - case InputDataType::Luminance8: - // Easy, just copy across. - fragment_shader += "fragColour3 = texture(textureName, textureCoordinate).rgb;"; - break; - - case InputDataType::PhaseLinkedLuminance8: - fragment_shader += - "uint iPhase = uint((compositeAngle * 2.0 / 3.141592654) ) & 3u;" // + phaseOffset*4.0 - "fragColour3 = vec3(texture(textureName, textureCoordinate)[iPhase]);"; - break; - - case InputDataType::Luminance8Phase8: - fragment_shader += - "vec2 yc = texture(textureName, textureCoordinate).rg;" - - "float phaseOffset = 3.141592654 * 2.0 * 2.0 * yc.y;" - "float rawChroma = step(yc.y, 0.75) * cos(compositeAngle + phaseOffset);" - "float level = mix(yc.x, yc.y * rawChroma, compositeAmplitude);" - "fragColour3 = vec3(level);"; - break; - - case InputDataType::Red1Green1Blue1: - case InputDataType::Red2Green2Blue2: - case InputDataType::Red4Green4Blue4: - case InputDataType::Red8Green8Blue8: - fragment_shader += - "vec3 colour = rgbToLumaChroma * texture(textureName, textureCoordinate).rgb;" - "vec2 quadrature = vec2(cos(compositeAngle), sin(compositeAngle));" - "float level = mix(colour.r, dot(quadrature, colour.gb), compositeAmplitude);" - "fragColour3 = vec3(level);"; - break; - } - } break; + case DisplayType::CompositeMonochrome: + fragment_shader += "fragColour3 = vec3(composite_sample(textureCoordinate, compositeAngle));"; + break; } + // TODO gamma and range corrections. fragment_shader += "fragColour = vec4(fragColour3, 0.64);" "}"; - // TODO gamma and range corrections. - const auto shader = new Shader( vertex_shader, fragment_shader, From 7c2c243985e3127be51772e10845a02b89421bd3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 12 Jan 2019 18:36:54 -0500 Subject: [PATCH 12/16] Corrects sample spacing, and removes a lot of detritus. --- Outputs/OpenGL/ScanTargetGLSLFragments.cpp | 289 ++------------------- 1 file changed, 17 insertions(+), 272 deletions(-) diff --git a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp index f76575f7c..7976ffac4 100644 --- a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp +++ b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp @@ -269,115 +269,6 @@ void ScanTarget::enable_vertex_attributes(ShaderType type, Shader &target) { } std::unique_ptr ScanTarget::composition_shader(InputDataType input_data_type) { -/* std::string fragment_shader = - "#version 150\n" - - "out vec3 fragColour;" - "in vec2 textureCoordinate;" - "in float compositeAngle;" - "in float oneOverCompositeAmplitude;" - - "uniform mat3 lumaChromaToRGB;" - "uniform mat3 rgbToLumaChroma;" - "uniform usampler2D textureName;" - "uniform float phaseOffset;" - - "void main(void) {"; - - DisplayType computed_display_type; - switch(input_data_type) { - case InputDataType::Luminance1: - computed_display_type = DisplayType::CompositeMonochrome; - fragment_shader += "fragColour = texture(textureName, textureCoordinate).rrr;"; - - if(computed_display_type != display_type) { - fragment_shader += "fragColour = clamp(fragColour, 0.0, 1.0);"; - } - break; - - case InputDataType::Luminance8: - computed_display_type = DisplayType::CompositeMonochrome; - fragment_shader += "fragColour = vec3(texture(textureName, textureCoordinate).r / 255.0);"; - break; - - case InputDataType::PhaseLinkedLuminance8: - computed_display_type = DisplayType::CompositeMonochrome; - fragment_shader += - "uint iPhase = uint((compositeAngle * 2.0 / 3.141592654) + phaseOffset*4.0) & 3u;" - "fragColour = vec3(texture(textureName, textureCoordinate)[iPhase] / 255.0);"; - break; - - case InputDataType::Luminance8Phase8: - computed_display_type = DisplayType::SVideo; - fragment_shader += - "vec2 yc = texture(textureName, textureCoordinate).rg / vec2(255.0);" - - "float phaseOffset = 3.141592654 * 2.0 * 2.0 * yc.y;" - "float rawChroma = step(yc.y, 0.75) * cos(compositeAngle + phaseOffset);" - "fragColour = vec3(yc.x, 0.5 + rawChroma*0.5, 0.0);"; - break; - - case InputDataType::Red1Green1Blue1: - computed_display_type = DisplayType::RGB; - fragment_shader += - "uint textureValue = texture(textureName, textureCoordinate).r;" - "fragColour = uvec3(textureValue) & uvec3(4u, 2u, 1u);"; - - if(computed_display_type != display_type) { - fragment_shader += "fragColour = clamp(fragColour, 0.0, 1.0);"; - } - break; - - case InputDataType::Red2Green2Blue2: - computed_display_type = DisplayType::RGB; - fragment_shader += - "uint textureValue = texture(textureName, textureCoordinate).r;" - "fragColour = vec3(float((textureValue >> 4) & 3u), float((textureValue >> 2) & 3u), float(textureValue & 3u)) / 3.0;"; - break; - - case InputDataType::Red4Green4Blue4: - computed_display_type = DisplayType::RGB; - fragment_shader += - "uvec2 textureValue = texture(textureName, textureCoordinate).rg;" - "fragColour = vec3(float(textureValue.r) / 15.0, float(textureValue.g & 240u) / 240.0, float(textureValue.g & 15u) / 15.0);"; - break; - - case InputDataType::Red8Green8Blue8: - computed_display_type = DisplayType::RGB; - fragment_shader += "fragColour = texture(textureName, textureCoordinate).rgb / vec3(255.0);"; - break; - }*/ - - // If the input type is RGB but the output type isn't then - // there'll definitely be an RGB to SVideo step. -// if(computed_display_type == DisplayType::RGB && display_type != DisplayType::RGB) { -// fragment_shader += -// "vec3 composite_colour = rgbToLumaChroma * fragColour;" -// "vec2 quadrature = vec2(cos(compositeAngle), sin(compositeAngle));" -// "fragColour = vec3(composite_colour.r, 0.5 + dot(quadrature, composite_colour.gb)*0.5, 0.0);"; -// } - - // If the output type is SVideo, throw in an attempt to separate the two chrominance - // channels here. -// if(display_type == DisplayType::SVideo) { -// if(computed_display_type != DisplayType::RGB) { -// fragment_shader += -// "vec2 quadrature = vec2(cos(compositeAngle), sin(compositeAngle));"; -// } -// fragment_shader += -// "vec2 chroma = (((fragColour.y - 0.5)*2.0) * quadrature)*0.5 + vec2(0.5);" -// "fragColour = vec3(fragColour.x, chroma);"; -// } - - // Add an SVideo to composite step if necessary. -// if( -// (display_type == DisplayType::CompositeMonochrome || display_type == DisplayType::CompositeColour) && -// computed_display_type != DisplayType::CompositeMonochrome -// ) { -// fragment_shader += "fragColour = vec3(mix(fragColour.r, 2.0*(fragColour.g - 0.5), 1.0 / oneOverCompositeAmplitude));"; -// } - - std::string fragment_shader = "#version 150\n" @@ -500,20 +391,20 @@ std::unique_ptr ScanTarget::conversion_shader(InputDataType input_data_t // Add the code to generate a proper output position; this applies to all display types. vertex_shader += - "void main(void) {" - "float lateral = float(gl_VertexID & 1);" - "float longitudinal = float((gl_VertexID & 2) >> 1);" - "vec2 centrePoint = mix(startPoint, endPoint, lateral) / scale;" - "vec2 height = normalize(endPoint - startPoint).yx * (longitudinal - 0.5) * rowHeight;" - "vec2 eyePosition = vec2(-1.0, 1.0) + vec2(2.0, -2.0) * (((centrePoint + height) - origin) / size);" - "gl_Position = vec4(eyePosition, 0.0, 1.0);"; + "void main(void) {" + "float lateral = float(gl_VertexID & 1);" + "float longitudinal = float((gl_VertexID & 2) >> 1);" + "vec2 centrePoint = mix(startPoint, endPoint, lateral) / scale;" + "vec2 height = normalize(endPoint - startPoint).yx * (longitudinal - 0.5) * rowHeight;" + "vec2 eyePosition = vec2(-1.0, 1.0) + vec2(2.0, -2.0) * (((centrePoint + height) - origin) / size);" + "gl_Position = vec4(eyePosition, 0.0, 1.0);"; // For everything other than RGB, calculate the two composite outputs. if(display_type != DisplayType::RGB) { vertex_shader += - "compositeAngle = (mix(startCompositeAngle, endCompositeAngle, lateral) / 32.0) * 3.141592654;" - "compositeAmplitude = lineCompositeAmplitude / 255.0;" - "oneOverCompositeAmplitude = mix(0.0, 255.0 / lineCompositeAmplitude, step(0.01, lineCompositeAmplitude));"; + "compositeAngle = (mix(startCompositeAngle, endCompositeAngle, lateral) / 32.0) * 3.141592654;" + "compositeAmplitude = lineCompositeAmplitude / 255.0;" + "oneOverCompositeAmplitude = mix(0.0, 255.0 / lineCompositeAmplitude, step(0.01, lineCompositeAmplitude));"; } // For RGB and monochrome composite, generate the single texture coordinate; otherwise generate either three @@ -527,12 +418,12 @@ std::unique_ptr ScanTarget::conversion_shader(InputDataType input_data_t case DisplayType::CompositeColour: vertex_shader += - "float centreCoordinate = mix(startClock, endClock, lateral);" - "float samplesPerAngle = (endClock - startClock) / (abs(endCompositeAngle - startCompositeAngle) / 32.0);" - "textureCoordinates[0] = vec2(centreCoordinate - 0.375*samplesPerAngle, lineY + 0.5) / textureSize(textureName, 0);" - "textureCoordinates[1] = vec2(centreCoordinate - 0.125*samplesPerAngle, lineY + 0.5) / textureSize(textureName, 0);" - "textureCoordinates[2] = vec2(centreCoordinate + 0.125*samplesPerAngle, lineY + 0.5) / textureSize(textureName, 0);" - "textureCoordinates[3] = vec2(centreCoordinate + 0.375*samplesPerAngle, lineY + 0.5) / textureSize(textureName, 0);"; + "float centreClock = mix(startClock, endClock, lateral);" + "float clocksPerAngle = (endClock - startClock) / (abs(endCompositeAngle - startCompositeAngle) / 64.0);" + "textureCoordinates[0] = vec2(centreClock - 0.375*clocksPerAngle, lineY + 0.5) / textureSize(textureName, 0);" + "textureCoordinates[1] = vec2(centreClock - 0.125*clocksPerAngle, lineY + 0.5) / textureSize(textureName, 0);" + "textureCoordinates[2] = vec2(centreClock + 0.125*clocksPerAngle, lineY + 0.5) / textureSize(textureName, 0);" + "textureCoordinates[3] = vec2(centreClock + 0.375*clocksPerAngle, lineY + 0.5) / textureSize(textureName, 0);"; break; case DisplayType::SVideo: @@ -632,7 +523,7 @@ std::unique_ptr ScanTarget::conversion_shader(InputDataType input_data_t "vec2 channels = vec2(" "dot(cos(angles), samples)," "dot(sin(angles), samples)" - ") / vec2(2.0);" + ") * vec2(0.25);" // Apply a colour space conversion to get RGB. "fragColour3 = lumaChromaToRGB * vec3(luminance, channels);"; @@ -675,149 +566,3 @@ std::unique_ptr ScanTarget::conversion_shader(InputDataType input_data_t return std::unique_ptr(shader); } - -// -//SignalProcessing::FIRFilter ScanTarget::colour_filter(int colour_cycle_numerator, int colour_cycle_denominator, int processing_width, float low_cutoff, float high_cutoff) { -// const float cycles_per_expanded_line = (float(colour_cycle_numerator) / float(colour_cycle_denominator)) / (float(processing_width) / float(LineBufferWidth)); -// return SignalProcessing::FIRFilter(15, float(LineBufferWidth), cycles_per_expanded_line * low_cutoff, cycles_per_expanded_line * high_cutoff); -//} -// -//std::unique_ptr ScanTarget::svideo_to_rgb_shader(int colour_cycle_numerator, int colour_cycle_denominator, int processing_width) { -// /* -// Composite to S-Video conversion is achieved by filtering the input signal to obtain luminance, and then subtracting that -// from the original to get chrominance. -// -// (Colour cycle numerator)/(Colour cycle denominator) gives the number of colour cycles in (processing_width / LineBufferWidth), -// there'll be at least four samples per colour clock and in practice at most just a shade more than 9. -// */ -// auto shader = std::unique_ptr(new Shader( -// glsl_globals(ShaderType::ProcessedScan) + glsl_default_vertex_shader(ShaderType::ProcessedScan), -// "#version 150\n" -// -// "in vec2 textureCoordinates[15];" -// "in vec2 chromaCoordinates[2];" -// "in float compositeAngle;" -// -//// "uniform vec4 chromaWeights[4];" -//// "uniform vec4 lumaWeights[4];" -// "uniform sampler2D textureName;" -// "uniform mat3 lumaChromaToRGB;" -// -// "out vec3 fragColour;" -// "void main() {" -// "vec2 angles = vec2(compositeAngle - 1.570795827, compositeAngle + 1.570795827);" -// -// "vec2 sines = sin(angles) * vec2(0.5) + vec2(0.5);" -// "vec2 coses = cos(angles);" -// "float denominator = sines.y * coses.x - sines.x * coses.y;" -// -// "vec2 samples = vec2(texture(textureName, chromaCoordinates[0]).g, texture(textureName, chromaCoordinates[1]).g);" -// -// "float channel1 = (samples.x * sines.x - samples.y * sines.y) / denominator;" -// "float channel2 = (samples.x * coses.x - samples.y * coses.y) / denominator;" -// -//// "fragColour = lumaChromaToRGB * vec3(texture(textureName, textureCoordinates[7]).r, channel1, channel2);" -// "fragColour = vec3(sines.x + sines.y, 0.0, 0.0);" -// //, 0.0);" -// -//// "fragColour = lumaChromaToRGB * vec3(texture(textureName, textureCoordinates[7]).g, 0.0, 0.0);" -//// "fragColour = vec3(0.5);" -///* "vec3 samples[15] = vec3[15](" -// "texture(textureName, textureCoordinates[0]).rgb," -// "texture(textureName, textureCoordinates[1]).rgb," -// "texture(textureName, textureCoordinates[2]).rgb," -// "texture(textureName, textureCoordinates[3]).rgb," -// "texture(textureName, textureCoordinates[4]).rgb," -// "texture(textureName, textureCoordinates[5]).rgb," -// "texture(textureName, textureCoordinates[6]).rgb," -// "texture(textureName, textureCoordinates[7]).rgb," -// "texture(textureName, textureCoordinates[8]).rgb," -// "texture(textureName, textureCoordinates[9]).rgb," -// "texture(textureName, textureCoordinates[10]).rgb," -// "texture(textureName, textureCoordinates[11]).rgb," -// "texture(textureName, textureCoordinates[12]).rgb," -// "texture(textureName, textureCoordinates[13]).rgb," -// "texture(textureName, textureCoordinates[14]).rgb" -// ");" -// "vec4 samples0[4] = vec4[4](" -// "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, samples[11].r)," -// "vec4(samples[12].r, samples[13].r, samples[14].r, 0.0)" -// ");" -// "vec4 samples1[4] = vec4[4](" -// "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, samples[11].g)," -// "vec4(samples[12].g, samples[13].g, samples[14].g, 0.0)" -// ");" -// "vec4 samples2[4] = vec4[4](" -// "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, samples[11].b)," -// "vec4(samples[12].b, samples[13].b, samples[14].b, 0.0)" -// ");" -// "float channel0 = dot(lumaWeights[0], samples0[0]) + dot(lumaWeights[1], samples0[1]) + dot(lumaWeights[2], samples0[2]) + dot(lumaWeights[3], samples0[3]);" -// "float channel1 = dot(chromaWeights[0], samples1[0]) + dot(chromaWeights[1], samples1[1]) + dot(chromaWeights[2], samples1[2]) + dot(chromaWeights[3], samples1[3]);" -// "float channel2 = dot(chromaWeights[0], samples2[0]) + dot(chromaWeights[1], samples2[1]) + dot(chromaWeights[2], samples2[2]) + dot(chromaWeights[3], samples2[3]);" -// "vec2 chroma = vec2(channel1, channel2)*2.0 - vec2(1.0);" -// "fragColour = lumaChromaToRGB * vec3(channel0, chroma);"*/ -// "}", -// attribute_bindings(ShaderType::ProcessedScan) -// )); -// -// const float cycles_per_expanded_line = (float(colour_cycle_numerator) / float(colour_cycle_denominator)) / (float(processing_width) / float(LineBufferWidth)); -// const float chroma_offset = 0.25f / cycles_per_expanded_line; -// shader->set_uniform("chromaOffset", chroma_offset); -// -//// auto chroma_coefficients = colour_filter(colour_cycle_numerator, colour_cycle_denominator, processing_width, 0.0f, 0.25f).get_coefficients(); -//// chroma_coefficients.push_back(0.0f); -//// shader->set_uniform("chromaWeights", 4, 4, chroma_coefficients.data()); -//// -//// auto luma_coefficients = colour_filter(colour_cycle_numerator, colour_cycle_denominator, processing_width, 0.0f, 0.15f).get_coefficients(); -//// luma_coefficients.push_back(0.0f); -//// shader->set_uniform("lumaWeights", 4, 4, luma_coefficients.data()); -// -// shader->set_uniform("edgeExpansion", 20); -// -// return shader; -//} -// -//std::unique_ptr ScanTarget::composite_to_svideo_shader(int colour_cycle_numerator, int colour_cycle_denominator, int processing_width) { -// auto shader = std::unique_ptr(new Shader( -// glsl_globals(ShaderType::ProcessedScan) + glsl_default_vertex_shader(ShaderType::ProcessedScan), -// "#version 150\n" -// -// "in vec2 textureCoordinates[15];" -// "in float compositeAngle;" -// "in float oneOverCompositeAmplitude;" -// -// "uniform vec4 lumaWeights[4];" -// "uniform sampler2D textureName;" -// -// "out vec3 fragColour;" -// "void main() {" -// "vec4 samples[4] = vec4[4](" -// "vec4(texture(textureName, textureCoordinates[0]).r, texture(textureName, textureCoordinates[1]).r, texture(textureName, textureCoordinates[2]).r, texture(textureName, textureCoordinates[3]).r)," -// "vec4(texture(textureName, textureCoordinates[4]).r, texture(textureName, textureCoordinates[5]).r, texture(textureName, textureCoordinates[6]).r, texture(textureName, textureCoordinates[7]).r)," -// "vec4(texture(textureName, textureCoordinates[8]).r, texture(textureName, textureCoordinates[9]).r, texture(textureName, textureCoordinates[10]).r, texture(textureName, textureCoordinates[11]).r)," -// "vec4(texture(textureName, textureCoordinates[12]).r, texture(textureName, textureCoordinates[13]).r, texture(textureName, textureCoordinates[14]).r, 0.0)" -// ");" -// "float luma = dot(lumaWeights[0], samples[0]) + dot(lumaWeights[1], samples[1]) + dot(lumaWeights[2], samples[2]) + dot(lumaWeights[3], samples[3]);" -// "vec2 quadrature = vec2(cos(compositeAngle), sin(compositeAngle));" -// "vec2 chroma = ((samples[1].a - luma) * oneOverCompositeAmplitude)*quadrature;" -// "fragColour = vec3(samples[1].a, chroma*0.5 + vec2(0.5));" -// "}", -// attribute_bindings(ShaderType::ProcessedScan) -// )); -// -// auto luma_low = colour_filter(colour_cycle_numerator, colour_cycle_denominator, processing_width, 0.0f, 0.9f); -// auto luma_coefficients = luma_low.get_coefficients(); -// luma_coefficients.push_back(0.0f); -// shader->set_uniform("lumaWeights", 4, 4, luma_coefficients.data()); -// -// shader->set_uniform("edgeExpansion", 10); -// -// return shader; -//} -// From e89e55a9bb4fcce9ba118c4990c66e633a42771b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 13 Jan 2019 14:45:17 -0500 Subject: [PATCH 13/16] Attempts to factor actual composite amplitude into output. --- Outputs/OpenGL/ScanTargetGLSLFragments.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp index 7976ffac4..55101473d 100644 --- a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp +++ b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp @@ -516,14 +516,14 @@ std::unique_ptr ScanTarget::conversion_shader(InputDataType input_data_t // Take the average to calculate luminance, then subtract that from all four samples to // give chrominance. - "float luminance = dot(samples, vec4(0.25));" + "float luminance = dot(samples, vec4(0.25 / (1.0 - compositeAmplitude)));" "samples -= vec4(luminance);" // Split and average chrominance. "vec2 channels = vec2(" "dot(cos(angles), samples)," "dot(sin(angles), samples)" - ") * vec2(0.25);" + ") * vec2(0.125 * oneOverCompositeAmplitude);" // Apply a colour space conversion to get RGB. "fragColour3 = lumaChromaToRGB * vec3(luminance, channels);"; From cc95e587db857ede7cc6b282bd1a350ef12dd1a1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 13 Jan 2019 19:19:01 -0500 Subject: [PATCH 14/16] Adds virtual destructors for various interface classes. --- Machines/AppleII/Card.hpp | 1 + Machines/MSX/ROMSlotHandler.hpp | 2 ++ Machines/Utility/Typer.hpp | 2 ++ Storage/Disk/Encodings/MFM/Encoder.cpp | 1 + Storage/Disk/Track/Track.hpp | 2 ++ 5 files changed, 8 insertions(+) diff --git a/Machines/AppleII/Card.hpp b/Machines/AppleII/Card.hpp index 42fb4fd23..2b1ae664f 100644 --- a/Machines/AppleII/Card.hpp +++ b/Machines/AppleII/Card.hpp @@ -39,6 +39,7 @@ namespace AppleII { */ class Card { public: + virtual ~Card() {} enum Select: int { None = 0, // No select line is active IO = 1 << 0, // IO select is active diff --git a/Machines/MSX/ROMSlotHandler.hpp b/Machines/MSX/ROMSlotHandler.hpp index a617fce9b..e0fadbc64 100644 --- a/Machines/MSX/ROMSlotHandler.hpp +++ b/Machines/MSX/ROMSlotHandler.hpp @@ -43,6 +43,8 @@ class MemoryMap { class ROMSlotHandler { public: + virtual ~ROMSlotHandler() {} + /*! Advances time by @c half_cycles. */ virtual void run_for(HalfCycles half_cycles) {} diff --git a/Machines/Utility/Typer.hpp b/Machines/Utility/Typer.hpp index 5304b1a73..e93403523 100644 --- a/Machines/Utility/Typer.hpp +++ b/Machines/Utility/Typer.hpp @@ -26,6 +26,8 @@ class CharacterMapper { /// @returns The EndSequence-terminated sequence of keys that would cause @c character to be typed. virtual uint16_t *sequence_for_character(char character) = 0; + virtual CharacterMaper() {} + protected: typedef uint16_t KeySequence[16]; diff --git a/Storage/Disk/Encodings/MFM/Encoder.cpp b/Storage/Disk/Encodings/MFM/Encoder.cpp index c6c333939..515ed50ef 100644 --- a/Storage/Disk/Encodings/MFM/Encoder.cpp +++ b/Storage/Disk/Encodings/MFM/Encoder.cpp @@ -19,6 +19,7 @@ using namespace Storage::Encodings::MFM; class MFMEncoder: public Encoder { public: MFMEncoder(std::vector &target) : Encoder(target) {} + virtual ~MFMEncoder() {} void add_byte(uint8_t input) { crc_generator_.add(input); diff --git a/Storage/Disk/Track/Track.hpp b/Storage/Disk/Track/Track.hpp index 5075401fc..63d2d7207 100644 --- a/Storage/Disk/Track/Track.hpp +++ b/Storage/Disk/Track/Track.hpp @@ -70,6 +70,8 @@ class HeadPosition { */ class Track { public: + virtual ~Track() {} + /*! Describes the location of a track, implementing < to allow for use as a set key. */ From 2ef6d4327cf9c0b9f4c781123b0eeb4d34e35001 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 13 Jan 2019 20:37:50 -0500 Subject: [PATCH 15/16] Resolves further build warnings. --- Components/1770/1770.cpp | 1 - Machines/Utility/Typer.hpp | 4 ++-- Storage/Disk/Encodings/MFM/Encoder.hpp | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index c030cb4df..5504d69c2 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -189,7 +189,6 @@ void WD1770::posit_event(int new_event_type) { interesting_event_mask_ &= ~new_event_type; } - Status new_status; BEGIN_SECTION() // Wait for a new command, branch to the appropriate handler. diff --git a/Machines/Utility/Typer.hpp b/Machines/Utility/Typer.hpp index e93403523..93e02d0f3 100644 --- a/Machines/Utility/Typer.hpp +++ b/Machines/Utility/Typer.hpp @@ -23,11 +23,11 @@ namespace Utility { */ class CharacterMapper { public: + virtual ~CharacterMapper() {} + /// @returns The EndSequence-terminated sequence of keys that would cause @c character to be typed. virtual uint16_t *sequence_for_character(char character) = 0; - virtual CharacterMaper() {} - protected: typedef uint16_t KeySequence[16]; diff --git a/Storage/Disk/Encodings/MFM/Encoder.hpp b/Storage/Disk/Encodings/MFM/Encoder.hpp index 643cbc610..225a1bb4f 100644 --- a/Storage/Disk/Encodings/MFM/Encoder.hpp +++ b/Storage/Disk/Encodings/MFM/Encoder.hpp @@ -45,6 +45,7 @@ std::shared_ptr GetFMTrackWithSectors(const std::vector &target); + virtual ~Encoder() {} virtual void add_byte(uint8_t input) = 0; virtual void add_index_address_mark() = 0; virtual void add_ID_address_mark() = 0; From 7aec5be61a738f7a49a69156395fe985bbe8fa89 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 13 Jan 2019 22:49:01 -0500 Subject: [PATCH 16/16] Cleans up and simplifies shader creation. --- Outputs/OpenGL/ScanTarget.cpp | 11 - Outputs/OpenGL/ScanTarget.hpp | 22 +- Outputs/OpenGL/ScanTargetGLSLFragments.cpp | 333 ++++++++------------- 3 files changed, 139 insertions(+), 227 deletions(-) diff --git a/Outputs/OpenGL/ScanTarget.cpp b/Outputs/OpenGL/ScanTarget.cpp index f1dcac4e5..ef3fb0f5c 100644 --- a/Outputs/OpenGL/ScanTarget.cpp +++ b/Outputs/OpenGL/ScanTarget.cpp @@ -100,17 +100,6 @@ void ScanTarget::set_modals(Modals modals) { is_drawing_.clear(); } -void Outputs::Display::OpenGL::ScanTarget::set_uniforms(ShaderType type, Shader &target) { - // Slightly over-amping rowHeight here is a cheap way to make sure that lines - // converge even allowing for the fact that they may not be spaced by exactly - // the expected distance. Cf. the stencil-powered logic for making sure all - // pixels are painted only exactly once per field. - target.set_uniform("rowHeight", GLfloat(1.05f / modals_.expected_vertical_lines)); - target.set_uniform("scale", GLfloat(modals_.output_scale.x), GLfloat(modals_.output_scale.y)); - target.set_uniform("processingWidth", GLfloat(processing_width_) / 2048.0f); - target.set_uniform("phaseOffset", GLfloat(modals_.input_data_tweaks.phase_linked_luminance_offset)); -} - Outputs::Display::ScanTarget::Scan *ScanTarget::begin_scan() { if(allocation_has_failed_) return nullptr; diff --git a/Outputs/OpenGL/ScanTarget.hpp b/Outputs/OpenGL/ScanTarget.hpp index a5a79639c..077adcf8a 100644 --- a/Outputs/OpenGL/ScanTarget.hpp +++ b/Outputs/OpenGL/ScanTarget.hpp @@ -157,24 +157,11 @@ class ScanTarget: public Outputs::Display::ScanTarget { Line }; - /*! - @returns A string containing GLSL code describing the standard set of - @c in and @c uniform variables to bind to the relevant struct - from [...]OpenGL::ScanTarget and a vertex function to provide - the standard varyings. - */ - static std::string glsl_globals(ShaderType type); - - /*! - */ - static std::string glsl_default_vertex_shader(ShaderType type); - /*! Calls @c taret.enable_vertex_attribute_with_pointer to attach all globals for shaders of @c type to @c target. */ static void enable_vertex_attributes(ShaderType type, Shader &target); - static std::vector attribute_bindings(ShaderType type); void set_uniforms(ShaderType type, Shader &target); GLsync fence_ = nullptr; @@ -184,7 +171,16 @@ class ScanTarget: public Outputs::Display::ScanTarget { std::unique_ptr input_shader_; std::unique_ptr output_shader_; + /*! + Produces a shader that composes fragment of the input stream to a single buffer, + normalising the data into one of four forms: RGB, 8-bit luminance, + phase-linked luminance or luminance+phase offset. + */ static std::unique_ptr composition_shader(InputDataType input_data_type); + /*! + Produces a shader that reads from a composition buffer and converts to host + output RGB, decoding composite or S-Video as necessary. + */ static std::unique_ptr conversion_shader(InputDataType input_data_type, DisplayType display_type, ColourSpace colour_space); }; diff --git a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp index 55101473d..b5004959b 100644 --- a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp +++ b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp @@ -10,166 +10,14 @@ using namespace Outputs::Display::OpenGL; -std::string ScanTarget::glsl_globals(ShaderType type) { - switch(type) { - case ShaderType::InputScan: - case ShaderType::ProcessedScan: - return - "#version 150\n" - - "uniform vec2 scale;" - - "uniform mat3 lumaChromaToRGB;" - "uniform mat3 rgbToLumaChroma;" - - "uniform float rowHeight;" - "uniform float processingWidth;" - - "in vec2 startPoint;" - "in float startDataX;" - "in float startCompositeAngle;" - "in float startClock;" - - "in vec2 endPoint;" - "in float endDataX;" - "in float endCompositeAngle;" - "in float endClock;" - - "in float dataY;" - "in float lineY;" - "in float compositeAmplitude;"; - - case ShaderType::Line: - return ""; - } -} - -std::vector ScanTarget::attribute_bindings(ShaderType type) { - switch(type) { - case ShaderType::InputScan: - case ShaderType::ProcessedScan: - return { - {"startPoint", 0}, - {"startDataX", 1}, - {"startCompositeAngle", 2}, - {"startClock", 3}, - {"endPoint", 4}, - {"endDataX", 5}, - {"endCompositeAngle", 6}, - {"endClock", 7}, - {"dataY", 8}, - {"lineY", 9}, - {"compositeAmplitude", 10}, - }; - - case ShaderType::Line: - return { - {"startPoint", 0}, - {"endPoint", 1}, - {"startClock", 2}, - {"endClock", 3}, - {"lineY", 4}, - {"lineCompositeAmplitude", 5}, - {"startCompositeAngle", 6}, - {"endCompositeAngle", 7}, - }; - } -} - -std::string ScanTarget::glsl_default_vertex_shader(ShaderType type) { - switch(type) { - case ShaderType::InputScan: - case ShaderType::ProcessedScan: { - std::string result; - - if(type == ShaderType::InputScan) { - result += - "out vec2 textureCoordinate;" - "uniform usampler2D textureName;"; - } else { - result += - "out vec2 textureCoordinates[15];" - "out vec2 chromaCoordinates[2];" - - "uniform sampler2D textureName;" - "uniform float chromaOffset;" - "uniform float edgeExpansion;"; - } - - result += - "out float compositeAngle;" - "out float oneOverCompositeAmplitude;" - - "void main(void) {" - "float lateral = float(gl_VertexID & 1);" - "float longitudinal = float((gl_VertexID & 2) >> 1);" - - "compositeAngle = (mix(startCompositeAngle, endCompositeAngle, lateral) / 32.0) * 3.141592654;" - "oneOverCompositeAmplitude = mix(0.0, 255.0 / compositeAmplitude, step(0.01, compositeAmplitude));"; - - if(type == ShaderType::InputScan) { - result += - "textureCoordinate = vec2(mix(startDataX, endDataX, lateral), dataY + 0.5) / textureSize(textureName, 0);" - "vec2 eyePosition = vec2(mix(startClock, endClock, lateral), lineY + longitudinal) / vec2(2048.0, 2048.0);"; - } else { - result += - "vec2 sourcePosition = vec2(mix(startPoint.x, endPoint.x, lateral) * processingWidth, lineY + 0.5);" - "vec2 eyePosition = (sourcePosition + vec2(0.0, longitudinal - 0.5)) / vec2(scale.x, 2048.0);" - "sourcePosition /= vec2(scale.x, 2048.0);" - -// "vec2 expansion = vec2(edgeExpansion, 0.0) / textureSize(textureName, 0);" -// "eyePosition = eyePosition + expansion;" -// "sourcePosition = sourcePosition + expansion;" - - "textureCoordinates[0] = sourcePosition + vec2(-7.0, 0.0) / textureSize(textureName, 0);" - "textureCoordinates[1] = sourcePosition + vec2(-6.0, 0.0) / textureSize(textureName, 0);" - "textureCoordinates[2] = sourcePosition + vec2(-5.0, 0.0) / textureSize(textureName, 0);" - "textureCoordinates[3] = sourcePosition + vec2(-4.0, 0.0) / textureSize(textureName, 0);" - "textureCoordinates[4] = sourcePosition + vec2(-3.0, 0.0) / textureSize(textureName, 0);" - "textureCoordinates[5] = sourcePosition + vec2(-2.0, 0.0) / textureSize(textureName, 0);" - "textureCoordinates[6] = sourcePosition + vec2(-1.0, 0.0) / textureSize(textureName, 0);" - "textureCoordinates[7] = sourcePosition;" - "textureCoordinates[8] = sourcePosition + vec2(1.0, 0.0) / textureSize(textureName, 0);" - "textureCoordinates[9] = sourcePosition + vec2(2.0, 0.0) / textureSize(textureName, 0);" - "textureCoordinates[10] = sourcePosition + vec2(3.0, 0.0) / textureSize(textureName, 0);" - "textureCoordinates[11] = sourcePosition + vec2(4.0, 0.0) / textureSize(textureName, 0);" - "textureCoordinates[12] = sourcePosition + vec2(5.0, 0.0) / textureSize(textureName, 0);" - "textureCoordinates[13] = sourcePosition + vec2(6.0, 0.0) / textureSize(textureName, 0);" - "textureCoordinates[14] = sourcePosition + vec2(7.0, 0.0) / textureSize(textureName, 0);" - - "chromaCoordinates[0] = sourcePosition + vec2(chromaOffset, 0.0);" - "chromaCoordinates[1] = sourcePosition - vec2(chromaOffset, 0.0);" - - "eyePosition = eyePosition;"; - } - - return result + - "gl_Position = vec4(eyePosition*2.0 - vec2(1.0), 0.0, 1.0);" - "}"; - } - - case ShaderType::Line: - return - "out vec2 textureCoordinates[4];" - - "out float compositeAngle;" - "out float oneOverCompositeAmplitude;" - - "void main(void) {" - "float lateral = float(gl_VertexID & 1);" - "float longitudinal = float((gl_VertexID & 2) >> 1);" - - "textureCoordinates[0] = vec2(mix(startClock, endClock, lateral), lineY + 0.5) / textureSize(textureName, 0);" - - "compositeAngle = (mix(startCompositeAngle, endCompositeAngle, lateral) / 32.0) * 3.141592654;" - "oneOverCompositeAmplitude = mix(0.0, 255.0 / compositeAmplitude, step(0.01, compositeAmplitude));" - - "vec2 centrePoint = mix(startPoint, endPoint, lateral) / scale;" - "vec2 height = normalize(endPoint - startPoint).yx * (longitudinal - 0.5) * rowHeight;" - "vec2 eyePosition = vec2(-1.0, 1.0) + vec2(2.0, -2.0) * (((centrePoint + height) - origin) / size);" - "gl_Position = vec4(eyePosition, 0.0, 1.0);" - "}"; - } +void Outputs::Display::OpenGL::ScanTarget::set_uniforms(ShaderType type, Shader &target) { + // Slightly over-amping rowHeight here is a cheap way to make sure that lines + // converge even allowing for the fact that they may not be spaced by exactly + // the expected distance. Cf. the stencil-powered logic for making sure all + // pixels are painted only exactly once per field. + target.set_uniform("rowHeight", GLfloat(1.05f / modals_.expected_vertical_lines)); + target.set_uniform("scale", GLfloat(modals_.output_scale.x), GLfloat(modals_.output_scale.y)); + target.set_uniform("phaseOffset", GLfloat(modals_.input_data_tweaks.phase_linked_luminance_offset)); } void ScanTarget::enable_vertex_attributes(ShaderType type, Shader &target) { @@ -179,24 +27,13 @@ void ScanTarget::enable_vertex_attributes(ShaderType type, Shader &target) { for(int c = 0; c < 2; ++c) { const std::string prefix = c ? "end" : "start"; - target.enable_vertex_attribute_with_pointer( - prefix + "Point", - 2, GL_UNSIGNED_SHORT, GL_FALSE, - sizeof(Scan), - reinterpret_cast(offsetof(Scan, scan.end_points[c].x)), - 1); target.enable_vertex_attribute_with_pointer( prefix + "DataX", 1, GL_UNSIGNED_SHORT, GL_FALSE, sizeof(Scan), reinterpret_cast(offsetof(Scan, scan.end_points[c].data_offset)), 1); - target.enable_vertex_attribute_with_pointer( - prefix + "CompositeAngle", - 1, GL_UNSIGNED_SHORT, GL_FALSE, - sizeof(Scan), - reinterpret_cast(offsetof(Scan, scan.end_points[c].composite_angle)), - 1); + target.enable_vertex_attribute_with_pointer( prefix + "Clock", 1, GL_UNSIGNED_SHORT, GL_FALSE, @@ -211,18 +48,13 @@ void ScanTarget::enable_vertex_attributes(ShaderType type, Shader &target) { sizeof(Scan), reinterpret_cast(offsetof(Scan, data_y)), 1); + target.enable_vertex_attribute_with_pointer( "lineY", 1, GL_UNSIGNED_SHORT, GL_FALSE, sizeof(Scan), reinterpret_cast(offsetof(Scan, line)), 1); - target.enable_vertex_attribute_with_pointer( - "compositeAmplitude", - 1, GL_UNSIGNED_BYTE, GL_FALSE, - sizeof(Scan), - reinterpret_cast(offsetof(Scan, scan.composite_amplitude)), - 1); break; case ShaderType::Line: @@ -269,6 +101,30 @@ void ScanTarget::enable_vertex_attributes(ShaderType type, Shader &target) { } std::unique_ptr ScanTarget::composition_shader(InputDataType input_data_type) { + const std::string vertex_shader = + "#version 150\n" + + "in float startDataX;" + "in float startClock;" + + "in float endDataX;" + "in float endClock;" + + "in float dataY;" + "in float lineY;" + + "out vec2 textureCoordinate;" + "uniform usampler2D textureName;" + + "void main(void) {" + "float lateral = float(gl_VertexID & 1);" + "float longitudinal = float((gl_VertexID & 2) >> 1);" + + "textureCoordinate = vec2(mix(startDataX, endDataX, lateral), dataY + 0.5) / textureSize(textureName, 0);" + "vec2 eyePosition = vec2(mix(startClock, endClock, lateral), lineY + longitudinal) / vec2(2048.0, 2048.0);" + "gl_Position = vec4(eyePosition*2.0 - vec2(1.0), 0.0, 1.0);" + "}"; + std::string fragment_shader = "#version 150\n" @@ -312,15 +168,20 @@ std::unique_ptr ScanTarget::composition_shader(InputDataType input_data_ } return std::unique_ptr(new Shader( - glsl_globals(ShaderType::InputScan) + glsl_default_vertex_shader(ShaderType::InputScan), + vertex_shader, fragment_shader + "}", - attribute_bindings(ShaderType::InputScan) + { + {"startDataX", 0}, + {"startClock", 1}, + {"endDataX", 2}, + {"endClock", 3}, + {"dataY", 4}, + {"lineY", 5}, + } )); } std::unique_ptr ScanTarget::conversion_shader(InputDataType input_data_type, DisplayType display_type, ColourSpace colour_space) { - display_type = DisplayType::CompositeColour; // Just a test. - // Compose a vertex shader. If the display type is RGB, generate just the proper // geometry position, plus a solitary textureCoordinate. // @@ -337,7 +198,6 @@ std::unique_ptr ScanTarget::conversion_shader(InputDataType input_data_t "uniform vec2 scale;" "uniform float rowHeight;" - "uniform float processingWidth;" "in vec2 startPoint;" "in vec2 endPoint;" @@ -379,14 +239,10 @@ std::unique_ptr ScanTarget::conversion_shader(InputDataType input_data_t break; case DisplayType::CompositeColour: + case DisplayType::SVideo: vertex_shader += "out vec2 textureCoordinates[4];"; fragment_shader += "in vec2 textureCoordinates[4];"; break; - - case DisplayType::SVideo: - vertex_shader += "out vec2 textureCoordinates[3];"; - fragment_shader += "in vec2 textureCoordinates[3];"; - break; } // Add the code to generate a proper output position; this applies to all display types. @@ -417,6 +273,7 @@ std::unique_ptr ScanTarget::conversion_shader(InputDataType input_data_t break; case DisplayType::CompositeColour: + case DisplayType::SVideo: vertex_shader += "float centreClock = mix(startClock, endClock, lateral);" "float clocksPerAngle = (endClock - startClock) / (abs(endCompositeAngle - startCompositeAngle) / 64.0);" @@ -425,10 +282,6 @@ std::unique_ptr ScanTarget::conversion_shader(InputDataType input_data_t "textureCoordinates[2] = vec2(centreClock + 0.125*clocksPerAngle, lineY + 0.5) / textureSize(textureName, 0);" "textureCoordinates[3] = vec2(centreClock + 0.375*clocksPerAngle, lineY + 0.5) / textureSize(textureName, 0);"; break; - - case DisplayType::SVideo: - // TODO - break; } vertex_shader += "}"; @@ -443,6 +296,46 @@ std::unique_ptr ScanTarget::conversion_shader(InputDataType input_data_t "uniform mat3 rgbToLumaChroma;"; } + if(display_type == DisplayType::SVideo) { + fragment_shader += + "vec2 svideo_sample(vec2 coordinate, float angle) {"; + + switch(input_data_type) { + case InputDataType::Luminance1: + case InputDataType::Luminance8: + // Easy, just copy across. + fragment_shader += "return vec2(texture(textureName, coordinate).r, 0.0);"; + break; + + case InputDataType::PhaseLinkedLuminance8: + fragment_shader += + "uint iPhase = uint((angle * 2.0 / 3.141592654) ) & 3u;" // + phaseOffset*4.0 + "return vec2(texture(textureName, coordinate)[iPhase], 0.0);"; + break; + + case InputDataType::Luminance8Phase8: + fragment_shader += + "vec2 yc = texture(textureName, coordinate).rg;" + + "float phaseOffset = 3.141592654 * 2.0 * 2.0 * yc.y;" + "float rawChroma = step(yc.y, 0.75) * cos(angle + phaseOffset);" + "return vec2(yc.x, rawChroma);"; + break; + + case InputDataType::Red1Green1Blue1: + case InputDataType::Red2Green2Blue2: + case InputDataType::Red4Green4Blue4: + case InputDataType::Red8Green8Blue8: + fragment_shader += + "vec3 colour = rgbToLumaChroma * texture(textureName, coordinate).rgb;" + "vec2 quadrature = vec2(cos(angle), sin(angle));" + "return vec2(colour.r, dot(quadrature, colour.gb));"; + break; + } + + fragment_shader += "}"; + } + if(display_type == DisplayType::CompositeMonochrome || display_type == DisplayType::CompositeColour) { fragment_shader += "float composite_sample(vec2 coordinate, float angle) {"; @@ -466,7 +359,7 @@ std::unique_ptr ScanTarget::conversion_shader(InputDataType input_data_t "float phaseOffset = 3.141592654 * 2.0 * 2.0 * yc.y;" "float rawChroma = step(yc.y, 0.75) * cos(angle + phaseOffset);" - "return mix(yc.x, yc.y * rawChroma, compositeAmplitude);"; + "return mix(yc.x, rawChroma, compositeAmplitude);"; break; case InputDataType::Red1Green1Blue1: @@ -487,25 +380,50 @@ std::unique_ptr ScanTarget::conversion_shader(InputDataType input_data_t "void main(void) {" "vec3 fragColour3;"; + if(display_type == DisplayType::CompositeColour || display_type == DisplayType::SVideo) { + fragment_shader += + // Figure out the four composite angles. + "vec4 angles = vec4(" + "compositeAngle - 2.356194490192345," + "compositeAngle - 0.785398163397448," + "compositeAngle + 0.785398163397448," + "compositeAngle + 2.356194490192345" + ");"; + } + switch(display_type) { case DisplayType::RGB: fragment_shader += "fragColour3 = texture(textureName, textureCoordinate).rgb;"; break; case DisplayType::SVideo: - // TODO + fragment_shader += + // Sample four times over, at proper angle offsets. + "vec2 samples[4] = vec2[4](" + "svideo_sample(textureCoordinates[0], angles[0])," + "svideo_sample(textureCoordinates[1], angles[1])," + "svideo_sample(textureCoordinates[2], angles[2])," + "svideo_sample(textureCoordinates[3], angles[3])" + ");" + "vec4 chrominances = vec4(" + "samples[0].y," + "samples[1].y," + "samples[2].y," + "samples[3].y" + ");" + + // Split and average chrominance. + "vec2 channels = vec2(" + "dot(cos(angles), chrominances)," + "dot(sin(angles), chrominances)" + ") * vec2(0.25);" + + // Apply a colour space conversion to get RGB. + "fragColour3 = lumaChromaToRGB * vec3(samples[1].x, channels);"; break; case DisplayType::CompositeColour: fragment_shader += - // Figure out the four composite angles. TODO: make these an input? - "vec4 angles = vec4(" - "compositeAngle - 2.356194490192345," - "compositeAngle - 0.785398163397448," - "compositeAngle + 0.785398163397448," - "compositeAngle + 2.356194490192345" - ");" - // Sample four times over, at proper angle offsets. "vec4 samples = vec4(" "composite_sample(textureCoordinates[0], angles[0])," @@ -542,7 +460,16 @@ std::unique_ptr ScanTarget::conversion_shader(InputDataType input_data_t const auto shader = new Shader( vertex_shader, fragment_shader, - attribute_bindings(ShaderType::Line) + { + {"startPoint", 0}, + {"endPoint", 1}, + {"startClock", 2}, + {"endClock", 3}, + {"lineY", 4}, + {"lineCompositeAmplitude", 5}, + {"startCompositeAngle", 6}, + {"endCompositeAngle", 7}, + } ); // If this isn't an RGB or composite colour shader, set the proper colour space.