From 0f247809e2eb83271ce4c6787a9d123a0f3193f8 Mon Sep 17 00:00:00 2001 From: hunterk Date: Tue, 25 Jul 2017 15:13:42 -0500 Subject: [PATCH] update mame-ntsc --- ntsc/mame-ntsc-singlepass.slangp | 5 - ntsc/mame-ntsc.slangp | 8 +- ntsc/shaders/mame-ntsc/constants.inc | 65 ++++++ ntsc/shaders/mame-ntsc/mame-ntsc.slang | 188 ++++++++++++++++ ntsc/shaders/mame-ntsc/mame-postproc.slang | 147 ++++++++++++ ...-pass0.slang => old-ntsc-mame-pass0.slang} | 0 ...-pass1.slang => old-ntsc-mame-pass1.slang} | 0 .../mame-ntsc/old-ntsc-mame-singlepass.slang | 209 ++++++++++++++++++ 8 files changed, 613 insertions(+), 9 deletions(-) delete mode 100644 ntsc/mame-ntsc-singlepass.slangp create mode 100644 ntsc/shaders/mame-ntsc/constants.inc create mode 100644 ntsc/shaders/mame-ntsc/mame-ntsc.slang create mode 100644 ntsc/shaders/mame-ntsc/mame-postproc.slang rename ntsc/shaders/mame-ntsc/{ntsc-mame-pass0.slang => old-ntsc-mame-pass0.slang} (100%) rename ntsc/shaders/mame-ntsc/{ntsc-mame-pass1.slang => old-ntsc-mame-pass1.slang} (100%) create mode 100644 ntsc/shaders/mame-ntsc/old-ntsc-mame-singlepass.slang diff --git a/ntsc/mame-ntsc-singlepass.slangp b/ntsc/mame-ntsc-singlepass.slangp deleted file mode 100644 index 8c349d2..0000000 --- a/ntsc/mame-ntsc-singlepass.slangp +++ /dev/null @@ -1,5 +0,0 @@ -shaders = 1 - -shader0 = shaders/mame-ntsc/ntsc-mame-singlepass.slang -scale_type0 = source -scale0 = 1.0 \ No newline at end of file diff --git a/ntsc/mame-ntsc.slangp b/ntsc/mame-ntsc.slangp index 3d0d094..e357201 100644 --- a/ntsc/mame-ntsc.slangp +++ b/ntsc/mame-ntsc.slangp @@ -1,9 +1,9 @@ shaders = 2 -shader0 = shaders/mame-ntsc/ntsc-mame-pass0.slang +shader0 = shaders/mame-ntsc/mame-ntsc.slang scale_type0 = source +filter_linear0 = true scale0 = 1.0 -shader1 = shaders/mame-ntsc/ntsc-mame-pass1.slang -scale_type1 = source -scale1 = 1.0 \ No newline at end of file +shader1 = shaders/mame-ntsc/mame-postproc.slang +filter_linear1 = true \ No newline at end of file diff --git a/ntsc/shaders/mame-ntsc/constants.inc b/ntsc/shaders/mame-ntsc/constants.inc new file mode 100644 index 0000000..8f617fd --- /dev/null +++ b/ntsc/shaders/mame-ntsc/constants.inc @@ -0,0 +1,65 @@ +// Useful Constants +const vec4 Zero = vec4(0.0); +const vec4 Half = vec4(0.5); +const vec4 One = vec4(1.0); +const vec4 Two = vec4(2.0); +const vec3 Gray = vec3(0.3, 0.59, 0.11); +const float Pi = 3.1415926535; +const float Pi2 = 6.283185307; + +// NTSC Constants +const vec4 A = vec4(0.5); +const vec4 A2 = vec4(1.0); +const vec4 B = vec4(0.5); +const float P = 1.0; +const float CCFrequency = 3.59754545; +const float NotchUpperFrequency = 3.59754545 + 2.0; +const float NotchLowerFrequency = 3.59754545 - 2.0; +const float YFrequency = 6.0; +const float IFrequency = 1.2; +const float QFrequency = 0.6; +const float NotchHalfWidth = 2.0; +const float ScanTime = 52.6; +const float Pi2ScanTime = 6.283185307 * 52.6; +const float MaxC = 2.1183; +const vec4 YTransform = vec4(0.299, 0.587, 0.114, 0.0); +const vec4 ITransform = vec4(0.595716, -0.274453, -0.321263, 0.0); +const vec4 QTransform = vec4(0.211456, -0.522591, 0.311135, 0.0); +const vec3 YIQ2R = vec3(1.0, 0.956, 0.621); +const vec3 YIQ2G = vec3(1.0, -0.272, -0.647); +const vec3 YIQ2B = vec3(1.0, -1.106, 1.703); +const vec4 MinC = vec4(-1.1183); +const vec4 CRange = vec4(3.2366); +const vec4 InvCRange = vec4(1.0/3.2366); +const float Pi2Length = Pi2 / 63.0; +const vec4 NotchOffset = vec4(0.0, 1.0, 2.0, 3.0); +const vec4 W = vec4(Pi2 * CCFrequency * ScanTime); + +// Color Convolution Constants +const vec3 RedMatrix = vec3(1.0, 0.0, 0.0); +const vec3 GrnMatrix = vec3(0.0, 1.0, 0.0); +const vec3 BluMatrix = vec3(0.0, 0.0, 1.0); +const vec3 DCOffset = vec3(0.0, 0.0, 0.0); +const vec3 ColorScale = vec3(0.95, 0.95, 0.95); +const float Saturation = 1.4; + +// Deconverge Constants +const vec3 ConvergeX = vec3(-0.4, 0.0, 0.2); +const vec3 ConvergeY = vec3( 0.0, -0.4, 0.2); +const vec3 RadialConvergeX = vec3(1.0, 1.0, 1.0); +const vec3 RadialConvergeY = vec3(1.0, 1.0, 1.0); + +// Scanline/Pincushion Constants +const float PincushionAmount = 0.015; +const float CurvatureAmount = 0.015; +//const float ScanlineAmount = 0.175; <- move to parameter +const float ScanlineScale = 1.0; +const float ScanlineHeight = 1.0; +const float ScanlineBrightScale = 1.0; +const float ScanlineBrightOffset = 0.0; +const float ScanlineOffset = 0.0; +const vec3 Floor = vec3(0.05, 0.05, 0.05); + +// 60Hz Bar Constants +const float SixtyHertzRate = (60.0 / 59.97 - 1.0); // Difference between NTSC and line frequency +const float SixtyHertzScale = 0.1; \ No newline at end of file diff --git a/ntsc/shaders/mame-ntsc/mame-ntsc.slang b/ntsc/shaders/mame-ntsc/mame-ntsc.slang new file mode 100644 index 0000000..1ad0098 --- /dev/null +++ b/ntsc/shaders/mame-ntsc/mame-ntsc.slang @@ -0,0 +1,188 @@ +#version 450 + +// This is a port of the NTSC encode/decode shader pair in MAME and MESS, modified to use only +// one pass rather than an encode pass and a decode pass. It accurately emulates the sort of +// signal decimation one would see when viewing a composite signal, though it could benefit from a +// pre-pass to re-size the input content to more accurately reflect the actual size that would +// be incoming from a composite signal source. +// +// To encode the composite signal, I convert the RGB value to YIQ, then subsequently evaluate +// the standard NTSC composite equation. Four composite samples per RGB pixel are generated from +// the incoming linearly-interpolated texels. +// +// The decode pass implements a Fixed Impulse Response (FIR) filter designed by MAME/MESS contributor +// "austere" in matlab (if memory serves correctly) to mimic the behavior of a standard television set +// as closely as possible. The filter window is 83 composite samples wide, and there is an additional +// notch filter pass on the luminance (Y) values in order to strip the color signal from the luminance +// signal prior to processing. +// +// Yes, this code could greatly use some cleaning up. + +// ported from UltraMoogleMan's "Full MAME/MESS Shader Pipe" shadertoy: https://www.shadertoy.com/view/ldf3Rf +// license: presumably MAME's license at the time, which was noncommercial + +layout(push_constant) uniform Push +{ + vec4 SourceSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; +} params; + +#include "constants.inc" + +layout(std140, set = 0, binding = 0) uniform UBO +{ + mat4 MVP; +} global; + +#pragma stage vertex +layout(location = 0) in vec4 Position; +layout(location = 1) in vec2 TexCoord; +layout(location = 0) out vec2 vTexCoord; + +void main() +{ + gl_Position = global.MVP * Position; + vTexCoord = TexCoord; +} + +#pragma stage fragment +layout(location = 0) in vec2 vTexCoord; +layout(location = 0) out vec4 FragColor; +layout(set = 0, binding = 2) uniform sampler2D Source; + +#define iChannel0 Source + +vec4 CompositeSample(vec2 UV, vec2 InverseRes) { + vec2 InverseP = vec2(P, 0.0) * InverseRes; + + // UVs for four linearly-interpolated samples spaced 0.25 texels apart + vec2 C0 = UV; + vec2 C1 = UV + InverseP * 0.25; + vec2 C2 = UV + InverseP * 0.50; + vec2 C3 = UV + InverseP * 0.75; + vec4 Cx = vec4(C0.x, C1.x, C2.x, C3.x); + vec4 Cy = vec4(C0.y, C1.y, C2.y, C3.y); + + vec4 Texel0 = texture(iChannel0, C0); + vec4 Texel1 = texture(iChannel0, C1); + vec4 Texel2 = texture(iChannel0, C2); + vec4 Texel3 = texture(iChannel0, C3); + + float Frequency = CCFrequency; + //Frequency = Frequency;// Uncomment for bad color sync + (sin(UV.y * 2.0 - 1.0) / CCFrequency) * 0.001; + + // Calculated the expected time of the sample. + vec4 T = A2 * Cy * vec4(params.SourceSize.y) + B + Cx; + vec4 W = vec4(Pi2ScanTime * Frequency); + vec4 TW = T * W; + vec4 Y = vec4(dot(Texel0, YTransform), dot(Texel1, YTransform), dot(Texel2, YTransform), dot(Texel3, YTransform)); + vec4 I = vec4(dot(Texel0, ITransform), dot(Texel1, ITransform), dot(Texel2, ITransform), dot(Texel3, ITransform)); + vec4 Q = vec4(dot(Texel0, QTransform), dot(Texel1, QTransform), dot(Texel2, QTransform), dot(Texel3, QTransform)); + + vec4 Encoded = Y + I * cos(TW) + Q * sin(TW); + return (Encoded - MinC) * InvCRange; +} + +vec4 NTSCCodec(vec2 UV, vec2 InverseRes) +{ + vec4 YAccum = Zero; + vec4 IAccum = Zero; + vec4 QAccum = Zero; + float QuadXSize = params.SourceSize.x * 4.0; + float TimePerSample = ScanTime / QuadXSize; + + // Frequency cutoffs for the individual portions of the signal that we extract. + // Y1 and Y2 are the positive and negative frequency limits of the notch filter on Y. + // Y3 is the center of the frequency response of the Y filter. + // I is the center of the frequency response of the I filter. + // Q is the center of the frequency response of the Q filter. + float Fc_y1 = NotchLowerFrequency * TimePerSample; + float Fc_y2 = NotchUpperFrequency * TimePerSample; + float Fc_y3 = YFrequency * TimePerSample; + float Fc_i = IFrequency * TimePerSample; + float Fc_q = QFrequency * TimePerSample; + float Pi2Fc_y1 = Fc_y1 * Pi2; + float Pi2Fc_y2 = Fc_y2 * Pi2; + float Pi2Fc_y3 = Fc_y3 * Pi2; + float Pi2Fc_i = Fc_i * Pi2; + float Pi2Fc_q = Fc_q * Pi2; + float Fc_y1_2 = Fc_y1 * 2.0; + float Fc_y2_2 = Fc_y2 * 2.0; + float Fc_y3_2 = Fc_y3 * 2.0; + float Fc_i_2 = Fc_i * 2.0; + float Fc_q_2 = Fc_q * 2.0; + vec4 CoordY = vec4(UV.y); + + // 83 composite samples wide, 4 composite pixels per texel + for(float n = -31.0; n < 32.0; n += 4.0) + { + vec4 n4 = n + NotchOffset; + vec4 CoordX = UV.x + InverseRes.x * n4 * 0.25; + vec2 TexCoord = vec2(CoordX.x, CoordY.x); + vec4 C = CompositeSample(TexCoord, InverseRes) * CRange + MinC; + vec4 WT = W * (CoordX + A2 * CoordY * params.SourceSize.y + B); + vec4 Cosine = 0.54 + 0.46 * cos(Pi2Length * n4); + + vec4 SincYIn1 = Pi2Fc_y1 * n4; + vec4 SincYIn2 = Pi2Fc_y2 * n4; + vec4 SincYIn3 = Pi2Fc_y3 * n4; + vec4 SincY1 = sin(SincYIn1) / SincYIn1; + vec4 SincY2 = sin(SincYIn2) / SincYIn2; + vec4 SincY3 = sin(SincYIn3) / SincYIn3; + + // These zero-checks could be made more efficient if WebGL supported mix(vec4, vec4, bvec4) + // Unfortunately, the universe hates us + if(SincYIn1.x == 0.0) SincY1.x = 1.0; + if(SincYIn1.y == 0.0) SincY1.y = 1.0; + if(SincYIn1.z == 0.0) SincY1.z = 1.0; + if(SincYIn1.w == 0.0) SincY1.w = 1.0; + if(SincYIn2.x == 0.0) SincY2.x = 1.0; + if(SincYIn2.y == 0.0) SincY2.y = 1.0; + if(SincYIn2.z == 0.0) SincY2.z = 1.0; + if(SincYIn2.w == 0.0) SincY2.w = 1.0; + if(SincYIn3.x == 0.0) SincY3.x = 1.0; + if(SincYIn3.y == 0.0) SincY3.y = 1.0; + if(SincYIn3.z == 0.0) SincY3.z = 1.0; + if(SincYIn3.w == 0.0) SincY3.w = 1.0; + vec4 IdealY = (Fc_y1_2 * SincY1 - Fc_y2_2 * SincY2) + Fc_y3_2 * SincY3; + vec4 FilterY = Cosine * IdealY; + + vec4 SincIIn = Pi2Fc_i * n4; + vec4 SincI = sin(SincIIn) / SincIIn; + if (SincIIn.x == 0.0) SincI.x = 1.0; + if (SincIIn.y == 0.0) SincI.y = 1.0; + if (SincIIn.z == 0.0) SincI.z = 1.0; + if (SincIIn.w == 0.0) SincI.w = 1.0; + vec4 IdealI = Fc_i_2 * SincI; + vec4 FilterI = Cosine * IdealI; + + vec4 SincQIn = Pi2Fc_q * n4; + vec4 SincQ = sin(SincQIn) / SincQIn; + if (SincQIn.x == 0.0) SincQ.x = 1.0; + if (SincQIn.y == 0.0) SincQ.y = 1.0; + if (SincQIn.z == 0.0) SincQ.z = 1.0; + if (SincQIn.w == 0.0) SincQ.w = 1.0; + vec4 IdealQ = Fc_q_2 * SincQ; + vec4 FilterQ = Cosine * IdealQ; + + YAccum += C * FilterY; + IAccum += C * cos(WT) * FilterI; + QAccum += C * sin(WT) * FilterQ; + } + + float Y = dot(YAccum, One); + float I = dot(IAccum, One) * 2.0; + float Q = dot(QAccum, One) * 2.0; + + vec3 YIQ = vec3(Y, I, Q); + vec3 OutRGB = vec3(dot(YIQ, YIQ2R), dot(YIQ, YIQ2G), dot(YIQ, YIQ2B)); + + return vec4(OutRGB, 1.0); +} + +void main() +{ + FragColor = vec4(NTSCCodec(vTexCoord, params.SourceSize.zw)); +} \ No newline at end of file diff --git a/ntsc/shaders/mame-ntsc/mame-postproc.slang b/ntsc/shaders/mame-ntsc/mame-postproc.slang new file mode 100644 index 0000000..fac7422 --- /dev/null +++ b/ntsc/shaders/mame-ntsc/mame-postproc.slang @@ -0,0 +1,147 @@ +#version 450 + +layout(push_constant) uniform Push +{ + vec4 SourceSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; + float scanlines; + float scandark; + float deconverge; + float pincushion; + float hertzroll; +} params; + +#pragma parameter scanlines "Scanline Toggle" 1.0 0.0 1.0 1.0 +#pragma parameter scandark "Scanline Intensity" 0.175 0.0 1.0 0.05 +#pragma parameter deconverge "Deconvergence/Convolution" 1.0 0.0 1.0 1.0 +#pragma parameter pincushion "Bezel Toggle" 0.0 0.0 1.0 1.0 +#pragma parameter hertzroll "Refresh Roll Toggle" 1.0 0.0 1.0 1.0 + +#include "constants.inc" + +layout(std140, set = 0, binding = 0) uniform UBO +{ + mat4 MVP; +} global; + +#pragma stage vertex +layout(location = 0) in vec4 Position; +layout(location = 1) in vec2 TexCoord; +layout(location = 0) out vec2 vTexCoord; + +void main() +{ + gl_Position = global.MVP * Position; + vTexCoord = TexCoord; +} + +#pragma stage fragment +layout(location = 0) in vec2 vTexCoord; +layout(location = 0) out vec4 FragColor; +layout(set = 0, binding = 2) uniform sampler2D Source; + +vec4 ColorConvolution(vec2 UV, vec2 InverseRes) +{ + vec3 InPixel = texture(Source, UV).rgb; + + // Color Matrix + float RedValue = dot(InPixel, RedMatrix); + float GrnValue = dot(InPixel, GrnMatrix); + float BluValue = dot(InPixel, BluMatrix); + vec3 OutColor = vec3(RedValue, GrnValue, BluValue); + + // DC Offset & Scale + OutColor = (OutColor * ColorScale) + DCOffset; + + // Saturation + float Luma = dot(OutColor, Gray); + vec3 Chroma = OutColor - Luma; + OutColor = (Chroma * Saturation) + Luma; + + return vec4(OutColor, 1.0); +} + +vec4 Deconverge(vec2 UV) +{ + vec2 InverseRes = 1.0 / params.SourceSize.xy; + vec2 InverseSrcRes = 1.0 / params.OriginalSize.xy; + + vec3 CoordX = UV.x * RadialConvergeX; + vec3 CoordY = UV.y * RadialConvergeY; + + CoordX += ConvergeX * InverseRes.x - (RadialConvergeX - 1.0) * 0.5; + CoordY += ConvergeY * InverseRes.y - (RadialConvergeY - 1.0) * 0.5; + + float RedValue = ColorConvolution(vec2(CoordX.x, CoordY.x), InverseSrcRes).r; + float GrnValue = ColorConvolution(vec2(CoordX.y, CoordY.y), InverseSrcRes).g; + float BluValue = ColorConvolution(vec2(CoordX.z, CoordY.z), InverseSrcRes).b; + + if (params.deconverge > 0.5) return vec4(RedValue, GrnValue, BluValue, 1.0); + else return vec4(texture(Source, UV)); +} + +vec4 ScanlinePincushion(vec2 UV) +{ + vec4 InTexel = Deconverge(UV); + + vec2 PinUnitCoord = UV * Two.xy - One.xy; + float PincushionR2 = pow(length(PinUnitCoord), 2.0); + vec2 PincushionCurve = PinUnitCoord * PincushionAmount * PincushionR2; + vec2 BaseCoord = UV; + vec2 ScanCoord = UV; + + BaseCoord *= One.xy - PincushionAmount * 0.2; // Warning: Magic constant + BaseCoord += PincushionAmount * 0.1; + BaseCoord += PincushionCurve; + + ScanCoord *= One.xy - PincushionAmount * 0.2; // Warning: Magic constant + ScanCoord += PincushionAmount * 0.1; + ScanCoord += PincushionCurve; + + vec2 CurveClipUnitCoord = UV * Two.xy - One.xy; + float CurvatureClipR2 = pow(length(CurveClipUnitCoord), 2.0); + vec2 CurvatureClipCurve = CurveClipUnitCoord * CurvatureAmount * CurvatureClipR2; + vec2 ScreenClipCoord = UV; + ScreenClipCoord -= Half.xy; + ScreenClipCoord *= One.xy - CurvatureAmount * 0.2; // Warning: Magic constant + ScreenClipCoord += Half.xy; + ScreenClipCoord += CurvatureClipCurve; + + if (params.pincushion > 0.5){ + // -- Alpha Clipping -- + if (BaseCoord.x < 0.0) return vec4(0.0, 0.0, 0.0, 1.0); + if (BaseCoord.y < 0.0) return vec4(0.0, 0.0, 0.0, 1.0); + if (BaseCoord.x > 1.0) return vec4(0.0, 0.0, 0.0, 1.0); + if (BaseCoord.y > 1.0) return vec4(0.0, 0.0, 0.0, 1.0); + } + + // -- Scanline Simulation -- + float InnerSine = ScanCoord.y * params.OriginalSize.y * ScanlineScale; + float ScanBrightMod = sin(InnerSine * Pi + ScanlineOffset * params.OriginalSize.y); + float ScanBrightness = mix(1.0, (pow(ScanBrightMod * ScanBrightMod, ScanlineHeight) * ScanlineBrightScale + 1.0) * 0.5, params.scandark); + vec3 ScanlineTexel = InTexel.rgb * ScanBrightness; + + // -- Color Compression (increasing the floor of the signal without affecting the ceiling) -- + ScanlineTexel = Floor + (One.xyz - Floor) * ScanlineTexel; + if (params.scanlines > 0.5) return vec4(ScanlineTexel, 1.0); + else return vec4(InTexel); +} + +vec4 SixtyHertz(vec2 UV) +{ + vec4 InPixel = ScanlinePincushion(UV); + float Milliseconds = float(params.FrameCount) * 15.0; + float TimeStep = fract(Milliseconds * SixtyHertzRate); + float BarPosition = 1.0 - fract(UV.y + TimeStep) * SixtyHertzScale; + vec4 OutPixel = InPixel * BarPosition; + if (params.hertzroll > 0.5) return OutPixel; + else return InPixel; +} + +void main() +{ + vec4 OutPixel = SixtyHertz(vTexCoord.xy); + FragColor = OutPixel; +} \ No newline at end of file diff --git a/ntsc/shaders/mame-ntsc/ntsc-mame-pass0.slang b/ntsc/shaders/mame-ntsc/old-ntsc-mame-pass0.slang similarity index 100% rename from ntsc/shaders/mame-ntsc/ntsc-mame-pass0.slang rename to ntsc/shaders/mame-ntsc/old-ntsc-mame-pass0.slang diff --git a/ntsc/shaders/mame-ntsc/ntsc-mame-pass1.slang b/ntsc/shaders/mame-ntsc/old-ntsc-mame-pass1.slang similarity index 100% rename from ntsc/shaders/mame-ntsc/ntsc-mame-pass1.slang rename to ntsc/shaders/mame-ntsc/old-ntsc-mame-pass1.slang diff --git a/ntsc/shaders/mame-ntsc/old-ntsc-mame-singlepass.slang b/ntsc/shaders/mame-ntsc/old-ntsc-mame-singlepass.slang new file mode 100644 index 0000000..e889c5f --- /dev/null +++ b/ntsc/shaders/mame-ntsc/old-ntsc-mame-singlepass.slang @@ -0,0 +1,209 @@ +#version 450 + +// This is a port of the NTSC encode/decode shader pair in MAME and MESS, modified to use only +// one pass rather than an encode pass and a decode pass. It accurately emulates the sort of +// signal decimation one would see when viewing a composite signal, though it could benefit from a +// pre-pass to re-size the input content to more accurately reflect the actual size that would +// be incoming from a composite signal source. +// +// To encode the composite signal, I convert the RGB value to YIQ, then subsequently evaluate +// the standard NTSC composite equation. Four composite samples per RGB pixel are generated from +// the incoming linearly-interpolated texels. +// +// The decode pass implements a Fixed Impulse Response (FIR) filter designed by MAME/MESS contributor +// "austere" in matlab (if memory serves correctly) to mimic the behavior of a standard television set +// as closely as possible. The filter window is 83 composite samples wide, and there is an additional +// notch filter pass on the luminance (Y) values in order to strip the color signal from the luminance +// signal prior to processing. +// +// - UltraMoogleMan [8/2/2013] + +layout(push_constant) uniform Push +{ + vec4 SourceSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; +} params; + +layout(std140, set = 0, binding = 0) uniform UBO +{ + mat4 MVP; +} global; + +// Useful Constants +const vec4 Zero = vec4(0.0); +const vec4 Half = vec4(0.5); +const vec4 One = vec4(1.0); +const vec4 Two = vec4(2.0); +const float Pi = 3.1415926535; +const float Pi2 = 6.283185307; + +// NTSC Constants +const vec4 A = vec4(0.5); +const vec4 B = vec4(0.5); +const float P = 1.0; +const float CCFrequency = 3.59754545; +const float YFrequency = 6.0; +const float IFrequency = 1.2; +const float QFrequency = 0.6; +const float NotchHalfWidth = 2.0; +const float ScanTime = 52.6; +const float MaxC = 2.1183; +const vec4 MinC = vec4(-1.1183); +const vec4 CRange = vec4(3.2366); + +#pragma stage vertex +layout(location = 0) in vec4 Position; +layout(location = 1) in vec2 TexCoord; +layout(location = 0) out vec2 vTexCoord; + +void main() +{ + gl_Position = global.MVP * Position; + vTexCoord = TexCoord; +} + +#pragma stage fragment +layout(location = 0) in vec2 vTexCoord; +layout(location = 0) out vec4 FragColor; +layout(set = 0, binding = 2) uniform sampler2D Source; + +vec4 CompositeSample(vec2 UV) { + vec2 InverseRes = params.SourceSize.zw; + vec2 InverseP = vec2(P, 0.0) * InverseRes; + + // UVs for four linearly-interpolated samples spaced 0.25 texels apart + vec2 C0 = UV; + vec2 C1 = UV + InverseP * 0.25; + vec2 C2 = UV + InverseP * 0.50; + vec2 C3 = UV + InverseP * 0.75; + vec4 Cx = vec4(C0.x, C1.x, C2.x, C3.x); + vec4 Cy = vec4(C0.y, C1.y, C2.y, C3.y); + + vec3 Texel0 = texture(Source, C0).rgb; + vec3 Texel1 = texture(Source, C1).rgb; + vec3 Texel2 = texture(Source, C2).rgb; + vec3 Texel3 = texture(Source, C3).rgb; + + // Calculated the expected time of the sample. + vec4 T = A * Cy * vec4(params.SourceSize.x) * Two + B + Cx; + + const vec3 YTransform = vec3(0.299, 0.587, 0.114); + const vec3 ITransform = vec3(0.595716, -0.274453, -0.321263); + const vec3 QTransform = vec3(0.211456, -0.522591, 0.311135); + + float Y0 = dot(Texel0, YTransform); + float Y1 = dot(Texel1, YTransform); + float Y2 = dot(Texel2, YTransform); + float Y3 = dot(Texel3, YTransform); + vec4 Y = vec4(Y0, Y1, Y2, Y3); + + float I0 = dot(Texel0, ITransform); + float I1 = dot(Texel1, ITransform); + float I2 = dot(Texel2, ITransform); + float I3 = dot(Texel3, ITransform); + vec4 I = vec4(I0, I1, I2, I3); + + float Q0 = dot(Texel0, QTransform); + float Q1 = dot(Texel1, QTransform); + float Q2 = dot(Texel2, QTransform); + float Q3 = dot(Texel3, QTransform); + vec4 Q = vec4(Q0, Q1, Q2, Q3); + + vec4 W = vec4(Pi2 * CCFrequency * ScanTime); + vec4 Encoded = Y + I * cos(T * W) + Q * sin(T * W); + return (Encoded - MinC) / CRange; +} + +vec4 NTSCCodec(vec2 UV) +{ + vec2 InverseRes = params.SourceSize.zw; + vec4 YAccum = Zero; + vec4 IAccum = Zero; + vec4 QAccum = Zero; + float QuadXSize = params.SourceSize.x * 4.0; + float TimePerSample = ScanTime / QuadXSize; + + // Frequency cutoffs for the individual portions of the signal that we extract. + // Y1 and Y2 are the positive and negative frequency limits of the notch filter on Y. + // + float Fc_y1 = (CCFrequency - NotchHalfWidth) * TimePerSample; + float Fc_y2 = (CCFrequency + NotchHalfWidth) * TimePerSample; + float Fc_y3 = YFrequency * TimePerSample; + float Fc_i = IFrequency * TimePerSample; + float Fc_q = QFrequency * TimePerSample; + float Pi2Length = Pi2 / 82.0; + vec4 NotchOffset = vec4(0.0, 1.0, 2.0, 3.0); + vec4 W = vec4(Pi2 * CCFrequency * ScanTime); + for(float n = -41.0; n < 42.0; n += 4.0) + { + vec4 n4 = n + NotchOffset; + vec4 CoordX = UV.x + InverseRes.x * n4 * 0.25; + vec4 CoordY = vec4(UV.y); + vec2 TexCoord = vec2(CoordX.r, CoordY.r); + vec4 C = CompositeSample(TexCoord) * CRange + MinC; + vec4 WT = W * (CoordX + A * CoordY * Two * params.SourceSize.x + B); + + vec4 SincYIn1 = Pi2 * Fc_y1 * n4; + vec4 SincYIn2 = Pi2 * Fc_y2 * n4; + vec4 SincYIn3 = Pi2 * Fc_y3 * n4; + bvec4 notEqual = notEqual(SincYIn1, Zero); + vec4 SincY1 = sin(SincYIn1) / SincYIn1; + vec4 SincY2 = sin(SincYIn2) / SincYIn2; + vec4 SincY3 = sin(SincYIn3) / SincYIn3; + if(SincYIn1.x == 0.0) SincY1.x = 1.0; + if(SincYIn1.y == 0.0) SincY1.y = 1.0; + if(SincYIn1.z == 0.0) SincY1.z = 1.0; + if(SincYIn1.w == 0.0) SincY1.w = 1.0; + if(SincYIn2.x == 0.0) SincY2.x = 1.0; + if(SincYIn2.y == 0.0) SincY2.y = 1.0; + if(SincYIn2.z == 0.0) SincY2.z = 1.0; + if(SincYIn2.w == 0.0) SincY2.w = 1.0; + if(SincYIn3.x == 0.0) SincY3.x = 1.0; + if(SincYIn3.y == 0.0) SincY3.y = 1.0; + if(SincYIn3.z == 0.0) SincY3.z = 1.0; + if(SincYIn3.w == 0.0) SincY3.w = 1.0; + //vec4 IdealY = (2.0 * Fc_y1 * SincY1 - 2.0 * Fc_y2 * SincY2) + 2.0 * Fc_y3 * SincY3; + vec4 IdealY = (2.0 * Fc_y1 * SincY1 - 2.0 * Fc_y2 * SincY2) + 2.0 * Fc_y3 * SincY3; + vec4 FilterY = (0.54 + 0.46 * cos(Pi2Length * n4)) * IdealY; + + vec4 SincIIn = Pi2 * Fc_i * n4; + vec4 SincI = sin(SincIIn) / SincIIn; + if (SincIIn.x == 0.0) SincI.x = 1.0; + if (SincIIn.y == 0.0) SincI.y = 1.0; + if (SincIIn.z == 0.0) SincI.z = 1.0; + if (SincIIn.w == 0.0) SincI.w = 1.0; + vec4 IdealI = 2.0 * Fc_i * SincI; + vec4 FilterI = (0.54 + 0.46 * cos(Pi2Length * n4)) * IdealI; + + vec4 SincQIn = Pi2 * Fc_q * n4; + vec4 SincQ = sin(SincQIn) / SincQIn; + if (SincQIn.x == 0.0) SincQ.x = 1.0; + if (SincQIn.y == 0.0) SincQ.y = 1.0; + if (SincQIn.z == 0.0) SincQ.z = 1.0; + if (SincQIn.w == 0.0) SincQ.w = 1.0; + vec4 IdealQ = 2.0 * Fc_q * SincQ; + vec4 FilterQ = (0.54 + 0.46 * cos(Pi2Length * n4)) * IdealQ; + + YAccum = YAccum + C * FilterY; + IAccum = IAccum + C * cos(WT) * FilterI; + QAccum = QAccum + C * sin(WT) * FilterQ; + } + + float Y = YAccum.r + YAccum.g + YAccum.b + YAccum.a; + float I = (IAccum.r + IAccum.g + IAccum.b + IAccum.a) * 2.0; + float Q = (QAccum.r + QAccum.g + QAccum.b + QAccum.a) * 2.0; + + vec3 YIQ = vec3(Y, I, Q); + + vec3 OutRGB = vec3(dot(YIQ, vec3(1.0, 0.956, 0.621)), dot(YIQ, vec3(1.0, -0.272, -0.647)), dot(YIQ, vec3(1.0, -1.106, 1.703))); + + return vec4(OutRGB, 1.0); +} + +void main() +{ + vec4 OutPixel = NTSCCodec(vTexCoord); + FragColor = vec4(OutPixel); +} \ No newline at end of file