diff --git a/OSBindings/Mac/Clock Signal/ScanTarget/CSScanTarget.mm b/OSBindings/Mac/Clock Signal/ScanTarget/CSScanTarget.mm index 6feedf896..71e3c04d9 100644 --- a/OSBindings/Mac/Clock Signal/ScanTarget/CSScanTarget.mm +++ b/OSBindings/Mac/Clock Signal/ScanTarget/CSScanTarget.mm @@ -15,6 +15,22 @@ #include "BufferingScanTarget.hpp" #include "FIRFilter.hpp" +/* + Pipelines in use: + + RGB input -> RGB display: + just output it. + + RGB input -> angular: + Composition in the display colour space (YIQ or YUV), conversion to and from S-Video or composite per output pixel. + + Luminance/Phase -> angular: + Composition, conversion per output pixel. + + Luminance -> composite: + Composition, conversion per input pixel. +*/ + namespace { struct Uniforms { @@ -154,9 +170,9 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget; // Create a composition texture up front. (TODO: is it worth switching to an 8bpp texture in composite mode?) MTLTextureDescriptor *const textureDescriptor = [MTLTextureDescriptor - texture2DDescriptorWithPixelFormat:MTLPixelFormatRG8Unorm + texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm width:2048 // This 'should do'. - height:NumBufferedLines + height:NumBufferedLines // TODO: I want to turn this down _considerably_. A frame and a bit should be sufficient, though probably I'd also want to adjust the buffering scan target to keep most recent data? mipmapped:NO]; textureDescriptor.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead; textureDescriptor.resourceOptions = MTLResourceStorageModePrivate; @@ -395,10 +411,10 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget; } // Whether S-Video or composite, apply the same relatively strong filter to colour channels. - SignalProcessing::FIRFilter chrominancefilter(15, cyclesPerLine, 0.0f, colourCyclesPerLine * 0.25f); + SignalProcessing::FIRFilter chrominancefilter(15, cyclesPerLine, 0.0f, colourCyclesPerLine); const auto calculatedCoefficients = chrominancefilter.get_coefficients(); for(size_t c = 0; c < 8; ++c) { - firCoefficients[c].y = firCoefficients[c].z = calculatedCoefficients[c] * (isSVideoOutput ? 4.0f : 1.0f); + firCoefficients[c].y = firCoefficients[c].z = calculatedCoefficients[c] * (isSVideoOutput ? 2.0f : 1.0f); } uniforms()->radiansPerPixel = (colourCyclesPerLine * 3.141592654f * 2.0f) / cyclesPerLine; diff --git a/OSBindings/Mac/Clock Signal/ScanTarget/ScanTarget.metal b/OSBindings/Mac/Clock Signal/ScanTarget/ScanTarget.metal index 89ca8a8bd..6fcbeb812 100644 --- a/OSBindings/Mac/Clock Signal/ScanTarget/ScanTarget.metal +++ b/OSBindings/Mac/Clock Signal/ScanTarget/ScanTarget.metal @@ -81,10 +81,9 @@ struct SourceInterpolator { float4 position [[position]]; float2 textureCoordinates; float colourPhase; - float colourAmplitude; + float colourAmplitude [[flat]]; }; - // MARK: - Vertex shaders. float2 textureLocation(constant Line *line, float offset) { @@ -202,6 +201,10 @@ vertex SourceInterpolator scanToComposition( constant Uniforms &uniforms [[buffe // MARK: - Various input format conversion samplers. +float2 quadrature(float phase) { + return float2(cos(phase), sin(phase)); +} + // There's only one meaningful way to sample the luminance formats. fragment float4 sampleLuminance1(SourceInterpolator vert [[stage_in]], texture2d texture [[texture(0)]]) { @@ -276,13 +279,7 @@ float3 convertRed1Green1Blue1(SourceInterpolator vert, texture2d texture \ fragment float4 svideoSample##name(SourceInterpolator vert [[stage_in]], texture2d texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \ const auto colour = uniforms.fromRGB * convert##name(vert, texture); \ - const float2 colourSubcarrier = float2(cos(vert.colourPhase), sin(vert.colourPhase)); \ - return float4( \ - colour.r, \ - dot(colour.gb, colourSubcarrier)*0.5 + 0.5, \ - 0.0, \ - 1.0 \ - ); \ + return float4(colour, 1.0); \ } \ \ fragment float4 compositeSample##name(SourceInterpolator vert [[stage_in]], texture2d texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \ @@ -297,6 +294,14 @@ float3 convertRed1Green1Blue1(SourceInterpolator vert, texture2d texture ); \ } +// const float2 colourSubcarrier = float2(cos(vert.colourPhase), sin(vert.colourPhase)); \ +// return float4( \ +// colour.r, \ +// dot(colour.gb, colourSubcarrier)*0.5 + 0.5, \ +// 0.0, \ +// 1.0 \ +// ); \ + DeclareShaders(Red8Green8Blue8, float) DeclareShaders(Red4Green4Blue4, ushort) DeclareShaders(Red2Green2Blue2, ushort) @@ -340,14 +345,33 @@ fragment float4 clearFragment() { // MARK: - Conversion fragment shaders fragment float4 filterSVideoFragment(SourceInterpolator vert [[stage_in]], texture2d texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { -#define Sample(x) texture.sample(standardSampler, vert.textureCoordinates + float2(x, 0.0f)).rg - float2(0.0f, 0.5f) - const float2 rawSamples[] = { +#define Sample(x) texture.sample(standardSampler, vert.textureCoordinates + float2(x, 0.0f)) + float4 rawSamples[] = { Sample(-7), Sample(-6), Sample(-5), Sample(-4), Sample(-3), Sample(-2), Sample(-1), Sample(0), Sample(1), Sample(2), Sample(3), Sample(4), Sample(5), Sample(6), Sample(7), }; #undef Sample +#define Offset(x) vert.colourPhase + (x)*uniforms.radiansPerPixel + const float angles[] = { + Offset(-7), Offset(-6), Offset(-5), Offset(-4), Offset(-3), Offset(-2), Offset(-1), + vert.colourPhase, + Offset(1), Offset(2), Offset(3), Offset(4), Offset(5), Offset(6), Offset(7) + }; +#undef Offset + +#define Map(x) { \ + const float2 colourSubcarrier = float2(cos(angles[x]), sin(angles[x])); \ + rawSamples[x].g = dot(rawSamples[x].gb, colourSubcarrier); \ + } + + Map(0); Map(1); Map(2); Map(3); Map(4); Map(5); + Map(6); Map(7); Map(8); Map(9); Map(10); Map(11); + Map(12); Map(13); Map(14); + +#undef Map + #define Sample(c, o, a) \ uniforms.firCoefficients[c] * float3(rawSamples[o].r, rawSamples[o].g*cos(vert.colourPhase + (a)*uniforms.radiansPerPixel), rawSamples[o].g*sin(vert.colourPhase + (a)*uniforms.radiansPerPixel))