1#version 450 2 3// This is a port of the NTSC encode/decode shader pair in MAME and MESS, modified to use only 4// one pass rather than an encode pass and a decode pass. It accurately emulates the sort of 5// signal decimation one would see when viewing a composite signal, though it could benefit from a 6// pre-pass to re-size the input content to more accurately reflect the actual size that would 7// be incoming from a composite signal source. 8// 9// To encode the composite signal, I convert the RGB value to YIQ, then subsequently evaluate 10// the standard NTSC composite equation. Four composite samples per RGB pixel are generated from 11// the incoming linearly-interpolated texels. 12// 13// The decode pass implements a Fixed Impulse Response (FIR) filter designed by MAME/MESS contributor 14// "austere" in matlab (if memory serves correctly) to mimic the behavior of a standard television set 15// as closely as possible. The filter window is 83 composite samples wide, and there is an additional 16// notch filter pass on the luminance (Y) values in order to strip the color signal from the luminance 17// signal prior to processing. 18// 19// - UltraMoogleMan [8/2/2013] 20 21layout(push_constant) uniform Push 22{ 23 vec4 SourceSize; 24 vec4 OriginalSize; 25 vec4 OutputSize; 26 uint FrameCount; 27} params; 28 29layout(std140, set = 0, binding = 0) uniform UBO 30{ 31 mat4 MVP; 32} global; 33 34// Useful Constants 35const vec4 Zero = vec4(0.0); 36const vec4 Half = vec4(0.5); 37const vec4 One = vec4(1.0); 38const vec4 Two = vec4(2.0); 39const float Pi = 3.1415926535; 40const float Pi2 = 6.283185307; 41 42// NTSC Constants 43const vec4 A = vec4(0.5); 44const vec4 B = vec4(0.5); 45const float P = 1.0; 46const float CCFrequency = 3.59754545; 47const float YFrequency = 6.0; 48const float IFrequency = 1.2; 49const float QFrequency = 0.6; 50const float NotchHalfWidth = 2.0; 51const float ScanTime = 52.6; 52const float MaxC = 2.1183; 53const vec4 MinC = vec4(-1.1183); 54const vec4 CRange = vec4(3.2366); 55 56#pragma stage vertex 57layout(location = 0) in vec4 Position; 58layout(location = 1) in vec2 TexCoord; 59layout(location = 0) out vec2 vTexCoord; 60 61void main() 62{ 63 gl_Position = global.MVP * Position; 64 vTexCoord = TexCoord; 65} 66 67#pragma stage fragment 68layout(location = 0) in vec2 vTexCoord; 69layout(location = 0) out vec4 FragColor; 70layout(set = 0, binding = 2) uniform sampler2D Source; 71 72vec4 CompositeSample(vec2 UV) { 73 vec2 InverseRes = params.SourceSize.zw; 74 vec2 InverseP = vec2(P, 0.0) * InverseRes; 75 76 // UVs for four linearly-interpolated samples spaced 0.25 texels apart 77 vec2 C0 = UV; 78 vec2 C1 = UV + InverseP * 0.25; 79 vec2 C2 = UV + InverseP * 0.50; 80 vec2 C3 = UV + InverseP * 0.75; 81 vec4 Cx = vec4(C0.x, C1.x, C2.x, C3.x); 82 vec4 Cy = vec4(C0.y, C1.y, C2.y, C3.y); 83 84 vec3 Texel0 = texture(Source, C0).rgb; 85 vec3 Texel1 = texture(Source, C1).rgb; 86 vec3 Texel2 = texture(Source, C2).rgb; 87 vec3 Texel3 = texture(Source, C3).rgb; 88 89 // Calculated the expected time of the sample. 90 vec4 T = A * Cy * vec4(params.SourceSize.x) * Two + B + Cx; 91 92 const vec3 YTransform = vec3(0.299, 0.587, 0.114); 93 const vec3 ITransform = vec3(0.595716, -0.274453, -0.321263); 94 const vec3 QTransform = vec3(0.211456, -0.522591, 0.311135); 95 96 float Y0 = dot(Texel0, YTransform); 97 float Y1 = dot(Texel1, YTransform); 98 float Y2 = dot(Texel2, YTransform); 99 float Y3 = dot(Texel3, YTransform); 100 vec4 Y = vec4(Y0, Y1, Y2, Y3); 101 102 float I0 = dot(Texel0, ITransform); 103 float I1 = dot(Texel1, ITransform); 104 float I2 = dot(Texel2, ITransform); 105 float I3 = dot(Texel3, ITransform); 106 vec4 I = vec4(I0, I1, I2, I3); 107 108 float Q0 = dot(Texel0, QTransform); 109 float Q1 = dot(Texel1, QTransform); 110 float Q2 = dot(Texel2, QTransform); 111 float Q3 = dot(Texel3, QTransform); 112 vec4 Q = vec4(Q0, Q1, Q2, Q3); 113 114 vec4 W = vec4(Pi2 * CCFrequency * ScanTime); 115 vec4 Encoded = Y + I * cos(T * W) + Q * sin(T * W); 116 return (Encoded - MinC) / CRange; 117} 118 119vec4 NTSCCodec(vec2 UV) 120{ 121 vec2 InverseRes = params.SourceSize.zw; 122 vec4 YAccum = Zero; 123 vec4 IAccum = Zero; 124 vec4 QAccum = Zero; 125 float QuadXSize = params.SourceSize.x * 4.0; 126 float TimePerSample = ScanTime / QuadXSize; 127 128 // Frequency cutoffs for the individual portions of the signal that we extract. 129 // Y1 and Y2 are the positive and negative frequency limits of the notch filter on Y. 130 // 131 float Fc_y1 = (CCFrequency - NotchHalfWidth) * TimePerSample; 132 float Fc_y2 = (CCFrequency + NotchHalfWidth) * TimePerSample; 133 float Fc_y3 = YFrequency * TimePerSample; 134 float Fc_i = IFrequency * TimePerSample; 135 float Fc_q = QFrequency * TimePerSample; 136 float Pi2Length = Pi2 / 82.0; 137 vec4 NotchOffset = vec4(0.0, 1.0, 2.0, 3.0); 138 vec4 W = vec4(Pi2 * CCFrequency * ScanTime); 139 for(float n = -41.0; n < 42.0; n += 4.0) 140 { 141 vec4 n4 = n + NotchOffset; 142 vec4 CoordX = UV.x + InverseRes.x * n4 * 0.25; 143 vec4 CoordY = vec4(UV.y); 144 vec2 TexCoord = vec2(CoordX.r, CoordY.r); 145 vec4 C = CompositeSample(TexCoord) * CRange + MinC; 146 vec4 WT = W * (CoordX + A * CoordY * Two * params.SourceSize.x + B); 147 148 vec4 SincYIn1 = Pi2 * Fc_y1 * n4; 149 vec4 SincYIn2 = Pi2 * Fc_y2 * n4; 150 vec4 SincYIn3 = Pi2 * Fc_y3 * n4; 151 bvec4 notEqual = notEqual(SincYIn1, Zero); 152 vec4 SincY1 = sin(SincYIn1) / SincYIn1; 153 vec4 SincY2 = sin(SincYIn2) / SincYIn2; 154 vec4 SincY3 = sin(SincYIn3) / SincYIn3; 155 if(SincYIn1.x == 0.0) SincY1.x = 1.0; 156 if(SincYIn1.y == 0.0) SincY1.y = 1.0; 157 if(SincYIn1.z == 0.0) SincY1.z = 1.0; 158 if(SincYIn1.w == 0.0) SincY1.w = 1.0; 159 if(SincYIn2.x == 0.0) SincY2.x = 1.0; 160 if(SincYIn2.y == 0.0) SincY2.y = 1.0; 161 if(SincYIn2.z == 0.0) SincY2.z = 1.0; 162 if(SincYIn2.w == 0.0) SincY2.w = 1.0; 163 if(SincYIn3.x == 0.0) SincY3.x = 1.0; 164 if(SincYIn3.y == 0.0) SincY3.y = 1.0; 165 if(SincYIn3.z == 0.0) SincY3.z = 1.0; 166 if(SincYIn3.w == 0.0) SincY3.w = 1.0; 167 //vec4 IdealY = (2.0 * Fc_y1 * SincY1 - 2.0 * Fc_y2 * SincY2) + 2.0 * Fc_y3 * SincY3; 168 vec4 IdealY = (2.0 * Fc_y1 * SincY1 - 2.0 * Fc_y2 * SincY2) + 2.0 * Fc_y3 * SincY3; 169 vec4 FilterY = (0.54 + 0.46 * cos(Pi2Length * n4)) * IdealY; 170 171 vec4 SincIIn = Pi2 * Fc_i * n4; 172 vec4 SincI = sin(SincIIn) / SincIIn; 173 if (SincIIn.x == 0.0) SincI.x = 1.0; 174 if (SincIIn.y == 0.0) SincI.y = 1.0; 175 if (SincIIn.z == 0.0) SincI.z = 1.0; 176 if (SincIIn.w == 0.0) SincI.w = 1.0; 177 vec4 IdealI = 2.0 * Fc_i * SincI; 178 vec4 FilterI = (0.54 + 0.46 * cos(Pi2Length * n4)) * IdealI; 179 180 vec4 SincQIn = Pi2 * Fc_q * n4; 181 vec4 SincQ = sin(SincQIn) / SincQIn; 182 if (SincQIn.x == 0.0) SincQ.x = 1.0; 183 if (SincQIn.y == 0.0) SincQ.y = 1.0; 184 if (SincQIn.z == 0.0) SincQ.z = 1.0; 185 if (SincQIn.w == 0.0) SincQ.w = 1.0; 186 vec4 IdealQ = 2.0 * Fc_q * SincQ; 187 vec4 FilterQ = (0.54 + 0.46 * cos(Pi2Length * n4)) * IdealQ; 188 189 YAccum = YAccum + C * FilterY; 190 IAccum = IAccum + C * cos(WT) * FilterI; 191 QAccum = QAccum + C * sin(WT) * FilterQ; 192 } 193 194 float Y = YAccum.r + YAccum.g + YAccum.b + YAccum.a; 195 float I = (IAccum.r + IAccum.g + IAccum.b + IAccum.a) * 2.0; 196 float Q = (QAccum.r + QAccum.g + QAccum.b + QAccum.a) * 2.0; 197 198 vec3 YIQ = vec3(Y, I, Q); 199 200 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))); 201 202 return vec4(OutRGB, 1.0); 203} 204 205void main() 206{ 207 vec4 OutPixel = NTSCCodec(vTexCoord); 208 FragColor = vec4(OutPixel); 209}