mame-ntsc.fs 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. #version 150
  2. // This is a port of the NTSC encode/decode shader pair in MAME and MESS, modified to use only
  3. // one pass rather than an encode pass and a decode pass. It accurately emulates the sort of
  4. // signal decimation one would see when viewing a composite signal, though it could benefit from a
  5. // pre-pass to re-size the input content to more accurately reflect the actual size that would
  6. // be incoming from a composite signal source.
  7. //
  8. // To encode the composite signal, I convert the RGB value to YIQ, then subsequently evaluate
  9. // the standard NTSC composite equation. Four composite samples per RGB pixel are generated from
  10. // the incoming linearly-interpolated texels.
  11. //
  12. // The decode pass implements a Fixed Impulse Response (FIR) filter designed by MAME/MESS contributor
  13. // "austere" in matlab (if memory serves correctly) to mimic the behavior of a standard television set
  14. // as closely as possible. The filter window is 83 composite samples wide, and there is an additional
  15. // notch filter pass on the luminance (Y) values in order to strip the color signal from the luminance
  16. // signal prior to processing.
  17. //
  18. // Yes, this code could greatly use some cleaning up.
  19. // ported from UltraMoogleMan's "Full MAME/MESS Shader Pipe" shadertoy: https://www.shadertoy.com/view/ldf3Rf
  20. // license: presumably MAME's license at the time, which was noncommercial
  21. uniform sampler2D source[];
  22. uniform vec4 sourceSize[];
  23. in Vertex {
  24. vec2 vTexCoord;
  25. };
  26. out vec4 FragColor;
  27. // Useful Constants
  28. const vec4 Zero = vec4(0.0);
  29. const vec4 Half = vec4(0.5);
  30. const vec4 One = vec4(1.0);
  31. const vec4 Two = vec4(2.0);
  32. const vec3 Gray = vec3(0.3, 0.59, 0.11);
  33. const float Pi = 3.1415926535;
  34. const float Pi2 = 6.283185307;
  35. // NTSC Constants
  36. const vec4 A = vec4(0.5);
  37. const vec4 A2 = vec4(1.0);
  38. const vec4 B = vec4(0.5);
  39. const float P = 1.0;
  40. const float CCFrequency = 3.59754545;
  41. const float NotchUpperFrequency = 5.59754545; //3.59754545 + 2.0;
  42. const float NotchLowerFrequency = 1.59754545; //3.59754545 - 2.0;
  43. const float YFrequency = 6.0;
  44. const float IFrequency = 1.2;
  45. const float QFrequency = 0.6;
  46. const float NotchHalfWidth = 2.0;
  47. const float ScanTime = 52.6;
  48. const float Pi2ScanTime = 330.4955471482;// 6.283185307 * 52.6;
  49. const float MaxC = 2.1183;
  50. const vec4 YTransform = vec4(0.299, 0.587, 0.114, 0.0);
  51. const vec4 ITransform = vec4(0.595716, -0.274453, -0.321263, 0.0);
  52. const vec4 QTransform = vec4(0.211456, -0.522591, 0.311135, 0.0);
  53. const vec3 YIQ2R = vec3(1.0, 0.956, 0.621);
  54. const vec3 YIQ2G = vec3(1.0, -0.272, -0.647);
  55. const vec3 YIQ2B = vec3(1.0, -1.106, 1.703);
  56. const vec4 MinC = vec4(-1.1183);
  57. const vec4 CRange = vec4(3.2366);
  58. const vec4 InvCRange = vec4(1.0/3.2366);
  59. const float Pi2Length = Pi2 / 63.0;
  60. const vec4 NotchOffset = vec4(0.0, 1.0, 2.0, 3.0);
  61. vec4 W = vec4(Pi2 * CCFrequency * ScanTime);
  62. // Color Convolution Constants
  63. const vec3 RedMatrix = vec3(1.0, 0.0, 0.0);
  64. const vec3 GrnMatrix = vec3(0.0, 1.0, 0.0);
  65. const vec3 BluMatrix = vec3(0.0, 0.0, 1.0);
  66. const vec3 DCOffset = vec3(0.0, 0.0, 0.0);
  67. const vec3 ColorScale = vec3(0.95, 0.95, 0.95);
  68. const float Saturation = 1.4;
  69. // Deconverge Constants
  70. const vec3 ConvergeX = vec3(-0.4, 0.0, 0.2);
  71. const vec3 ConvergeY = vec3( 0.0, -0.4, 0.2);
  72. const vec3 RadialConvergeX = vec3(1.0, 1.0, 1.0);
  73. const vec3 RadialConvergeY = vec3(1.0, 1.0, 1.0);
  74. // Scanline/Pincushion Constants
  75. const float PincushionAmount = 0.015;
  76. const float CurvatureAmount = 0.015;
  77. //const float ScanlineAmount = 0.175; <- move to parameter
  78. const float ScanlineScale = 1.0;
  79. const float ScanlineHeight = 1.0;
  80. const float ScanlineBrightScale = 1.0;
  81. const float ScanlineBrightOffset = 0.0;
  82. const float ScanlineOffset = 0.0;
  83. const vec3 Floor = vec3(0.05, 0.05, 0.05);
  84. // 60Hz Bar Constants
  85. const float SixtyHertzRate = (60.0 / 59.97 - 1.0); // Difference between NTSC and line frequency
  86. const float SixtyHertzScale = 0.1;
  87. vec4 CompositeSample(vec2 UV, vec2 InverseRes) {
  88. vec2 InverseP = vec2(P, 0.0) * InverseRes;
  89. // UVs for four linearly-interpolated samples spaced 0.25 texels apart
  90. vec2 C0 = UV;
  91. vec2 C1 = UV + InverseP * 0.25;
  92. vec2 C2 = UV + InverseP * 0.50;
  93. vec2 C3 = UV + InverseP * 0.75;
  94. vec4 Cx = vec4(C0.x, C1.x, C2.x, C3.x);
  95. vec4 Cy = vec4(C0.y, C1.y, C2.y, C3.y);
  96. vec4 Texel0 = texture(source[0], C0);
  97. vec4 Texel1 = texture(source[0], C1);
  98. vec4 Texel2 = texture(source[0], C2);
  99. vec4 Texel3 = texture(source[0], C3);
  100. float Frequency = CCFrequency;
  101. //Frequency = Frequency;// Uncomment for bad color sync + (sin(UV.y * 2.0 - 1.0) / CCFrequency) * 0.001;
  102. // Calculated the expected time of the sample.
  103. vec4 T = A2 * Cy * vec4(sourceSize[0].y) + B + Cx;
  104. vec4 W = vec4(Pi2ScanTime * Frequency);
  105. vec4 TW = T * W;
  106. vec4 Y = vec4(dot(Texel0, YTransform), dot(Texel1, YTransform), dot(Texel2, YTransform), dot(Texel3, YTransform));
  107. vec4 I = vec4(dot(Texel0, ITransform), dot(Texel1, ITransform), dot(Texel2, ITransform), dot(Texel3, ITransform));
  108. vec4 Q = vec4(dot(Texel0, QTransform), dot(Texel1, QTransform), dot(Texel2, QTransform), dot(Texel3, QTransform));
  109. vec4 Encoded = Y + I * cos(TW) + Q * sin(TW);
  110. return (Encoded - MinC) * InvCRange;
  111. }
  112. vec4 NTSCCodec(vec2 UV, vec2 InverseRes)
  113. {
  114. vec4 YAccum = Zero;
  115. vec4 IAccum = Zero;
  116. vec4 QAccum = Zero;
  117. float QuadXSize = sourceSize[0].x * 4.0;
  118. float TimePerSample = ScanTime / QuadXSize;
  119. // Frequency cutoffs for the individual portions of the signal that we extract.
  120. // Y1 and Y2 are the positive and negative frequency limits of the notch filter on Y.
  121. // Y3 is the center of the frequency response of the Y filter.
  122. // I is the center of the frequency response of the I filter.
  123. // Q is the center of the frequency response of the Q filter.
  124. float Fc_y1 = NotchLowerFrequency * TimePerSample;
  125. float Fc_y2 = NotchUpperFrequency * TimePerSample;
  126. float Fc_y3 = YFrequency * TimePerSample;
  127. float Fc_i = IFrequency * TimePerSample;
  128. float Fc_q = QFrequency * TimePerSample;
  129. float Pi2Fc_y1 = Fc_y1 * Pi2;
  130. float Pi2Fc_y2 = Fc_y2 * Pi2;
  131. float Pi2Fc_y3 = Fc_y3 * Pi2;
  132. float Pi2Fc_i = Fc_i * Pi2;
  133. float Pi2Fc_q = Fc_q * Pi2;
  134. float Fc_y1_2 = Fc_y1 * 2.0;
  135. float Fc_y2_2 = Fc_y2 * 2.0;
  136. float Fc_y3_2 = Fc_y3 * 2.0;
  137. float Fc_i_2 = Fc_i * 2.0;
  138. float Fc_q_2 = Fc_q * 2.0;
  139. vec4 CoordY = vec4(UV.y);
  140. // 83 composite samples wide, 4 composite pixels per texel
  141. for(float n = -31.0; n < 32.0; n += 4.0)
  142. {
  143. vec4 n4 = n + NotchOffset;
  144. vec4 CoordX = UV.x + InverseRes.x * n4 * 0.25;
  145. vec2 TexCoord = vec2(CoordX.x, CoordY.x);
  146. vec4 C = CompositeSample(TexCoord, InverseRes) * CRange + MinC;
  147. vec4 WT = W * (CoordX + A2 * CoordY * sourceSize[0].y + B);
  148. vec4 Cosine = 0.54 + 0.46 * cos(Pi2Length * n4);
  149. vec4 SincYIn1 = Pi2Fc_y1 * n4;
  150. vec4 SincYIn2 = Pi2Fc_y2 * n4;
  151. vec4 SincYIn3 = Pi2Fc_y3 * n4;
  152. vec4 SincY1 = sin(SincYIn1) / SincYIn1;
  153. vec4 SincY2 = sin(SincYIn2) / SincYIn2;
  154. vec4 SincY3 = sin(SincYIn3) / SincYIn3;
  155. // These zero-checks could be made more efficient if WebGL supported mix(vec4, vec4, bvec4)
  156. // Unfortunately, the universe hates us
  157. if(SincYIn1.x == 0.0) SincY1.x = 1.0;
  158. if(SincYIn1.y == 0.0) SincY1.y = 1.0;
  159. if(SincYIn1.z == 0.0) SincY1.z = 1.0;
  160. if(SincYIn1.w == 0.0) SincY1.w = 1.0;
  161. if(SincYIn2.x == 0.0) SincY2.x = 1.0;
  162. if(SincYIn2.y == 0.0) SincY2.y = 1.0;
  163. if(SincYIn2.z == 0.0) SincY2.z = 1.0;
  164. if(SincYIn2.w == 0.0) SincY2.w = 1.0;
  165. if(SincYIn3.x == 0.0) SincY3.x = 1.0;
  166. if(SincYIn3.y == 0.0) SincY3.y = 1.0;
  167. if(SincYIn3.z == 0.0) SincY3.z = 1.0;
  168. if(SincYIn3.w == 0.0) SincY3.w = 1.0;
  169. vec4 IdealY = (Fc_y1_2 * SincY1 - Fc_y2_2 * SincY2) + Fc_y3_2 * SincY3;
  170. vec4 FilterY = Cosine * IdealY;
  171. vec4 SincIIn = Pi2Fc_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 = Fc_i_2 * SincI;
  178. vec4 FilterI = Cosine * IdealI;
  179. vec4 SincQIn = Pi2Fc_q * n4;
  180. vec4 SincQ = sin(SincQIn) / SincQIn;
  181. if (SincQIn.x == 0.0) SincQ.x = 1.0;
  182. if (SincQIn.y == 0.0) SincQ.y = 1.0;
  183. if (SincQIn.z == 0.0) SincQ.z = 1.0;
  184. if (SincQIn.w == 0.0) SincQ.w = 1.0;
  185. vec4 IdealQ = Fc_q_2 * SincQ;
  186. vec4 FilterQ = Cosine * IdealQ;
  187. YAccum += C * FilterY;
  188. IAccum += C * cos(WT) * FilterI;
  189. QAccum += C * sin(WT) * FilterQ;
  190. }
  191. float Y = dot(YAccum, One);
  192. float I = dot(IAccum, One) * 2.0;
  193. float Q = dot(QAccum, One) * 2.0;
  194. vec3 YIQ = vec3(Y, I, Q);
  195. vec3 OutRGB = vec3(dot(YIQ, YIQ2R), dot(YIQ, YIQ2G), dot(YIQ, YIQ2B));
  196. return vec4(OutRGB, 1.0);
  197. }
  198. void main() {
  199. FragColor = vec4(NTSCCodec(vTexCoord, sourceSize[0].zw));
  200. }