diff --git a/OSBindings/Mac/Clock Signal/ScanTarget/CSScanTarget.mm b/OSBindings/Mac/Clock Signal/ScanTarget/CSScanTarget.mm index e64723934..924b31047 100644 --- a/OSBindings/Mac/Clock Signal/ScanTarget/CSScanTarget.mm +++ b/OSBindings/Mac/Clock Signal/ScanTarget/CSScanTarget.mm @@ -25,7 +25,7 @@ struct Uniforms { simd::float3x3 fromRGB; float zoom; simd::float2 offset; - float firCoefficients[8]; + simd::float3 firCoefficients[8]; }; constexpr size_t NumBufferedScans = 2048; @@ -373,16 +373,33 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget; _compositionRenderPass.colorAttachments[0].loadAction = MTLLoadActionClear; _compositionRenderPass.colorAttachments[0].storeAction = MTLStoreActionStore; - // TODO: set proper clear colour for S-Video. + // TODO: set proper clear colour for S-Video (and fragment function, below). - // TODO: work out fir coefficients, for real. + simd::float3 *const firCoefficients = uniforms()->firCoefficients; const float cyclesPerLine = float(modals.cycles_per_line); const float colourCyclesPerLine = float(modals.colour_cycle_numerator) / float(modals.colour_cycle_denominator); - SignalProcessing::FIRFilter filter(15, cyclesPerLine, 0.0f, 16.0f * cyclesPerLine / colourCyclesPerLine); - float *const firCoefficients = uniforms()->firCoefficients; - const auto calculatedCoefficients = filter.get_coefficients(); - memcpy(firCoefficients, calculatedCoefficients.data(), calculatedCoefficients.size() * sizeof(float)); + if(isSVideoOutput) { + // In S-Video, don't filter luminance. + for(size_t c = 0; c < 7; ++c) { + firCoefficients[c].x = 0.0f; + } + firCoefficients[7].x = 1.0f; + } else { + // In composite, filter luminance gently. + SignalProcessing::FIRFilter luminancefilter(15, cyclesPerLine, 0.0f, colourCyclesPerLine * 0.75f); + const auto calculatedCoefficients = luminancefilter.get_coefficients(); + for(size_t c = 0; c < 8; ++c) { + firCoefficients[c].x = calculatedCoefficients[c]; + } + } + + // Whether S-Video or composite, apply the same relatively strong filter to colour channels. + SignalProcessing::FIRFilter chrominancefilter(15, cyclesPerLine, 0.0f, colourCyclesPerLine * 0.125f); + const auto calculatedCoefficients = chrominancefilter.get_coefficients(); + for(size_t c = 0; c < 8; ++c) { + firCoefficients[c].y = firCoefficients[c].z = calculatedCoefficients[c]; + } } // Build the output pipeline. @@ -390,8 +407,7 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget; pipelineDescriptor.vertexFunction = [library newFunctionWithName:_isUsingCompositionPipeline ? @"lineToDisplay" : @"scanToDisplay"]; if(_isUsingCompositionPipeline) { - // TODO! - pipelineDescriptor.fragmentFunction = [library newFunctionWithName:@"convertComposite"]; + pipelineDescriptor.fragmentFunction = [library newFunctionWithName:@"filterFragment"]; } else { const bool isRGBOutput = modals.display_type == Outputs::Display::DisplayType::RGB; pipelineDescriptor.fragmentFunction = diff --git a/OSBindings/Mac/Clock Signal/ScanTarget/ScanTarget.metal b/OSBindings/Mac/Clock Signal/ScanTarget/ScanTarget.metal index 0308260d0..c4ddf11ce 100644 --- a/OSBindings/Mac/Clock Signal/ScanTarget/ScanTarget.metal +++ b/OSBindings/Mac/Clock Signal/ScanTarget/ScanTarget.metal @@ -33,7 +33,7 @@ struct Uniforms { // Describes the FIR filter in use; it'll be 15 coefficients but they're // symmetrical around the centre. - float firCoefficients[8]; + float3 firCoefficients[8]; }; namespace { @@ -270,8 +270,11 @@ float3 convertRed1Green1Blue1(SourceInterpolator vert, texture2d texture fragment float4 compositeSample##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(sin(vert.colourPhase), cos(vert.colourPhase)); \ + const float level = mix(colour.r, dot(colour.gb, colourSubcarrier), vert.colourAmplitude); \ return float4( \ - float3(mix(colour.r, dot(colour.gb, colourSubcarrier), vert.colourAmplitude)), \ + level, \ + 0.5 + 0.5*level*sin(vert.colourPhase),\ + 0.5 + 0.5*level*cos(vert.colourPhase),\ 1.0 \ ); \ } @@ -318,7 +321,7 @@ fragment float4 clearFragment() { // MARK: - Conversion fragment shaders -fragment float4 convertComposite(CopyInterpolator vert [[stage_in]], texture2d texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { +fragment float4 filterFragment(SourceInterpolator vert [[stage_in]], texture2d texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { const float3 colour = uniforms.firCoefficients[0] * texture.sample(standardSampler, vert.textureCoordinates - float2(7.0, 0.0)).rgb + uniforms.firCoefficients[1] * texture.sample(standardSampler, vert.textureCoordinates - float2(6.0, 0.0)).rgb + @@ -336,5 +339,5 @@ fragment float4 convertComposite(CopyInterpolator vert [[stage_in]], texture2d