summaryrefslogtreecommitdiffstatshomepage
path: root/src/osd
diff options
context:
space:
mode:
author cgwg <cgwg@users.noreply.github.com>2021-02-21 04:47:28 +0100
committer GitHub <noreply@github.com>2021-02-21 14:47:28 +1100
commit92b292175429c88a0c993636575e662f5844855d (patch)
treead6de6d98a57582bea1a7e50ab43a711548a9e7f /src/osd
parent0c20cabef6952635219244793167a3d227e3a1b2 (diff)
bgfx: crt-geom and crt-geom-deluxe enhancements (#7766)
Added a "brightness boost" feature for the shadow mask that works by making the brightness ratio between bright and dark mask pixels closer to 1 for the brighter parts of the image. Added clamping to zero so that underscanning produces a black border. Added a "raster bloom" effect to crt-geom-deluxe that makes the image grow slightly when the average brightness of the screen is high, mimicking a common defect in CRTs.
Diffstat (limited to 'src/osd')
-rw-r--r--src/osd/modules/render/bgfx/shaders/chains/crt-geom/fs_crt-geom-deluxe.sc74
-rw-r--r--src/osd/modules/render/bgfx/shaders/chains/crt-geom/fs_crt-geom.sc48
-rw-r--r--src/osd/modules/render/bgfx/shaders/chains/crt-geom/fs_mipmap8.sc148
-rw-r--r--src/osd/modules/render/bgfx/shaders/chains/crt-geom/vs_mipmap8.sc11
4 files changed, 248 insertions, 33 deletions
diff --git a/src/osd/modules/render/bgfx/shaders/chains/crt-geom/fs_crt-geom-deluxe.sc b/src/osd/modules/render/bgfx/shaders/chains/crt-geom/fs_crt-geom-deluxe.sc
index a060de88ddd..82af2ebd3ec 100644
--- a/src/osd/modules/render/bgfx/shaders/chains/crt-geom/fs_crt-geom-deluxe.sc
+++ b/src/osd/modules/render/bgfx/shaders/chains/crt-geom/fs_crt-geom-deluxe.sc
@@ -28,12 +28,17 @@ $input v_sinangle, v_cosangle, v_stretch, v_one, v_texCoord
SAMPLER2D(mpass_texture, 0);
SAMPLER2D(mask_texture, 1);
SAMPLER2D(blur_texture, 2);
+SAMPLER2D(mipmap_texture, 3);
+vec4 TEX2D(vec2 c)
+{
+ vec2 underscan = step(0.0,c) * step(0.0,vec2_splat(1.0)-c);
+ vec4 col = texture2D(mpass_texture, c) * vec4_splat(underscan.x*underscan.y);
#ifdef LINEAR_PROCESSING
-# define TEX2D(c) pow(texture2D(mpass_texture, (c)), vec4(CRTgamma))
-#else
-# define TEX2D(c) texture2D(mpass_texture, (c))
+ col = pow(col, vec4_splat(CRTgamma.x));
#endif
+ return col;
+}
// Enable screen curvature.
uniform vec4 curvature;
@@ -43,6 +48,7 @@ uniform vec4 u_tex_size1;
uniform vec4 u_quad_dims;
uniform vec4 aperture_strength;
+uniform vec4 aperture_brightboost;
uniform vec4 CRTgamma;
uniform vec4 monitorgamma;
@@ -57,6 +63,22 @@ uniform vec4 cornersize;
uniform vec4 cornersmooth;
uniform vec4 halation;
+uniform vec4 rasterbloom;
+
+uniform vec4 blurwidth;
+
+vec3 texblur(vec2 c)
+{
+ vec3 col = pow(texture2D(blur_texture,c).rgb, vec3_splat(CRTgamma.x));
+ // taper the blur texture outside its border with a gaussian
+ float w = blurwidth.x / 320.0;
+ c = min(c, vec2_splat(1.0)-c) * aspect.xy * vec2_splat(1.0/w);
+ vec2 e2c = exp(-c*c);
+ // approximation of erf gives smooth step
+ // (convolution of gaussian with step)
+ c = (step(0.0,c)-vec2_splat(0.5)) * sqrt(vec2_splat(1.0)-e2c) * (vec2_splat(1.0) + vec2_splat(0.1749)*e2c) + vec2_splat(0.5);
+ return col * vec3_splat( c.x * c.y );
+}
float intersect(vec2 xy , vec2 sinangle, vec2 cosangle)
{
@@ -157,8 +179,13 @@ void main()
xy = transform(v_texCoord, v_stretch, v_sinangle, v_cosangle);
else
xy = (v_texCoord-vec2_splat(0.5))/overscan.xy+vec2_splat(0.5);
- vec2 xy0 = xy;
float cval = corner(xy);
+ // extract average brightness from the mipmap texture
+ float avgbright = dot(texture2D(mipmap_texture,vec2(1.0,1.0)).rgb,vec3_splat(1.0))/3.0;
+ float rbloom = 1.0 - rasterbloom.x * ( avgbright - 0.5 );
+ // expand the screen when average brightness is higher
+ xy = (xy - vec2_splat(0.5)) * rbloom + vec2_splat(0.5);
+ vec2 xy0 = xy;
// Of all the pixels that are mapped onto the texel we are
// currently rendering, which pixel are we currently rendering?
@@ -191,13 +218,13 @@ void main()
// using the Lanczos coefficients above.
vec4 col = clamp(TEX2D(xy + vec2(-v_one.x, 0.0))*coeffs.x +
TEX2D(xy)*coeffs.y +
- TEX2D(xy +vec2(v_one.x, 0.0))*coeffs.z +
- TEX2D(xy + vec2(2.0 * v_one.x, 0.0))*coeffs.w , 0.0, 1.0);
+ TEX2D(xy +vec2(v_one.x, 0.0))*coeffs.z +
+ TEX2D(xy + vec2(2.0 * v_one.x, 0.0))*coeffs.w , 0.0, 1.0);
vec4 col2 = clamp(TEX2D(xy + vec2(-v_one.x, v_one.y))*coeffs.x +
- TEX2D(xy + vec2(0.0, v_one.y))*coeffs.y +
- TEX2D(xy + v_one)*coeffs.z +
- TEX2D(xy + vec2(2.0 * v_one.x, v_one.y))*coeffs.w , 0.0, 1.0);
+ TEX2D(xy + vec2(0.0, v_one.y))*coeffs.y +
+ TEX2D(xy + v_one)*coeffs.z +
+ TEX2D(xy + vec2(2.0 * v_one.x, v_one.y))*coeffs.w , 0.0, 1.0);
#ifndef LINEAR_PROCESSING
@@ -220,16 +247,29 @@ void main()
vec3 mul_res = (col * weights + col2 * weights2).rgb;
// halation and corners
- vec3 blur = pow(texture2D(blur_texture,xy0).rgb, vec3_splat(CRTgamma.x));
- mul_res = mix(mul_res, blur, halation.x) * vec3_splat(cval);
-
- // Convert the image gamma for display on our output device.
- mul_res = pow(mul_res, vec3_splat(1.0 / monitorgamma.x));
+ vec3 blur = texblur(xy0);
+ // include factor of rbloom:
+ // (probably imperceptible) brightness reduction when raster grows
+ mul_res = mix(mul_res, blur, halation.x) * vec3_splat(cval*rbloom);
// Shadow mask
xy = v_texCoord.xy * u_quad_dims.xy / u_tex_size1.xy;
- vec3 mask = texture2D(mask_texture, xy).rgb;
- mask = mix(vec3_splat(1.0), mask, aperture_strength.x);
+ vec4 mask = texture2D(mask_texture, xy);
+ // count of total bright pixels is encoded in the mask's alpha channel
+ float nbright = 255.0 - 255.0*mask.a;
+ // fraction of bright pixels in the mask
+ float fbright = nbright / ( u_tex_size1.x * u_tex_size1.y );
+ // average darkening factor of the mask
+ float aperture_average = mix(1.0-aperture_strength.x*(1.0-aperture_brightboost.x), 1.0, fbright);
+ // colour of dark mask pixels
+ vec3 clow = vec3_splat(1.0-aperture_strength.x) * mul_res + vec3_splat(aperture_strength.x*(aperture_brightboost.x)) * mul_res * mul_res;
+ float ifbright = 1.0 / fbright;
+ // colour of bright mask pixels
+ vec3 chi = vec3_splat(ifbright*aperture_average) * mul_res - vec3_splat(ifbright - 1.0) * clow;
+ vec3 cout = mix(clow,chi,mask.rgb); // mask texture selects dark vs bright
+
+ // Convert the image gamma for display on our output device.
+ cout = pow(cout, vec3_splat(1.0 / monitorgamma.x));
- gl_FragColor = vec4(mul_res*mask, col.a);
+ gl_FragColor = vec4(cout,1.0);
}
diff --git a/src/osd/modules/render/bgfx/shaders/chains/crt-geom/fs_crt-geom.sc b/src/osd/modules/render/bgfx/shaders/chains/crt-geom/fs_crt-geom.sc
index 658e745cf14..f3f5513584a 100644
--- a/src/osd/modules/render/bgfx/shaders/chains/crt-geom/fs_crt-geom.sc
+++ b/src/osd/modules/render/bgfx/shaders/chains/crt-geom/fs_crt-geom.sc
@@ -27,11 +27,16 @@ $input v_sinangle, v_cosangle, v_stretch, v_one, v_texCoord
SAMPLER2D(mpass_texture, 0);
SAMPLER2D(mask_texture, 1);
+
+vec4 TEX2D(vec2 c)
+{
+ vec2 underscan = step(0.0,c) * step(0.0,vec2_splat(1.0)-c);
+ vec4 col = texture2D(mpass_texture, c) * vec4_splat(underscan.x*underscan.y);
#ifdef LINEAR_PROCESSING
-# define TEX2D(c) pow(texture2D(mpass_texture, (c)), vec4(CRTgamma))
-#else
-# define TEX2D(c) texture2D(mpass_texture, (c))
+ col = pow(col, vec4_splat(CRTgamma.x));
#endif
+ return col;
+}
// Enable screen curvature.
uniform vec4 curvature;
@@ -41,6 +46,7 @@ uniform vec4 u_tex_size1;
uniform vec4 u_quad_dims;
uniform vec4 aperture_strength;
+uniform vec4 aperture_brightboost;
uniform vec4 CRTgamma;
uniform vec4 monitorgamma;
@@ -186,13 +192,13 @@ void main()
// using the Lanczos coefficients above.
vec4 col = clamp(TEX2D(xy + vec2(-v_one.x, 0.0))*coeffs.x +
TEX2D(xy)*coeffs.y +
- TEX2D(xy +vec2(v_one.x, 0.0))*coeffs.z +
- TEX2D(xy + vec2(2.0 * v_one.x, 0.0))*coeffs.w , 0.0, 1.0);
+ TEX2D(xy +vec2(v_one.x, 0.0))*coeffs.z +
+ TEX2D(xy + vec2(2.0 * v_one.x, 0.0))*coeffs.w , 0.0, 1.0);
vec4 col2 = clamp(TEX2D(xy + vec2(-v_one.x, v_one.y))*coeffs.x +
- TEX2D(xy + vec2(0.0, v_one.y))*coeffs.y +
- TEX2D(xy + v_one)*coeffs.z +
- TEX2D(xy + vec2(2.0 * v_one.x, v_one.y))*coeffs.w , 0.0, 1.0);
+ TEX2D(xy + vec2(0.0, v_one.y))*coeffs.y +
+ TEX2D(xy + v_one)*coeffs.z +
+ TEX2D(xy + vec2(2.0 * v_one.x, v_one.y))*coeffs.w , 0.0, 1.0);
#ifndef LINEAR_PROCESSING
@@ -214,14 +220,24 @@ void main()
#endif
vec3 mul_res = (col * weights + col2 * weights2).rgb * vec3_splat(cval);
- // Convert the image gamma for display on our output device.
- mul_res = pow(mul_res, vec3_splat(1.0 / monitorgamma.x));
-
- // Color the texel.
-
+ // Shadow mask
xy = v_texCoord.xy * u_quad_dims.xy / u_tex_size1.xy;
- vec3 mask = texture2D(mask_texture, xy).rgb;
- mask = mix(vec3_splat(1.0), mask, aperture_strength.x);
+ vec4 mask = texture2D(mask_texture, xy);
+ // count of total bright pixels is encoded in the mask's alpha channel
+ float nbright = 255.0 - 255.0*mask.a;
+ // fraction of bright pixels in the mask
+ float fbright = nbright / ( u_tex_size1.x * u_tex_size1.y );
+ // average darkening factor of the mask
+ float aperture_average = mix(1.0-aperture_strength.x*(1.0-aperture_brightboost.x), 1.0, fbright);
+ // colour of dark mask pixels
+ vec3 clow = vec3_splat(1.0-aperture_strength.x) * mul_res + vec3_splat(aperture_strength.x*(aperture_brightboost.x)) * mul_res * mul_res;
+ float ifbright = 1.0 / fbright;
+ // colour of bright mask pixels
+ vec3 chi = vec3_splat(ifbright*aperture_average) * mul_res - vec3_splat(ifbright - 1.0) * clow;
+ vec3 cout = mix(clow,chi,mask.rgb); // mask texture selects dark vs bright
+
+ // Convert the image gamma for display on our output device.
+ cout = pow(cout, vec3_splat(1.0 / monitorgamma.x));
- gl_FragColor = vec4(mul_res*mask, col.a);
+ gl_FragColor = vec4(cout,1.0);
}
diff --git a/src/osd/modules/render/bgfx/shaders/chains/crt-geom/fs_mipmap8.sc b/src/osd/modules/render/bgfx/shaders/chains/crt-geom/fs_mipmap8.sc
new file mode 100644
index 00000000000..0f86bdbc7ba
--- /dev/null
+++ b/src/osd/modules/render/bgfx/shaders/chains/crt-geom/fs_mipmap8.sc
@@ -0,0 +1,148 @@
+$input v_texCoord
+
+#include "common.sh"
+
+SAMPLER2D(s_screen, 0);
+SAMPLER2D(s_mipmap, 1);
+
+uniform vec4 u_tex_size0;
+uniform vec4 u_tex_size1;
+uniform vec4 u_smooth;
+
+/*
+ * Based loosely on the idea outlined at
+ * https://nyorain.github.io/mipmap-compute-shader.html
+ * Each higher mipmap level is delayed by one frame.
+ *
+ * A rectangle containing (1,1) has the last mipmap level
+ * (normally a single pixel containing the average of the frame),
+ * passed through a two-tap IIR temporal filter.
+ */
+
+const vec2 v05 = vec2(0.5,0.5);
+
+// pix has integer coordinates -> average four pixels using bilinear filtering
+vec3 sample_screen(vec2 pix)
+{
+ vec2 border = step(-0.5, u_tex_size0.xy - pix);
+ float weight = border.x * border.y;
+
+ // avoid boundary! if dimensions are odd, average 1 or 2 pixels
+ vec2 x = clamp(pix, vec2_splat(0.0), u_tex_size0.xy - v05);
+ // if we're at the boundary, include weight factor
+ // -> effectively always average 4 pixels, with some of them possibly zero
+ border = clamp(u_tex_size0.xy-x,0.0,1.0);
+ weight *= border.x * border.y;
+
+ vec3 tex = texture2D(s_screen, x / u_tex_size0.xy).rgb;
+
+ return tex * vec3_splat(weight);
+}
+
+vec3 sample_mipmap(vec2 pix, vec2 offset, vec2 size)
+{
+ vec2 border = step(-0.5, size - pix) * step(-0.5, pix);
+ float weight = border.x * border.y;
+
+ // avoid boundary! if dimensions are odd, average 1 or 2 pixels
+ vec2 x = clamp(pix, vec2_splat(0.0), size - v05);
+ // if we're at the boundary, include weight factor
+ // -> effectively always average 4 pixels, with some of them possibly zero
+ border = clamp(size-x,0.0,1.0);
+ weight *= border.x * border.y;
+
+ vec3 tex = texture2D(s_mipmap, (x + offset) / u_tex_size0.xy).rgb;
+
+ return tex * vec3_splat(weight);
+}
+
+void main()
+{
+ vec2 pix = v_texCoord.xy * u_tex_size0.xy;
+ vec2 x = pix*vec2_splat(8.0);
+ if (pix.x < u_tex_size0.x * 0.5) {
+ // first level: sample from screen texture
+
+ // sample 4x4 grid
+ // each sample uses bilinear filtering to average a 2x2 region
+ // overall reduction by 8x8
+ vec3 tex = ( sample_screen(x + vec2(-3.0,-3.0))
+ + sample_screen(x + vec2(-3.0,-1.0))
+ + sample_screen(x + vec2(-3.0, 1.0))
+ + sample_screen(x + vec2(-3.0, 3.0))
+ + sample_screen(x + vec2(-1.0,-3.0))
+ + sample_screen(x + vec2(-1.0,-1.0))
+ + sample_screen(x + vec2(-1.0, 1.0))
+ + sample_screen(x + vec2(-1.0, 3.0))
+ + sample_screen(x + vec2( 1.0,-3.0))
+ + sample_screen(x + vec2( 1.0,-1.0))
+ + sample_screen(x + vec2( 1.0, 1.0))
+ + sample_screen(x + vec2( 1.0, 3.0))
+ + sample_screen(x + vec2( 3.0,-3.0))
+ + sample_screen(x + vec2( 3.0,-1.0))
+ + sample_screen(x + vec2( 3.0, 1.0))
+ + sample_screen(x + vec2( 3.0, 3.0)) ) * 0.0625;
+
+ gl_FragColor = vec4(tex.rgb,1.0);
+ } else {
+ // higher levels: sample from mipmap texture
+ vec2 newsize = u_tex_size0.xy; // size of texture being sampled
+ float fac = 8.0; // keep track of the linear reduction factor
+ float dx = u_tex_size0.x;
+ vec2 offset = vec2_splat(0.0);
+ vec2 offset0 = offset;
+ bool fc = false;
+
+ for (int i = 0; i < 3; i++) { // 3 is sufficient for dimensions up to 4k
+ // place subsequent levels to the right
+ if (pix.x > dx * 0.5) {
+ dx = dx * 0.5;
+ newsize = ceil(newsize*v05*v05*v05); // size of sampled texture is eighthed
+ fac *= 8.0;
+ offset0 = offset;
+ offset.x = offset.x + dx;
+ pix.x -= dx;
+ x = pix * vec2_splat(8.0);
+ if (newsize.x < 8.5 && newsize.y < 8.5) { // reducing to a single pixel
+ fc = true;
+ break;
+ }
+ } else break;
+ }
+
+ if (fc)
+ x = vec2(4.0,4.0);
+
+ vec3 tex = ( sample_mipmap(x + vec2(-3.0,-3.0),offset0,newsize)
+ + sample_mipmap(x + vec2(-3.0,-1.0),offset0,newsize)
+ + sample_mipmap(x + vec2(-3.0, 1.0),offset0,newsize)
+ + sample_mipmap(x + vec2(-3.0, 3.0),offset0,newsize)
+ + sample_mipmap(x + vec2(-1.0,-3.0),offset0,newsize)
+ + sample_mipmap(x + vec2(-1.0,-1.0),offset0,newsize)
+ + sample_mipmap(x + vec2(-1.0, 1.0),offset0,newsize)
+ + sample_mipmap(x + vec2(-1.0, 3.0),offset0,newsize)
+ + sample_mipmap(x + vec2( 1.0,-3.0),offset0,newsize)
+ + sample_mipmap(x + vec2( 1.0,-1.0),offset0,newsize)
+ + sample_mipmap(x + vec2( 1.0, 1.0),offset0,newsize)
+ + sample_mipmap(x + vec2( 1.0, 3.0),offset0,newsize)
+ + sample_mipmap(x + vec2( 3.0,-3.0),offset0,newsize)
+ + sample_mipmap(x + vec2( 3.0,-1.0),offset0,newsize)
+ + sample_mipmap(x + vec2( 3.0, 1.0),offset0,newsize)
+ + sample_mipmap(x + vec2( 3.0, 3.0),offset0,newsize) ) * 0.0625;
+
+ if (fc) { // this sample yields average over the whole screen
+ // apply a compensation for the zeros included in the 8^n average
+ float f = fac*fac / (u_tex_size0.x * u_tex_size0.y);
+ vec3 col = tex * vec3_splat(f);
+ if (v_texCoord.y * u_tex_size1.y < ( offset0.y + u_tex_size1.y ) * 0.5) {
+ gl_FragColor = vec4(col,1.0);
+ } else {
+ // we are near (1,1): in this region include the temporal filter
+ vec4 old = texture2D(s_mipmap, v_texCoord.xy);
+ gl_FragColor = vec4(mix(col,old.rgb,u_smooth.x),1.0);
+ }
+ } else {
+ gl_FragColor = vec4(tex.rgb,1.0);
+ }
+ }
+}
diff --git a/src/osd/modules/render/bgfx/shaders/chains/crt-geom/vs_mipmap8.sc b/src/osd/modules/render/bgfx/shaders/chains/crt-geom/vs_mipmap8.sc
new file mode 100644
index 00000000000..bdd09148403
--- /dev/null
+++ b/src/osd/modules/render/bgfx/shaders/chains/crt-geom/vs_mipmap8.sc
@@ -0,0 +1,11 @@
+$input a_position, a_texcoord0, a_color0
+$output v_texCoord
+
+#include "common.sh"
+
+void main()
+{
+ // Do the standard vertex processing.
+ gl_Position = mul(u_viewProj, vec4(a_position.xy, 0.0, 1.0));
+ v_texCoord = a_texcoord0;
+}