1$input v_sinangle, v_cosangle, v_stretch, v_one, v_texCoord
2
3/*  CRT shader
4 *
5 *  Copyright (C) 2010-2016 cgwg, Themaister and DOLLS
6 *
7 *  This program is free software; you can redistribute it and/or modify it
8 *  under the terms of the GNU General Public License as published by the Free
9 *  Software Foundation; either version 2 of the License, or (at your option)
10 *  any later version.
11 */
12
13#include "common.sh"
14
15// Comment the next line to disable interpolation in linear gamma (and gain speed).
16//#define LINEAR_PROCESSING
17
18// Enable 3x oversampling of the beam profile
19#define OVERSAMPLE
20
21// Use the older, purely gaussian beam profile
22#define USEGAUSSIAN
23
24// Macros.
25#define FIX(c) max(abs(c), 1e-5)
26#define PI 3.141592653589
27
28SAMPLER2D(mpass_texture, 0);
29SAMPLER2D(mask_texture, 1);
30#ifdef LINEAR_PROCESSING
31#       define TEX2D(c) pow(texture2D(mpass_texture, (c)), vec4(CRTgamma))
32#else
33#       define TEX2D(c) texture2D(mpass_texture, (c))
34#endif
35
36// Enable screen curvature.
37uniform vec4 curvature;
38
39uniform vec4 u_tex_size0;
40uniform vec4 u_tex_size1;
41uniform vec4 u_quad_dims;
42
43uniform vec4 aperture_strength;
44
45uniform vec4 CRTgamma;
46uniform vec4 monitorgamma;
47
48uniform vec4 overscan;
49uniform vec4 aspect;
50
51uniform vec4 d;
52uniform vec4 R;
53
54uniform vec4 cornersize;
55uniform vec4 cornersmooth;
56
57float intersect(vec2 xy , vec2 sinangle, vec2 cosangle)
58{
59  float A = dot(xy,xy)+d.x*d.x;
60  float B = 2.0*(R.x*(dot(xy,sinangle)-d.x*cosangle.x*cosangle.y)-d.x*d.x);
61  float C = d.x*d.x + 2.0*R.x*d.x*cosangle.x*cosangle.y;
62  return (-B-sqrt(B*B-4.0*A*C))/(2.0*A);
63}
64
65vec2 bkwtrans(vec2 xy, vec2 sinangle, vec2 cosangle)
66{
67  float c = intersect(xy, sinangle, cosangle);
68  vec2 pt = vec2_splat(c)*xy;
69  pt -= vec2_splat(-R.x)*sinangle;
70  pt /= vec2_splat(R.x);
71  vec2 tang = sinangle/cosangle;
72  vec2 poc = pt/cosangle;
73  float A = dot(tang,tang)+1.0;
74  float B = -2.0*dot(poc,tang);
75  float C = dot(poc,poc)-1.0;
76  float a = (-B+sqrt(B*B-4.0*A*C))/(2.0*A);
77  vec2 uv = (pt-a*sinangle)/cosangle;
78  float r = FIX(R.x*acos(a));
79  return uv*r/sin(r/R.x);
80}
81
82vec2 transform(vec2 coord, vec3 stretch, vec2 sinangle, vec2 cosangle)
83{
84  coord = (coord-vec2_splat(0.5))*aspect.xy*stretch.z+stretch.xy;
85  return (bkwtrans(coord, sinangle, cosangle)/overscan.xy/aspect.xy+vec2_splat(0.5));
86}
87
88float corner(vec2 coord)
89{
90  coord = (coord - vec2_splat(0.5)) * overscan.xy + vec2_splat(0.5);
91  coord = min(coord, vec2_splat(1.0)-coord) * aspect.xy;
92  vec2 cdist = vec2_splat(cornersize.x);
93  coord = (cdist - min(coord,cdist));
94  float dist = sqrt(dot(coord,coord));
95  return clamp((max(cdist.x,1e-3)-dist)*cornersmooth.x,0.0, 1.0);
96}
97
98// Calculate the influence of a scanline on the current pixel.
99//
100// 'distance' is the distance in texture coordinates from the current
101// pixel to the scanline in question.
102// 'color' is the colour of the scanline at the horizontal location of
103// the current pixel.
104vec4 scanlineWeights(float distance, vec4 color)
105{
106  // "wid" controls the width of the scanline beam, for each RGB channel
107  // The "weights" lines basically specify the formula that gives
108  // you the profile of the beam, i.e. the intensity as
109  // a function of distance from the vertical center of the
110  // scanline. In this case, it is gaussian if width=2, and
111  // becomes nongaussian for larger widths. Ideally this should
112  // be normalized so that the integral across the beam is
113  // independent of its width. That is, for a narrower beam
114  // "weights" should have a higher peak at the center of the
115  // scanline than for a wider beam.
116#ifdef USEGAUSSIAN
117  vec4 wid = 0.3 + 0.1 * pow(color, vec4_splat(3.0));
118  vec4 weights = vec4(distance / wid);
119  return 0.4 * exp(-weights * weights) / wid;
120#else
121  vec4 wid = 2.0 + 2.0 * pow(color, vec4_splat(4.0));
122  vec4 weights = vec4_splat(distance / 0.3);
123  return 1.4 * exp(-pow(weights * inversesqrt(0.5 * wid), wid)) / (0.6 + 0.2 * wid);
124#endif
125}
126
127void main()
128{
129  // Here's a helpful diagram to keep in mind while trying to
130  // understand the code:
131  //
132  //  |      |      |      |      |
133  // -------------------------------
134  //  |      |      |      |      |
135  //  |  01  |  11  |  21  |  31  | <-- current scanline
136  //  |      | @    |      |      |
137  // -------------------------------
138  //  |      |      |      |      |
139  //  |  02  |  12  |  22  |  32  | <-- next scanline
140  //  |      |      |      |      |
141  // -------------------------------
142  //  |      |      |      |      |
143  //
144  // Each character-cell represents a pixel on the output
145  // surface, "@" represents the current pixel (always somewhere
146  // in the bottom half of the current scan-line, or the top-half
147  // of the next scanline). The grid of lines represents the
148  // edges of the texels of the underlying texture.
149
150  // Texture coordinates of the texel containing the active pixel.
151  vec2 xy;
152  if (curvature.x > 0.5)
153    xy = transform(v_texCoord, v_stretch, v_sinangle, v_cosangle);
154  else
155    xy = (v_texCoord-vec2_splat(0.5))/overscan.xy+vec2_splat(0.5);
156  float cval = corner(xy);
157
158  // Of all the pixels that are mapped onto the texel we are
159  // currently rendering, which pixel are we currently rendering?
160  vec2 ratio_scale = xy * u_tex_size0.xy - vec2_splat(0.5);
161
162#ifdef OVERSAMPLE
163  float filter = fwidth(ratio_scale.y);
164#endif
165  vec2 uv_ratio = fract(ratio_scale);
166
167  // Snap to the center of the underlying texel.
168  xy = (floor(ratio_scale) + vec2_splat(0.5)) / u_tex_size0.xy;
169
170  // Calculate Lanczos scaling coefficients describing the effect
171  // of various neighbour texels in a scanline on the current
172  // pixel.
173  vec4 coeffs = PI * vec4(1.0 + uv_ratio.x, uv_ratio.x, 1.0 - uv_ratio.x, 2.0 - uv_ratio.x);
174
175  // Prevent division by zero.
176  coeffs = FIX(coeffs);
177
178  // Lanczos2 kernel.
179  coeffs = 2.0 * sin(coeffs) * sin(coeffs / 2.0) / (coeffs * coeffs);
180
181  // Normalize.
182  coeffs /= dot(coeffs, vec4_splat(1.0));
183
184  // Calculate the effective colour of the current and next
185  // scanlines at the horizontal location of the current pixel,
186  // using the Lanczos coefficients above.
187  vec4 col = clamp(TEX2D(xy + vec2(-v_one.x, 0.0))*coeffs.x +
188                   TEX2D(xy)*coeffs.y +
189		   TEX2D(xy +vec2(v_one.x, 0.0))*coeffs.z +
190		   TEX2D(xy + vec2(2.0 * v_one.x, 0.0))*coeffs.w , 0.0, 1.0);
191
192  vec4 col2 = clamp(TEX2D(xy + vec2(-v_one.x, v_one.y))*coeffs.x +
193		    TEX2D(xy + vec2(0.0, v_one.y))*coeffs.y +
194		    TEX2D(xy + v_one)*coeffs.z +
195		    TEX2D(xy + vec2(2.0 * v_one.x, v_one.y))*coeffs.w , 0.0, 1.0);
196
197
198#ifndef LINEAR_PROCESSING
199  col  = pow(col , vec4_splat(CRTgamma.x));
200  col2 = pow(col2, vec4_splat(CRTgamma.x));
201#endif
202
203  // Calculate the influence of the current and next scanlines on
204  // the current pixel.
205  vec4 weights  = scanlineWeights(uv_ratio.y, col);
206  vec4 weights2 = scanlineWeights(1.0 - uv_ratio.y, col2);
207#ifdef OVERSAMPLE
208  uv_ratio.y =uv_ratio.y+1.0/3.0*filter;
209  weights = (weights+scanlineWeights(uv_ratio.y, col))/3.0;
210  weights2=(weights2+scanlineWeights(abs(1.0-uv_ratio.y), col2))/3.0;
211  uv_ratio.y =uv_ratio.y-2.0/3.0*filter;
212  weights=weights+scanlineWeights(abs(uv_ratio.y), col)/3.0;
213  weights2=weights2+scanlineWeights(abs(1.0-uv_ratio.y), col2)/3.0;
214#endif
215  vec3 mul_res  = (col * weights + col2 * weights2).rgb * vec3_splat(cval);
216
217  // Convert the image gamma for display on our output device.
218  mul_res = pow(mul_res, vec3_splat(1.0 / monitorgamma.x));
219
220  // Color the texel.
221
222  xy = v_texCoord.xy * u_quad_dims.xy / u_tex_size1.xy;
223  vec3 mask = texture2D(mask_texture, xy).rgb;
224  mask = mix(vec3_splat(1.0), mask, aperture_strength.x);
225
226  gl_FragColor = vec4(mul_res*mask, col.a);
227}
228