diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 110fd91e2..4ee8db9cc 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1173,7 +1173,6 @@ 4BD424E62193B5830097291A /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD424E12193B5820097291A /* Shader.cpp */; }; 4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD468F51D8DF41D0084958B /* 1770.cpp */; }; 4BD4A8D01E077FD20020D856 /* PCMTrackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */; }; - 4BD5D2692199148100DDF17D /* ScanTargetGLSLFragments.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5D2672199148100DDF17D /* ScanTargetGLSLFragments.cpp */; }; 4BD61664206B2AC800236112 /* QuickLoadOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BD61662206B2AC700236112 /* QuickLoadOptions.xib */; }; 4BD67DCB209BE4D700AB2146 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD67DCA209BE4D600AB2146 /* StaticAnalyser.cpp */; }; 4BD67DCC209BE4D700AB2146 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD67DCA209BE4D600AB2146 /* StaticAnalyser.cpp */; }; @@ -2489,7 +2488,6 @@ 4BD468F51D8DF41D0084958B /* 1770.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 1770.cpp; sourceTree = ""; }; 4BD468F61D8DF41D0084958B /* 1770.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 1770.hpp; sourceTree = ""; }; 4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMTrackTests.mm; sourceTree = ""; }; - 4BD5D2672199148100DDF17D /* ScanTargetGLSLFragments.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ScanTargetGLSLFragments.cpp; sourceTree = ""; }; 4BD601A920D89F2A00CBCE57 /* Log.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Log.hpp; sourceTree = ""; }; 4BD61663206B2AC700236112 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/QuickLoadOptions.xib"; sourceTree = SOURCE_ROOT; }; 4BD67DC9209BE4D600AB2146 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = ""; }; @@ -5397,7 +5395,6 @@ isa = PBXGroup; children = ( 4BD191F22191180E0042E144 /* ScanTarget.cpp */, - 4BD5D2672199148100DDF17D /* ScanTargetGLSLFragments.cpp */, 4BA3AF5C2EF252320088C3BC /* API.hpp */, 4BD191D9219113B80042E144 /* OpenGL.hpp */, 4BD191F32191180E0042E144 /* ScanTarget.hpp */, @@ -6286,7 +6283,6 @@ 4B7BA03123C2B19C00B98D9E /* Jasmin.cpp in Sources */, 4B7F188F2154825E00388727 /* MasterSystem.cpp in Sources */, 4B055AA51FAE85EF0060FFFF /* Encoder.cpp in Sources */, - 4BD5D2692199148100DDF17D /* ScanTargetGLSLFragments.cpp in Sources */, 4B894529201967B4007DE474 /* Disk.cpp in Sources */, 4B055AEA1FAE9B990060FFFF /* 6502Storage.cpp in Sources */, 4B055AA71FAE85EF0060FFFF /* SegmentParser.cpp in Sources */, diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme index e9342ff83..c3b3a472f 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme @@ -69,7 +69,7 @@ isEnabled = "YES"> ; @@ -125,7 +103,6 @@ ScanTarget::ScanTarget(const API api, const GLuint target_framebuffer, const flo api_(api), target_framebuffer_(target_framebuffer), output_gamma_(output_gamma), - unprocessed_line_texture_(api, LineBufferWidth, LineBufferHeight, UnprocessedLineBufferTextureUnit, GL_NEAREST, false), full_display_rectangle_(api, -1.0f, -1.0f, 2.0f, 2.0f), scans_(scan_buffer_), lines_(line_buffer_), @@ -134,10 +111,6 @@ ScanTarget::ScanTarget(const API api, const GLuint target_framebuffer, const flo set_scan_buffer(scan_buffer_.data(), scan_buffer_.size()); set_line_buffer(line_buffer_.data(), line_metadata_buffer_.data(), line_buffer_.size()); - // Allocate space for the scans and lines. - allocate_buffer(scan_buffer_, scan_buffer_name_, scan_vertex_array_); - allocate_buffer(line_buffer_, line_buffer_name_, line_vertex_array_); - // TODO: if this is OpenGL 4.4 or newer, use glBufferStorage rather than glBufferData // and specify GL_MAP_PERSISTENT_BIT. Then map the buffer now, and let the client // write straight into it. @@ -146,14 +119,7 @@ ScanTarget::ScanTarget(const API api, const GLuint target_framebuffer, const flo test_gl([&]{ glBlendColor(0.4f, 0.4f, 0.4f, 1.0f); }); // Establish initial state for is_drawing_to_accumulation_buffer_. - is_drawing_to_accumulation_buffer_.clear(); -} - -ScanTarget::~ScanTarget() { - perform([&] { - glDeleteBuffers(1, &scan_buffer_name_); - glDeleteVertexArrays(1, &scan_vertex_array_); - }); + is_drawing_to_output_.clear(); } void ScanTarget::set_target_framebuffer(GLuint target_framebuffer) { @@ -178,84 +144,14 @@ void ScanTarget::setup_pipeline() { ); } - // Resize the texture only if required. + // Resize the texture if required. const size_t required_size = WriteAreaWidth*WriteAreaHeight*data_type_size; if(required_size != write_area_texture_.size()) { write_area_texture_.resize(required_size); set_write_area(write_area_texture_.data()); } - // Prepare to bind line shaders. - test_gl([&]{ glBindVertexArray(line_vertex_array_); }); - test_gl([&]{ glBindBuffer(GL_ARRAY_BUFFER, line_buffer_name_); }); - - // Destroy or create a QAM buffer and shader, if appropriate. - if(!existing_modals_ || existing_modals_->display_type != modals.display_type) { - 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( - api_, - LineBufferWidth, - LineBufferHeight, - QAMChromaTextureUnit, - GL_NEAREST, - false - ); - } - - qam_separation_shader_ = qam_separation_shader(); - enable_vertex_attributes(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. - output_shader_ = conversion_shader(); - enable_vertex_attributes(ShaderType::Conversion, *output_shader_); - set_uniforms(ShaderType::Conversion, *output_shader_); - - output_shader_->set_uniform("textureName", GLint(UnprocessedLineBufferTextureUnit - GL_TEXTURE0)); - output_shader_->set_uniform("qamTextureName", GLint(QAMChromaTextureUnit - GL_TEXTURE0)); - } - - if(qam_separation_shader_) { - set_uniforms(ShaderType::QAMSeparation, *qam_separation_shader_); - } - - // Select whichever of a letterbox or pillarbox avoids cropping. - constexpr float output_ratio = 4.0f / 3.0f; - const float aspect_ratio_stretch = modals.aspect_ratio / output_ratio; - - auto adjusted_rect = modals.visible_area; - const float letterbox_scale = adjusted_rect.size.height / (adjusted_rect.size.width * aspect_ratio_stretch); - if(letterbox_scale > 1.0f) { - adjusted_rect.scale(letterbox_scale, 1.0f); - } else { - adjusted_rect.scale(1.0f, 1.0f / letterbox_scale); - } - - // Provide to shader. - output_shader_->set_uniform("origin", adjusted_rect.origin.x, adjusted_rect.origin.y); - output_shader_->set_uniform("size", 1.0f / adjusted_rect.size.width, 1.0f / adjusted_rect.size.height); - - // Establish an input shader. - if(!existing_modals_ || existing_modals_->input_data_type != modals.input_data_type) { - input_shader_ = composition_shader(); - test_gl([&]{ glBindVertexArray(scan_vertex_array_); }); - test_gl([&]{ glBindBuffer(GL_ARRAY_BUFFER, scan_buffer_name_); }); - enable_vertex_attributes(ShaderType::Composition, *input_shader_); - set_uniforms(ShaderType::Composition, *input_shader_); - input_shader_->set_uniform("textureName", GLint(SourceDataTextureUnit - GL_TEXTURE0)); - } - - // - // New pipeline starts here! - // + // Determine new sizing metrics. const auto buffer_width = FilterGenerator::SuggestedBufferWidth; const auto subcarrier_frequency = [](const Modals &modals) { return float(modals.colour_cycle_numerator) / float(modals.colour_cycle_denominator); @@ -404,38 +300,15 @@ void ScanTarget::update(const int output_width, const int output_height) { std::chrono::high_resolution_clock::now() - line_submission_begin_time_, true); - // Make sure there's a buffer. - const auto output_buffer_width = output_width * 2; - const auto output_buffer_height = output_height * 2; - if( - output_buffer_.empty() || - output_buffer_.width() != output_buffer_width || - output_buffer_.height() != output_buffer_height - ) { - output_buffer_ = TextureTarget( - api_, - output_buffer_width, - output_buffer_height, - OutputTextureUnit, - GL_NEAREST, - false // TODO: should probably be true, if I'm going to use stencil (?) - ); -// fill_random(output_buffer_); - } - // Grab the new output list. perform([&] { const OutputArea area = get_output_area(); // Establish the pipeline if necessary. const auto new_modals = BufferingScanTarget::new_modals(); - const bool did_setup_pipeline = [&] { - if(bool(new_modals)) { - setup_pipeline(); - return true; - } - return false; - } (); + if(bool(new_modals)) { + setup_pipeline(); + } // Determine the start time of this submission group and the number of lines it will contain. line_submission_begin_time_ = std::chrono::high_resolution_clock::now(); @@ -476,81 +349,6 @@ void ScanTarget::update(const int output_width, const int output_height) { if(area.end.scan != area.start.scan) { const size_t new_scans = (area.end.scan - area.start.scan + scan_buffer_.size()) % scan_buffer_.size(); - // - // OLD PIPELINE: submit new scans. - // - test_gl([&]{ glBindBuffer(GL_ARRAY_BUFFER, scan_buffer_name_); }); - - // Map only the required portion of the buffer. - const size_t new_scans_size = new_scans * sizeof(Scan); - const auto destination = static_cast( - glMapBufferRange( - GL_ARRAY_BUFFER, - 0, - GLsizeiptr(new_scans_size), - GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT - ) - ); - test_gl_error(); - - // Copy as a single chunk if possible; otherwise copy in two parts. - if(area.start.scan < area.end.scan) { - std::copy_n(scan_buffer_.begin() + area.start.scan, new_scans, destination); - } else { - const size_t first_portion_count = scan_buffer_.size() - area.start.scan; - std::copy_n(scan_buffer_.begin() + area.start.scan, first_portion_count, destination); - std::copy_n(scan_buffer_.begin(), new_scans - first_portion_count, destination + first_portion_count); - } - - // Flush and unmap the buffer. - test_gl([&]{ glFlushMappedBufferRange(GL_ARRAY_BUFFER, 0, GLsizeiptr(new_scans_size)); }); - test_gl([&]{ glUnmapBuffer(GL_ARRAY_BUFFER); }); - - // - // OLD PIPELINE: draw new scans. - // - - // Push new input to the unprocessed line buffer. - unprocessed_line_texture_.bind_framebuffer(); - - // Clear newly-touched lines; that is everything from (read+1) to submit. - const auto first_line_to_clear = GLsizei((area.start.line+1)%line_buffer_.size()); - const auto final_line_to_clear = GLsizei(area.end.line); - if(first_line_to_clear != final_line_to_clear) { - test_gl([&]{ glEnable(GL_SCISSOR_TEST); }); - - // 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) { - // Supply both a zero luminance and a colour-subcarrier-disengaging phase. - test_gl([&]{ glClearColor(0.0f, 1.0f, 0.0f, 0.0f); }); - } else { - test_gl([&]{ glClearColor(0.0f, 0.0f, 0.0f, 0.0f); }); - } - - if(first_line_to_clear < final_line_to_clear) { - test_gl([&]{ glScissor(GLint(0), GLint(first_line_to_clear), unprocessed_line_texture_.width(), final_line_to_clear - first_line_to_clear); }); - test_gl([&]{ glClear(GL_COLOR_BUFFER_BIT); }); - } else { - test_gl([&]{ glScissor(GLint(0), GLint(0), unprocessed_line_texture_.width(), final_line_to_clear); }); - test_gl([&]{ glClear(GL_COLOR_BUFFER_BIT); }); - test_gl([&]{ glScissor(GLint(0), GLint(first_line_to_clear), unprocessed_line_texture_.width(), unprocessed_line_texture_.height() - first_line_to_clear); }); - test_gl([&]{ glClear(GL_COLOR_BUFFER_BIT); }); - } - - test_gl([&]{ glDisable(GL_SCISSOR_TEST); }); - } - - // Apply new spans. They definitely always go to the first buffer. - test_gl([&]{ glBindVertexArray(scan_vertex_array_); }); - input_shader_->bind(); - test_gl([&]{ glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, GLsizei(new_scans)); }); - - - // - // NEW PIPELINE. - // - // Submit new scans. // First implementation: put all new scans at the start of the buffer, for a simple // glDrawArraysInstanced call below. @@ -581,73 +379,30 @@ void ScanTarget::update(const int output_width, const int output_height) { test_gl([&]{ glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, GLsizei(new_scans)); }); } - // Logic for reducing resolution: start doing so if the metrics object reports that - // it's a good idea. Go up to a quarter of the requested resolution, subject to - // clamping at each stage. If the output resolution changes, or anything else about - // the output pipeline, just start trying the highest size again. - if(display_metrics_.should_lower_resolution() && is_soft_display_type()) { - resolution_reduction_level_ = std::min(resolution_reduction_level_+1, 4); - } - if(output_height_ != output_height || did_setup_pipeline) { - resolution_reduction_level_ = 1; - output_height_ = output_height; - } - - // Ensure the accumulation buffer is properly sized, allowing for the metrics object's - // feelings about whether too high a resolution is being used. - const int framebuffer_height = std::max(output_height / resolution_reduction_level_, std::min(540, output_height)); - const int proportional_width = (framebuffer_height * 4) / 3; - const bool did_create_accumulation_texture = - !accumulation_texture_ || - ( - accumulation_texture_->width() != proportional_width || - accumulation_texture_->height() != framebuffer_height - ); - // Work with the accumulation_buffer_ potentially starts from here onwards; set its flag. - while(is_drawing_to_accumulation_buffer_.test_and_set()); - if(did_create_accumulation_texture) { - Logger::info().append("Changed output resolution to %d by %d", proportional_width, framebuffer_height); - display_metrics_.announce_did_resize(); - auto new_framebuffer = std::make_unique( + while(is_drawing_to_output_.test_and_set()); + + // Make sure there's an appropriately-sized buffer. + const auto output_buffer_width = output_width * 2; + const auto output_buffer_height = output_height * 2; + if( + output_buffer_.empty() || + output_buffer_.width() != output_buffer_width || + output_buffer_.height() != output_buffer_height + ) { + output_buffer_ = TextureTarget( api_, - GLsizei(proportional_width), - GLsizei(framebuffer_height), - AccumulationTextureUnit, + output_buffer_width, + output_buffer_height, + OutputTextureUnit, GL_NEAREST, - true + false // TODO: should probably be true, if I'm going to use stencil (?) ); - if(accumulation_texture_) { - new_framebuffer->bind_framebuffer(); - test_gl([&]{ glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); }); - - test_gl([&]{ glActiveTexture(AccumulationTextureUnit); }); - accumulation_texture_->bind_texture(); - accumulation_texture_->draw(4.0f / 3.0f); - - test_gl([&]{ glClear(GL_STENCIL_BUFFER_BIT); }); - - new_framebuffer->bind_texture(); - } - accumulation_texture_ = std::move(new_framebuffer); - - // In the absence of a way to resize a stencil buffer, just mark - // what's currently present as invalid to avoid an improper clear - // for this frame. - stencil_is_valid_ = false; - } - - if(did_setup_pipeline || did_create_accumulation_texture) { - set_sampling_window(proportional_width, framebuffer_height, *output_shader_); } // Figure out how many new lines are ready. if(area.end.line != area.start.line) { - auto new_lines = (area.end.line - area.start.line + LineBufferHeight) % LineBufferHeight; - - // - // New pipeline. - // + const auto new_lines = (area.end.line - area.start.line + LineBufferHeight) % LineBufferHeight; // Populate dirty zones, and record quantity. // const int num_dirty_zones = 1 + (area.start.line >= area.end.line); @@ -718,111 +473,10 @@ void ScanTarget::update(const int output_width, const int output_height) { // TODO: end-of-frame blanking of untouched areas. - // - // Old pipeline. - // - - // Prepare to output lines. - test_gl([&]{ glBindVertexArray(line_vertex_array_); }); - - // Bind the accumulation framebuffer, unless there's going to be QAM work first. - if(!qam_separation_shader_ || line_metadata_buffer_[area.start.line].is_first_in_frame) { - accumulation_texture_->bind_framebuffer(); - output_shader_->bind(); - - // Enable blending and stenciling. - test_gl([&]{ glEnable(GL_BLEND); }); - test_gl([&]{ glEnable(GL_STENCIL_TEST); }); - } - - // Set the proper stencil function regardless. - test_gl([&]{ glStencilFunc(GL_EQUAL, 0, GLuint(~0)); }); - test_gl([&]{ glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); }); - - // Prepare to upload data that will consitute lines. - test_gl([&]{ glBindBuffer(GL_ARRAY_BUFFER, line_buffer_name_); }); - - // Divide spans by which frame they're in. - auto start_line = area.start.line; - while(new_lines) { - uint16_t end_line = (start_line + 1) % LineBufferHeight; - - // Find the limit of spans to draw in this cycle. - size_t lines = 1; - while(end_line != area.end.line && !line_metadata_buffer_[end_line].is_first_in_frame) { - end_line = (end_line + 1) % LineBufferHeight; - ++lines; - } - - // If this is start-of-frame, clear any untouched pixels and flush the stencil buffer - if(line_metadata_buffer_[start_line].is_first_in_frame) { - if(stencil_is_valid_ && line_metadata_buffer_[start_line].previous_frame_was_complete) { - full_display_rectangle_.draw(0.0f, 0.0f, 0.0f); - } - stencil_is_valid_ = true; - test_gl([&]{ glClear(GL_STENCIL_BUFFER_BIT); }); - - // Rebind the program for span output. - test_gl([&]{ glBindVertexArray(line_vertex_array_); }); - if(!qam_separation_shader_) { - output_shader_->bind(); - } - } - - // Upload. - const auto buffer_size = lines * sizeof(Line); - if(!end_line || end_line > start_line) { - test_gl([&]{ glBufferSubData(GL_ARRAY_BUFFER, 0, GLsizeiptr(buffer_size), &line_buffer_[start_line]); }); - } else { - auto destination = static_cast( - glMapBufferRange( - GL_ARRAY_BUFFER, - 0, - GLsizeiptr(buffer_size), - GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT - ) - ); - assert(destination); - test_gl_error(); - - std::copy_n(line_buffer_.begin(), end_line, destination + line_buffer_.size() - start_line); - std::copy_n(line_buffer_.begin() + start_line, line_buffer_.size() - start_line, destination); - - test_gl([&]{ glFlushMappedBufferRange(GL_ARRAY_BUFFER, 0, GLsizeiptr(buffer_size)); }); - test_gl([&]{ glUnmapBuffer(GL_ARRAY_BUFFER); }); - } - - // Produce colour information, if required. - if(qam_separation_shader_) { - qam_separation_shader_->bind(); - qam_chroma_texture_->bind_framebuffer(); - test_gl([&]{ 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. - - test_gl([&]{ glDisable(GL_BLEND); }); - test_gl([&]{ glDisable(GL_STENCIL_TEST); }); - test_gl([&]{ glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, GLsizei(lines)); }); - - accumulation_texture_->bind_framebuffer(); - output_shader_->bind(); - test_gl([&]{ glEnable(GL_BLEND); }); - test_gl([&]{ glEnable(GL_STENCIL_TEST); }); - } - - // Render to the output. - test_gl([&]{ glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, GLsizei(lines)); }); - - start_line = end_line; - new_lines -= lines; - } - - // Disable blending and the stencil test again. - test_gl([&]{ glDisable(GL_STENCIL_TEST); }); - test_gl([&]{ glDisable(GL_BLEND); }); } // That's it for operations affecting the accumulation buffer. - is_drawing_to_accumulation_buffer_.clear(); + is_drawing_to_output_.clear(); // Grab a fence sync object to avoid busy waiting upon the next extry into this // function, and reset the is_updating_ flag. @@ -832,26 +486,14 @@ void ScanTarget::update(const int output_width, const int output_height) { } void ScanTarget::draw(int output_width, int output_height) { - while(is_drawing_to_accumulation_buffer_.test_and_set(std::memory_order_acquire)); - -// if(accumulation_texture_) { -// // Copy the accumulation texture to the target. -// test_gl([&]{ glBindFramebuffer(GL_FRAMEBUFFER, target_framebuffer_); }); -// test_gl([&]{ glViewport(0, 0, (GLsizei)output_width, (GLsizei)output_height); }); -// -// test_gl([&]{ glClearColor(0.0f, 0.0f, 0.0f, 0.0f); }); -// test_gl([&]{ glClear(GL_COLOR_BUFFER_BIT); }); -// accumulation_texture_->bind_texture(); -// accumulation_texture_->draw(float(output_width) / float(output_height), 4.0f / 255.0f); -// } + while(is_drawing_to_output_.test_and_set(std::memory_order_acquire)); if(!composition_buffer_.empty()) { // Copy the accumulation texture to the target. test_gl([&]{ glBindFramebuffer(GL_FRAMEBUFFER, target_framebuffer_); }); test_gl([&]{ glViewport(0, 0, (GLsizei)output_width, (GLsizei)output_height); }); - copy_shader_.perform(OutputTextureUnit); // OutputTextureUnit - // DemodulationTextureUnit // CompositionTextureUnit + copy_shader_.perform(OutputTextureUnit); } - is_drawing_to_accumulation_buffer_.clear(std::memory_order_release); + is_drawing_to_output_.clear(std::memory_order_release); } diff --git a/Outputs/OpenGL/ScanTarget.hpp b/Outputs/OpenGL/ScanTarget.hpp index b42715857..338a79605 100644 --- a/Outputs/OpenGL/ScanTarget.hpp +++ b/Outputs/OpenGL/ScanTarget.hpp @@ -45,7 +45,6 @@ namespace Outputs::Display::OpenGL { class ScanTarget: public Outputs::Display::BufferingScanTarget { // TODO: use private inheritance and expose only display_metrics() and a custom cast? public: ScanTarget(API, GLuint target_framebuffer = 0, float output_gamma = 2.2f); - ~ScanTarget(); void set_target_framebuffer(GLuint); @@ -56,8 +55,9 @@ public: private: API api_; - static constexpr int LineBufferWidth = 2048; static constexpr int LineBufferHeight = 2048; + float output_gamma_; + size_t lines_submitted_ = 0; #ifndef NDEBUG struct OpenGLVersionDumper { @@ -72,79 +72,19 @@ private: #endif GLuint target_framebuffer_; - const float output_gamma_; - - int resolution_reduction_level_ = 1; - int output_height_ = 0; - - size_t lines_submitted_ = 0; std::chrono::high_resolution_clock::time_point line_submission_begin_time_; - // Contains the first composition of scans into lines; - // they're accumulated prior to output to allow for continuous - // application of any necessary conversions — e.g. composite processing. - 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_; Rectangle full_display_rectangle_; bool stencil_is_valid_ = false; - // OpenGL storage handles for buffer data. - GLuint scan_buffer_name_ = 0, scan_vertex_array_ = 0; - GLuint line_buffer_name_ = 0, line_vertex_array_ = 0; - // Receives scan target modals. std::optional existing_modals_; void setup_pipeline(); - enum class ShaderType { - Composition, - Conversion, - QAMSeparation - }; - - /*! - Calls @c taret.enable_vertex_attribute_with_pointer to attach all - globals for shaders of @c type to @c target. - */ - static void enable_vertex_attributes(ShaderType type, Shader &target); - void set_uniforms(ShaderType type, Shader &target) const; - std::vector bindings(ShaderType type) const; - GLsync fence_ = nullptr; - std::atomic_flag is_drawing_to_accumulation_buffer_; - - 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, - normalising the data into one of four forms: RGB, 8-bit luminance, - phase-linked luminance or luminance+phase offset. - */ - std::unique_ptr composition_shader() const; - /*! - Produces a shader that reads from a composition buffer and converts to host - 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; - - void set_sampling_window(int output_Width, int output_height, Shader &target); - - std::string sampling_function() const; + std::atomic_flag is_drawing_to_output_; /*! @returns true if the current display type is a 'soft' one, i.e. one in which diff --git a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp b/Outputs/OpenGL/ScanTargetGLSLFragments.cpp deleted file mode 100644 index dcb27b964..000000000 --- a/Outputs/OpenGL/ScanTargetGLSLFragments.cpp +++ /dev/null @@ -1,680 +0,0 @@ -// -// ScanTargetVertexArrayAttributs.cpp -// Clock Signal -// -// Created by Thomas Harte on 11/11/2018. -// Copyright © 2018 Thomas Harte. All rights reserved. -// - -#include "ScanTarget.hpp" - -#include -#include - -using namespace Outputs::Display::OpenGL; - -// MARK: - State setup for compiled shaders. - -void 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 - // 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)); - 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 * std::numbers::pi_v); - } - 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; - } -} - -void ScanTarget::set_sampling_window(int output_width, int, Shader &target) { - 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) { - texture_offsets[c] = 1.0f * (((one_pixel_width * float(c)) / 3.0f) - (one_pixel_width * 0.5f)); - angles[c] = GLfloat((texture_offsets[c] / clocks_per_angle) * 2.0f * std::numbers::pi_v); - } - target.set_uniform("textureCoordinateOffsets", 1, 4, texture_offsets); - target.set_uniform("compositeAngleOffsets", 4, 1, angles); - } -} - -void ScanTarget::enable_vertex_attributes(ShaderType type, Shader &target) { -#define rt_offset_of(field, source) (reinterpret_cast(&source.field) - reinterpret_cast(&source)) - // test_scan and test_line are here so that the byte offsets that need to be - // calculated inside a loop can be done so validly; offsetof requires constant arguments. - Scan test_scan; - Line test_line; - - // Some GPUs require alignment and will need to copy vertex data to a - // shadow buffer otherwise - static_assert(sizeof(Scan) % 4 == 0); - static_assert(sizeof(Line) % 4 == 0); - - switch(type) { - case ShaderType::Composition: - for(int c = 0; c < 2; ++c) { - const std::string prefix = c ? "end" : "start"; - - target.enable_vertex_attribute_with_pointer( - prefix + "DataX", - 1, GL_UNSIGNED_SHORT, GL_FALSE, - sizeof(Scan), - reinterpret_cast(rt_offset_of(scan.end_points[c].data_offset, test_scan)), - 1); - - target.enable_vertex_attribute_with_pointer( - prefix + "Clock", - 1, GL_UNSIGNED_SHORT, GL_FALSE, - sizeof(Scan), - reinterpret_cast(rt_offset_of(scan.end_points[c].cycles_since_end_of_horizontal_retrace, test_scan)), - 1); - } - - target.enable_vertex_attribute_with_pointer( - "dataY", - 1, GL_UNSIGNED_SHORT, GL_FALSE, - sizeof(Scan), - reinterpret_cast(offsetof(Scan, data_y)), - 1); - - target.enable_vertex_attribute_with_pointer( - "lineY", - 1, GL_UNSIGNED_SHORT, GL_FALSE, - sizeof(Scan), - reinterpret_cast(offsetof(Scan, line)), - 1); - break; - - default: - for(int c = 0; c < 2; ++c) { - const std::string prefix = c ? "end" : "start"; - - 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", - 1, GL_UNSIGNED_SHORT, GL_FALSE, - sizeof(Line), - reinterpret_cast(rt_offset_of(end_points[c].cycles_since_end_of_horizontal_retrace, test_line)), - 1); - - target.enable_vertex_attribute_with_pointer( - prefix + "CompositeAngle", - 1, GL_SHORT, GL_FALSE, - sizeof(Line), - reinterpret_cast(rt_offset_of(end_points[c].composite_angle, test_line)), - 1); - } - - target.enable_vertex_attribute_with_pointer( - "lineY", - 1, GL_UNSIGNED_SHORT, GL_FALSE, - sizeof(Line), - reinterpret_cast(offsetof(Line, line)), - 1); - - target.enable_vertex_attribute_with_pointer( - "lineCompositeAmplitude", - 1, GL_UNSIGNED_BYTE, GL_FALSE, - sizeof(Line), - reinterpret_cast(offsetof(Line, composite_amplitude)), - 1); - break; - } -#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 { - std::string fragment_shader; - const auto modals = BufferingScanTarget::modals(); - const bool is_svideo = modals.display_type == DisplayType::SVideo; - - if(is_svideo) { - fragment_shader += - "vec2 svideo_sample(vec2 coordinate, float angle) {"; - } else { - fragment_shader += - "float composite_sample(vec2 coordinate, float angle) {"; - } - - 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.0).r, 0.0);" : - "return textureLod(textureName, coordinate, 0.0).r;"; - break; - - case InputDataType::PhaseLinkedLuminance8: - fragment_shader += - "uint iPhase = uint(step(sign(angle), 0.0) * 3) ^ uint(abs(angle * 2.0 / 3.141592654) ) & 3u;"; - - fragment_shader += - is_svideo ? - "return vec2(textureLod(textureName, coordinate, 0.0)[iPhase], 0.0);" : - "return textureLod(textureName, coordinate, 0.0)[iPhase];"; - break; - - case InputDataType::Luminance8Phase8: - fragment_shader += - "vec2 yc = textureLod(textureName, coordinate, 0.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.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 += "}"; - - return fragment_shader; -} - -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. - // - // 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 = R"glsl( - uniform vec2 scale; - uniform float rowHeight; - - in vec2 startPoint; - in vec2 endPoint; - - in float startClock; - in float startCompositeAngle; - in float endClock; - in float endCompositeAngle; - - in float lineY; - in float lineCompositeAmplitude; - - uniform sampler2D textureName; - uniform sampler2D qamTextureName; - uniform vec2 origin; - uniform vec2 size; - - uniform float textureCoordinateOffsets[4]; - out vec2 textureCoordinates[4]; - )glsl"; - - std::string fragment_shader = R"glsl( - uniform sampler2D textureName; - uniform sampler2D qamTextureName; - - in vec2 textureCoordinates[4]; - - out vec4 fragColour; - )glsl"; - - if(modals.display_type != DisplayType::RGB) { - vertex_shader += R"glsl( - out float compositeAngle; - out float compositeAmplitude; - out float oneOverCompositeAmplitude; - - uniform float angleOffsets[4]; - )glsl"; - fragment_shader += R"glsl( - in float compositeAngle; - in float compositeAmplitude; - in float oneOverCompositeAmplitude; - - uniform vec4 compositeAngleOffsets; - )glsl"; - } - - if(modals.display_type == DisplayType::SVideo || modals.display_type == DisplayType::CompositeColour) { - vertex_shader += "out vec2 qamTextureCoordinates[4];"; - fragment_shader += "in vec2 qamTextureCoordinates[4];"; - } - - // Add the code to generate a proper output position; this applies to all display types. - vertex_shader += R"glsl( - void main(void) { - float lateral = float(gl_VertexID & 1); - float longitudinal = float((gl_VertexID & 2) >> 1); - vec2 centrePoint = mix(startPoint, vec2(endPoint.x, startPoint.y), lateral) / scale; - vec2 height = normalize(vec2(endPoint.x, startPoint.y) - 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); - )glsl"; - - // For everything other than RGB, calculate the two composite outputs. - if(modals.display_type != DisplayType::RGB) { - vertex_shader += R"glsl( - compositeAngle = (mix(startCompositeAngle, endCompositeAngle, lateral) / 32.0) * 3.141592654; - compositeAmplitude = lineCompositeAmplitude / 255.0; - oneOverCompositeAmplitude = mix(0.0, 255.0 / lineCompositeAmplitude, step(0.95, lineCompositeAmplitude)); - )glsl"; - } - - vertex_shader += R"glsl( - float centreClock = mix(startClock, endClock, lateral); - textureCoordinates[0] = vec2(centreClock + textureCoordinateOffsets[0], lineY + 0.5) / vec2(textureSize(textureName, 0)); - textureCoordinates[1] = vec2(centreClock + textureCoordinateOffsets[1], lineY + 0.5) / vec2(textureSize(textureName, 0)); - textureCoordinates[2] = vec2(centreClock + textureCoordinateOffsets[2], lineY + 0.5) / vec2(textureSize(textureName, 0)); - textureCoordinates[3] = vec2(centreClock + textureCoordinateOffsets[3], lineY + 0.5) / vec2(textureSize(textureName, 0)); - )glsl"; - - if((modals.display_type == DisplayType::SVideo) || (modals.display_type == DisplayType::CompositeColour)) { - vertex_shader += R"glsl( - float centreCompositeAngle = abs(mix(startCompositeAngle, endCompositeAngle, lateral)) * 4.0 / 64.0; - centreCompositeAngle = floor(centreCompositeAngle); - qamTextureCoordinates[0] = vec2(centreCompositeAngle - 1.5, lineY + 0.5) / vec2(textureSize(textureName, 0)); - qamTextureCoordinates[1] = vec2(centreCompositeAngle - 0.5, lineY + 0.5) / vec2(textureSize(textureName, 0)); - qamTextureCoordinates[2] = vec2(centreCompositeAngle + 0.5, lineY + 0.5) / vec2(textureSize(textureName, 0)); - qamTextureCoordinates[3] = vec2(centreCompositeAngle + 1.5, lineY + 0.5) / vec2(textureSize(textureName, 0)); - )glsl"; - } - - vertex_shader += "}"; - - // Compose a fragment shader. - - if(modals.display_type != DisplayType::RGB) { - fragment_shader += - "uniform mat3 lumaChromaToRGB;" - "uniform mat3 rgbToLumaChroma;"; - - fragment_shader += sampling_function(); - } - - fragment_shader += - "void main(void) {" - "vec3 fragColour3;"; - - switch(modals.display_type) { - case DisplayType::CompositeColour: - fragment_shader += R"glsl( - 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) - ); - - // The outer structure of the OpenGL scan target means in practice that - // compositeAmplitude will be the same value across a piece of - // geometry. I am therefore optimistic that this conditional will not - // cause a divergence in fragment execution. - if(compositeAmplitude < 0.01) { - // Compute only a luminance for use if there's no colour information. - fragColour3 = vec3(dot(samples, vec4(0.15, 0.35, 0.35, 0.15))); - } else { - // Take the average to calculate luminance, then subtract that from all four samples to - // give chrominance. - float luminance = dot(samples, vec4(0.25)); - - // Split and average chrominance. - vec2 chrominances[4] = vec2[4]( - textureLod(qamTextureName, qamTextureCoordinates[0], 0.0).gb, - textureLod(qamTextureName, qamTextureCoordinates[1], 0.0).gb, - textureLod(qamTextureName, qamTextureCoordinates[2], 0.0).gb, - textureLod(qamTextureName, qamTextureCoordinates[3], 0.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(luminance / (1.0 - compositeAmplitude), channels); - } - )glsl"; - break; - - case DisplayType::CompositeMonochrome: - fragment_shader += - "vec4 angles = compositeAngle + compositeAngleOffsets;" - "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)" - ");" - "fragColour3 = vec3(dot(samples, vec4(0.15, 0.35, 0.35, 0.25)));"; - break; - - case DisplayType::RGB: - fragment_shader += - "vec3 samples[4] = vec3[4](" - "textureLod(textureName, textureCoordinates[0], 0.0).rgb," - "textureLod(textureName, textureCoordinates[1], 0.0).rgb," - "textureLod(textureName, textureCoordinates[2], 0.0).rgb," - "textureLod(textureName, textureCoordinates[3], 0.0).rgb" - ");" - "fragColour3 = samples[0]*0.15 + samples[1]*0.35 + samples[2]*0.35 + samples[2]*0.15;"; - break; - - case DisplayType::SVideo: - fragment_shader += - // Sample the S-Video stream to obtain luminance. - "vec4 angles = compositeAngle + compositeAngleOffsets;" - "vec4 samples = vec4(" - "svideo_sample(textureCoordinates[0], angles.x).x," - "svideo_sample(textureCoordinates[1], angles.y).x," - "svideo_sample(textureCoordinates[2], angles.z).x," - "svideo_sample(textureCoordinates[3], angles.w).x" - ");" - "float luminance = dot(samples, vec4(0.15, 0.35, 0.35, 0.25));" - - // Split and average chrominance. - "vec2 chrominances[4] = vec2[4](" - "textureLod(qamTextureName, qamTextureCoordinates[0], 0.0).gb," - "textureLod(qamTextureName, qamTextureCoordinates[1], 0.0).gb," - "textureLod(qamTextureName, qamTextureCoordinates[2], 0.0).gb," - "textureLod(qamTextureName, qamTextureCoordinates[3], 0.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(luminance, channels);"; - break; - } - - // Apply a brightness adjustment if requested. - 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; - fragment_shader += "fragColour3 = pow(fragColour3, vec3(" + std::to_string(gamma_ratio) + "));"; - } - - fragment_shader += - "fragColour = vec4(fragColour3, 0.64);" - "}"; - - return std::make_unique( - api_, - vertex_shader, - fragment_shader, - bindings(ShaderType::Conversion) - ); -} - -std::unique_ptr ScanTarget::composition_shader() const { - const auto modals = BufferingScanTarget::modals(); - const std::string vertex_shader = R"glsl( - 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) / vec2(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); - } - )glsl"; - - std::string fragment_shader = R"x( - out vec4 fragColour; - in vec2 textureCoordinate; - - uniform usampler2D textureName; - - void main(void) { - )x"; - - switch(modals.input_data_type) { - case InputDataType::Luminance1: - fragment_shader += "fragColour = textureLod(textureName, textureCoordinate, 0.0).rrrr;"; - break; - - case InputDataType::Luminance8: - fragment_shader += "fragColour = textureLod(textureName, textureCoordinate, 0.0).rrrr / vec4(255.0);"; - break; - - case InputDataType::PhaseLinkedLuminance8: - case InputDataType::Luminance8Phase8: - case InputDataType::Red8Green8Blue8: - fragment_shader += "fragColour = textureLod(textureName, textureCoordinate, 0.0) / vec4(255.0);"; - break; - - case InputDataType::Red1Green1Blue1: - fragment_shader += "fragColour = vec4(textureLod(textureName, textureCoordinate, 0.0).rrr & uvec3(4u, 2u, 1u), 1.0);"; - break; - - case InputDataType::Red2Green2Blue2: - fragment_shader += - "uint textureValue = textureLod(textureName, textureCoordinate, 0.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.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::make_unique( - api_, - vertex_shader, - fragment_shader + "}", - bindings(ShaderType::Composition) - ); -} - -std::unique_ptr ScanTarget::qam_separation_shader() const { - 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. - std::string vertex_shader = - "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 = - "uniform sampler2D textureName;" - "uniform mat3 rgbToLumaChroma;" - - "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 += "in vec2 textureCoordinate;"; - } else { - vertex_shader += "out vec2 textureCoordinates[4];"; - fragment_shader += "in vec2 textureCoordinates[4];"; - } - - vertex_shader += - "void main(void) {" - "float lateral = float(gl_VertexID & 1);" - "float longitudinal = float((gl_VertexID & 2) >> 1);" - "float centreClock = mix(startClock, endClock, lateral);" - - "compositeAngle = mix(startCompositeAngle, endCompositeAngle, lateral) / 64.0;" - - "float snappedCompositeAngle = floor(abs(compositeAngle) * 4.0);" - "vec2 eyePosition = vec2(snappedCompositeAngle, lineY + longitudinal) / vec2(2048.0, 2048.0);" - "gl_Position = vec4(eyePosition*2.0 - vec2(1.0), 0.0, 1.0);" - - "compositeAngle = compositeAngle * 2.0 * 3.141592654;" - "compositeAmplitude = lineCompositeAmplitude / 255.0;" - "oneOverCompositeAmplitude = mix(0.0, 255.0 / lineCompositeAmplitude, step(0.95, lineCompositeAmplitude));"; - - if(is_svideo) { - vertex_shader += - "textureCoordinate = vec2(centreClock, lineY + 0.5) / vec2(textureSize(textureName, 0));"; - } else { - vertex_shader += - "textureCoordinates[0] = vec2(centreClock + textureCoordinateOffsets[0], lineY + 0.5) / vec2(textureSize(textureName, 0));" - "textureCoordinates[1] = vec2(centreClock + textureCoordinateOffsets[1], lineY + 0.5) / vec2(textureSize(textureName, 0));" - "textureCoordinates[2] = vec2(centreClock + textureCoordinateOffsets[2], lineY + 0.5) / vec2(textureSize(textureName, 0));" - "textureCoordinates[3] = vec2(centreClock + textureCoordinateOffsets[3], lineY + 0.5) / vec2(textureSize(textureName, 0));"; - } - - vertex_shader += "}"; - - fragment_shader += - sampling_function() + - "void main(void) {"; - - 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 = (dot(samples.yz, vec2(0.5)) - luminance) * oneOverCompositeAmplitude;" - - // Pack that all up and send it on its way. - "fragColour = vec4(luminance, vec2(cos(compositeAngle), sin(compositeAngle)) * chrominance, 1.0);"; - }; - - fragment_shader += - "fragColour = fragColour*0.5 + vec4(0.5);" - "}"; - - return std::make_unique( - api_, - vertex_shader, - fragment_shader, - bindings(ShaderType::QAMSeparation) - ); -} diff --git a/cmake/CLK_SOURCES.cmake b/cmake/CLK_SOURCES.cmake index f859dfa8f..6d80e2e30 100644 --- a/cmake/CLK_SOURCES.cmake +++ b/cmake/CLK_SOURCES.cmake @@ -169,7 +169,6 @@ set(CLK_SOURCES Outputs/OpenGL/Shaders/LineOutputShader.cpp Outputs/OpenGL/Shaders/Rectangle.cpp Outputs/OpenGL/ScanTarget.cpp - Outputs/OpenGL/ScanTargetGLSLFragments.cpp Outputs/ScanTarget.cpp Outputs/ScanTargets/BufferingScanTarget.cpp Outputs/ScanTargets/FilterGenerator.cpp