From 798cc58f765b2dbade846542d84edf66310550c9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 5 Feb 2019 19:22:35 -0500 Subject: [PATCH 01/15] Simplifies the composite colour shader no longer to handle colour. --- Outputs/OpenGL/ScanTargetGLSLFragments.cpp | 81 +++++++++------------- 1 file changed, 33 insertions(+), 48 deletions(-) diff --git a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp index d97d6c3f4..01ebba6e0 100644 --- a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp +++ b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp @@ -240,14 +240,14 @@ std::unique_ptr ScanTarget::conversion_shader() const { "out float compositeAmplitude;" "out float oneOverCompositeAmplitude;" - "uniform float textureCoordinateOffsets[7];" + "uniform float textureCoordinateOffsets[4];" "uniform float angleOffsets[4];"; fragment_shader += "in float compositeAngle;" "in float compositeAmplitude;" "in float oneOverCompositeAmplitude;" - "uniform vec4 compositeAngleOffsets[2];"; + "uniform vec4 compositeAngleOffsets;"; } switch(modals_.display_type){ @@ -260,9 +260,9 @@ std::unique_ptr ScanTarget::conversion_shader() const { case DisplayType::CompositeColour: case DisplayType::SVideo: vertex_shader += - "out vec2 textureCoordinates[7];"; + "out vec2 textureCoordinates[4];"; fragment_shader += - "in vec2 textureCoordinates[7];"; + "in vec2 textureCoordinates[4];"; break; } @@ -300,10 +300,7 @@ std::unique_ptr ScanTarget::conversion_shader() const { "textureCoordinates[0] = vec2(centreClock + textureCoordinateOffsets[0], lineY + 0.5) / textureSize(textureName, 0);" "textureCoordinates[1] = vec2(centreClock + textureCoordinateOffsets[1], lineY + 0.5) / textureSize(textureName, 0);" "textureCoordinates[2] = vec2(centreClock + textureCoordinateOffsets[2], lineY + 0.5) / textureSize(textureName, 0);" - "textureCoordinates[3] = vec2(centreClock + textureCoordinateOffsets[3], lineY + 0.5) / textureSize(textureName, 0);" - "textureCoordinates[4] = vec2(centreClock + textureCoordinateOffsets[4], lineY + 0.5) / textureSize(textureName, 0);" - "textureCoordinates[5] = vec2(centreClock + textureCoordinateOffsets[5], lineY + 0.5) / textureSize(textureName, 0);" - "textureCoordinates[6] = vec2(centreClock + textureCoordinateOffsets[6], lineY + 0.5) / textureSize(textureName, 0);"; + "textureCoordinates[3] = vec2(centreClock + textureCoordinateOffsets[3], lineY + 0.5) / textureSize(textureName, 0);"; break; } @@ -413,53 +410,41 @@ std::unique_ptr ScanTarget::conversion_shader() const { case DisplayType::CompositeColour: fragment_shader += - "vec4 angles[2] = vec4[2](" - "vec4(compositeAngle) + compositeAngleOffsets[0]," - "vec4(compositeAngle) + compositeAngleOffsets[1]" - ");" + "vec4 angles = compositeAngle + compositeAngleOffsets;" // Sample four times over, at proper angle offsets. - "vec4 samples[2] = vec4[2](vec4(" - "composite_sample(textureCoordinates[0], angles[0].x)," - "composite_sample(textureCoordinates[1], angles[0].y)," - "composite_sample(textureCoordinates[2], angles[0].z)," - "composite_sample(textureCoordinates[3], angles[0].w)" - "), vec4(" - "composite_sample(textureCoordinates[4], angles[1].x)," - "composite_sample(textureCoordinates[5], angles[1].y)," - "composite_sample(textureCoordinates[6], angles[1].z)," - "0.0" - "));" + "vec4 samples = vec4(" + "composite_sample(textureCoordinates[0], angles.x)," + "composite_sample(textureCoordinates[1], angles.y)," + "composite_sample(textureCoordinates[2], angles.z)," + "composite_sample(textureCoordinates[3], angles.w)" + ");" // Compute a luminance for use if there's no colour information, now, before // modifying samples. - "float mono_luminance = dot(vec3(samples[0].zw, samples[1].x), vec3(0.15, 0.7, 0.15));" + "float mono_luminance = dot(samples, vec4(0.15, 0.35, 0.35, 0.15));" // TODO: figure out proper coefficients. // Take the average to calculate luminance, then subtract that from all four samples to // give chrominance. - "float luminances[4] = float[4](" - "dot(samples[0], vec4(0.25))," - "dot(vec4(samples[0].yzw, samples[1].x), vec4(0.25))," - "dot(vec4(samples[0].zw, samples[1].xy), vec4(0.25))," - "dot(vec4(samples[0].w, samples[1].xyz), vec4(0.25))" - ");" + "float luminance = dot(samples, vec4(0.25));" // Split and average chrominance. - "vec4 chrominances = vec4(" - "samples[0].y - luminances[0]," - "samples[0].z - luminances[1]," - "samples[0].w - luminances[2]," - "samples[1].x - luminances[3]" - ");" - "vec4 chrominance_angles = vec4(angles[0].yzw, angles[1].x);" - "vec2 channels = vec2(" - "dot(cos(chrominance_angles), chrominances)," - "dot(sin(chrominance_angles), chrominances)" - ") * vec2(0.125 * oneOverCompositeAmplitude);" +// "vec4 chrominances = vec4(" +// "samples[0].y - luminances[0]," +// "samples[0].z - luminances[1]," +// "samples[0].w - luminances[2]," +// "samples[1].x - luminances[3]" +// ");" +// "vec4 chrominance_angles = vec4(angles[0].yzw, angles[1].x);" +// "vec2 channels = vec2(" +// "dot(cos(chrominance_angles), chrominances)," +// "dot(sin(chrominance_angles), chrominances)" +// ") * vec2(0.125 * oneOverCompositeAmplitude);" + "vec2 channels = vec2(0.0);" // Apply a colour space conversion to get RGB. "fragColour3 = mix(" - "lumaChromaToRGB * vec3(luminances[2] / (1.0 - compositeAmplitude), channels)," + "lumaChromaToRGB * vec3(luminance / (1.0 - compositeAmplitude), channels)," "vec3(mono_luminance)," "step(oneOverCompositeAmplitude, 0.01)" ");"; @@ -503,15 +488,15 @@ std::unique_ptr ScanTarget::conversion_shader() const { // If this isn't an RGB or composite colour shader, set the proper colour space. if(modals_.display_type != DisplayType::RGB) { const float clocks_per_angle = float(modals_.cycles_per_line) * float(modals_.colour_cycle_denominator) / float(modals_.colour_cycle_numerator); - GLfloat texture_offsets[7]; - GLfloat angles[8]; - for(int c = 0; c < 7; ++c) { - GLfloat angle = (GLfloat(c) - 3.5f) / 4.0f; + GLfloat texture_offsets[4]; + GLfloat angles[4]; + for(int c = 0; c < 4; ++c) { + GLfloat angle = (GLfloat(c) - 1.5f) / 4.0f; texture_offsets[c] = angle * clocks_per_angle; angles[c] = GLfloat(angle * 2.0f * M_PI); } - shader->set_uniform("textureCoordinateOffsets", 1, 7, texture_offsets); - shader->set_uniform("compositeAngleOffsets", 4, 2, angles); + shader->set_uniform("textureCoordinateOffsets", 1, 4, texture_offsets); + shader->set_uniform("compositeAngleOffsets", 4, 1, angles); switch(modals_.composite_colour_space) { case ColourSpace::YIQ: { From 75987f64ec8cec5aee0f848d6e4640e60645e1cf Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 5 Feb 2019 21:42:39 -0500 Subject: [PATCH 02/15] Restores Oric audio. --- Machines/Oric/Oric.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index eab547133..34b847688 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -219,6 +219,7 @@ template class Co via_(via_port_handler_), diskii_(2000000) { set_clock_rate(1000000); + speaker_.set_input_rate(1000000.0f); via_port_handler_.set_interrupt_delegate(this); tape_player_.set_delegate(this); Memory::Fuzz(ram_, sizeof(ram_)); From eecd4417e75b9c9108e46458906c2ad19ed6e68a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 9 Feb 2019 16:54:31 -0500 Subject: [PATCH 03/15] Bites the bullet and accepts that an additional texture will be useful for QAM separation. --- Outputs/OpenGL/ScanTarget.cpp | 54 ++- Outputs/OpenGL/ScanTarget.hpp | 18 +- Outputs/OpenGL/ScanTargetGLSLFragments.cpp | 386 ++++++++++++++------- 3 files changed, 323 insertions(+), 135 deletions(-) diff --git a/Outputs/OpenGL/ScanTarget.cpp b/Outputs/OpenGL/ScanTarget.cpp index 79c3f23d9..eef867d41 100644 --- a/Outputs/OpenGL/ScanTarget.cpp +++ b/Outputs/OpenGL/ScanTarget.cpp @@ -24,7 +24,14 @@ constexpr GLenum SourceDataTextureUnit = GL_TEXTURE0; constexpr GLenum UnprocessedLineBufferTextureUnit = GL_TEXTURE1; /// The texture unit that contains the current display. -constexpr GLenum AccumulationTextureUnit = GL_TEXTURE2; +constexpr GLenum AccumulationTextureUnit = GL_TEXTURE3; + +/// The texture unit that contains a pre-lowpass-filtered but fixed-resolution version of the chroma signal; +/// this is used when processing composite video only, and for chroma information only. Luminance is calculated +/// at the fidelity permitted by the output target, but my efforts to separate, demodulate and filter +/// chrominance during output without either massively sampling or else incurring significant high-frequency +/// noise that sampling reduces into a Moire, have proven to be unsuccessful for the time being. +constexpr GLenum QAMChromaTextureUnit = GL_TEXTURE2; #define TextureAddress(x, y) (((y) << 11) | (x)) #define TextureAddressGetY(v) uint16_t((v) >> 11) @@ -299,9 +306,23 @@ void ScanTarget::setup_pipeline() { write_pointers_.write_area = 0; } - // 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; + // Destroy or create a QAM buffer and shader, if appropriate. + const bool needs_qam_buffer = (modals_.display_type == DisplayType::CompositeColour || modals_.display_type == DisplayType::SVideo); + if(needs_qam_buffer && !qam_chroma_texture_) { + qam_chroma_texture_.reset(new TextureTarget(LineBufferWidth, LineBufferHeight, QAMChromaTextureUnit, GL_NEAREST, false)); + } else { + qam_chroma_texture_.reset(); + qam_separation_shader_.reset(); + } + + if(needs_qam_buffer) { + qam_separation_shader_ = qam_separation_shader(); + glBindVertexArray(line_vertex_array_); + glBindBuffer(GL_ARRAY_BUFFER, line_buffer_name_); + enable_vertex_attributes(ShaderType::QAMSeparation, *qam_separation_shader_); + set_uniforms(ShaderType::QAMSeparation, *qam_separation_shader_); + qam_separation_shader_->set_uniform("textureName", GLint(UnprocessedLineBufferTextureUnit - GL_TEXTURE0)); + } // Establish an output shader. output_shader_ = conversion_shader(); @@ -312,6 +333,7 @@ void ScanTarget::setup_pipeline() { 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); output_shader_->set_uniform("textureName", GLint(UnprocessedLineBufferTextureUnit - GL_TEXTURE0)); + output_shader_->set_uniform("qamTextureName", GLint(QAMChromaTextureUnit - GL_TEXTURE0)); // Establish an input shader. input_shader_ = composition_shader(); @@ -487,8 +509,10 @@ void ScanTarget::draw(bool synchronous, int output_width, int output_height) { // Figure out how many new spans are ostensible ready; use two less than that. uint16_t new_spans = (submit_pointers.line + LineBufferHeight - read_pointers.line) % LineBufferHeight; if(new_spans) { - // Bind the accumulation framebuffer. - accumulation_texture_->bind_framebuffer(); + // Bind the accumulation framebuffer, unless there's going to be QAM work. + if(!qam_separation_shader_) { + accumulation_texture_->bind_framebuffer(); + } // Enable blending and stenciling, and ensure spans increment the stencil buffer. glEnable(GL_BLEND); @@ -525,10 +549,12 @@ void ScanTarget::draw(bool synchronous, int output_width, int output_height) { // Rebind the program for span output. glBindVertexArray(line_vertex_array_); - output_shader_->bind(); + if(!qam_separation_shader_) { + output_shader_->bind(); + } } - // Upload and draw. + // Upload. const auto buffer_size = spans * sizeof(Line); if(!end_line || end_line > start_line) { glBufferSubData(GL_ARRAY_BUFFER, 0, GLsizeiptr(buffer_size), &line_buffer_[start_line]); @@ -547,6 +573,18 @@ void ScanTarget::draw(bool synchronous, int output_width, int output_height) { glUnmapBuffer(GL_ARRAY_BUFFER); } + // Produce colour information, if required. + if(qam_separation_shader_) { + qam_chroma_texture_->bind_framebuffer(); + qam_separation_shader_->bind(); + + glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, GLsizei(spans)); + + accumulation_texture_->bind_framebuffer(); + output_shader_->bind(); + } + + // Render to the output. glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, GLsizei(spans)); start_line = end_line; diff --git a/Outputs/OpenGL/ScanTarget.hpp b/Outputs/OpenGL/ScanTarget.hpp index d3b6337bc..c53ec3ef0 100644 --- a/Outputs/OpenGL/ScanTarget.hpp +++ b/Outputs/OpenGL/ScanTarget.hpp @@ -124,6 +124,10 @@ class ScanTarget: public Outputs::Display::ScanTarget { // application of any necessary conversions — e.g. composite processing. TextureTarget unprocessed_line_texture_; + // Contains pre-lowpass-filtered chrominance information that is + // part-QAM-demoduled, if dealing with a QAM data source. + std::unique_ptr qam_chroma_texture_; + // Scans are accumulated to the accumulation texture; the full-display // rectangle is used to ensure untouched pixels properly decay. std::unique_ptr accumulation_texture_; @@ -165,7 +169,8 @@ class ScanTarget: public Outputs::Display::ScanTarget { enum class ShaderType { Composition, - Conversion + Conversion, + QAMSeparation }; /*! @@ -178,9 +183,10 @@ class ScanTarget: public Outputs::Display::ScanTarget { GLsync fence_ = nullptr; std::atomic_flag is_drawing_; - int processing_width_ = 0; std::unique_ptr input_shader_; std::unique_ptr output_shader_; + std::unique_ptr qam_separation_shader_; + /*! Produces a shader that composes fragment of the input stream to a single buffer, @@ -193,6 +199,14 @@ class ScanTarget: public Outputs::Display::ScanTarget { output RGB, decoding composite or S-Video as necessary. */ std::unique_ptr conversion_shader() const; + /*! + Produces a shader that writes separated but not-yet filtered QAM components + from the unprocessed line texture to the QAM chroma texture, at a fixed + size of four samples per colour clock, point sampled. + */ + std::unique_ptr qam_separation_shader() const; + + std::string sampling_function() const; }; } diff --git a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp index 01ebba6e0..19a9cb1fa 100644 --- a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp +++ b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp @@ -12,6 +12,8 @@ using namespace Outputs::Display::OpenGL; +// MARK: - State setup for compiled shaders. + 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 @@ -69,16 +71,18 @@ void ScanTarget::enable_vertex_attributes(ShaderType type, Shader &target) { 1); break; - case ShaderType::Conversion: + default: 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(Line), - reinterpret_cast(rt_offset_of(end_points[c].x, test_line)), - 1); + if(type == ShaderType::Conversion) { + target.enable_vertex_attribute_with_pointer( + prefix + "Point", + 2, GL_UNSIGNED_SHORT, GL_FALSE, + sizeof(Line), + reinterpret_cast(rt_offset_of(end_points[c].x, test_line)), + 1); + } target.enable_vertex_attribute_with_pointer( prefix + "Clock", @@ -113,85 +117,71 @@ void ScanTarget::enable_vertex_attributes(ShaderType type, Shader &target) { #undef rt_offset_of } -std::unique_ptr ScanTarget::composition_shader() const { - const std::string vertex_shader = - "#version 150\n" +// MARK: - Shader code. - "in float startDataX;" - "in float startClock;" +std::string ScanTarget::sampling_function() const { + std::string fragment_shader; - "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" - - "out vec4 fragColour;" - "in vec2 textureCoordinate;" - - "uniform usampler2D textureName;" - - "void main(void) {"; + if(modals_.display_type == DisplayType::SVideo) { + fragment_shader += + "vec2 svideo_sample(vec2 coordinate, float angle) {"; + } else { + fragment_shader += + "float composite_sample(vec2 coordinate, float angle) {"; + } + const bool is_svideo = modals_.display_type == DisplayType::SVideo; switch(modals_.input_data_type) { case InputDataType::Luminance1: - fragment_shader += "fragColour = textureLod(textureName, textureCoordinate, 0).rrrr;"; - break; - case InputDataType::Luminance8: - fragment_shader += "fragColour = textureLod(textureName, textureCoordinate, 0).rrrr / vec4(255.0);"; + // Easy, just copy across. + fragment_shader += + is_svideo ? + "return vec2(textureLod(textureName, coordinate, 0).r, 0.0);" : + "return textureLod(textureName, coordinate, 0).r;"; break; case InputDataType::PhaseLinkedLuminance8: + fragment_shader += + "uint iPhase = uint((angle * 2.0 / 3.141592654) ) & 3u;"; + + fragment_shader += + is_svideo ? + "return vec2(textureLod(textureName, coordinate, 0)[iPhase], 0.0);" : + "return textureLod(textureName, coordinate, 0)[iPhase];"; + break; + case InputDataType::Luminance8Phase8: - case InputDataType::Red8Green8Blue8: - fragment_shader += "fragColour = textureLod(textureName, textureCoordinate, 0) / vec4(255.0);"; + fragment_shader += + "vec2 yc = textureLod(textureName, coordinate, 0).rg;" + + "float phaseOffset = 3.141592654 * 2.0 * 2.0 * yc.y;" + "float rawChroma = step(yc.y, 0.75) * cos(angle + phaseOffset);"; + + fragment_shader += + is_svideo ? + "return vec2(yc.x, rawChroma);" : + "return mix(yc.x, rawChroma, compositeAmplitude);"; break; case InputDataType::Red1Green1Blue1: - fragment_shader += "fragColour = vec4(textureLod(textureName, textureCoordinate, 0).rrr & uvec3(4u, 2u, 1u), 1.0);"; - break; - case InputDataType::Red2Green2Blue2: - fragment_shader += - "uint textureValue = textureLod(textureName, textureCoordinate, 0).r;" - "fragColour = vec4(float((textureValue >> 4) & 3u), float((textureValue >> 2) & 3u), float(textureValue & 3u), 3.0) / 3.0;"; - break; - case InputDataType::Red4Green4Blue4: + case InputDataType::Red8Green8Blue8: fragment_shader += - "uvec2 textureValue = textureLod(textureName, textureCoordinate, 0).rg;" - "fragColour = vec4(float(textureValue.r) / 15.0, float(textureValue.g & 240u) / 240.0, float(textureValue.g & 15u) / 15.0, 1.0);"; + "vec3 colour = rgbToLumaChroma * textureLod(textureName, coordinate, 0).rgb;" + "vec2 quadrature = vec2(cos(angle), sin(angle));"; + + fragment_shader += + is_svideo ? + "return vec2(colour.r, dot(quadrature, colour.gb));" : + "return mix(colour.r, dot(quadrature, colour.gb), compositeAmplitude);"; break; } - return std::unique_ptr(new Shader( - vertex_shader, - fragment_shader + "}", - { - "startDataX", - "startClock", - "endDataX", - "endClock", - "dataY", - "lineY", - } - )); + fragment_shader += "}"; + + return fragment_shader; } std::unique_ptr ScanTarget::conversion_shader() const { @@ -224,6 +214,7 @@ std::unique_ptr ScanTarget::conversion_shader() const { "in float lineCompositeAmplitude;" "uniform sampler2D textureName;" + "uniform sampler2D qamTextureName;" "uniform vec2 origin;" "uniform vec2 size;"; @@ -231,6 +222,7 @@ std::unique_ptr ScanTarget::conversion_shader() const { "#version 150\n" "uniform sampler2D textureName;" + "uniform sampler2D qamTextureName;" "out vec4 fragColour;"; @@ -313,64 +305,7 @@ std::unique_ptr ScanTarget::conversion_shader() const { "uniform mat3 lumaChromaToRGB;" "uniform mat3 rgbToLumaChroma;"; - if(modals_.display_type == DisplayType::SVideo) { - fragment_shader += - "vec2 svideo_sample(vec2 coordinate, float angle) {"; - } else { - fragment_shader += - "float composite_sample(vec2 coordinate, float angle) {"; - } - - const bool is_svideo = modals_.display_type == DisplayType::SVideo; - switch(modals_.input_data_type) { - case InputDataType::Luminance1: - case InputDataType::Luminance8: - // Easy, just copy across. - fragment_shader += - is_svideo ? - "return vec2(textureLod(textureName, coordinate, 0).r, 0.0);" : - "return textureLod(textureName, coordinate, 0).r;"; - break; - - case InputDataType::PhaseLinkedLuminance8: - fragment_shader += - "uint iPhase = uint((angle * 2.0 / 3.141592654) ) & 3u;"; - - fragment_shader += - is_svideo ? - "return vec2(textureLod(textureName, coordinate, 0)[iPhase], 0.0);" : - "return textureLod(textureName, coordinate, 0)[iPhase];"; - break; - - case InputDataType::Luminance8Phase8: - fragment_shader += - "vec2 yc = textureLod(textureName, coordinate, 0).rg;" - - "float phaseOffset = 3.141592654 * 2.0 * 2.0 * yc.y;" - "float rawChroma = step(yc.y, 0.75) * cos(angle + phaseOffset);"; - - fragment_shader += - is_svideo ? - "return vec2(yc.x, rawChroma);" : - "return mix(yc.x, rawChroma, compositeAmplitude);"; - break; - - case InputDataType::Red1Green1Blue1: - case InputDataType::Red2Green2Blue2: - case InputDataType::Red4Green4Blue4: - case InputDataType::Red8Green8Blue8: - fragment_shader += - "vec3 colour = rgbToLumaChroma * textureLod(textureName, coordinate, 0).rgb;" - "vec2 quadrature = vec2(cos(angle), sin(angle));"; - - fragment_shader += - is_svideo ? - "return vec2(colour.r, dot(quadrature, colour.gb));" : - "return mix(colour.r, dot(quadrature, colour.gb), compositeAmplitude);"; - break; - } - - fragment_shader += "}"; + fragment_shader += sampling_function(); } fragment_shader += @@ -517,3 +452,204 @@ std::unique_ptr ScanTarget::conversion_shader() const { return std::unique_ptr(shader); } + +std::unique_ptr ScanTarget::composition_shader() const { + 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" + + "out vec4 fragColour;" + "in vec2 textureCoordinate;" + + "uniform usampler2D textureName;" + + "void main(void) {"; + + switch(modals_.input_data_type) { + case InputDataType::Luminance1: + fragment_shader += "fragColour = textureLod(textureName, textureCoordinate, 0).rrrr;"; + break; + + case InputDataType::Luminance8: + fragment_shader += "fragColour = textureLod(textureName, textureCoordinate, 0).rrrr / vec4(255.0);"; + break; + + case InputDataType::PhaseLinkedLuminance8: + case InputDataType::Luminance8Phase8: + case InputDataType::Red8Green8Blue8: + fragment_shader += "fragColour = textureLod(textureName, textureCoordinate, 0) / vec4(255.0);"; + break; + + case InputDataType::Red1Green1Blue1: + fragment_shader += "fragColour = vec4(textureLod(textureName, textureCoordinate, 0).rrr & uvec3(4u, 2u, 1u), 1.0);"; + break; + + case InputDataType::Red2Green2Blue2: + fragment_shader += + "uint textureValue = textureLod(textureName, textureCoordinate, 0).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 = textureLod(textureName, textureCoordinate, 0).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( + vertex_shader, + fragment_shader + "}", + { + "startDataX", + "startClock", + "endDataX", + "endClock", + "dataY", + "lineY", + } + )); +} + +std::unique_ptr ScanTarget::qam_separation_shader() const { + const bool is_svideo = modals_.display_type == DisplayType::SVideo; + + // Sets up texture coordinates to run between startClock and endClock, mapping to + // coordinates that correlate with four times the absolute value of the composite angle. + std::string vertex_shader = + "#version 150\n" + + "in float startClock;" + "in float startCompositeAngle;" + "in float endClock;" + "in float endCompositeAngle;" + + "in float lineY;" + "in float lineCompositeAmplitude;" + + "uniform sampler2D textureName;" + "uniform float textureCoordinateOffsets[4];" + + "out float compositeAngle;" + "out float compositeAmplitude;" + "out float oneOverCompositeAmplitude;"; + + std::string fragment_shader = + "#version 150\n" + + "uniform sampler2D textureName;" + + "in float compositeAngle;" + "in float compositeAmplitude;" + "in float oneOverCompositeAmplitude;" + + "out vec4 fragColour;" + "uniform vec4 compositeAngleOffsets;"; + + if(is_svideo) { + vertex_shader += "out vec2 textureCoordinate;"; + fragment_shader += "out vec2 textureCoordinate;"; + } else { + vertex_shader += "out vec2 textureCoordinates[4];"; + fragment_shader += "out vec2 textureCoordinates[4];"; + } + + vertex_shader += + "void main(void) {" + "float lateral = float(gl_VertexID & 1);" + "float longitudinal = float((gl_VertexID & 2) >> 1);" + + "vec2 eyePosition = vec2(abs(mix(startCompositeAngle, endCompositeAngle, lateral) * 4.0), lineY + longitudinal) / vec2(2048.0, 2048.0);" + "gl_Position = vec4(eyePosition*2.0 - vec2(1.0), 0.0, 1.0);" + + "compositeAngle = (mix(startCompositeAngle, endCompositeAngle, lateral) / 32.0) * 3.141592654;" + "compositeAmplitude = lineCompositeAmplitude / 255.0;" + "oneOverCompositeAmplitude = mix(0.0, 255.0 / lineCompositeAmplitude, step(0.01, lineCompositeAmplitude));" + + "float centreClock = mix(startClock, endClock, lateral);"; + + if(is_svideo) { + vertex_shader += + "textureCoordinate = vec2(centreClock + textureCoordinateOffsets[0], lineY + 0.5) / textureSize(textureName, 0)"; + } else { + vertex_shader += + "textureCoordinates[0] = vec2(centreClock + textureCoordinateOffsets[0], lineY + 0.5) / textureSize(textureName, 0);" + "textureCoordinates[1] = vec2(centreClock + textureCoordinateOffsets[1], lineY + 0.5) / textureSize(textureName, 0);" + "textureCoordinates[2] = vec2(centreClock + textureCoordinateOffsets[2], lineY + 0.5) / textureSize(textureName, 0);" + "textureCoordinates[3] = vec2(centreClock + textureCoordinateOffsets[3], lineY + 0.5) / textureSize(textureName, 0);"; + } + + vertex_shader += "}"; + + + fragment_shader += + sampling_function() + + "void main(void) {"; + + // TODO: properly map range of composite value. + + if(modals_.display_type == DisplayType::SVideo) { + fragment_shader += + "fragColour = vec4(svideo_sample(textureCoordinate, compositeAngle).rgg * vec3(1.0, cos(compositeAngle), sin(compositeAngle)), 1.0);"; + } else { + fragment_shader += + "vec4 angles = compositeAngle + compositeAngleOffsets;" + + // Sample four times over, at proper angle offsets. + "vec4 samples = vec4(" + "composite_sample(textureCoordinates[0], angles.x)," + "composite_sample(textureCoordinates[1], angles.y)," + "composite_sample(textureCoordinates[2], angles.z)," + "composite_sample(textureCoordinates[3], angles.w)" + ");" + + // Take the average to calculate luminance, then subtract that from all four samples to + // give chrominance. + "float luminance = dot(samples, vec4(0.25));" + "float chrominance = (samples.y - luminance) * oneOverCompositeAmplitude;" + + "vec2 channels = vec2(cos(compositeAngle), sin(compositeAngle)) * chrominance;" + + // Apply a colour space conversion to get RGB. + "fragColour = vec4(luminance, channels, 1.0);"; + }; + + fragment_shader += "}"; + + return std::unique_ptr(new Shader( + vertex_shader, + fragment_shader, + { + "startClock", + "startCompositeAngle", + "endClock", + "endCompositeAngle", + + "lineY", + "lineCompositeAmplitude" + } + )); +} From 1cd6d58f17969af3b05c30fee36ae8c67c3b8c58 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 9 Feb 2019 17:13:43 -0500 Subject: [PATCH 04/15] Restores S-Video through line, as monochrome. --- Outputs/OpenGL/ScanTargetGLSLFragments.cpp | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp index 19a9cb1fa..c7975bee4 100644 --- a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp +++ b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp @@ -320,27 +320,13 @@ std::unique_ptr ScanTarget::conversion_shader() const { case DisplayType::SVideo: 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" - ");" + "vec2 sample = svideo_sample(textureCoordinates[1], compositeAngle);" // Split and average chrominance. - "vec2 channels = vec2(" - "dot(cos(angles), chrominances)," - "dot(sin(angles), chrominances)" - ") * vec2(0.25);" + "vec2 channels = vec2(0.0, 0.0);" // Apply a colour space conversion to get RGB. - "fragColour3 = lumaChromaToRGB * vec3(samples[1].x, channels);"; + "fragColour3 = lumaChromaToRGB * vec3(sample.r, channels);"; break; case DisplayType::CompositeColour: @@ -593,7 +579,7 @@ std::unique_ptr ScanTarget::qam_separation_shader() const { if(is_svideo) { vertex_shader += - "textureCoordinate = vec2(centreClock + textureCoordinateOffsets[0], lineY + 0.5) / textureSize(textureName, 0)"; + "textureCoordinate = vec2(centreClock, lineY + 0.5) / textureSize(textureName, 0);"; } else { vertex_shader += "textureCoordinates[0] = vec2(centreClock + textureCoordinateOffsets[0], lineY + 0.5) / textureSize(textureName, 0);" From b3b4b7cf0c906402f0236c43e89fc9f5f15eb5c5 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 9 Feb 2019 17:20:13 -0500 Subject: [PATCH 05/15] Corrects QAM texture generation logic. --- Outputs/OpenGL/ScanTarget.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Outputs/OpenGL/ScanTarget.cpp b/Outputs/OpenGL/ScanTarget.cpp index eef867d41..ca45e9fdd 100644 --- a/Outputs/OpenGL/ScanTarget.cpp +++ b/Outputs/OpenGL/ScanTarget.cpp @@ -308,20 +308,20 @@ void ScanTarget::setup_pipeline() { // Destroy or create a QAM buffer and shader, if appropriate. const bool needs_qam_buffer = (modals_.display_type == DisplayType::CompositeColour || modals_.display_type == DisplayType::SVideo); - if(needs_qam_buffer && !qam_chroma_texture_) { - qam_chroma_texture_.reset(new TextureTarget(LineBufferWidth, LineBufferHeight, QAMChromaTextureUnit, GL_NEAREST, false)); - } else { - qam_chroma_texture_.reset(); - qam_separation_shader_.reset(); - } - if(needs_qam_buffer) { + if(!qam_chroma_texture_) { + qam_chroma_texture_.reset(new TextureTarget(LineBufferWidth, LineBufferHeight, QAMChromaTextureUnit, GL_NEAREST, false)); + } + qam_separation_shader_ = qam_separation_shader(); glBindVertexArray(line_vertex_array_); glBindBuffer(GL_ARRAY_BUFFER, line_buffer_name_); enable_vertex_attributes(ShaderType::QAMSeparation, *qam_separation_shader_); set_uniforms(ShaderType::QAMSeparation, *qam_separation_shader_); qam_separation_shader_->set_uniform("textureName", GLint(UnprocessedLineBufferTextureUnit - GL_TEXTURE0)); + } else { + qam_chroma_texture_.reset(); + qam_separation_shader_.reset(); } // Establish an output shader. From e35a3ab566a94ea03c254044d90da76a7b407f37 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 9 Feb 2019 18:35:14 -0500 Subject: [PATCH 06/15] Ensures proper uniforms and varyings for the qam_separation_shader. --- Outputs/OpenGL/ScanTargetGLSLFragments.cpp | 79 ++++++++++------------ 1 file changed, 37 insertions(+), 42 deletions(-) diff --git a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp index c7975bee4..86cd24177 100644 --- a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp +++ b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp @@ -20,11 +20,38 @@ void Outputs::Display::OpenGL::ScanTarget::set_uniforms(ShaderType type, Shader // the expected distance. Cf. the stencil-powered logic for making sure all // pixels are painted only exactly once per field. switch(type) { - default: break; - case ShaderType::Conversion: + case ShaderType::Composition: break; + default: 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)); + + const float clocks_per_angle = float(modals_.cycles_per_line) * float(modals_.colour_cycle_denominator) / float(modals_.colour_cycle_numerator); + GLfloat texture_offsets[4]; + GLfloat angles[4]; + for(int c = 0; c < 4; ++c) { + GLfloat angle = (GLfloat(c) - 1.5f) / 4.0f; + texture_offsets[c] = angle * clocks_per_angle; + angles[c] = GLfloat(angle * 2.0f * M_PI); + } + target.set_uniform("textureCoordinateOffsets", 1, 4, texture_offsets); + target.set_uniform("compositeAngleOffsets", 4, 1, angles); + + 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}; + target.set_uniform_matrix("lumaChromaToRGB", 3, false, yiqToRGB); + target.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}; + target.set_uniform_matrix("lumaChromaToRGB", 3, false, yuvToRGB); + target.set_uniform_matrix("rgbToLumaChroma", 3, false, rgbToYUV); + } break; + } break; } } @@ -391,7 +418,7 @@ std::unique_ptr ScanTarget::conversion_shader() const { "fragColour = vec4(fragColour3, 0.64);" "}"; - const auto shader = new Shader( + return std::unique_ptr(new Shader( vertex_shader, fragment_shader, { @@ -404,39 +431,7 @@ std::unique_ptr ScanTarget::conversion_shader() const { "startCompositeAngle", "endCompositeAngle" } - ); - - // If this isn't an RGB or composite colour shader, set the proper colour space. - if(modals_.display_type != DisplayType::RGB) { - const float clocks_per_angle = float(modals_.cycles_per_line) * float(modals_.colour_cycle_denominator) / float(modals_.colour_cycle_numerator); - GLfloat texture_offsets[4]; - GLfloat angles[4]; - for(int c = 0; c < 4; ++c) { - GLfloat angle = (GLfloat(c) - 1.5f) / 4.0f; - texture_offsets[c] = angle * clocks_per_angle; - angles[c] = GLfloat(angle * 2.0f * M_PI); - } - shader->set_uniform("textureCoordinateOffsets", 1, 4, texture_offsets); - shader->set_uniform("compositeAngleOffsets", 4, 1, angles); - - 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; - } - } - - return std::unique_ptr(shader); + )); } std::unique_ptr ScanTarget::composition_shader() const { @@ -547,6 +542,7 @@ std::unique_ptr ScanTarget::qam_separation_shader() const { "#version 150\n" "uniform sampler2D textureName;" + "uniform mat3 rgbToLumaChroma;" "in float compositeAngle;" "in float compositeAmplitude;" @@ -557,10 +553,10 @@ std::unique_ptr ScanTarget::qam_separation_shader() const { if(is_svideo) { vertex_shader += "out vec2 textureCoordinate;"; - fragment_shader += "out vec2 textureCoordinate;"; + fragment_shader += "in vec2 textureCoordinate;"; } else { vertex_shader += "out vec2 textureCoordinates[4];"; - fragment_shader += "out vec2 textureCoordinates[4];"; + fragment_shader += "in vec2 textureCoordinates[4];"; } vertex_shader += @@ -590,13 +586,10 @@ std::unique_ptr ScanTarget::qam_separation_shader() const { vertex_shader += "}"; - fragment_shader += sampling_function() + "void main(void) {"; - // TODO: properly map range of composite value. - if(modals_.display_type == DisplayType::SVideo) { fragment_shader += "fragColour = vec4(svideo_sample(textureCoordinate, compositeAngle).rgg * vec3(1.0, cos(compositeAngle), sin(compositeAngle)), 1.0);"; @@ -623,7 +616,9 @@ std::unique_ptr ScanTarget::qam_separation_shader() const { "fragColour = vec4(luminance, channels, 1.0);"; }; - fragment_shader += "}"; + fragment_shader += + "fragColour = fragColour*vec4(0.5) + vec4(0.5);" + "}"; return std::unique_ptr(new Shader( vertex_shader, From d341f98b094588f0d27713de4c0fcda8211e36bf Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 9 Feb 2019 18:52:43 -0500 Subject: [PATCH 07/15] Corrects horizontal scale. --- Outputs/OpenGL/ScanTargetGLSLFragments.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp index 86cd24177..edc64bc3f 100644 --- a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp +++ b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp @@ -564,7 +564,7 @@ std::unique_ptr ScanTarget::qam_separation_shader() const { "float lateral = float(gl_VertexID & 1);" "float longitudinal = float((gl_VertexID & 2) >> 1);" - "vec2 eyePosition = vec2(abs(mix(startCompositeAngle, endCompositeAngle, lateral) * 4.0), lineY + longitudinal) / vec2(2048.0, 2048.0);" + "vec2 eyePosition = vec2(abs(mix(startCompositeAngle, endCompositeAngle, lateral) * 4.0/64.0), lineY + longitudinal) / vec2(2048.0, 2048.0);" "gl_Position = vec4(eyePosition*2.0 - vec2(1.0), 0.0, 1.0);" "compositeAngle = (mix(startCompositeAngle, endCompositeAngle, lateral) / 32.0) * 3.141592654;" From c94acb1ca2e903d6144ef36f37a48fc959a03bdd Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 9 Feb 2019 22:45:20 -0500 Subject: [PATCH 08/15] With a little more debug logging, discovered an issue with incrementing by four. --- Outputs/OpenGL/Primitives/Shader.cpp | 17 +++++++- Outputs/OpenGL/ScanTarget.cpp | 51 ++++++++++++---------- Outputs/OpenGL/ScanTargetGLSLFragments.cpp | 13 +++--- 3 files changed, 52 insertions(+), 29 deletions(-) diff --git a/Outputs/OpenGL/Primitives/Shader.cpp b/Outputs/OpenGL/Primitives/Shader.cpp index 79845784d..3e141e751 100644 --- a/Outputs/OpenGL/Primitives/Shader.cpp +++ b/Outputs/OpenGL/Primitives/Shader.cpp @@ -53,7 +53,7 @@ Shader::Shader(const std::string &vertex_shader, const std::string &fragment_sha GLuint index = 0; for(const auto &name: binding_names) { bindings.emplace_back(name, index); - index += 4; + ++index; } init(vertex_shader, fragment_shader, bindings); } @@ -68,6 +68,21 @@ void Shader::init(const std::string &vertex_shader, const std::string &fragment_ for(const auto &binding : attribute_bindings) { glBindAttribLocation(shader_program_, binding.index, binding.name.c_str()); +#ifndef NDEBUG + const auto error = glGetError(); + switch(error) { + case 0: break; + case GL_INVALID_VALUE: + LOG("GL_INVALID_VALUE when attempting to bind " << binding.name << " to index " << binding.index << " (i.e. index is greater than or equal to GL_MAX_VERTEX_ATTRIBS)"); + break; + case GL_INVALID_OPERATION: + LOG("GL_INVALID_OPERATION when attempting to bind " << binding.name << " to index " << binding.index << " (i.e. name begins with gl_)"); + break; + default: + LOG("Error " << error << " when attempting to bind " << binding.name << " to index " << binding.index); + break; + } +#endif } glLinkProgram(shader_program_); diff --git a/Outputs/OpenGL/ScanTarget.cpp b/Outputs/OpenGL/ScanTarget.cpp index ca45e9fdd..9ea0a6ae4 100644 --- a/Outputs/OpenGL/ScanTarget.cpp +++ b/Outputs/OpenGL/ScanTarget.cpp @@ -23,9 +23,6 @@ constexpr GLenum SourceDataTextureUnit = GL_TEXTURE0; /// The texture unit which contains raw line-by-line composite, S-Video or RGB data. constexpr GLenum UnprocessedLineBufferTextureUnit = GL_TEXTURE1; -/// The texture unit that contains the current display. -constexpr GLenum AccumulationTextureUnit = GL_TEXTURE3; - /// The texture unit that contains a pre-lowpass-filtered but fixed-resolution version of the chroma signal; /// this is used when processing composite video only, and for chroma information only. Luminance is calculated /// at the fidelity permitted by the output target, but my efforts to separate, demodulate and filter @@ -33,6 +30,9 @@ constexpr GLenum AccumulationTextureUnit = GL_TEXTURE3; /// noise that sampling reduces into a Moire, have proven to be unsuccessful for the time being. constexpr GLenum QAMChromaTextureUnit = GL_TEXTURE2; +/// The texture unit that contains the current display. +constexpr GLenum AccumulationTextureUnit = GL_TEXTURE3; + #define TextureAddress(x, y) (((y) << 11) | (x)) #define TextureAddressGetY(v) uint16_t((v) >> 11) #define TextureAddressGetX(v) uint16_t((v) & 0x7ff) @@ -506,37 +506,37 @@ void ScanTarget::draw(bool synchronous, int output_width, int output_height) { stencil_is_valid_ = false; } - // Figure out how many new spans are ostensible ready; use two less than that. - uint16_t new_spans = (submit_pointers.line + LineBufferHeight - read_pointers.line) % LineBufferHeight; - if(new_spans) { + // Figure out how many new lines are ready. + uint16_t new_lines = (submit_pointers.line + LineBufferHeight - read_pointers.line) % LineBufferHeight; + if(new_lines) { + // Prepare to output lines. + glBindVertexArray(line_vertex_array_); + // Bind the accumulation framebuffer, unless there's going to be QAM work. if(!qam_separation_shader_) { accumulation_texture_->bind_framebuffer(); - } + output_shader_->bind(); - // Enable blending and stenciling, and ensure spans increment the stencil buffer. - glEnable(GL_BLEND); - glEnable(GL_STENCIL_TEST); + // Enable blending and stenciling, and ensure spans increment the stencil buffer. + glEnable(GL_BLEND); + glEnable(GL_STENCIL_TEST); + } glStencilFunc(GL_EQUAL, 0, GLuint(~0)); glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); - // Prepare to output lines. - glBindVertexArray(line_vertex_array_); - output_shader_->bind(); - // Prepare to upload data that will consitute lines. glBindBuffer(GL_ARRAY_BUFFER, line_buffer_name_); // Divide spans by which frame they're in. uint16_t start_line = read_pointers.line; - while(new_spans) { + while(new_lines) { uint16_t end_line = start_line+1; // Find the limit of spans to draw in this cycle. - size_t spans = 1; + size_t lines = 1; while(end_line != submit_pointers.line && !line_metadata_buffer_[end_line].is_first_in_frame) { end_line = (end_line + 1) % LineBufferHeight; - ++spans; + ++lines; } // If this is start-of-frame, clear any untouched pixels and flush the stencil buffer @@ -555,7 +555,7 @@ void ScanTarget::draw(bool synchronous, int output_width, int output_height) { } // Upload. - const auto buffer_size = spans * sizeof(Line); + const auto buffer_size = lines * sizeof(Line); if(!end_line || end_line > start_line) { glBufferSubData(GL_ARRAY_BUFFER, 0, GLsizeiptr(buffer_size), &line_buffer_[start_line]); } else { @@ -575,20 +575,25 @@ void ScanTarget::draw(bool synchronous, int output_width, int output_height) { // Produce colour information, if required. if(qam_separation_shader_) { - qam_chroma_texture_->bind_framebuffer(); qam_separation_shader_->bind(); + qam_chroma_texture_->bind_framebuffer(); +// glClear(GL_COLOR_BUFFER_BIT); - glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, GLsizei(spans)); + glDisable(GL_BLEND); + glDisable(GL_STENCIL_TEST); + glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, GLsizei(lines)); accumulation_texture_->bind_framebuffer(); output_shader_->bind(); + glEnable(GL_BLEND); + glEnable(GL_STENCIL_TEST); } // Render to the output. - glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, GLsizei(spans)); + glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, GLsizei(lines)); start_line = end_line; - new_spans -= spans; + new_lines -= lines; } // Disable blending and the stencil test again. @@ -603,6 +608,8 @@ void ScanTarget::draw(bool synchronous, int output_width, int output_height) { glClear(GL_COLOR_BUFFER_BIT); accumulation_texture_->bind_texture(); accumulation_texture_->draw(float(output_width) / float(output_height), 4.0f / 255.0f); +// qam_chroma_texture_->bind_texture(); +// qam_chroma_texture_->draw(float(output_width) / float(output_height)); // All data now having been spooled to the GPU, update the read pointers to // the submit pointer location. diff --git a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp index edc64bc3f..c3f224a51 100644 --- a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp +++ b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp @@ -563,15 +563,16 @@ std::unique_ptr ScanTarget::qam_separation_shader() const { "void main(void) {" "float lateral = float(gl_VertexID & 1);" "float longitudinal = float((gl_VertexID & 2) >> 1);" + "float centreClock = mix(startClock, endClock, lateral);" - "vec2 eyePosition = vec2(abs(mix(startCompositeAngle, endCompositeAngle, lateral) * 4.0/64.0), lineY + longitudinal) / vec2(2048.0, 2048.0);" + "compositeAngle = mix(startCompositeAngle, endCompositeAngle, lateral) / 64.0;" + + "vec2 eyePosition = vec2(abs(compositeAngle) * 4.0, lineY + longitudinal) / vec2(2048.0, 2048.0);" "gl_Position = vec4(eyePosition*2.0 - vec2(1.0), 0.0, 1.0);" - "compositeAngle = (mix(startCompositeAngle, endCompositeAngle, lateral) / 32.0) * 3.141592654;" + "compositeAngle = compositeAngle * 2.0 * 3.141592654;" "compositeAmplitude = lineCompositeAmplitude / 255.0;" - "oneOverCompositeAmplitude = mix(0.0, 255.0 / lineCompositeAmplitude, step(0.01, lineCompositeAmplitude));" - - "float centreClock = mix(startClock, endClock, lateral);"; + "oneOverCompositeAmplitude = mix(0.0, 255.0 / lineCompositeAmplitude, step(0.01, lineCompositeAmplitude));"; if(is_svideo) { vertex_shader += @@ -617,7 +618,7 @@ std::unique_ptr ScanTarget::qam_separation_shader() const { }; fragment_shader += - "fragColour = fragColour*vec4(0.5) + vec4(0.5);" + "fragColour = fragColour*0.5 + vec4(0.5);" "}"; return std::unique_ptr(new Shader( From 008f50832c8f00f7ed10a9e498d647214b65aeae Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Feb 2019 22:39:24 -0500 Subject: [PATCH 09/15] Fixed: the two shaders that use a common input array should use common bindings. --- Outputs/OpenGL/ScanTarget.cpp | 13 +++-- Outputs/OpenGL/ScanTarget.hpp | 3 +- Outputs/OpenGL/ScanTargetGLSLFragments.cpp | 56 +++++++++++----------- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/Outputs/OpenGL/ScanTarget.cpp b/Outputs/OpenGL/ScanTarget.cpp index 9ea0a6ae4..6c9375a12 100644 --- a/Outputs/OpenGL/ScanTarget.cpp +++ b/Outputs/OpenGL/ScanTarget.cpp @@ -306,6 +306,10 @@ void ScanTarget::setup_pipeline() { write_pointers_.write_area = 0; } + // Prepare to bind line shaders. + glBindVertexArray(line_vertex_array_); + glBindBuffer(GL_ARRAY_BUFFER, line_buffer_name_); + // Destroy or create a QAM buffer and shader, if appropriate. const bool needs_qam_buffer = (modals_.display_type == DisplayType::CompositeColour || modals_.display_type == DisplayType::SVideo); if(needs_qam_buffer) { @@ -314,8 +318,6 @@ void ScanTarget::setup_pipeline() { } qam_separation_shader_ = qam_separation_shader(); - glBindVertexArray(line_vertex_array_); - glBindBuffer(GL_ARRAY_BUFFER, line_buffer_name_); enable_vertex_attributes(ShaderType::QAMSeparation, *qam_separation_shader_); set_uniforms(ShaderType::QAMSeparation, *qam_separation_shader_); qam_separation_shader_->set_uniform("textureName", GLint(UnprocessedLineBufferTextureUnit - GL_TEXTURE0)); @@ -326,8 +328,6 @@ void ScanTarget::setup_pipeline() { // Establish an output shader. output_shader_ = conversion_shader(); - glBindVertexArray(line_vertex_array_); - glBindBuffer(GL_ARRAY_BUFFER, line_buffer_name_); enable_vertex_attributes(ShaderType::Conversion, *output_shader_); set_uniforms(ShaderType::Conversion, *output_shader_); output_shader_->set_uniform("origin", modals_.visible_area.origin.x, modals_.visible_area.origin.y); @@ -577,7 +577,8 @@ void ScanTarget::draw(bool synchronous, int output_width, int output_height) { if(qam_separation_shader_) { qam_separation_shader_->bind(); qam_chroma_texture_->bind_framebuffer(); -// glClear(GL_COLOR_BUFFER_BIT); + glClear(GL_COLOR_BUFFER_BIT); // TODO: this is here as a hint that the old framebuffer doesn't need reloading; + // test whether that's a valid optimisation on desktop OpenGL. glDisable(GL_BLEND); glDisable(GL_STENCIL_TEST); @@ -608,8 +609,6 @@ void ScanTarget::draw(bool synchronous, int output_width, int output_height) { glClear(GL_COLOR_BUFFER_BIT); accumulation_texture_->bind_texture(); accumulation_texture_->draw(float(output_width) / float(output_height), 4.0f / 255.0f); -// qam_chroma_texture_->bind_texture(); -// qam_chroma_texture_->draw(float(output_width) / float(output_height)); // All data now having been spooled to the GPU, update the read pointers to // the submit pointer location. diff --git a/Outputs/OpenGL/ScanTarget.hpp b/Outputs/OpenGL/ScanTarget.hpp index c53ec3ef0..dbd2fe467 100644 --- a/Outputs/OpenGL/ScanTarget.hpp +++ b/Outputs/OpenGL/ScanTarget.hpp @@ -178,7 +178,8 @@ class ScanTarget: public Outputs::Display::ScanTarget { globals for shaders of @c type to @c target. */ static void enable_vertex_attributes(ShaderType type, Shader &target); - void set_uniforms(ShaderType type, Shader &target); + void set_uniforms(ShaderType type, Shader &target) const; + std::vector bindings(ShaderType type) const; GLsync fence_ = nullptr; std::atomic_flag is_drawing_; diff --git a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp index c3f224a51..35412ec0f 100644 --- a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp +++ b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp @@ -14,7 +14,7 @@ using namespace Outputs::Display::OpenGL; // MARK: - State setup for compiled shaders. -void Outputs::Display::OpenGL::ScanTarget::set_uniforms(ShaderType type, Shader &target) { +void Outputs::Display::OpenGL::ScanTarget::set_uniforms(ShaderType type, Shader &target) const { // 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 @@ -144,6 +144,30 @@ void ScanTarget::enable_vertex_attributes(ShaderType type, Shader &target) { #undef rt_offset_of } +std::vector ScanTarget::bindings(ShaderType type) const { + switch(type) { + case ShaderType::Composition: return { + "startDataX", + "startClock", + "endDataX", + "endClock", + "dataY", + "lineY" + }; + + default: return { + "startPoint", + "endPoint", + "startClock", + "endClock", + "lineY", + "lineCompositeAmplitude", + "startCompositeAngle", + "endCompositeAngle" + }; + } +} + // MARK: - Shader code. std::string ScanTarget::sampling_function() const { @@ -421,16 +445,7 @@ std::unique_ptr ScanTarget::conversion_shader() const { return std::unique_ptr(new Shader( vertex_shader, fragment_shader, - { - "startPoint", - "endPoint", - "startClock", - "endClock", - "lineY", - "lineCompositeAmplitude", - "startCompositeAngle", - "endCompositeAngle" - } + bindings(ShaderType::Conversion) )); } @@ -504,14 +519,7 @@ std::unique_ptr ScanTarget::composition_shader() const { return std::unique_ptr(new Shader( vertex_shader, fragment_shader + "}", - { - "startDataX", - "startClock", - "endDataX", - "endClock", - "dataY", - "lineY", - } + bindings(ShaderType::Composition) )); } @@ -624,14 +632,6 @@ std::unique_ptr ScanTarget::qam_separation_shader() const { return std::unique_ptr(new Shader( vertex_shader, fragment_shader, - { - "startClock", - "startCompositeAngle", - "endClock", - "endCompositeAngle", - - "lineY", - "lineCompositeAmplitude" - } + bindings(ShaderType::QAMSeparation) )); } From cda0a2de7934aef2ddd131530567e4a0946204f5 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Feb 2019 23:02:31 -0500 Subject: [PATCH 10/15] Establishes QAM colour buffer lookups within the composite colour path. Subject to errors in channel scaling and absolute position. --- Outputs/OpenGL/ScanTargetGLSLFragments.cpp | 51 ++++++++++++++-------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp index 35412ec0f..4f058b94c 100644 --- a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp +++ b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp @@ -300,12 +300,22 @@ std::unique_ptr ScanTarget::conversion_shader() const { fragment_shader += "in vec2 textureCoordinate;"; break; - case DisplayType::CompositeColour: case DisplayType::SVideo: vertex_shader += - "out vec2 textureCoordinates[4];"; + "out vec2 textureCoordinate;" + "out vec2 qamTextureCoordinates[4];"; fragment_shader += - "in vec2 textureCoordinates[4];"; + "in vec2 textureCoordinate;" + "in vec2 qamTextureCoordinates[4];"; + break; + + case DisplayType::CompositeColour: + vertex_shader += + "out vec2 textureCoordinates[4];" + "out vec2 qamTextureCoordinates[4];"; + fragment_shader += + "in vec2 textureCoordinates[4];" + "in vec2 qamTextureCoordinates[4];"; break; } @@ -329,15 +339,15 @@ std::unique_ptr ScanTarget::conversion_shader() const { // 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(modals_.display_type){ + switch(modals_.display_type) { case DisplayType::RGB: case DisplayType::CompositeMonochrome: + case DisplayType::SVideo: vertex_shader += "textureCoordinate = vec2(mix(startClock, endClock, lateral), lineY + 0.5) / textureSize(textureName, 0);"; break; case DisplayType::CompositeColour: - case DisplayType::SVideo: vertex_shader += "float centreClock = mix(startClock, endClock, lateral);" "textureCoordinates[0] = vec2(centreClock + textureCoordinateOffsets[0], lineY + 0.5) / textureSize(textureName, 0);" @@ -347,6 +357,15 @@ std::unique_ptr ScanTarget::conversion_shader() const { break; } + if((modals_.display_type == DisplayType::SVideo) || (modals_.display_type == DisplayType::CompositeColour)) { + vertex_shader += + "float centreCompositeAngle = abs(mix(startCompositeAngle, endCompositeAngle, lateral)) * 4.0 / 64.0;" + "qamTextureCoordinates[0] = vec2(centreCompositeAngle - 1.5, lineY + 0.5) / textureSize(textureName, 0);" + "qamTextureCoordinates[1] = vec2(centreCompositeAngle - 0.5, lineY + 0.5) / textureSize(textureName, 0);" + "qamTextureCoordinates[2] = vec2(centreCompositeAngle + 0.5, lineY + 0.5) / textureSize(textureName, 0);" + "qamTextureCoordinates[3] = vec2(centreCompositeAngle + 1.5, lineY + 0.5) / textureSize(textureName, 0);"; + } + vertex_shader += "}"; // Compose a fragment shader. @@ -401,18 +420,16 @@ std::unique_ptr ScanTarget::conversion_shader() const { "float luminance = dot(samples, vec4(0.25));" // Split and average chrominance. -// "vec4 chrominances = vec4(" -// "samples[0].y - luminances[0]," -// "samples[0].z - luminances[1]," -// "samples[0].w - luminances[2]," -// "samples[1].x - luminances[3]" -// ");" -// "vec4 chrominance_angles = vec4(angles[0].yzw, angles[1].x);" -// "vec2 channels = vec2(" -// "dot(cos(chrominance_angles), chrominances)," -// "dot(sin(chrominance_angles), chrominances)" -// ") * vec2(0.125 * oneOverCompositeAmplitude);" - "vec2 channels = vec2(0.0);" + "vec2 chrominances[4] = vec2[4](" + "textureLod(textureName, qamTextureCoordinates[0], 0).gb," + "textureLod(textureName, qamTextureCoordinates[1], 0).gb," + "textureLod(textureName, qamTextureCoordinates[2], 0).gb," + "textureLod(textureName, qamTextureCoordinates[3], 0).gb" + ");" + "vec2 channels = vec2(" + "dot(vec4(chrominances[0].x, chrominances[1].x, chrominances[2].x, chrominances[3].x), vec4(0.25))*2.0 - 1.0," + "dot(vec4(chrominances[0].y, chrominances[1].y, chrominances[2].y, chrominances[3].y), vec4(0.25))*2.0 - 1.0" + ");" // Apply a colour space conversion to get RGB. "fragColour3 = mix(" From 2fa4c59523c474730fe926f4a1b156920c0a0ed6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 12 Feb 2019 18:42:28 -0500 Subject: [PATCH 11/15] Correction: use the QAM texture for colours. --- Outputs/OpenGL/ScanTargetGLSLFragments.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp index 4f058b94c..857325bea 100644 --- a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp +++ b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp @@ -421,15 +421,12 @@ std::unique_ptr ScanTarget::conversion_shader() const { // Split and average chrominance. "vec2 chrominances[4] = vec2[4](" - "textureLod(textureName, qamTextureCoordinates[0], 0).gb," - "textureLod(textureName, qamTextureCoordinates[1], 0).gb," - "textureLod(textureName, qamTextureCoordinates[2], 0).gb," - "textureLod(textureName, qamTextureCoordinates[3], 0).gb" - ");" - "vec2 channels = vec2(" - "dot(vec4(chrominances[0].x, chrominances[1].x, chrominances[2].x, chrominances[3].x), vec4(0.25))*2.0 - 1.0," - "dot(vec4(chrominances[0].y, chrominances[1].y, chrominances[2].y, chrominances[3].y), vec4(0.25))*2.0 - 1.0" + "textureLod(qamTextureName, qamTextureCoordinates[0], 0).gb," + "textureLod(qamTextureName, qamTextureCoordinates[1], 0).gb," + "textureLod(qamTextureName, qamTextureCoordinates[2], 0).gb," + "textureLod(qamTextureName, qamTextureCoordinates[3], 0).gb" ");" + "vec2 channels = (chrominances[0] + chrominances[1] + chrominances[2] + chrominances[3])*0.5 - vec2(1.0);" // Apply a colour space conversion to get RGB. "fragColour3 = mix(" From 208ef70e31d9784a577f2d5dd4eab9af2087238d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 12 Feb 2019 18:55:58 -0500 Subject: [PATCH 12/15] Corrects documentation. --- Outputs/OpenGL/Primitives/Shader.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Outputs/OpenGL/Primitives/Shader.hpp b/Outputs/OpenGL/Primitives/Shader.hpp index 4fceb82f9..221aedc5f 100644 --- a/Outputs/OpenGL/Primitives/Shader.hpp +++ b/Outputs/OpenGL/Primitives/Shader.hpp @@ -50,7 +50,7 @@ public: Attempts to compile a shader, throwing @c VertexShaderCompilationError, @c FragmentShaderCompilationError or @c ProgramLinkageError upon failure. @param vertex_shader The vertex shader source code. @param fragment_shader The fragment shader source code. - @param binding_names A list of attributes to generate bindings for; these will be given indices 0, 4, 8 ... 4(n-1). + @param binding_names A list of attributes to generate bindings for; these will be given indices 0, 1, 2 ... n-1. */ Shader(const std::string &vertex_shader, const std::string &fragment_shader, const std::vector &binding_names); ~Shader(); From 037cbd534e7c6b37ce06de5212541522cffa01b5 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 12 Feb 2019 19:24:28 -0500 Subject: [PATCH 13/15] Corrects phase error in chrominance separation. --- Outputs/OpenGL/ScanTargetGLSLFragments.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp index 857325bea..e9b8e591b 100644 --- a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp +++ b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp @@ -631,12 +631,10 @@ std::unique_ptr ScanTarget::qam_separation_shader() const { // Take the average to calculate luminance, then subtract that from all four samples to // give chrominance. "float luminance = dot(samples, vec4(0.25));" - "float chrominance = (samples.y - luminance) * oneOverCompositeAmplitude;" + "float chrominance = (dot(samples.yz, vec2(0.5)) - luminance) * oneOverCompositeAmplitude;" - "vec2 channels = vec2(cos(compositeAngle), sin(compositeAngle)) * chrominance;" - - // Apply a colour space conversion to get RGB. - "fragColour = vec4(luminance, channels, 1.0);"; + // Pack that all up and send it on its way. + "fragColour = vec4(luminance, vec2(cos(compositeAngle), sin(compositeAngle)) * chrominance, 1.0);"; }; fragment_shader += From ec8f1157c8ee17e5ae5c84bd705503c5570df003 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 12 Feb 2019 19:31:12 -0500 Subject: [PATCH 14/15] Corrects S-Video output. --- Outputs/OpenGL/ScanTargetGLSLFragments.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp index e9b8e591b..2f8a0da09 100644 --- a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp +++ b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp @@ -389,11 +389,17 @@ std::unique_ptr ScanTarget::conversion_shader() const { case DisplayType::SVideo: fragment_shader += - // Sample four times over, at proper angle offsets. - "vec2 sample = svideo_sample(textureCoordinates[1], compositeAngle);" + // Sample the S-Video stream once, to obtain luminance. + "vec2 sample = svideo_sample(textureCoordinate, compositeAngle);" // Split and average chrominance. - "vec2 channels = vec2(0.0, 0.0);" + "vec2 chrominances[4] = vec2[4](" + "textureLod(qamTextureName, qamTextureCoordinates[0], 0).gb," + "textureLod(qamTextureName, qamTextureCoordinates[1], 0).gb," + "textureLod(qamTextureName, qamTextureCoordinates[2], 0).gb," + "textureLod(qamTextureName, qamTextureCoordinates[3], 0).gb" + ");" + "vec2 channels = (chrominances[0] + chrominances[1] + chrominances[2] + chrominances[3])*0.5 - vec2(1.0);" // Apply a colour space conversion to get RGB. "fragColour3 = lumaChromaToRGB * vec3(sample.r, channels);"; From 3e0b5433b9e96d89cd7ed86439679cb67698342b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 12 Feb 2019 19:52:32 -0500 Subject: [PATCH 15/15] Institutes colour/monochrome screen selection as an Apple II option. Allowing me to test that straight-through composite still works. --- Machines/AppleII/AppleII.cpp | 38 +++++++++++++++- Machines/AppleII/AppleII.hpp | 3 ++ Machines/AppleII/Video.cpp | 4 ++ Machines/AppleII/Video.hpp | 3 ++ Machines/Utility/MachineForTarget.cpp | 1 + .../Base.lproj/AppleIIOptions.xib | 44 +++++++++++-------- .../Clock Signal/Documents/MachinePanel.swift | 1 + .../Mac/Clock Signal/Machine/CSMachine.h | 3 +- .../Mac/Clock Signal/Machine/CSMachine.mm | 14 +++--- .../StaticAnalyser/CSStaticAnalyser.mm | 2 +- 10 files changed, 84 insertions(+), 29 deletions(-) diff --git a/Machines/AppleII/AppleII.cpp b/Machines/AppleII/AppleII.cpp index b9e072219..a8121e534 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/AppleII/AppleII.cpp @@ -28,12 +28,19 @@ #include "../../Analyser/Static/AppleII/Target.hpp" #include "../../ClockReceiver/ForceInline.hpp" +#include "../../Configurable/StandardOptions.hpp" #include #include #include -namespace { +namespace AppleII { + +std::vector> get_options() { + return Configurable::standard_options( + static_cast(Configurable::DisplayCompositeMonochrome | Configurable::DisplayCompositeColour) + ); +} #define is_iie() ((model == Analyser::Static::AppleII::Target::Model::IIe) || (model == Analyser::Static::AppleII::Target::Model::EnhancedIIe)) @@ -43,6 +50,7 @@ template class ConcreteMachine: public KeyboardMachine::MappedMachine, public CPU::MOS6502::BusHandler, public Inputs::Keyboard, + public Configurable::Device, public AppleII::Machine, public Activity::Source, public JoystickMachine::Machine, @@ -84,7 +92,6 @@ template class ConcreteMachine: uint8_t ram_[65536], aux_ram_[65536]; std::vector rom_; -// std::vector character_rom_; uint8_t keyboard_input_ = 0x00; bool key_is_down_ = false; @@ -401,6 +408,11 @@ template class ConcreteMachine: video_.set_scan_target(scan_target); } + /// Sets the type of display. + void set_display_type(Outputs::Display::DisplayType display_type) override { + video_.set_display_type(display_type); + } + Outputs::Speaker::Speaker *get_speaker() override { return &speaker_; } @@ -804,6 +816,28 @@ template class ConcreteMachine: string_serialiser_.reset(new Utility::StringSerialiser(string, true)); } + // MARK:: Configuration options. + std::vector> get_options() override { + return AppleII::get_options(); + } + + void set_selections(const Configurable::SelectionSet &selections_by_option) override { + Configurable::Display display; + if(Configurable::get_display(selections_by_option, display)) { + set_video_signal_configurable(display); + } + } + + Configurable::SelectionSet get_accurate_selections() override { + Configurable::SelectionSet selection_set; + Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour); + return selection_set; + } + + Configurable::SelectionSet get_user_friendly_selections() override { + return get_accurate_selections(); + } + // MARK: MediaTarget bool insert_media(const Analyser::Static::Media &media) override { if(!media.disks.empty()) { diff --git a/Machines/AppleII/AppleII.hpp b/Machines/AppleII/AppleII.hpp index b0b8180aa..c680b3163 100644 --- a/Machines/AppleII/AppleII.hpp +++ b/Machines/AppleII/AppleII.hpp @@ -18,6 +18,9 @@ namespace AppleII { +/// @returns The options available for an Apple II. +std::vector> get_options(); + class Machine { public: virtual ~Machine(); diff --git a/Machines/AppleII/Video.cpp b/Machines/AppleII/Video.cpp index 18875ef74..548974fc7 100644 --- a/Machines/AppleII/Video.cpp +++ b/Machines/AppleII/Video.cpp @@ -42,6 +42,10 @@ void VideoBase::set_scan_target(Outputs::Display::ScanTarget *scan_target) { crt_.set_scan_target(scan_target); } +void VideoBase::set_display_type(Outputs::Display::DisplayType display_type) { + crt_.set_display_type(display_type); +} + /* Rote setters and getters. */ diff --git a/Machines/AppleII/Video.hpp b/Machines/AppleII/Video.hpp index 387c247d0..e5cfbf2bc 100644 --- a/Machines/AppleII/Video.hpp +++ b/Machines/AppleII/Video.hpp @@ -39,6 +39,9 @@ class VideoBase { /// Sets the scan target. void set_scan_target(Outputs::Display::ScanTarget *scan_target); + /// Sets the type of output. + void set_display_type(Outputs::Display::DisplayType); + /* Descriptions for the setters below are taken verbatim from the Apple IIe Technical Reference. Addresses are the conventional diff --git a/Machines/Utility/MachineForTarget.cpp b/Machines/Utility/MachineForTarget.cpp index 44f0c43db..2ab1f0dac 100644 --- a/Machines/Utility/MachineForTarget.cpp +++ b/Machines/Utility/MachineForTarget.cpp @@ -132,6 +132,7 @@ std::map>> Machin std::map>> options; options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AmstradCPC), AmstradCPC::get_options())); + options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AppleII), AppleII::get_options())); options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), Electron::get_options())); options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::MSX), MSX::get_options())); options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Oric), Oric::get_options())); diff --git a/OSBindings/Mac/Clock Signal/Base.lproj/AppleIIOptions.xib b/OSBindings/Mac/Clock Signal/Base.lproj/AppleIIOptions.xib index 4553fbb77..8c80a36f1 100644 --- a/OSBindings/Mac/Clock Signal/Base.lproj/AppleIIOptions.xib +++ b/OSBindings/Mac/Clock Signal/Base.lproj/AppleIIOptions.xib @@ -1,8 +1,8 @@ - + - + @@ -13,37 +13,43 @@ - + - + - + - + - - - - + + + + - + - + diff --git a/OSBindings/Mac/Clock Signal/Documents/MachinePanel.swift b/OSBindings/Mac/Clock Signal/Documents/MachinePanel.swift index ec3584b37..19630c625 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachinePanel.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachinePanel.swift @@ -32,6 +32,7 @@ class MachinePanel: NSPanel { switch tag { case 1: return .composite case 2: return .sVideo + case 3: return .monochromeComposite default: break } return .RGB diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.h b/OSBindings/Mac/Clock Signal/Machine/CSMachine.h index eb4d820d9..dcfa52da2 100644 --- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.h +++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.h @@ -24,7 +24,8 @@ typedef NS_ENUM(NSInteger, CSMachineVideoSignal) { CSMachineVideoSignalComposite, CSMachineVideoSignalSVideo, - CSMachineVideoSignalRGB + CSMachineVideoSignalRGB, + CSMachineVideoSignalMonochromeComposite }; typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) { diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm index d3b5756a4..4309b3fda 100644 --- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm @@ -461,9 +461,10 @@ struct ActivityObserver: public Activity::Observer { Configurable::SelectionSet selection_set; Configurable::Display display; switch(videoSignal) { - case CSMachineVideoSignalRGB: display = Configurable::Display::RGB; break; - case CSMachineVideoSignalSVideo: display = Configurable::Display::SVideo; break; - case CSMachineVideoSignalComposite: display = Configurable::Display::CompositeColour; break; + case CSMachineVideoSignalRGB: display = Configurable::Display::RGB; break; + case CSMachineVideoSignalSVideo: display = Configurable::Display::SVideo; break; + case CSMachineVideoSignalComposite: display = Configurable::Display::CompositeColour; break; + case CSMachineVideoSignalMonochromeComposite: display = Configurable::Display::CompositeMonochrome; break; } append_display_selection(selection_set, display); configurable_device->set_selections(selection_set); @@ -483,9 +484,10 @@ struct ActivityObserver: public Activity::Observer { // Get the standard option for this video signal. Configurable::StandardOptions option; switch(videoSignal) { - case CSMachineVideoSignalRGB: option = Configurable::DisplayRGB; break; - case CSMachineVideoSignalSVideo: option = Configurable::DisplaySVideo; break; - case CSMachineVideoSignalComposite: option = Configurable::DisplayCompositeColour; break; + case CSMachineVideoSignalRGB: option = Configurable::DisplayRGB; break; + case CSMachineVideoSignalSVideo: option = Configurable::DisplaySVideo; break; + case CSMachineVideoSignalComposite: option = Configurable::DisplayCompositeColour; break; + case CSMachineVideoSignalMonochromeComposite: option = Configurable::DisplayCompositeMonochrome; break; } std::unique_ptr display_option = std::move(standard_options(option).front()); Configurable::ListOption *display_list_option = dynamic_cast(display_option.get()); diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm index d9e5ccfe5..cee116c1e 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm @@ -189,7 +189,7 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K - (NSString *)optionsPanelNibName { switch(_targets.front()->machine) { case Analyser::Machine::AmstradCPC: return @"CompositeOptions"; -// case Analyser::Machine::AppleII: return @"AppleIIOptions"; + case Analyser::Machine::AppleII: return @"AppleIIOptions"; case Analyser::Machine::Atari2600: return @"Atari2600Options"; case Analyser::Machine::Electron: return @"QuickLoadCompositeOptions"; case Analyser::Machine::MasterSystem: return @"CompositeOptions";