diff --git a/Outputs/OpenGL/ScanTarget.cpp b/Outputs/OpenGL/ScanTarget.cpp index d6a99467c..3a1078422 100644 --- a/Outputs/OpenGL/ScanTarget.cpp +++ b/Outputs/OpenGL/ScanTarget.cpp @@ -114,7 +114,8 @@ void ScanTarget::set_target_framebuffer(GLuint target_framebuffer) { } void ScanTarget::setup_pipeline() { - const auto data_type_size = Outputs::Display::size_for_data_type(modals_.input_data_type); + auto modals = BufferingScanTarget::modals(); + const auto data_type_size = Outputs::Display::size_for_data_type(modals.input_data_type); // Resize the texture only if required. if(data_type_size != write_area_data_size()) { @@ -127,7 +128,7 @@ void ScanTarget::setup_pipeline() { test_gl(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); + const bool needs_qam_buffer = (modals.display_type == DisplayType::CompositeColour || modals.display_type == DisplayType::SVideo); if(needs_qam_buffer) { if(!qam_chroma_texture_) { qam_chroma_texture_ = std::make_unique(LineBufferWidth, LineBufferHeight, QAMChromaTextureUnit, GL_NEAREST, false); @@ -146,8 +147,8 @@ void ScanTarget::setup_pipeline() { output_shader_ = conversion_shader(); 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); - output_shader_->set_uniform("size", modals_.visible_area.size.width, modals_.visible_area.size.height); + 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)); @@ -161,7 +162,8 @@ void ScanTarget::setup_pipeline() { } bool ScanTarget::is_soft_display_type() { - return modals_.display_type == DisplayType::CompositeColour || modals_.display_type == DisplayType::CompositeMonochrome; + const auto display_type = modals().display_type; + return display_type == DisplayType::CompositeColour || display_type == DisplayType::CompositeMonochrome; } void ScanTarget::update(int, int output_height) { @@ -186,10 +188,10 @@ void ScanTarget::update(int, int output_height) { // Grab the new output list. perform([=] (const OutputArea &area) { // Establish the pipeline if necessary. - const bool did_setup_pipeline = modals_are_dirty_; - if(modals_are_dirty_) { + const auto new_modals = BufferingScanTarget::new_modals(); + const bool did_setup_pipeline = bool(new_modals); + if(did_setup_pipeline) { setup_pipeline(); - modals_are_dirty_ = false; } // Determine the start time of this submission group and the number of lines it will contain. @@ -291,7 +293,7 @@ void ScanTarget::update(int, int output_height) { // Determine the proper clear colour — this needs to be anything that describes black // in the input colour encoding at use. - if(modals_.input_data_type == InputDataType::Luminance8Phase8) { + if(modals().input_data_type == InputDataType::Luminance8Phase8) { // Supply both a zero luminance and a colour-subcarrier-disengaging phase. test_gl(glClearColor, 0.0f, 1.0f, 0.0f, 0.0f); } else { diff --git a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp index 67e872015..9e5b83e53 100644 --- a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp +++ b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp @@ -23,14 +23,15 @@ void ScanTarget::set_uniforms(ShaderType type, Shader &target) const { // 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. + const auto modals = BufferingScanTarget::modals(); switch(type) { 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) * modals_.aspect_ratio * (3.0f / 4.0f)); - target.set_uniform("phaseOffset", GLfloat(modals_.input_data_tweaks.phase_linked_luminance_offset)); + 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) * modals.aspect_ratio * (3.0f / 4.0f)); + 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); + 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) { @@ -41,7 +42,7 @@ void ScanTarget::set_uniforms(ShaderType type, Shader &target) const { target.set_uniform("textureCoordinateOffsets", 1, 4, texture_offsets); target.set_uniform("compositeAngleOffsets", 4, 1, angles); - switch(modals_.composite_colour_space) { + 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}; @@ -61,9 +62,10 @@ void ScanTarget::set_uniforms(ShaderType type, Shader &target) const { } void ScanTarget::set_sampling_window(int output_width, int, Shader &target) { - if(modals_.display_type != DisplayType::CompositeColour) { - const float one_pixel_width = float(modals_.cycles_per_line) * modals_.visible_area.size.width / float(output_width); - const float clocks_per_angle = float(modals_.cycles_per_line) * float(modals_.colour_cycle_denominator) / float(modals_.colour_cycle_numerator); + const auto modals = BufferingScanTarget::modals(); + if(modals.display_type != DisplayType::CompositeColour) { + const float one_pixel_width = float(modals.cycles_per_line) * modals.visible_area.size.width / float(output_width); + 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) { @@ -191,8 +193,9 @@ std::vector ScanTarget::bindings(ShaderType type) const { std::string ScanTarget::sampling_function() const { std::string fragment_shader; + const auto modals = BufferingScanTarget::modals(); - if(modals_.display_type == DisplayType::SVideo) { + if(modals.display_type == DisplayType::SVideo) { fragment_shader += "vec2 svideo_sample(vec2 coordinate, float angle) {"; } else { @@ -200,8 +203,8 @@ std::string ScanTarget::sampling_function() const { "float composite_sample(vec2 coordinate, float angle) {"; } - const bool is_svideo = modals_.display_type == DisplayType::SVideo; - switch(modals_.input_data_type) { + const bool is_svideo = modals.display_type == DisplayType::SVideo; + switch(modals.input_data_type) { case InputDataType::Luminance1: case InputDataType::Luminance8: // Easy, just copy across. @@ -255,6 +258,8 @@ std::string ScanTarget::sampling_function() const { } std::unique_ptr ScanTarget::conversion_shader() const { + const auto modals = BufferingScanTarget::modals(); + // Compose a vertex shader. If the display type is RGB, generate just the proper // geometry position, plus a solitary textureCoordinate. // @@ -301,7 +306,7 @@ std::unique_ptr ScanTarget::conversion_shader() const { "out vec4 fragColour;"; - if(modals_.display_type != DisplayType::RGB) { + if(modals.display_type != DisplayType::RGB) { vertex_shader += "out float compositeAngle;" "out float compositeAmplitude;" @@ -316,7 +321,7 @@ std::unique_ptr ScanTarget::conversion_shader() const { "uniform vec4 compositeAngleOffsets;"; } - if(modals_.display_type == DisplayType::SVideo || modals_.display_type == DisplayType::CompositeColour) { + if(modals.display_type == DisplayType::SVideo || modals.display_type == DisplayType::CompositeColour) { vertex_shader += "out vec2 qamTextureCoordinates[4];"; fragment_shader += "in vec2 qamTextureCoordinates[4];"; } @@ -332,7 +337,7 @@ std::unique_ptr ScanTarget::conversion_shader() const { "gl_Position = vec4(eyePosition, 0.0, 1.0);"; // For everything other than RGB, calculate the two composite outputs. - if(modals_.display_type != DisplayType::RGB) { + if(modals.display_type != DisplayType::RGB) { vertex_shader += "compositeAngle = (mix(startCompositeAngle, endCompositeAngle, lateral) / 32.0) * 3.141592654;" "compositeAmplitude = lineCompositeAmplitude / 255.0;" @@ -346,7 +351,7 @@ std::unique_ptr ScanTarget::conversion_shader() const { "textureCoordinates[2] = vec2(centreClock + textureCoordinateOffsets[2], lineY + 0.5) / textureSize(textureName, 0);" "textureCoordinates[3] = vec2(centreClock + textureCoordinateOffsets[3], lineY + 0.5) / textureSize(textureName, 0);"; - if((modals_.display_type == DisplayType::SVideo) || (modals_.display_type == DisplayType::CompositeColour)) { + if((modals.display_type == DisplayType::SVideo) || (modals.display_type == DisplayType::CompositeColour)) { vertex_shader += "float centreCompositeAngle = abs(mix(startCompositeAngle, endCompositeAngle, lateral)) * 4.0 / 64.0;" "centreCompositeAngle = floor(centreCompositeAngle);" @@ -360,7 +365,7 @@ std::unique_ptr ScanTarget::conversion_shader() const { // Compose a fragment shader. - if(modals_.display_type != DisplayType::RGB) { + if(modals.display_type != DisplayType::RGB) { fragment_shader += "uniform mat3 lumaChromaToRGB;" "uniform mat3 rgbToLumaChroma;"; @@ -372,7 +377,7 @@ std::unique_ptr ScanTarget::conversion_shader() const { "void main(void) {" "vec3 fragColour3;"; - switch(modals_.display_type) { + switch(modals.display_type) { case DisplayType::CompositeColour: fragment_shader += "vec4 angles = compositeAngle + compositeAngleOffsets;" @@ -460,13 +465,13 @@ std::unique_ptr ScanTarget::conversion_shader() const { } // Apply a brightness adjustment if requested. - if(fabs(modals_.brightness - 1.0f) > 0.05f) { - fragment_shader += "fragColour3 = fragColour3 * " + std::to_string(modals_.brightness) + ";"; + if(fabs(modals.brightness - 1.0f) > 0.05f) { + fragment_shader += "fragColour3 = fragColour3 * " + std::to_string(modals.brightness) + ";"; } // Apply a gamma correction if required. - if(fabs(output_gamma_ - modals_.intended_gamma) > 0.05f) { - const float gamma_ratio = output_gamma_ / modals_.intended_gamma; + if(fabs(output_gamma_ - modals.intended_gamma) > 0.05f) { + const float gamma_ratio = output_gamma_ / modals.intended_gamma; fragment_shader += "fragColour3 = pow(fragColour3, vec3(" + std::to_string(gamma_ratio) + "));"; } @@ -482,6 +487,7 @@ std::unique_ptr ScanTarget::conversion_shader() const { } std::unique_ptr ScanTarget::composition_shader() const { + const auto modals = BufferingScanTarget::modals(); const std::string vertex_shader = "#version 150\n" @@ -516,7 +522,7 @@ std::unique_ptr ScanTarget::composition_shader() const { "void main(void) {"; - switch(modals_.input_data_type) { + switch(modals.input_data_type) { case InputDataType::Luminance1: fragment_shader += "fragColour = textureLod(textureName, textureCoordinate, 0).rrrr;"; break; @@ -556,7 +562,8 @@ std::unique_ptr ScanTarget::composition_shader() const { } std::unique_ptr ScanTarget::qam_separation_shader() const { - const bool is_svideo = modals_.display_type == DisplayType::SVideo; + const auto modals = BufferingScanTarget::modals(); + 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. @@ -632,7 +639,7 @@ std::unique_ptr ScanTarget::qam_separation_shader() const { sampling_function() + "void main(void) {"; - if(modals_.display_type == DisplayType::SVideo) { + 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 { diff --git a/Outputs/ScanTargets/BufferingScanTarget.cpp b/Outputs/ScanTargets/BufferingScanTarget.cpp index e8344653a..6f2975ad3 100644 --- a/Outputs/ScanTargets/BufferingScanTarget.cpp +++ b/Outputs/ScanTargets/BufferingScanTarget.cpp @@ -310,3 +310,15 @@ void BufferingScanTarget::set_line_buffer(Line *line_buffer, LineMetadata *metad line_metadata_buffer_ = metadata_buffer; line_buffer_size_ = size; } + +const Outputs::Display::ScanTarget::Modals *BufferingScanTarget::new_modals() { + if(!modals_are_dirty_) { + return nullptr; + } + modals_are_dirty_ = false; + return &modals_; +} + +const Outputs::Display::ScanTarget::Modals &BufferingScanTarget::modals() const { + return modals_; +} diff --git a/Outputs/ScanTargets/BufferingScanTarget.hpp b/Outputs/ScanTargets/BufferingScanTarget.hpp index d5897faf7..cb0250c62 100644 --- a/Outputs/ScanTargets/BufferingScanTarget.hpp +++ b/Outputs/ScanTargets/BufferingScanTarget.hpp @@ -94,10 +94,12 @@ class BufferingScanTarget: public Outputs::Display::ScanTarget { /// Sets the area of memory to use as line and line metadata buffers. void set_line_buffer(Line *line_buffer, LineMetadata *metadata_buffer, size_t size); - // These are safe to read only within a `perform` block. - // TODO: can I do better than that? - Modals modals_; - bool modals_are_dirty_ = false; + /// @returns new Modals if any have been set since the last call to get_new_modals(). + /// The caller must be within a @c perform block. + const Modals *new_modals(); + + /// @returns the current @c Modals. + const Modals &modals() const; /// Sets a new base address for the texture. /// When called this will flush all existing data and load up the @@ -213,6 +215,11 @@ class BufferingScanTarget: public Outputs::Display::ScanTarget { Line *line_buffer_ = nullptr; LineMetadata *line_metadata_buffer_ = nullptr; size_t line_buffer_size_ = 0; + + // Current modals and whether they've yet been returned + // from a call to @c get_new_modals. + Modals modals_; + bool modals_are_dirty_ = false; };