From 65ebcd9c997bd626ca7cc73fd1704013ddc3dcd8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 4 Feb 2026 14:41:34 -0500 Subject: [PATCH] Attempt a line output shader. --- .../Clock Signal.xcodeproj/project.pbxproj | 6 + Outputs/OpenGL/ScanTarget.cpp | 12 ++ Outputs/OpenGL/ScanTarget.hpp | 2 + Outputs/OpenGL/Shaders/CommonAtrributes.hpp | 10 ++ Outputs/OpenGL/Shaders/LineOutputShader.cpp | 125 ++++++++++++++++++ Outputs/OpenGL/Shaders/LineOutputShader.hpp | 30 +++++ Outputs/ScanTargets/BufferingScanTarget.hpp | 1 + cmake/CLK_SOURCES.cmake | 1 + 8 files changed, 187 insertions(+) create mode 100644 Outputs/OpenGL/Shaders/LineOutputShader.cpp create mode 100644 Outputs/OpenGL/Shaders/LineOutputShader.hpp diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index cc3e6d1e7..110fd91e2 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1237,6 +1237,7 @@ 4BF0BC722973318E00CCA2B5 /* RP5C01.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF0BC6F2973318E00CCA2B5 /* RP5C01.cpp */; }; 4BF437EE209D0F7E008CBD6B /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */; }; 4BF437EF209D0F7E008CBD6B /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */; }; + 4BF64D672F33D27B001EB900 /* LineOutputShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF64D662F33D27B001EB900 /* LineOutputShader.cpp */; }; 4BF701A026FFD32300996424 /* AmigaBlitterTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BF7019F26FFD32300996424 /* AmigaBlitterTests.mm */; }; 4BF8D4C82516E27A00BBE21B /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BB8617024E22F4900A00E03 /* Accelerate.framework */; }; 4BF8D4D5251C11DD00BBE21B /* 65816Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8D4D4251C11DD00BBE21B /* 65816Storage.cpp */; }; @@ -2577,6 +2578,8 @@ 4BF437F0209D112F008CBD6B /* Sector.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Sector.hpp; sourceTree = ""; }; 4BF4A2D91F534DB300B171F4 /* TargetPlatforms.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TargetPlatforms.hpp; sourceTree = ""; }; 4BF52672218E752E00313227 /* ScanTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ScanTarget.hpp; sourceTree = ""; }; + 4BF64D652F33D27B001EB900 /* LineOutputShader.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LineOutputShader.hpp; sourceTree = ""; }; + 4BF64D662F33D27B001EB900 /* LineOutputShader.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = LineOutputShader.cpp; sourceTree = ""; }; 4BF6606A1F281573002CB053 /* ClockReceiver.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ClockReceiver.hpp; sourceTree = ""; }; 4BF7019F26FFD32300996424 /* AmigaBlitterTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AmigaBlitterTests.mm; sourceTree = ""; }; 4BF8D4CD251C0C9C00BBE21B /* 65816.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 65816.hpp; sourceTree = ""; }; @@ -5411,12 +5414,14 @@ 4BD235BF2F312C0C0094AFAE /* CompositionShader.cpp */, 4BD235C12F312C0C0094AFAE /* CopyShader.cpp */, 4BD235CB2F32A4C80094AFAE /* KernelShaders.cpp */, + 4BF64D662F33D27B001EB900 /* LineOutputShader.cpp */, 4BD235C62F312C3A0094AFAE /* Rectangle.cpp */, 4BD235C92F32A2780094AFAE /* CommonAtrributes.hpp */, 4BD235BE2F312C0C0094AFAE /* CompositionShader.hpp */, 4BD235C02F312C0C0094AFAE /* CopyShader.hpp */, 4BD235C82F326A280094AFAE /* DirtyZone.hpp */, 4BD235CA2F32A4C80094AFAE /* KernelShaders.hpp */, + 4BF64D652F33D27B001EB900 /* LineOutputShader.hpp */, 4BD235C52F312C3A0094AFAE /* Rectangle.hpp */, ); path = Shaders; @@ -6502,6 +6507,7 @@ 4B302185208A550100773308 /* DiskII.cpp in Sources */, 42EB81292B23AAC300429AF4 /* IMD.cpp in Sources */, 4B051CB1267C1CA200CA44E8 /* Keyboard.cpp in Sources */, + 4BF64D672F33D27B001EB900 /* LineOutputShader.cpp in Sources */, 4B8855A72E84D51B00E251DD /* SAA5050.cpp in Sources */, 4B0F1BB32602645900B85C66 /* StaticAnalyser.cpp in Sources */, 4B055A931FAE85B50060FFFF /* BinaryDump.cpp in Sources */, diff --git a/Outputs/OpenGL/ScanTarget.cpp b/Outputs/OpenGL/ScanTarget.cpp index 9007e386b..5f336feb7 100644 --- a/Outputs/OpenGL/ScanTarget.cpp +++ b/Outputs/OpenGL/ScanTarget.cpp @@ -14,6 +14,7 @@ #include "Outputs/OpenGL/Shaders/CompositionShader.hpp" #include "Outputs/OpenGL/Shaders/CopyShader.hpp" #include "Outputs/OpenGL/Shaders/KernelShaders.hpp" +#include "Outputs/OpenGL/Shaders/LineOutputShader.hpp" #include #include @@ -127,6 +128,7 @@ ScanTarget::ScanTarget(const API api, const GLuint target_framebuffer, const flo 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_), dirty_zones_(dirty_zones_buffer_) { set_scan_buffer(scan_buffer_.data(), scan_buffer_.size()); @@ -327,8 +329,18 @@ void ScanTarget::setup_pipeline() { dirty_zones_, is_svideo(modals.display_type) ? CompositionTextureUnit : SeparationTextureUnit ); + + line_output_shader_ = OpenGL::line_output_shader( + api_, + buffer_width, 2048, + modals.expected_vertical_lines, + modals.output_scale.x, modals.output_scale.y, + lines_, + DemodulationTextureUnit + ); } else { demodulation_shader_.reset(); + line_output_shader_.reset(); } } diff --git a/Outputs/OpenGL/ScanTarget.hpp b/Outputs/OpenGL/ScanTarget.hpp index fa0befbb5..b42715857 100644 --- a/Outputs/OpenGL/ScanTarget.hpp +++ b/Outputs/OpenGL/ScanTarget.hpp @@ -164,6 +164,7 @@ private: std::array dirty_zones_buffer_{}; VertexArray scans_; + VertexArray lines_; VertexArray dirty_zones_; Texture source_texture_; @@ -175,6 +176,7 @@ private: Shader composition_shader_; Shader separation_shader_; Shader demodulation_shader_; + Shader line_output_shader_; CopyShader copy_shader_; }; diff --git a/Outputs/OpenGL/Shaders/CommonAtrributes.hpp b/Outputs/OpenGL/Shaders/CommonAtrributes.hpp index 64731fe4d..08fa30fea 100644 --- a/Outputs/OpenGL/Shaders/CommonAtrributes.hpp +++ b/Outputs/OpenGL/Shaders/CommonAtrributes.hpp @@ -40,4 +40,14 @@ inline std::vector dirty_zone_attributes() { }; } +inline std::vector line_attributes() { + return std::vector{ + "lineEndpoint0Position", + "lineEndpoint0CyclesSinceRetrace", + "lineEndpoint1Position", + "lineEndpoint1CyclesSinceRetrace", + "lineLine", + }; +} + } diff --git a/Outputs/OpenGL/Shaders/LineOutputShader.cpp b/Outputs/OpenGL/Shaders/LineOutputShader.cpp new file mode 100644 index 000000000..0ecc2c466 --- /dev/null +++ b/Outputs/OpenGL/Shaders/LineOutputShader.cpp @@ -0,0 +1,125 @@ +// +// LineOutputShader.cpp +// Clock Signal Kiosk +// +// Created by Thomas Harte on 04/02/2026. +// Copyright © 2026 Thomas Harte. All rights reserved. +// + +#include "LineOutputShader.hpp" + +#include "CommonAtrributes.hpp" +#include "Outputs/ScanTargets/BufferingScanTarget.hpp" + +namespace { + +constexpr char vertex_shader[] = R"glsl( + +uniform mediump vec2 sourceSize; +uniform highp vec2 positionScale; +uniform mediump float lineHeight; + +// TODO: programmable crop should affect scaling via uniforms. + +in highp vec2 lineEndpoint0Position; +in highp float lineEndpoint0CyclesSinceRetrace; + +in highp vec2 lineEndpoint1Position; +in highp float lineEndpoint1CyclesSinceRetrace; + +in highp float lineLine; + +out mediump vec2 coordinate; + +void main(void) { + float lateral = float(gl_VertexID & 1); + float longitudinal = float((gl_VertexID & 2) >> 1); + + coordinate = vec2( + mix( + lineEndpoint0CyclesSinceRetrace, + lineEndpoint1CyclesSinceRetrace, + lateral + ), + lineLine + 0.5 + ) / sourceSize; + + vec2 tangent = normalize(lineEndpoint1Position - lineEndpoint0Position); + vec2 normal = vec2(tangent.y, -tangent.x); + + vec2 centre = + mix( + lineEndpoint0Position, + lineEndpoint1Position, + lateral + ) / positionScale; + gl_Position = + vec4( + centre + (longitudinal - 0.5) * normal, + 0.0, + 1.0 + ); +} + +)glsl"; + +constexpr char fragment_shader[] = R"glsl( + +uniform lowp sampler2D source; +in mediump vec2 coordinate; + +out lowp vec4 outputColour; + +void main(void) { + outputColour = texture(source, coordinate); +} + +)glsl"; + +} + +using namespace Outputs::Display; + +OpenGL::Shader OpenGL::line_output_shader( + const API api, + const int source_width, + const int source_height, + const int expected_vertical_lines, + const int scale_x, + const int scale_y, + const VertexArray &vertex_array, + const GLenum source_texture_unit +) { + auto shader = OpenGL::Shader( + api, + vertex_shader, + fragment_shader, + line_attributes() + ); + + BufferingScanTarget::Line line; + vertex_array.bind_all(); + const auto enable = [&](const std::string &name, uint16_t &element, const GLint size) { + shader.enable_vertex_attribute_with_pointer( + name, + size, + GL_UNSIGNED_SHORT, + GL_FALSE, + sizeof(line), + reinterpret_cast((reinterpret_cast(&element) - reinterpret_cast(&line))), + 1 + ); + }; + enable("lineEndpoint0Position", line.end_points[0].x, 2); + enable("lineEndpoint0CyclesSinceRetrace", line.end_points[0].cycles_since_end_of_horizontal_retrace, 1); + enable("lineEndpoint1Position", line.end_points[1].x, 2); + enable("lineEndpoin10CyclesSinceRetrace", line.end_points[1].cycles_since_end_of_horizontal_retrace, 1); + enable("lineLine", line.line, 1); + + shader.set_uniform("lineHeight", 1.0f / float(expected_vertical_lines)); + shader.set_uniform("positionScale", float(scale_x), float(scale_y)); + shader.set_uniform("sourceSize", float(source_width), float(source_height)); + shader.set_uniform("source", GLint(source_texture_unit - GL_TEXTURE0)); + + return shader; +} diff --git a/Outputs/OpenGL/Shaders/LineOutputShader.hpp b/Outputs/OpenGL/Shaders/LineOutputShader.hpp new file mode 100644 index 000000000..e96f13831 --- /dev/null +++ b/Outputs/OpenGL/Shaders/LineOutputShader.hpp @@ -0,0 +1,30 @@ +// +// LineOutputShader.hpp +// Clock Signal Kiosk +// +// Created by Thomas Harte on 04/02/2026. +// Copyright © 2026 Thomas Harte. All rights reserved. +// + +#pragma once + +#include "Outputs/OpenGL/Primitives/VertexArray.hpp" +#include "Outputs/OpenGL/Primitives/Shader.hpp" + +namespace Outputs::Display::OpenGL { + +/*! + Using `Line`s as input, draws output spans. +*/ +Shader line_output_shader( + API, + int source_width, + int source_height, + int expected_vertical_lines, + int scale_x, + int scale_y, + const VertexArray &, + GLenum source_texture_unit +); + +} diff --git a/Outputs/ScanTargets/BufferingScanTarget.hpp b/Outputs/ScanTargets/BufferingScanTarget.hpp index 5f219b5cd..e5fe2ed60 100644 --- a/Outputs/ScanTargets/BufferingScanTarget.hpp +++ b/Outputs/ScanTargets/BufferingScanTarget.hpp @@ -74,6 +74,7 @@ public: uint8_t composite_amplitude; uint16_t line; }; + static_assert(sizeof(Line) == 20); /// Provides additional metadata about lines; this is separate because it's unlikely to be of /// interest to the GPU, unlike the fields in Line. diff --git a/cmake/CLK_SOURCES.cmake b/cmake/CLK_SOURCES.cmake index 98cef81e7..f859dfa8f 100644 --- a/cmake/CLK_SOURCES.cmake +++ b/cmake/CLK_SOURCES.cmake @@ -166,6 +166,7 @@ set(CLK_SOURCES Outputs/OpenGL/Shaders/CompositionShader.cpp Outputs/OpenGL/Shaders/CopyShader.cpp Outputs/OpenGL/Shaders/KernelShaders.cpp + Outputs/OpenGL/Shaders/LineOutputShader.cpp Outputs/OpenGL/Shaders/Rectangle.cpp Outputs/OpenGL/ScanTarget.cpp Outputs/OpenGL/ScanTargetGLSLFragments.cpp