#version 450 // license:BSD-3-Clause // copyright-holders:Ryan Holtz,ImJezze // Adapted from mame_ntsc_decode.slang for SDL3 GPU / Vulkan SPIRV layout(location=0) in vec2 v_uv; layout(location=0) out vec4 FragColor; layout(set=2, binding=0) uniform sampler2D Source; layout(set=3, binding=0) uniform NTSCParams { float source_width; float source_height; float a_value; float b_value; float cc_value; float scan_time; float notch_width; float y_freq; float i_freq; float q_freq; float _pad0; float _pad1; } u; const float PI = 3.1415927; const float PI2 = PI * 2.0; const vec3 RDot = vec3(1.0, 0.956, 0.621); const vec3 GDot = vec3(1.0, -0.272, -0.647); const vec3 BDot = vec3(1.0, -1.106, 1.703); const vec4 NotchOffset = vec4(0.0, 1.0, 2.0, 3.0); const int SampleCount = 64; const int HalfSampleCount = 32; void main() { vec2 source_dims = vec2(u.source_width, u.source_height); vec4 BaseTexel = texture(Source, v_uv); float CCValue = u.cc_value; float ScanTime = u.scan_time; float NotchHalfWidth = u.notch_width / 2.0; float YFreqResponse = u.y_freq; float IFreqResponse = u.i_freq; float QFreqResponse = u.q_freq; float AValue = u.a_value; float BValue = u.b_value; float TimePerSample = ScanTime / (source_dims.x * 4.0); float Fc_y1 = (CCValue - NotchHalfWidth) * TimePerSample; float Fc_y2 = (CCValue + NotchHalfWidth) * TimePerSample; float Fc_y3 = YFreqResponse * TimePerSample; float Fc_i = IFreqResponse * TimePerSample; float Fc_q = QFreqResponse * TimePerSample; float Fc_i_2 = Fc_i * 2.0; float Fc_q_2 = Fc_q * 2.0; 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_pi2 = Fc_i * PI2; float Fc_q_pi2 = Fc_q * PI2; float Fc_y1_pi2 = Fc_y1 * PI2; float Fc_y2_pi2 = Fc_y2 * PI2; float Fc_y3_pi2 = Fc_y3 * PI2; float PI2Length = PI2 / float(SampleCount); float W = PI2 * CCValue * ScanTime; float WoPI = W / PI; float HOffset = BValue / WoPI; float VScale = AValue * source_dims.y / WoPI; vec4 YAccum = vec4(0.0); vec4 IAccum = vec4(0.0); vec4 QAccum = vec4(0.0); vec4 Cy = vec4(v_uv.y); vec4 VPosition = Cy; for (float i = 0.0; i < float(SampleCount); i += 4.0) { float n = i - float(HalfSampleCount); vec4 n4 = n + NotchOffset; vec4 Cx = vec4(v_uv.x) + (n4 * 0.25) / source_dims.x; vec4 HPosition = Cx; vec4 C = texture(Source, vec2(Cx.r, Cy.r)); vec4 T = HPosition + vec4(HOffset) + VPosition * vec4(VScale); vec4 WT = W * T; vec4 SincKernel = 0.54 + 0.46 * cos(PI2Length * n4); vec4 SincYIn1 = Fc_y1_pi2 * n4; vec4 SincYIn2 = Fc_y2_pi2 * n4; vec4 SincYIn3 = Fc_y3_pi2 * n4; vec4 SincIIn = Fc_i_pi2 * n4; vec4 SincQIn = Fc_q_pi2 * n4; vec4 SincY1, SincY2, SincY3; SincY1.x = (SincYIn1.x != 0.0) ? sin(SincYIn1.x) / SincYIn1.x : 1.0; SincY1.y = (SincYIn1.y != 0.0) ? sin(SincYIn1.y) / SincYIn1.y : 1.0; SincY1.z = (SincYIn1.z != 0.0) ? sin(SincYIn1.z) / SincYIn1.z : 1.0; SincY1.w = (SincYIn1.w != 0.0) ? sin(SincYIn1.w) / SincYIn1.w : 1.0; SincY2.x = (SincYIn2.x != 0.0) ? sin(SincYIn2.x) / SincYIn2.x : 1.0; SincY2.y = (SincYIn2.y != 0.0) ? sin(SincYIn2.y) / SincYIn2.y : 1.0; SincY2.z = (SincYIn2.z != 0.0) ? sin(SincYIn2.z) / SincYIn2.z : 1.0; SincY2.w = (SincYIn2.w != 0.0) ? sin(SincYIn2.w) / SincYIn2.w : 1.0; SincY3.x = (SincYIn3.x != 0.0) ? sin(SincYIn3.x) / SincYIn3.x : 1.0; SincY3.y = (SincYIn3.y != 0.0) ? sin(SincYIn3.y) / SincYIn3.y : 1.0; SincY3.z = (SincYIn3.z != 0.0) ? sin(SincYIn3.z) / SincYIn3.z : 1.0; SincY3.w = (SincYIn3.w != 0.0) ? sin(SincYIn3.w) / SincYIn3.w : 1.0; vec4 IdealY = Fc_y1_2 * SincY1 - Fc_y2_2 * SincY2 + Fc_y3_2 * SincY3; vec4 IdealI, IdealQ; IdealI.x = Fc_i_2 * ((SincIIn.x != 0.0) ? sin(SincIIn.x) / SincIIn.x : 1.0); IdealI.y = Fc_i_2 * ((SincIIn.y != 0.0) ? sin(SincIIn.y) / SincIIn.y : 1.0); IdealI.z = Fc_i_2 * ((SincIIn.z != 0.0) ? sin(SincIIn.z) / SincIIn.z : 1.0); IdealI.w = Fc_i_2 * ((SincIIn.w != 0.0) ? sin(SincIIn.w) / SincIIn.w : 1.0); IdealQ.x = Fc_q_2 * ((SincQIn.x != 0.0) ? sin(SincQIn.x) / SincQIn.x : 1.0); IdealQ.y = Fc_q_2 * ((SincQIn.y != 0.0) ? sin(SincQIn.y) / SincQIn.y : 1.0); IdealQ.z = Fc_q_2 * ((SincQIn.z != 0.0) ? sin(SincQIn.z) / SincQIn.z : 1.0); IdealQ.w = Fc_q_2 * ((SincQIn.w != 0.0) ? sin(SincQIn.w) / SincQIn.w : 1.0); vec4 FilterY = SincKernel * IdealY; vec4 FilterI = SincKernel * IdealI; vec4 FilterQ = SincKernel * IdealQ; YAccum += C * FilterY; IAccum += C * cos(WT) * FilterI; QAccum += C * sin(WT) * FilterQ; } vec3 YIQ = vec3( (YAccum.r + YAccum.g + YAccum.b + YAccum.a), (IAccum.r + IAccum.g + IAccum.b + IAccum.a) * 2.0, (QAccum.r + QAccum.g + QAccum.b + QAccum.a) * 2.0); vec3 RGB = vec3( dot(YIQ, RDot), dot(YIQ, GDot), dot(YIQ, BDot)); FragColor = vec4(RGB, BaseTexel.a); }