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) {} }; /*!