New CRT shader from Guest + CRT Guest Advanced updates

Worked fine here. It’s a simple if/else situation, shouldn’t cause a driver issue, but dunno…

Nevertheless, i continued working on the shader and improved it a lot.

First of all, it’s much faster. Before i was hiting like 1050 FPS on test image, now it’s like 1700 FPS. I blame my traditional ways of thinking for this. :slight_smile: Since it’s aiming for 4k a bit, speed really matters.

Second, there is only one scanline function now, but has more parameters, so you can shape it around. Old behaviour is like 8/8 scanline params, new defaults are 4/12 which is more like former scanline type 1, but more consistent and brighter on dark areas.

Also new is the increased bright beam control variable. So there can be thicker/slimer bright “beams”.

Last, scanline saturation can be altered. Unsaturated scanlines look somewhat dull. Former solution was a bit bulky and had issues with different settings.

Some parameters became obsolete, so the overall number of them is the same.

It would be great if it’s tested a bit (works nice here though), feedback is welcome. I would also like to mention that the mask part is unchanged and works like before.

crt-guest-sm.glsl

/*
   CRT - Guest - SM (Scanline Mask) Shader
   
   Copyright (C) 2019 guest(r) - [email protected]

   Big thanks to Nesguy from the Libretro forums for the masks and other ideas.
   
   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version 2
   of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
   
*/

/*   README - MASKS GUIDE

To obtain the best results with masks 0, 1, 3, 4: 
must leave “mask size” at 1 and the display must be set to its native resolution to result in evenly spaced “active” LCD subpixels.

Mask 0: Uses a magenta and green pattern for even spacing of the LCD subpixels.

Mask 1: Intended for displays that have RBG subpixels (as opposed to the more common RGB). 
Uses a yellow/blue pattern for even spacing of the LCD subpixels.

Mask 2: Common red/green/blue pattern.

Mask 3: This is useful for 4K displays, where masks 0 and 1 can look too fine. 
Uses a red/yellow/cyan/blue pattern to result in even spacing of the LCD subpixels.

Mask 4: Intended for displays that have the less common RBG subpixel pattern. 
This is useful for 4K displays, where masks 0 and 1 can look too fine. 
Uses a red/magenta/cyan/green pattern for even spacing of the LCD subpixels.

*/


// Parameter lines go here:
#pragma parameter smart "Smart Y Integer Scaling" 0.0 0.0 1.0 1.0
#pragma parameter brightboost1 "Bright boost dark colors" 1.50 0.5 2.5 0.05
#pragma parameter brightboost2 "Bright boost bright colors" 1.10 0.5 2.0 0.05
#pragma parameter stype "Scanline Type" 0.0 0.0 2.0 1.0
#pragma parameter scanline1 "Scanline Shape Center" 6.0 2.0 14.0 0.5
#pragma parameter scanline2 "Scanline Shape Edges" 10.0 4.0 16.0 0.5
#pragma parameter beam_min "Scanline dark" 1.40 0.5 2.0 0.02
#pragma parameter beam_max "Scanline bright" 1.10 0.5 2.0 0.02
#pragma parameter s_beam "Overgrown Bright Beam" 0.75 0.0 1.0 0.05
#pragma parameter saturation1 "Scanline Saturation" 2.25 0.0 6.0 0.25
#pragma parameter h_sharp "Horizontal sharpness" 2.0 1.0 5.0 0.05
#pragma parameter mask "CRT Mask (3&4 are 4k masks)" 0.0 0.0 4.0 1.0
#pragma parameter maskmode "CRT Mask Mode: Classic, Fine, Coarse" 0.0 0.0 2.0 1.0
#pragma parameter maskdark "CRT Mask Strength Dark Pixels" 1.0 0.0 1.5 0.05
#pragma parameter maskbright "CRT Mask Strength Bright Pixels" 0.20 -0.5 1.0 0.05
#pragma parameter masksize "CRT Mask Size" 1.0 1.0 2.0 1.0
#pragma parameter gamma_out "Gamma Out" 2.30 1.0 3.0 0.05

#if defined(VERTEX)

#if __VERSION__ >= 130
#define COMPAT_VARYING out
#define COMPAT_ATTRIBUTE in
#define COMPAT_TEXTURE texture
#else
#define COMPAT_VARYING varying 
#define COMPAT_ATTRIBUTE attribute 
#define COMPAT_TEXTURE texture2D
#endif

#ifdef GL_ES
#define COMPAT_PRECISION mediump
#else
#define COMPAT_PRECISION
#endif

COMPAT_ATTRIBUTE vec4 VertexCoord;
COMPAT_ATTRIBUTE vec4 COLOR;
COMPAT_ATTRIBUTE vec4 TexCoord;
COMPAT_VARYING vec4 COL0;
COMPAT_VARYING vec4 TEX0;

vec4 _oPosition1; 
uniform mat4 MVPMatrix;
uniform COMPAT_PRECISION int FrameDirection;
uniform COMPAT_PRECISION int FrameCount;
uniform COMPAT_PRECISION vec2 OutputSize;
uniform COMPAT_PRECISION vec2 TextureSize;
uniform COMPAT_PRECISION vec2 InputSize;

void main()
{
    gl_Position = MVPMatrix * VertexCoord;
    COL0 = COLOR;
    TEX0.xy = TexCoord.xy * 1.00001;
}

#elif defined(FRAGMENT)

#if __VERSION__ >= 130
#define COMPAT_VARYING in
#define COMPAT_TEXTURE texture
out vec4 FragColor;
#else
#define COMPAT_VARYING varying
#define FragColor gl_FragColor
#define COMPAT_TEXTURE texture2D
#endif

#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#define COMPAT_PRECISION mediump
#else
#define COMPAT_PRECISION
#endif

uniform COMPAT_PRECISION int FrameDirection;
uniform COMPAT_PRECISION int FrameCount;
uniform COMPAT_PRECISION vec2 OutputSize;
uniform COMPAT_PRECISION vec2 TextureSize;
uniform COMPAT_PRECISION vec2 InputSize;
uniform sampler2D Texture;
COMPAT_VARYING vec4 TEX0;

// compatibility #defines
#define Source Texture
#define vTexCoord TEX0.xy

#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize
#define OutputSize vec4(OutputSize, 1.0 / OutputSize)

#ifdef PARAMETER_UNIFORM
// All parameter floats need to have COMPAT_PRECISION in front of them
uniform COMPAT_PRECISION float smart;
uniform COMPAT_PRECISION float brightboost1;
uniform COMPAT_PRECISION float brightboost2;
uniform COMPAT_PRECISION float stype;
uniform COMPAT_PRECISION float scanline1;
uniform COMPAT_PRECISION float scanline2;
uniform COMPAT_PRECISION float beam_min;
uniform COMPAT_PRECISION float beam_max;
uniform COMPAT_PRECISION float s_beam;
uniform COMPAT_PRECISION float saturation1;
uniform COMPAT_PRECISION float h_sharp;
uniform COMPAT_PRECISION float mask;
uniform COMPAT_PRECISION float maskmode;
uniform COMPAT_PRECISION float maskdark;
uniform COMPAT_PRECISION float maskbright;
uniform COMPAT_PRECISION float masksize;
uniform COMPAT_PRECISION float gamma_out;
#else
#define smart        0.00     // smart Y integer scaling
#define brightboost1 1.50     // adjust brightness - dark pixels
#define brightboost2 1.10     // adjust brightness - bright pixels
#define stype        1.00     // scanline type
#define scanline1    6.00     // scanline shape, center
#define scanline2   10.00     // scanline shape, edges
#define beam_min     1.40     // dark area beam min - narrow
#define beam_max     1.10     // bright area beam max - wide
#define s_beam       0.75     // overgrown bright beam
#define saturation1  2.25     // scanline saturation
#define h_sharp      2.00     // pixel sharpness
#define mask         0.00     // crt mask type
#define maskmode     0.00     // crt mask mode
#define maskdark     1.00     // crt mask strength dark pixels
#define maskbright   0.20     // crt mask strength bright pixels
#define masksize     1.00     // crt mask size
#define gamma_out    2.30     // gamma out
#endif


float st(float x)
{
	return exp2(-10.0*x*x);
}  

float st1(float x, float scan)
{
	return exp2(-scan*x*x);
}  

float sw1(float x, vec3 color, float scan)
{	
	float mx = max(max(color.r,color.g),color.b);
	float ex = mix((2.75 - 1.75*stype)*beam_min, beam_max, mx);
	ex = mix(beam_max, ex, pow(x, mx + 0.25))*x;
	return exp2(-scan*ex*ex);
}

float sw2(float x, vec3 color)
{	
	float mx = max(max(color.r,color.g),color.b);
	float ex = mix(beam_min, beam_max, mx) - 1.0;
	float start = 0.20 - 0.40*ex;
	float end   = 1.50 - 2.20*ex;
	float x1 = smoothstep(start, end, x+x);
	return 1.0-x1;
}

float Overscan(float pos, float dy){
	pos=pos*2.0-1.0;    
	pos*=dy;
	return pos*0.5+0.5;
}


void main()
{
	vec2 tex = TEX0.xy;

	if (smart == 1.0)
	{
		float factor = OutputSize.y/InputSize.y;
		float intfactor = round(factor);
		float diff = factor/intfactor;
		tex.y = Overscan(tex.y*(SourceSize.y/InputSize.y), diff)*(InputSize.y/SourceSize.y); 
	}
	
	vec2 OGL2Pos = tex * SourceSize.xy - vec2(0.5);
	vec2 fp = fract(OGL2Pos);

	vec2 pC4 = (floor(OGL2Pos) + vec2(0.5)) * SourceSize.zw;	
	
	// Reading the texels
	vec3 ul = COMPAT_TEXTURE(Texture, pC4                         ).xyz; ul*=ul;
	vec3 ur = COMPAT_TEXTURE(Texture, pC4 + vec2(SourceSize.z,0.0)).xyz; ur*=ur;
	vec3 dl = COMPAT_TEXTURE(Texture, pC4 + vec2(0.0,SourceSize.w)).xyz; dl*=dl;
	vec3 dr = COMPAT_TEXTURE(Texture, pC4 + SourceSize.zw         ).xyz; dr*=dr;
	
	float lx = fp.x;        lx = pow(lx, h_sharp);
	float rx = 1.0 - fp.x;  rx = pow(rx, h_sharp);
	
	float w = 1.0/(lx+rx);
	float f1 = fp.y;
	float f2 = 1.0 - fp.y;
	float f3 = fract(tex.y * SourceSize.y); f3 = abs(f3-0.5);
	
	vec3 color;
	float t1 = st(f1);
	float t2 = st(f2);
	float wt = 1.0/(t1+t2);
	
// calculating scanlines

	vec3 cl = (ul*t1 + dl*t2)*wt;
	vec3 cr = (ur*t1 + dr*t2)*wt;	
	
	vec3 ref_ul = mix(cl, ul, s_beam);
	vec3 ref_ur = mix(cr, ur, s_beam);
	vec3 ref_dl = mix(cl, dl, s_beam);
	vec3 ref_dr = mix(cr, dr, s_beam);	
	
	float scan1 = mix(scanline1, scanline2, f1);
	float scan2 = mix(scanline1, scanline2, f2);
	float scan0 = mix(scanline1, scanline2, f3);
	
	float w1, w2, w3, w4 = 0.0;

	if (stype < 2.0)
	{
		w1 = sw1(f1, ref_ul, scan1);
		w2 = sw1(f2, ref_dl, scan2);
		w3 = sw1(f1, ref_ur, scan1);
		w4 = sw1(f2, ref_dr, scan2);
	}
	else
	{
		w1 = sw2(f1, ref_ul);
		w2 = sw2(f2, ref_dl);
		w3 = sw2(f1, ref_ur);
		w4 = sw2(f2, ref_dr);	
	}

	vec3 colorl = w1*ul + w2*dl;
	vec3 colorr = w3*ur + w4*dr;
	color = w*(colorr*lx + colorl*rx);
	color = min(color,1.0);	
	
	vec3 ctemp = w*(cr*lx + cl*rx);

	cl*=cl*cl; cl*=cl; cr*=cr*cr; cr*=cr;

	vec3 sctemp = w*(cr*lx + cl*rx); sctemp = pow(sctemp, vec3(1.0/6.0));

	float mx1 = max(max(color.r,color.g),color.b);
	vec3 saturated_color = color*color;
	float mx2 = max(max(saturated_color.r,saturated_color.g),saturated_color.b) + 1e-12;
	saturated_color*=mx1/mx2;
	saturated_color = max(mix(color, saturated_color, saturation1),0.0);
	
	color = mix(saturated_color, color, st1(f3,scan0));
	
	vec3 scan3 = vec3(0.0);
	float spos = floor((gl_FragCoord.x * 1.000001)/masksize); float spos1 = 0.0;

	vec3 tmp1 = pow(sctemp, vec3(0.75));
	
	color*=mix(brightboost1, brightboost2, max(max(ctemp.r,ctemp.g),ctemp.b));
	color = min(color,1.0);	
	
	float mboost = 1.25;
	
	if (mask == 0.0)
	{
		spos1 = fract(spos*0.5);
		if      (spos1 < 0.5)  scan3.rb = color.rb;
		else                   scan3.g  = color.g;	
	}
	else
	if (mask == 1.0)
	{
		spos1 = fract(spos*0.5);
		if      (spos1 < 0.5)  scan3.rg = color.rg;
		else                   scan3.b  = color.b;
	}
	else
	if (mask == 2.0)
	{
		mboost = 1.0;
		spos1 = fract(spos/3.0);
		if      (spos1 < 0.333)  scan3.r = color.r;
		else if (spos1 < 0.666)  scan3.g = color.g;
		else                     scan3.b = color.b;
	}
	else
	if (mask == 3.0)
	{
		spos1 = fract(spos*0.25);
		if      (spos1 < 0.25)  scan3.r = color.r;
		else if (spos1 < 0.50)  scan3.rg = color.rg;
		else if (spos1 < 0.75)  scan3.gb = color.gb;	
		else                    scan3.b  = color.b;	
	}
	else	
	{
		spos1 = fract(spos*0.25);
		if      (spos1 < 0.25)  scan3.r = color.r;
		else if (spos1 < 0.50)  scan3.rb = color.rb;
		else if (spos1 < 0.75)  scan3.gb = color.gb;
		else                    scan3.g =  color.g;
	}
	
	vec3 lerpmask = tmp1;
	if (maskmode == 1.0) lerpmask = vec3(max(max(tmp1.r,tmp1.g),tmp1.b)); else
	if (maskmode == 2.0) lerpmask = color;
	
	color = max(mix( mix(color, mboost*scan3, maskdark), mix(color, scan3, maskbright), lerpmask), 0.0);
	
	color = pow(color, vec3(1.0/gamma_out));	
	FragColor = vec4(color, 1.0);
} 
#endif

Edit: both scanline types are here now, speed impact is very low.

Edit2: Think this is the final version now.

2 Likes

Would something like the scanline saturation benefit guest-venom?

Either way the new shader is doing/looking great.

1 Like

It has something to do with RGB scanline calculations. Crt-guest-dr-venom uses the traditional type, which is slower, but doesn’t need a tweak. If something of all, it would make it faster.

The new shader calculates a single float weight (3x faster) and adds saturation later. The tweak is more consistent with greater dark scanline values, and crt-guest-sm aims at this setting a bit.

Hello,

This new version is very impressive in terms of scanlines settings.

CPS-1 and CPS-2 games become really nice, for me it lacks a dithering effect like GTU which could be beneficial for the games 8 and 16bits like nes snes genesis …

On which games do you test your shader?

I would like to test the scanlines parameter on a white image but I don’t know how to do it

I don’t know if the glow parameter is difficult to add (I did not succeed but I really know nothing about it), but I find it a shame not to add it because it can be easily set to off / 0 for people who don’t want it.

I must say that if someone could integrate the GTU shader into your combination I think we would really be close to a perfect shader.

Thanks

2 Likes

I would like to test the scanlines parameter on a white image but I don’t know how to do it.

I recommend the reference libretro “test image”. It shows a lot of details.

upscale-test

It has white areas. If you have some trouble setting it up, just mention it.

I don’t know if the glow parameter is difficult to add (I did not succeed but I really know nothing about it), but I find it a shame not to add it because it can be easily set to off / 0 for people who don’t want it.

Glow would need extra passes and the shader would become dependent on these, which i don’t like. It has many benefits if the shader is “independent”.

On which games do you test your shader?

Upscale test image mostly, because it shows what’s going in with the image processing. And there is the 240p snes suite which is very fine for brightness and scanlines testing. My fauforite game for every shader is Ghouls’n Ghosts, it’s really great how it treats shading.

I must say that if someone could integrate the GTU shader into your combination I think we would really be close to a perfect shader.

GTU or composite? I’ll test it and post a preset, np.

Edit: Here is the preset with GTU. Some tweaking is needed, but i got nice results.

shaders = 3

shader0 = shaders/gtu-v050/pass1.glsl
scale_type0 = source
scale0 = 1.0
float_framebuffer0 = true

shader1 = shaders/gtu-v050/pass2.glsl
scale_type_x1 = viewport
scale_x1 = 0.5 
scale_type_y1 = source
scale_y1 = 1.0
filter_linear1 = false
float_framebuffer1 = true

shader2 = shaders/guest/crt-guest-sm.glsl
scale_type2 = viewport
scale_2 = 1.0
filter_linear2 = false
2 Likes

“oversaturated output” - is this based on something objective? IMO, the defaults look somewhat under-saturated, now. Is there a way to restore the original “oversaturated” output?

1 Like

Edit: Here is the preset with GTU. Some tweaking is needed, but i got nice results.

Hello,

Sorry for the delay, I will test your combination with GTU and keep you informed.

Thank you

2 Likes

Sure, thanks for reminding me. Output gamma higher than 2.0 desaturates the colors, although it’s still OK to use it to correct brightness. New shader template tends to retain original saturation, but gamma desaturated it.

This version retains the saturation, gamma only increases brightness and you can play around with the “Scanline saturation” param a bit. If someone wan’t to desaturate the image, the first color pass of the preset has this option.

crt-guest-sm.glsl:

/*
   CRT - Guest - SM (Scanline Mask) Shader
   
   Copyright (C) 2019 guest(r) - [email protected]

   Big thanks to Nesguy from the Libretro forums for the masks and other ideas.
   
   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version 2
   of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
   
*/

/*   README - MASKS GUIDE

To obtain the best results with masks 0, 1, 3, 4: 
must leave “mask size” at 1 and the display must be set to its native resolution to result in evenly spaced “active” LCD subpixels.

Mask 0: Uses a magenta and green pattern for even spacing of the LCD subpixels.

Mask 1: Intended for displays that have RBG subpixels (as opposed to the more common RGB). 
Uses a yellow/blue pattern for even spacing of the LCD subpixels.

Mask 2: Common red/green/blue pattern.

Mask 3: This is useful for 4K displays, where masks 0 and 1 can look too fine. 
Uses a red/yellow/cyan/blue pattern to result in even spacing of the LCD subpixels.

Mask 4: Intended for displays that have the less common RBG subpixel pattern. 
This is useful for 4K displays, where masks 0 and 1 can look too fine. 
Uses a red/magenta/cyan/green pattern for even spacing of the LCD subpixels.

*/


// Parameter lines go here:
#pragma parameter smart "Smart Y Integer Scaling" 0.0 0.0 1.0 1.0
#pragma parameter brightboost1 "Bright boost dark colors" 1.45 0.5 2.5 0.05
#pragma parameter brightboost2 "Bright boost bright colors" 1.10 0.5 2.0 0.05
#pragma parameter stype "Scanline Type" 0.0 0.0 2.0 1.0
#pragma parameter scanline1 "Scanline Shape Center" 8.0 2.0 14.0 0.5
#pragma parameter scanline2 "Scanline Shape Edges" 8.0 4.0 16.0 0.5
#pragma parameter beam_min "Scanline dark" 1.40 0.5 2.0 0.02
#pragma parameter beam_max "Scanline bright" 1.10 0.5 2.0 0.02
#pragma parameter s_beam "Overgrown Bright Beam" 0.75 0.0 1.0 0.05
#pragma parameter saturation1 "Scanline Saturation" 2.75 0.0 6.0 0.25
#pragma parameter h_sharp "Horizontal sharpness" 2.0 1.0 5.0 0.05
#pragma parameter mask "CRT Mask (3&4 are 4k masks)" 0.0 0.0 4.0 1.0
#pragma parameter maskmode "CRT Mask Mode: Classic, Fine, Coarse" 0.0 0.0 2.0 1.0
#pragma parameter maskdark "CRT Mask Strength Dark Pixels" 1.0 0.0 1.5 0.05
#pragma parameter maskbright "CRT Mask Strength Bright Pixels" 0.20 -0.5 1.0 0.05
#pragma parameter masksize "CRT Mask Size" 1.0 1.0 2.0 1.0
#pragma parameter gamma_out "Gamma Out" 2.40 1.0 3.0 0.05

#if defined(VERTEX)

#if __VERSION__ >= 130
#define COMPAT_VARYING out
#define COMPAT_ATTRIBUTE in
#define COMPAT_TEXTURE texture
#else
#define COMPAT_VARYING varying 
#define COMPAT_ATTRIBUTE attribute 
#define COMPAT_TEXTURE texture2D
#endif

#ifdef GL_ES
#define COMPAT_PRECISION mediump
#else
#define COMPAT_PRECISION
#endif

COMPAT_ATTRIBUTE vec4 VertexCoord;
COMPAT_ATTRIBUTE vec4 COLOR;
COMPAT_ATTRIBUTE vec4 TexCoord;
COMPAT_VARYING vec4 COL0;
COMPAT_VARYING vec4 TEX0;

vec4 _oPosition1; 
uniform mat4 MVPMatrix;
uniform COMPAT_PRECISION int FrameDirection;
uniform COMPAT_PRECISION int FrameCount;
uniform COMPAT_PRECISION vec2 OutputSize;
uniform COMPAT_PRECISION vec2 TextureSize;
uniform COMPAT_PRECISION vec2 InputSize;

void main()
{
    gl_Position = MVPMatrix * VertexCoord;
    COL0 = COLOR;
    TEX0.xy = TexCoord.xy * 1.00001;
}

#elif defined(FRAGMENT)

#if __VERSION__ >= 130
#define COMPAT_VARYING in
#define COMPAT_TEXTURE texture
out vec4 FragColor;
#else
#define COMPAT_VARYING varying
#define FragColor gl_FragColor
#define COMPAT_TEXTURE texture2D
#endif

#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#define COMPAT_PRECISION mediump
#else
#define COMPAT_PRECISION
#endif

uniform COMPAT_PRECISION int FrameDirection;
uniform COMPAT_PRECISION int FrameCount;
uniform COMPAT_PRECISION vec2 OutputSize;
uniform COMPAT_PRECISION vec2 TextureSize;
uniform COMPAT_PRECISION vec2 InputSize;
uniform sampler2D Texture;
COMPAT_VARYING vec4 TEX0;

// compatibility #defines
#define Source Texture
#define vTexCoord TEX0.xy

#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize
#define OutputSize vec4(OutputSize, 1.0 / OutputSize)

#ifdef PARAMETER_UNIFORM
// All parameter floats need to have COMPAT_PRECISION in front of them
uniform COMPAT_PRECISION float smart;
uniform COMPAT_PRECISION float brightboost1;
uniform COMPAT_PRECISION float brightboost2;
uniform COMPAT_PRECISION float stype;
uniform COMPAT_PRECISION float scanline1;
uniform COMPAT_PRECISION float scanline2;
uniform COMPAT_PRECISION float beam_min;
uniform COMPAT_PRECISION float beam_max;
uniform COMPAT_PRECISION float s_beam;
uniform COMPAT_PRECISION float saturation1;
uniform COMPAT_PRECISION float h_sharp;
uniform COMPAT_PRECISION float mask;
uniform COMPAT_PRECISION float maskmode;
uniform COMPAT_PRECISION float maskdark;
uniform COMPAT_PRECISION float maskbright;
uniform COMPAT_PRECISION float masksize;
uniform COMPAT_PRECISION float gamma_out;
#else
#define smart        0.00     // smart Y integer scaling
#define brightboost1 1.45     // adjust brightness - dark pixels
#define brightboost2 1.10     // adjust brightness - bright pixels
#define stype        1.00     // scanline type
#define scanline1    8.00     // scanline shape, center
#define scanline2    8.00     // scanline shape, edges
#define beam_min     1.40     // dark area beam min - narrow
#define beam_max     1.10     // bright area beam max - wide
#define s_beam       0.75     // overgrown bright beam
#define saturation1  2.75     // scanline saturation
#define h_sharp      2.00     // pixel sharpness
#define mask         0.00     // crt mask type
#define maskmode     0.00     // crt mask mode
#define maskdark     1.00     // crt mask strength dark pixels
#define maskbright   0.20     // crt mask strength bright pixels
#define masksize     1.00     // crt mask size
#define gamma_out    2.40     // gamma out
#endif


float st(float x)
{
	return exp2(-10.0*x*x);
}  

float st1(float x, float scan)
{
	return exp2(-scan*x*x);
}  

float sw1(float x, vec3 color, float scan)
{	
	float mx = max(max(color.r,color.g),color.b);
	float ex = mix((2.75 - 1.75*stype)*beam_min, beam_max, mx);
	ex = mix(beam_max, ex, pow(x, mx + 0.25))*x;
	return exp2(-scan*ex*ex);
}

float sw2(float x, vec3 color)
{	
	float mx = max(max(color.r,color.g),color.b);
	float ex = mix(2.0*beam_min, beam_max, mx);
	float m = 0.5*ex;
	x = x*ex; float xx = x*x;
	xx = mix(xx, x*xx, m);
	return exp2(-10.0*xx);
}

float Overscan(float pos, float dy){
	pos=pos*2.0-1.0;    
	pos*=dy;
	return pos*0.5+0.5;
}


void main()
{
	vec2 tex = TEX0.xy;

	if (smart == 1.0)
	{
		float factor = OutputSize.y/InputSize.y;
		float intfactor = round(factor);
		float diff = factor/intfactor;
		tex.y = Overscan(tex.y*(SourceSize.y/InputSize.y), diff)*(InputSize.y/SourceSize.y); 
	}
	
	vec2 OGL2Pos = tex * SourceSize.xy - vec2(0.5);
	vec2 fp = fract(OGL2Pos);

	vec2 pC4 = (floor(OGL2Pos) + vec2(0.5)) * SourceSize.zw;	
	
	// Reading the texels
	vec3 ul = COMPAT_TEXTURE(Texture, pC4                         ).xyz; ul*=ul;
	vec3 ur = COMPAT_TEXTURE(Texture, pC4 + vec2(SourceSize.z,0.0)).xyz; ur*=ur;
	vec3 dl = COMPAT_TEXTURE(Texture, pC4 + vec2(0.0,SourceSize.w)).xyz; dl*=dl;
	vec3 dr = COMPAT_TEXTURE(Texture, pC4 + SourceSize.zw         ).xyz; dr*=dr;
	
	float lx = fp.x;        lx = pow(lx, h_sharp);
	float rx = 1.0 - fp.x;  rx = pow(rx, h_sharp);
	
	float w = 1.0/(lx+rx);
	float f1 = fp.y;
	float f2 = 1.0 - fp.y;
	float f3 = fract(tex.y * SourceSize.y); f3 = abs(f3-0.5);
	
	vec3 color;
	float t1 = st(f1);
	float t2 = st(f2);
	float wt = 1.0/(t1+t2);
	
// calculating scanlines

	vec3 cl = (ul*t1 + dl*t2)*wt;
	vec3 cr = (ur*t1 + dr*t2)*wt;	
	
	vec3 ref_ul = mix(cl, ul, s_beam);
	vec3 ref_ur = mix(cr, ur, s_beam);
	vec3 ref_dl = mix(cl, dl, s_beam);
	vec3 ref_dr = mix(cr, dr, s_beam);	
	
	float scan1 = mix(scanline1, scanline2, f1);
	float scan2 = mix(scanline1, scanline2, f2);
	float scan0 = mix(scanline1, scanline2, f3);
	f3 = st1(f3,scan0);
	f3 = f3*f3*(3.0-2.0*f3);
	
	float w1, w2, w3, w4 = 0.0;

	if (stype < 2.0)
	{
		w1 = sw1(f1, ref_ul, scan1);
		w2 = sw1(f2, ref_dl, scan2);
		w3 = sw1(f1, ref_ur, scan1);
		w4 = sw1(f2, ref_dr, scan2);
	}
	else
	{
		w1 = sw2(f1, ref_ul);
		w2 = sw2(f2, ref_dl);
		w3 = sw2(f1, ref_ur);
		w4 = sw2(f2, ref_dr);	
	}

	vec3 colorl = w1*ul + w2*dl;
	vec3 colorr = w3*ur + w4*dr;
	color = w*(colorr*lx + colorl*rx);
	color = min(color,1.0);	
	
	vec3 ctemp = w*(cr*lx + cl*rx);

	cl*=cl*cl; cl*=cl; cr*=cr*cr; cr*=cr;

	vec3 sctemp = w*(cr*lx + cl*rx); sctemp = pow(sctemp, vec3(1.0/6.0));

	float mx1 = max(max(color.r,color.g),color.b);
	float sp = (stype == 1.0) ? (0.5*saturation1) : saturation1;
	vec3 saturated_color = max((1.0+sp)*color - 0.5*sp*(color+mx1), 0.0);
	color = mix(saturated_color, color, f3);
	
	vec3 scan3 = vec3(0.0);
	float spos = floor((gl_FragCoord.x * 1.000001)/masksize); float spos1 = 0.0;

	vec3 tmp1 = 0.5*(sqrt(ctemp) + sctemp);
	
	color*=mix(brightboost1, brightboost2, max(max(ctemp.r,ctemp.g),ctemp.b));
	color = min(color,1.0);	
	
	float mboost = 1.25;
	
	if (mask == 0.0)
	{
		spos1 = fract(spos*0.5);
		if      (spos1 < 0.5)  scan3.rb = color.rb;
		else                   scan3.g  = color.g;	
	}
	else
	if (mask == 1.0)
	{
		spos1 = fract(spos*0.5);
		if      (spos1 < 0.5)  scan3.rg = color.rg;
		else                   scan3.b  = color.b;
	}
	else
	if (mask == 2.0)
	{
		mboost = 1.0;
		spos1 = fract(spos/3.0);
		if      (spos1 < 0.333)  scan3.r = color.r;
		else if (spos1 < 0.666)  scan3.g = color.g;
		else                     scan3.b = color.b;
	}
	else
	if (mask == 3.0)
	{
		spos1 = fract(spos*0.25);
		if      (spos1 < 0.25)  scan3.r = color.r;
		else if (spos1 < 0.50)  scan3.rg = color.rg;
		else if (spos1 < 0.75)  scan3.gb = color.gb;	
		else                    scan3.b  = color.b;	
	}
	else	
	{
		spos1 = fract(spos*0.25);
		if      (spos1 < 0.25)  scan3.r = color.r;
		else if (spos1 < 0.50)  scan3.rb = color.rb;
		else if (spos1 < 0.75)  scan3.gb = color.gb;
		else                    scan3.g =  color.g; 
	}
	
	vec3 lerpmask = tmp1;
	if (maskmode == 1.0) lerpmask = vec3(max(max(tmp1.r,tmp1.g),tmp1.b)); else
	if (maskmode == 2.0) lerpmask = color;
	
	color = max(mix( mix(color, mboost*scan3, maskdark), mix(color, scan3, maskbright), lerpmask), 0.0);
	
	vec3 color1 = pow(color, vec3(1.0/2.1));

	if (stype != 1.0)
	{
		vec3 color2 = pow(color, vec3(1.0/gamma_out));			
		mx1 = max(max(color1.r,color1.g),color1.b) + 1e-12;	
		float mx2 = max(max(color2.r,color2.g),color2.b);
		color1*=mx2/mx1;		
	}
	
	FragColor = vec4(color1, 1.0);
} 
#endif

Edit: some scanline type 1 friendly tweaks.

Edit2: another speedup, this should be it.

2 Likes

Fantastic! Much better, IMO.

I’m very impressed with how well this shader avoids clipping the colors when adjusting the brightness, saturation, etc. It’s even better than CRT-Aperture in that regard.

Here’s my most recent attempt at something PVM-like. Mask strength is as high as it can go without resulting in inversion artifacts on my display. Any PVM owners want to weigh in?

(Adjust backlight to 100% and view at full size. Must be viewed on a 1080p display @ 1080p for the mask to work correctly.)

shaders = "2"
shader0 = "shaders_glsl/crt/shaders/guest/d65-d50.glsl"
filter_linear0 = "false"
wrap_mode0 = "clamp_to_border"
mipmap_input0 = "false"
alias0 = ""
float_framebuffer0 = "false"
srgb_framebuffer0 = "false"
scale_type_x0 = "source"
scale_x0 = "1.000000"
scale_type_y0 = "source"
scale_y0 = "1.000000"
shader1 = "shaders_glsl/crt/shaders/guest/crt-guest-sm.glsl"
wrap_mode1 = "clamp_to_border"
mipmap_input1 = "false"
alias1 = ""
float_framebuffer1 = "false"
srgb_framebuffer1 = "false"
parameters = "WP;wp_saturation;smart;brightboost1;brightboost2;stype;scanline1;scanline2;beam_min;beam_max;s_beam;saturation1;h_sharp;mask;maskmode;maskdark;maskbright;masksize;gamma_out"
WP = "0.000000"
wp_saturation = "1.100000"
smart = "1.000000"
brightboost1 = "1.400000"
brightboost2 = "1.400000"
stype = "0.000000"
scanline1 = "9.000000"
scanline2 = "9.000000"
beam_min = "1.200000"
beam_max = "1.200000"
s_beam = "0.000000"
saturation1 = "4.000000"
h_sharp = "1.500000"
mask = "0.000000"
maskmode = "0.000000"
maskdark = "1.000000"
maskbright = "0.900000"
masksize = "1.000000"
gamma_out = "2.400000"
3 Likes

@guest.r

Would it be possible for a shader to automatically calculate the amount of lost saturation, contrast, etc from whatever scanline/mask settings are applied, and then automatically adjust saturation, contrast, etc accordingly to restore the original values? (hope that makes sense)

1 Like

Saturation is easy to “fix”, because it’s simple to maintain. Early versions were non-consistent with diverse settings, so i decided for a different approach wich works as intended and is also faster. The general speedup is up to 25-40%, which is very fine (minding Intel, 4k…).

Brightnes is much more problematic, because it would require to code a multi-parameter-non-linear function with multiple outputs to control varibles, and it would only be a suggestion. Second option is to code some built-in presets, but they would be likely immune to further menu tweaks, as shaders are pretty limited. Same with contrast. It would hurt me somewhat to loose shader performance too.

I belive RA has done very well with some solutions, like preset saving, per-game presets…and i think sometimes it’s fun to stick with a fauvorite setting and even more fun to try some tweaking. :wink:

2 Likes

I thought about this shader a lot and after some consideration i think it’s working very nice feature and implementation wise. I also made a slang port of the latest version, so it could be updated without troubling @hunterk too much (and also many thanks for great support in the past).

What’s new/improved (compared with official versions):

  1. speed is very nice
  2. all scanline types make good sense + scanline saturation option is added
  3. saturation is preserved, some normal scanlines (type1) auto correctures added
  4. masks work as before, older looks can be achieved np + some new looks

I tested both versions and all parameters work ok…

crt-guest-sm.glsl

/*
   CRT - Guest - SM (Scanline Mask) Shader
   
   Copyright (C) 2019 guest(r) - [email protected]

   Big thanks to Nesguy from the Libretro forums for the masks and other ideas.
   
   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version 2
   of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
   
*/

/*   README - MASKS GUIDE

To obtain the best results with masks 0, 1, 3, 4: 
must leave “mask size” at 1 and the display must be set to its native resolution to result in evenly spaced “active” LCD subpixels.

Mask 0: Uses a magenta and green pattern for even spacing of the LCD subpixels.

Mask 1: Intended for displays that have RBG subpixels (as opposed to the more common RGB). 
Uses a yellow/blue pattern for even spacing of the LCD subpixels.

Mask 2: Common red/green/blue pattern.

Mask 3: This is useful for 4K displays, where masks 0 and 1 can look too fine. 
Uses a red/yellow/cyan/blue pattern to result in even spacing of the LCD subpixels.

Mask 4: Intended for displays that have the less common RBG subpixel pattern. 
This is useful for 4K displays, where masks 0 and 1 can look too fine. 
Uses a red/magenta/cyan/green pattern for even spacing of the LCD subpixels.

*/


// Parameter lines go here:
#pragma parameter smart "Smart Y Integer Scaling" 0.0 0.0 1.0 1.0
#pragma parameter brightboost1 "Bright boost dark colors" 1.4 0.5 3.0 0.05
#pragma parameter brightboost2 "Bright boost bright colors" 1.20 0.5 2.0 0.05
#pragma parameter stype "Scanline Type" 0.0 0.0 2.0 1.0
#pragma parameter scanline1 "Scanline Shape Center" 8.0 2.0 14.0 0.5
#pragma parameter scanline2 "Scanline Shape Edges" 8.0 4.0 16.0 0.5
#pragma parameter beam_min "Scanline dark" 1.40 0.5 2.0 0.02
#pragma parameter beam_max "Scanline bright" 1.15 0.5 2.0 0.02
#pragma parameter s_beam "Overgrown Bright Beam" 0.75 0.0 1.0 0.05
#pragma parameter saturation1 "Scanline Saturation" 2.75 0.0 6.0 0.25
#pragma parameter h_sharp "Horizontal sharpness" 2.0 1.0 5.0 0.05
#pragma parameter mask "CRT Mask (3&4 are 4k masks)" 0.0 0.0 4.0 1.0
#pragma parameter maskmode "CRT Mask Mode: Classic, Fine, Coarse" 0.0 0.0 2.0 1.0
#pragma parameter maskdark "CRT Mask Strength Dark Pixels" 1.0 0.0 1.5 0.05
#pragma parameter maskbright "CRT Mask Strength Bright Pixels" 0.20 -0.5 1.0 0.05
#pragma parameter masksize "CRT Mask Size" 1.0 1.0 2.0 1.0
#pragma parameter gamma_out "Gamma Out" 2.40 1.0 3.0 0.05

#if defined(VERTEX)

#if __VERSION__ >= 130
#define COMPAT_VARYING out
#define COMPAT_ATTRIBUTE in
#define COMPAT_TEXTURE texture
#else
#define COMPAT_VARYING varying 
#define COMPAT_ATTRIBUTE attribute 
#define COMPAT_TEXTURE texture2D
#endif

#ifdef GL_ES
#define COMPAT_PRECISION mediump
#else
#define COMPAT_PRECISION
#endif

COMPAT_ATTRIBUTE vec4 VertexCoord;
COMPAT_ATTRIBUTE vec4 COLOR;
COMPAT_ATTRIBUTE vec4 TexCoord;
COMPAT_VARYING vec4 COL0;
COMPAT_VARYING vec4 TEX0;

vec4 _oPosition1; 
uniform mat4 MVPMatrix;
uniform COMPAT_PRECISION int FrameDirection;
uniform COMPAT_PRECISION int FrameCount;
uniform COMPAT_PRECISION vec2 OutputSize;
uniform COMPAT_PRECISION vec2 TextureSize;
uniform COMPAT_PRECISION vec2 InputSize;

void main()
{
    gl_Position = MVPMatrix * VertexCoord;
    COL0 = COLOR;
    TEX0.xy = TexCoord.xy * 1.00001;
}

#elif defined(FRAGMENT)

#if __VERSION__ >= 130
#define COMPAT_VARYING in
#define COMPAT_TEXTURE texture
out vec4 FragColor;
#else
#define COMPAT_VARYING varying
#define FragColor gl_FragColor
#define COMPAT_TEXTURE texture2D
#endif

#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#define COMPAT_PRECISION mediump
#else
#define COMPAT_PRECISION
#endif

uniform COMPAT_PRECISION int FrameDirection;
uniform COMPAT_PRECISION int FrameCount;
uniform COMPAT_PRECISION vec2 OutputSize;
uniform COMPAT_PRECISION vec2 TextureSize;
uniform COMPAT_PRECISION vec2 InputSize;
uniform sampler2D Texture;
COMPAT_VARYING vec4 TEX0;

// compatibility #defines
#define Source Texture
#define vTexCoord TEX0.xy

#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize
#define OutputSize vec4(OutputSize, 1.0 / OutputSize)

#ifdef PARAMETER_UNIFORM
// All parameter floats need to have COMPAT_PRECISION in front of them
uniform COMPAT_PRECISION float smart;
uniform COMPAT_PRECISION float brightboost1;
uniform COMPAT_PRECISION float brightboost2;
uniform COMPAT_PRECISION float stype;
uniform COMPAT_PRECISION float scanline1;
uniform COMPAT_PRECISION float scanline2;
uniform COMPAT_PRECISION float beam_min;
uniform COMPAT_PRECISION float beam_max;
uniform COMPAT_PRECISION float s_beam;
uniform COMPAT_PRECISION float saturation1;
uniform COMPAT_PRECISION float h_sharp;
uniform COMPAT_PRECISION float mask;
uniform COMPAT_PRECISION float maskmode;
uniform COMPAT_PRECISION float maskdark;
uniform COMPAT_PRECISION float maskbright;
uniform COMPAT_PRECISION float masksize;
uniform COMPAT_PRECISION float gamma_out;
#else
#define smart        0.00     // smart Y integer scaling
#define brightboost1 1.40     // adjust brightness - dark pixels
#define brightboost2 1.20     // adjust brightness - bright pixels
#define stype        0.00     // scanline type
#define scanline1    8.00     // scanline shape, center
#define scanline2    8.00     // scanline shape, edges
#define beam_min     1.40     // dark area beam min - narrow
#define beam_max     1.15     // bright area beam max - wide
#define s_beam       0.75     // overgrown bright beam
#define saturation1  2.75     // scanline saturation
#define h_sharp      2.00     // pixel sharpness
#define mask         0.00     // crt mask type
#define maskmode     0.00     // crt mask mode
#define maskdark     1.00     // crt mask strength dark pixels
#define maskbright   0.20     // crt mask strength bright pixels
#define masksize     1.00     // crt mask size
#define gamma_out    2.40     // gamma out
#endif


float st(float x)
{
	return exp2(-10.0*x*x);
}  

float st1(float x, float scan)
{
	return exp2(-scan*x*x);
}  

float sw1(float x, vec3 color, float scan)
{	
	float mx = max(max(color.r,color.g),color.b);
	float ex = mix((2.75 - 1.75*stype)*beam_min, beam_max, mx);
	ex = mix(beam_max, ex, pow(x, mx + 0.25))*x;
	return exp2(-scan*ex*ex);
}

float sw2(float x, vec3 color)
{	
	float mx = max(max(color.r,color.g),color.b);
	float ex = mix(2.0*beam_min, beam_max, mx);
	float m = 0.5*ex;
	x = x*ex; float xx = x*x;
	xx = mix(xx, x*xx, m);
	return exp2(-10.0*xx);
}

float Overscan(float pos, float dy){
	pos=pos*2.0-1.0;    
	pos*=dy;
	return pos*0.5+0.5;
}


void main()
{
	vec2 tex = TEX0.xy;

	if (smart == 1.0)
	{
		float factor = OutputSize.y/InputSize.y;
		float intfactor = round(factor);
		float diff = factor/intfactor;
		tex.y = Overscan(tex.y*(SourceSize.y/InputSize.y), diff)*(InputSize.y/SourceSize.y); 
	}
	
	vec2 OGL2Pos = tex * SourceSize.xy - vec2(0.5);
	vec2 fp = fract(OGL2Pos);

	vec2 pC4 = (floor(OGL2Pos) + vec2(0.5)) * SourceSize.zw;	
	
	// Reading the texels
	vec3 ul = COMPAT_TEXTURE(Texture, pC4                         ).xyz; ul*=ul;
	vec3 ur = COMPAT_TEXTURE(Texture, pC4 + vec2(SourceSize.z,0.0)).xyz; ur*=ur;
	vec3 dl = COMPAT_TEXTURE(Texture, pC4 + vec2(0.0,SourceSize.w)).xyz; dl*=dl;
	vec3 dr = COMPAT_TEXTURE(Texture, pC4 + SourceSize.zw         ).xyz; dr*=dr;
	
	float lx = fp.x;        lx = pow(lx, h_sharp);
	float rx = 1.0 - fp.x;  rx = pow(rx, h_sharp);
	
	float w = 1.0/(lx+rx);
	float f1 = fp.y;
	float f2 = 1.0 - fp.y;
	float f3 = fract(tex.y * SourceSize.y); f3 = abs(f3-0.5);
	
	vec3 color;
	float t1 = st(f1);
	float t2 = st(f2);
	float wt = 1.0/(t1+t2);
	
// calculating scanlines

	vec3 cl = (ul*t1 + dl*t2)*wt;
	vec3 cr = (ur*t1 + dr*t2)*wt;	
	
	vec3 ref_ul = mix(cl, ul, s_beam);
	vec3 ref_ur = mix(cr, ur, s_beam);
	vec3 ref_dl = mix(cl, dl, s_beam);
	vec3 ref_dr = mix(cr, dr, s_beam);	
	
	float scan1 = mix(scanline1, scanline2, f1);
	float scan2 = mix(scanline1, scanline2, f2);
	float scan0 = mix(scanline1, scanline2, f3);
	f3 = st1(f3,scan0);
	f3 = f3*f3*(3.0-2.0*f3);
	
	float w1, w2, w3, w4 = 0.0;

	if (stype < 2.0)
	{
		w1 = sw1(f1, ref_ul, scan1);
		w2 = sw1(f2, ref_dl, scan2);
		w3 = sw1(f1, ref_ur, scan1);
		w4 = sw1(f2, ref_dr, scan2);
	}
	else
	{
		w1 = sw2(f1, ref_ul);
		w2 = sw2(f2, ref_dl);
		w3 = sw2(f1, ref_ur);
		w4 = sw2(f2, ref_dr);	
	}

	vec3 colorl = w1*ul + w2*dl;
	vec3 colorr = w3*ur + w4*dr;
	color = w*(colorr*lx + colorl*rx);
	color = min(color,1.0);	
	
	vec3 ctemp = w*(cr*lx + cl*rx);

	cl*=cl*cl; cl*=cl; cr*=cr*cr; cr*=cr;

	vec3 sctemp = w*(cr*lx + cl*rx); sctemp = pow(sctemp, vec3(1.0/6.0));

	float mx1 = max(max(color.r,color.g),color.b);
	float sp = (stype == 1.0) ? (0.5*saturation1) : saturation1;
	vec3 saturated_color = max((1.0+sp)*color - 0.5*sp*(color+mx1), 0.0);
	color = mix(saturated_color, color, f3);
	
	vec3 scan3 = vec3(0.0);
	float spos = floor((gl_FragCoord.x * 1.000001)/masksize); float spos1 = 0.0;

	vec3 tmp1 = 0.5*(sqrt(ctemp) + sctemp);
	
	color*=mix(brightboost1, brightboost2, max(max(ctemp.r,ctemp.g),ctemp.b));
	color = min(color,1.0);	
	
	float mboost = 1.25;
	
	if (mask == 0.0)
	{
		spos1 = fract(spos*0.5);
		if      (spos1 < 0.5)  scan3.rb = color.rb;
		else                   scan3.g  = color.g;	
	}
	else
	if (mask == 1.0)
	{
		spos1 = fract(spos*0.5);
		if      (spos1 < 0.5)  scan3.rg = color.rg;
		else                   scan3.b  = color.b;
	}
	else
	if (mask == 2.0)
	{
		mboost = 1.0;
		spos1 = fract(spos/3.0);
		if      (spos1 < 0.333)  scan3.r = color.r;
		else if (spos1 < 0.666)  scan3.g = color.g;
		else                     scan3.b = color.b;
	}
	else
	if (mask == 3.0)
	{
		spos1 = fract(spos*0.25);
		if      (spos1 < 0.25)  scan3.r = color.r;
		else if (spos1 < 0.50)  scan3.rg = color.rg;
		else if (spos1 < 0.75)  scan3.gb = color.gb;	
		else                    scan3.b  = color.b;	
	}
	else	
	{
		spos1 = fract(spos*0.25);
		if      (spos1 < 0.25)  scan3.r = color.r;
		else if (spos1 < 0.50)  scan3.rb = color.rb;
		else if (spos1 < 0.75)  scan3.gb = color.gb;
		else                    scan3.g =  color.g; 
	}
	
	vec3 lerpmask = tmp1;
	if (maskmode == 1.0) lerpmask = vec3(max(max(tmp1.r,tmp1.g),tmp1.b)); else
	if (maskmode == 2.0) lerpmask = color;
	
	color = max(mix( mix(color, mboost*scan3, maskdark), mix(color, scan3, maskbright), lerpmask), 0.0);
	
	vec3 color1 = pow(color, vec3(1.0/2.1));

	if (stype != 1.0)
	{
		vec3 color2 = pow(color, vec3(1.0/gamma_out));			
		mx1 = max(max(color1.r,color1.g),color1.b) + 1e-12;	
		float mx2 = max(max(color2.r,color2.g),color2.b);
		color1*=mx2/mx1;		
	}
	
	FragColor = vec4(color1, 1.0);
} 
#endif

crt-guest-sm.slang

#version 450

/*
   CRT - Guest - SM (Scanline Mask) Shader
   
   Copyright (C) 2019 guest(r) - [email protected]

   Big thanks to Nesguy from the Libretro forums for the masks and other ideas.
   
   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version 2
   of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
   
*/

/*   README - MASKS GUIDE

To obtain the best results with masks 0, 1, 3, 4: 
must leave “mask size” at 1 and the display must be set to its native resolution to result in evenly spaced “active” LCD subpixels.

Mask 0: Uses a magenta and green pattern for even spacing of the LCD subpixels.

Mask 1: Intended for displays that have RBG subpixels (as opposed to the more common RGB). 
Uses a yellow/blue pattern for even spacing of the LCD subpixels.

Mask 2: Common red/green/blue pattern.

Mask 3: This is useful for 4K displays, where masks 0 and 1 can look too fine. 
Uses a red/yellow/cyan/blue pattern to result in even spacing of the LCD subpixels.

Mask 4: Intended for displays that have the less common RBG subpixel pattern. 
This is useful for 4K displays, where masks 0 and 1 can look too fine. 
Uses a red/magenta/cyan/green pattern for even spacing of the LCD subpixels.

*/

layout(push_constant) uniform Push
{
	vec4 SourceSize;
	vec4 OriginalSize;
	vec4 OutputSize;
	uint FrameCount;
	float smart, brightboost1, brightboost2, stype, scanline1, scanline2, beam_min, beam_max, s_beam, saturation1, h_sharp, mask, maskmode, maskdark, maskbright, masksize, gamma_out;
} params;

// smart Y integer scaline
#pragma parameter smart "Smart Y Integer Scaling" 0.0 0.0 1.0 1.0
// adjust brightness dark colors
#pragma parameter brightboost1 "Bright boost dark colors" 1.40 0.5 3.0 0.05
// adjust brightness bright colors
#pragma parameter brightboost2 "Bright boost bright colors" 1.20 0.5 2.0 0.05
// scanline type
#pragma parameter stype "Scanline Type" 0.0 0.0 2.0 1.0
// scanline param, vertical sharpness
#pragma parameter scanline1 "Scanline Shape Center" 8.0 2.0 14.0 0.5
// scanline param, vertical sharpness
#pragma parameter scanline2 "Scanline Shape Edges" 8.0 4.0 16.0 0.5
// dark area beam min - narrow
#pragma parameter beam_min "Scanline dark" 1.40 0.5 2.0 0.05
// bright area beam max -wide
#pragma parameter beam_max "Scanline bright" 1.15 0.5 2.0 0.05
// Overgrown Bright Beam
#pragma parameter s_beam "Overgrown Bright Beam" 0.75 0.0 1.0 0.05
// Scanline Saturation
#pragma parameter saturation1 "Scanline Saturation" 2.75 0.0 6.0 0.25
// pixel sharpness
#pragma parameter h_sharp "Horizontal sharpness" 2.0 1.0 5.0 0.05
// crt mask
#pragma parameter mask "CRT Mask (3&4 are 4k masks)" 0.0 0.0 4.0 1.0
// CRT Mask Mode: Classic, Fine, Coarse
#pragma parameter maskmode "CRT Mask Mode: Classic, Fine, Coarse" 0.0 0.0 2.0 1.0
// CRT Mask Strength Dark Pixels
#pragma parameter maskdark "CRT Mask Strength Dark Pixels" 1.0 0.0 1.5 0.05
// CRT Mask Strength Bright Pixels
#pragma parameter maskbright "CRT Mask Strength Bright Pixels" 0.20 -0.5 1.0 0.05
// crt mask size
#pragma parameter masksize "CRT Mask Size" 1.0 1.0 2.0 1.0
// gamma out
#pragma parameter gamma_out "Gamma Out" 2.40 1.0 3.0 0.05

#define smart params.smart
#define brightboost1 params.brightboost1
#define brightboost2 params.brightboost2
#define stype params.stype
#define scanline1 params.scanline1
#define scanline2 params.scanline2
#define beam_min params.beam_min
#define beam_max params.beam_max
#define s_beam params.s_beam
#define saturation1 params.saturation1
#define h_sharp params.h_sharp
#define mask params.mask
#define maskmode params.maskmode
#define maskdark params.maskdark
#define maskbright params.maskbright
#define masksize params.masksize
#define gamma_out params.gamma_out

#define TEX0 vTexCoord
#define COMPAT_TEXTURE(c,d) texture(c,d)
#define Texture Source
#define InputSize SourceSize

layout(std140, set = 0, binding = 0) uniform UBO
{
	mat4 MVP;
} global;

#pragma stage vertex
layout(location = 0) in vec4 Position;
layout(location = 1) in vec2 TexCoord;
layout(location = 0) out vec2 vTexCoord;

void main()
{
   gl_Position = global.MVP * Position;
   vTexCoord = TexCoord;
}

#pragma stage fragment
layout(location = 0) in vec2 vTexCoord;
layout(location = 0) out vec4 FragColor;
layout(set = 0, binding = 2) uniform sampler2D Source;

float st(float x)
{
	return exp2(-10.0*x*x);
}  

float st1(float x, float scan)
{
	return exp2(-scan*x*x);
}  

float sw1(float x, vec3 color, float scan)
{	
	float mx = max(max(color.r,color.g),color.b);
	float ex = mix((2.75 - 1.75*stype)*beam_min, beam_max, mx);
	ex = mix(beam_max, ex, pow(x, mx + 0.25))*x;
	return exp2(-scan*ex*ex);
}

float sw2(float x, vec3 color)
{	
	float mx = max(max(color.r,color.g),color.b);
	float ex = mix(2.0*beam_min, beam_max, mx);
	float m = 0.5*ex;
	x = x*ex; float xx = x*x;
	xx = mix(xx, x*xx, m);
	return exp2(-10.0*xx);
}

float Overscan(float pos, float dy){
	pos=pos*2.0-1.0;    
	pos*=dy;
	return pos*0.5+0.5;
}

void main()
{
	vec2 tex = TEX0.xy * 1.000001;

	if (smart == 1.0)
	{
		float factor = params.OutputSize.y/params.InputSize.y;
		float intfactor = round(factor);
		float diff = factor/intfactor;
		tex.y = Overscan(tex.y*(params.SourceSize.y/params.InputSize.y), diff)*(params.InputSize.y/params.SourceSize.y); 
	}
	
	vec2 OGL2Pos = tex * params.SourceSize.xy - vec2(0.5);
	vec2 fp = fract(OGL2Pos);

	vec2 pC4 = (floor(OGL2Pos) + vec2(0.5)) * params.SourceSize.zw;	
	
	// Reading the texels
	vec3 ul = COMPAT_TEXTURE(Texture, pC4                         ).xyz; ul*=ul;
	vec3 ur = COMPAT_TEXTURE(Texture, pC4 + vec2(params.SourceSize.z,0.0)).xyz; ur*=ur;
	vec3 dl = COMPAT_TEXTURE(Texture, pC4 + vec2(0.0,params.SourceSize.w)).xyz; dl*=dl;
	vec3 dr = COMPAT_TEXTURE(Texture, pC4 + params.SourceSize.zw         ).xyz; dr*=dr;
	
	float lx = fp.x;        lx = pow(lx, h_sharp);
	float rx = 1.0 - fp.x;  rx = pow(rx, h_sharp);
	
	float w = 1.0/(lx+rx);
	float f1 = fp.y;
	float f2 = 1.0 - fp.y;
	float f3 = fract(tex.y * params.SourceSize.y); f3 = abs(f3-0.5);
	
	vec3 color;
	float t1 = st(f1);
	float t2 = st(f2);
	float wt = 1.0/(t1+t2);
	
// calculating scanlines

	vec3 cl = (ul*t1 + dl*t2)*wt;
	vec3 cr = (ur*t1 + dr*t2)*wt;	
	
	vec3 ref_ul = mix(cl, ul, s_beam);
	vec3 ref_ur = mix(cr, ur, s_beam);
	vec3 ref_dl = mix(cl, dl, s_beam);
	vec3 ref_dr = mix(cr, dr, s_beam);	
	
	float scan1 = mix(scanline1, scanline2, f1);
	float scan2 = mix(scanline1, scanline2, f2);
	float scan0 = mix(scanline1, scanline2, f3);
	f3 = st1(f3,scan0);
	f3 = f3*f3*(3.0-2.0*f3);
	
	float w1, w2, w3, w4 = 0.0;

	if (stype < 2.0)
	{
		w1 = sw1(f1, ref_ul, scan1);
		w2 = sw1(f2, ref_dl, scan2);
		w3 = sw1(f1, ref_ur, scan1);
		w4 = sw1(f2, ref_dr, scan2);
	}
	else
	{
		w1 = sw2(f1, ref_ul);
		w2 = sw2(f2, ref_dl);
		w3 = sw2(f1, ref_ur);
		w4 = sw2(f2, ref_dr);	
	}

	vec3 colorl = w1*ul + w2*dl;
	vec3 colorr = w3*ur + w4*dr;
	color = w*(colorr*lx + colorl*rx);
	color = min(color,1.0);	
	
	vec3 ctemp = w*(cr*lx + cl*rx);

	cl*=cl*cl; cl*=cl; cr*=cr*cr; cr*=cr;

	vec3 sctemp = w*(cr*lx + cl*rx); sctemp = pow(sctemp, vec3(1.0/6.0));

	float mx1 = max(max(color.r,color.g),color.b);
	float sp = (stype == 1.0) ? (0.5*saturation1) : saturation1;
	vec3 saturated_color = max((1.0+sp)*color - 0.5*sp*(color+mx1), 0.0);
	color = mix(saturated_color, color, f3);
	
	vec3 scan3 = vec3(0.0);
	float spos = floor((gl_FragCoord.x * 1.000001)/masksize); float spos1 = 0.0;

	vec3 tmp1 = 0.5*(sqrt(ctemp) + sctemp);
	
	color*=mix(brightboost1, brightboost2, max(max(ctemp.r,ctemp.g),ctemp.b));
	color = min(color,1.0);	
	
	float mboost = 1.25;
	
	if (mask == 0.0)
	{
		spos1 = fract(spos*0.5);
		if      (spos1 < 0.5)  scan3.rb = color.rb;
		else                   scan3.g  = color.g;	
	}
	else
	if (mask == 1.0)
	{
		spos1 = fract(spos*0.5);
		if      (spos1 < 0.5)  scan3.rg = color.rg;
		else                   scan3.b  = color.b;
	}
	else
	if (mask == 2.0)
	{
		mboost = 1.0;
		spos1 = fract(spos/3.0);
		if      (spos1 < 0.333)  scan3.r = color.r;
		else if (spos1 < 0.666)  scan3.g = color.g;
		else                     scan3.b = color.b;
	}
	else
	if (mask == 3.0)
	{
		spos1 = fract(spos*0.25);
		if      (spos1 < 0.25)  scan3.r = color.r;
		else if (spos1 < 0.50)  scan3.rg = color.rg;
		else if (spos1 < 0.75)  scan3.gb = color.gb;	
		else                    scan3.b  = color.b;	
	}
	else	
	{
		spos1 = fract(spos*0.25);
		if      (spos1 < 0.25)  scan3.r = color.r;
		else if (spos1 < 0.50)  scan3.rb = color.rb;
		else if (spos1 < 0.75)  scan3.gb = color.gb;
		else                    scan3.g =  color.g; 
	}
	
	vec3 lerpmask = tmp1;
	if (maskmode == 1.0) lerpmask = vec3(max(max(tmp1.r,tmp1.g),tmp1.b)); else
	if (maskmode == 2.0) lerpmask = color;
	
	color = max(mix( mix(color, mboost*scan3, maskdark), mix(color, scan3, maskbright), lerpmask), 0.0);
	
	vec3 color1 = pow(color, vec3(1.0/2.1));

	if (stype != 1.0)
	{
		vec3 color2 = pow(color, vec3(1.0/gamma_out));			
		mx1 = max(max(color1.r,color1.g),color1.b) + 1e-12;	
		float mx2 = max(max(color2.r,color2.g),color2.b);
		color1*=mx2/mx1;		
	}
	
    FragColor = vec4(color1, 1.0);
}

Happy holidays! :santa:

Edit: a minor contrast improvement.

11 Likes

Thanks for the update, I’m really liking this shader :slight_smile:

Love the separation in parameters for brightboost dark and bright colors, and thoughtful other parameters. Output looks really great. Very nice to have enough tweaking possibilty on relevant parameters without becoming too complicating :+1:

Regarding the three scanline types, could you say what their main differences are “technically”? Is one or the other superior (closer to real CRT), or is it more a matter of taste?

1 Like

I’m glad you like the shader. :slight_smile:

Yeah, i’m also happy how it turned out with parameters, all seem to change something visually. :smile: More effects/parameters would reduce shader speed or made it dependent on previous passes, which i don’t endorse. Currently the performance is similar to crt-hyllian single pass or crt easymode, which is nice.

Regarding the three scanline types, could you say what their main differences are “technically”? Is one or the other superior (closer to real CRT), or is it more a matter of taste?

Type 1 scanlines are sortoff classic, similar to a common crt display. Type 0 and 2 are reminding of a trinitron look.

Type 0 and 1 are like “2” and “0” in crt-guest-dr-venom shader.

Type 2 can be similar to type 0, but the dark scanlines are much flatter, which i wanted to implement. My first implementation (type “2”) used an alternate approach, but it only looked nice with integer scaling. IMO new version is even better and needs integer scaling only with really high “scanline dark” values.

In the end, it’s a matter of taste, ofc. And i wanted the shader to look interesting from a short distance to display too. :nerd_face: From a medium distance colors and artifacts are almost more important (for a real crt display feel), but a custom preset is needed for this.

3 Likes

After playing around with different settings, I’ve found that anything you do to saturation through the shader is going to make the image worse in some way.

A basic problem with trying to emulate a CRT on an sRGB display is that NTSC allowed for much more deeply saturated greens than sRGB. If you increase saturation through the shader, then you can make green look better in some situations but then red and blue are oversaturated. If you just try to increase the green channel (with color mangler, for example), then everything has a greenish tint.

To compensate for the loss of brightness, contrast and saturation that occurs when using shaders, the only thing you can do is crank up the backlight on your display. Anything else you do to the image through software is distorting the image in some way.

Basic instructions for configuring this shader for the most realistic result:

  1. max out the backlight on your display
  2. adjust gamma gradually, one step at a time, until the inner bars of the PLUGE in Fudoh’s 240p test suite are just barely visible. I landed on 2.65 for gamma but this will vary depending on the display being used.
  3. set scanline settings and bright boost to your personal preference while avoiding clipping of the colors and ensuring that scanlines are still visible over pure white.
  4. set mask dark to 100 and mask bright as close to 100 as possible while still maintaining an adequately bright image (at least 100 nits when displaying a white screen). A setting of 50 for mask bright is in the ballpark for most displays. Settings close to 100 will require a very bright display (500 nits or more). If mask bright is too high, the colors will appear desaturated and dull. If the backlight is already maxed out, lower mask bright until the colors appear well saturated and the image is sufficiently bright.

Type 0 looks like most of the CRTs I’ve owned. The other two types looked strange to me, but maybe they would look better with some combination of settings that I just haven’t found yet.

2 Likes

Type 0 looks like most of the CRTs I’ve owned. The other two types looked strange to me, but maybe they would look better with some combination of settings that I just haven’t found yet.

Yeah, some CRT’s have/used to have weaker scanlines, best tweaked with scanline type 1. Scanlines type 2 are similar to 0, but darker colors are “flat”. That’s the main difference to 0.

On my TV it’s looking similar to this (a quick setup). Unfortunately it’s hard to maintain the saturation while seeing the dotmask in full strength on a regular 1080p. So it’s more like “looks from a medium distance” situation. :crazy_face:

Or did it look more like this? The mask seemed cruder…

4 Likes

Second shot looks like a Sony Wega with the contrast turned up way too high, causing the scanlines to bloom to the point where the black lines disappear altogether over white. Of course, that mask will not properly map to the LCD subpixels, but it makes for a nice screenshot.

First shot is strange because the mask is relatively high TVL (540 TVL), but combined with scanlines that look like they’re from a low TVL TV.

It’s a bit ironic but it’s just really tough to have an accurate emulation of a low TVL CRT. It’s much easier to emulate a PVM or BVM given the line counts and pixel structure of modern displays.

2 Likes

This is a fairly convincing macro, if I do say so myself. :smile:

Taken with my phone camera; colors look better in person.

4 Likes

@guest.r

Regarding the sharpness setting; is the lowest settting as blurry as you can make the image without bilinear filtering it? I just want to have as wide a range as possible for this setting without it being as blurry as bilinear filter.

1 Like

Lowest setting is some sort of horizontal bilinear filtering, but it gets a treatment which makes it look much sharper. I’m not happy about it either, because better quality algorithms exist. Different approach brings a set of new problems with this shader and i think i managed quite ok to get things in shape.

With this slang version below the edges look way better IMO, but i would like to have a feedback on this and saturation, sometimes i miss some things. And the sharp/smooth range is greater. Due the new method, the variable is best set around 4 and something (instead of 1.5 for example).

The shader (slang): crt-guest-sm.slang

#version 450

/*
   CRT - Guest - SM (Scanline Mask) Shader
   
   Copyright (C) 2019-2020 guest(r) - [email protected]

   Big thanks to Nesguy from the Libretro forums for the masks and other ideas.
   
   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version 2
   of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
   
*/

/*   README - MASKS GUIDE

To obtain the best results with masks 0, 1, 3, 4: 
must leave “mask size” at 1 and the display must be set to its native resolution to result in evenly spaced “active” LCD subpixels.

Mask 0: Uses a magenta and green pattern for even spacing of the LCD subpixels.

Mask 1: Intended for displays that have RBG subpixels (as opposed to the more common RGB). 
Uses a yellow/blue pattern for even spacing of the LCD subpixels.

Mask 2: Common red/green/blue pattern.

Mask 3: This is useful for 4K displays, where masks 0 and 1 can look too fine. 
Uses a red/yellow/cyan/blue pattern to result in even spacing of the LCD subpixels.

Mask 4: Intended for displays that have the less common RBG subpixel pattern. 
This is useful for 4K displays, where masks 0 and 1 can look too fine. 
Uses a red/magenta/cyan/green pattern for even spacing of the LCD subpixels.

*/

layout(push_constant) uniform Push
{
	vec4 SourceSize;
	vec4 OriginalSize;
	vec4 OutputSize;
	uint FrameCount;
	float smart, brightboost1, brightboost2, stype, scanline1, scanline2, beam_min, beam_max, s_beam, saturation1, h_sharp, mask, maskmode, maskdark, maskbright, masksize, gamma_out;
} params;

// smart Y integer scaline
#pragma parameter smart "Smart Y Integer Scaling" 0.0 0.0 1.0 1.0
// adjust brightness dark colors
#pragma parameter brightboost1 "Bright boost dark colors" 1.50 0.5 3.0 0.05
// adjust brightness bright colors
#pragma parameter brightboost2 "Bright boost bright colors" 1.10 0.5 2.0 0.05
// scanline type
#pragma parameter stype "Scanline Type" 0.0 0.0 2.0 1.0
// scanline param, vertical sharpness
#pragma parameter scanline1 "Scanline Shape Center" 8.0 2.0 14.0 0.5
// scanline param, vertical sharpness
#pragma parameter scanline2 "Scanline Shape Edges" 8.0 4.0 16.0 0.5
// dark area beam min - narrow
#pragma parameter beam_min "Scanline dark" 1.40 0.5 2.0 0.05
// bright area beam max -wide
#pragma parameter beam_max "Scanline bright" 1.10 0.5 2.0 0.05
// Overgrown Bright Beam
#pragma parameter s_beam "Overgrown Bright Beam" 0.75 0.0 1.0 0.05
// pixel sharpness
#pragma parameter h_sharp "Horizontal sharpness" 4.5 2.0 10.0 0.25
// crt mask
#pragma parameter mask "CRT Mask (3&4 are 4k masks)" 0.0 0.0 4.0 1.0
// CRT Mask Mode: Classic, Fine, Coarse
#pragma parameter maskmode "CRT Mask Mode: Classic, Fine, Coarse" 0.0 0.0 2.0 1.0
// CRT Mask Strength Dark Pixels
#pragma parameter maskdark "CRT Mask Strength Dark Pixels" 1.0 0.0 1.5 0.05
// CRT Mask Strength Bright Pixels
#pragma parameter maskbright "CRT Mask Strength Bright Pixels" 0.20 -0.5 1.0 0.05
// crt mask size
#pragma parameter masksize "CRT Mask Size" 1.0 1.0 2.0 1.0
// gamma out
#pragma parameter gamma_out "Gamma Out" 2.40 1.0 3.0 0.05

#define smart params.smart
#define brightboost1 params.brightboost1
#define brightboost2 params.brightboost2
#define stype params.stype
#define scanline1 params.scanline1
#define scanline2 params.scanline2
#define beam_min params.beam_min
#define beam_max params.beam_max
#define s_beam params.s_beam
#define saturation1 params.saturation1
#define h_sharp params.h_sharp
#define mask params.mask
#define maskmode params.maskmode
#define maskdark params.maskdark
#define maskbright params.maskbright
#define masksize params.masksize
#define gamma_out params.gamma_out

#define TEX0 vTexCoord
#define COMPAT_TEXTURE(c,d) texture(c,d)
#define Texture Source
#define InputSize SourceSize

layout(std140, set = 0, binding = 0) uniform UBO
{
	mat4 MVP;
} global;

#pragma stage vertex
layout(location = 0) in vec4 Position;
layout(location = 1) in vec2 TexCoord;
layout(location = 0) out vec2 vTexCoord;

void main()
{
   gl_Position = global.MVP * Position;
   vTexCoord = TexCoord;
}

#pragma stage fragment
layout(location = 0) in vec2 vTexCoord;
layout(location = 0) out vec4 FragColor;
layout(set = 0, binding = 2) uniform sampler2D Source;

float st(float x)
{
	return exp2(-10.0*x*x);
}   

vec3 sw1(float x, vec3 color, float scan)
{
	vec3 tmp = mix(vec3((2.75 - 1.75*stype)),vec3(beam_max), color);
	tmp = mix(vec3(beam_max), tmp, pow(vec3(x), color + 0.30));
	vec3 ex = vec3(x)*tmp;
	vec3 res = exp2(-scan*ex*ex);
	float mx = max(max(res.r,res.g),res.b);
	return mix(res, vec3(mx), 0.2*(1.0-stype));
}

vec3 sw2(float x, vec3 color)
{	
	vec3 ex = mix(vec3(2.0*beam_min), vec3(beam_max), color);
	vec3 m = 0.5*ex;
	ex = x*ex; vec3 xx = ex*ex;
	xx = mix(xx, ex*xx, m);
	vec3 res = exp2(-10.0*xx);
	float mx = max(max(res.r,res.g),res.b);
	return (0.65*res + 0.35*mx);	
}

float Overscan(float pos, float dy){
	pos=pos*2.0-1.0;    
	pos*=dy;
	return pos*0.5+0.5;
}

void main()
{
	vec2 tex = TEX0.xy * 1.00000001;

	if (smart == 1.0)
	{
		float factor = params.OutputSize.y/params.InputSize.y;
		float intfactor = round(factor);
		float diff = factor/intfactor;
		tex.y = Overscan(tex.y*(params.SourceSize.y/params.InputSize.y), diff)*(params.InputSize.y/params.SourceSize.y); 
	}
	
	vec2 OGL2Pos = tex * params.SourceSize.xy - vec2(0.0,0.5);
	vec2 fp = fract(OGL2Pos);

	vec2 pC4 = (floor(OGL2Pos) + vec2(0.5)) * params.SourceSize.zw;	
	
	// Reading the texels
	vec2 dx = vec2(params.SourceSize.z,0.0);
	vec2 dy = vec2(0.0,params.SourceSize.w);
	
	float wl1 = 0.5 + fp.x;
	float wct = 0.5 - fp.x;
	float wr1 = 1.5 - fp.x;
	
	wl1*=wl1; wl1 = max(exp2(-h_sharp*wl1) - 0.04,0.0);
	wct*=wct; wct = max(exp2(-h_sharp*wct) - 0.04,0.0);
	wr1*=wr1; wr1 = max(exp2(-h_sharp*wr1) - 0.04,0.0);

	float wtt =  1.0/(wl1+wct+wr1);

	vec3 l1 = COMPAT_TEXTURE(Texture, pC4 - dx).xyz; l1*=l1;
	vec3 ct = COMPAT_TEXTURE(Texture, pC4     ).xyz; ct*=ct;
	vec3 r1 = COMPAT_TEXTURE(Texture, pC4 + dx).xyz; r1*=r1;

	vec3 color1 = (wl1*l1 + wct*ct + wr1*r1)*wtt;
	l1*=l1*l1; l1*=l1; ct*=ct*ct; ct*=ct; r1*=r1*r1; r1*=r1;
	vec3 scolor1 = (wl1*l1 + wct*ct + wr1*r1)*wtt;
	scolor1 = pow(scolor1, vec3(1.0/6.0));
	scolor1 = mix(color1, scolor1, 1.25);
	
	pC4+=dy;
	l1 = COMPAT_TEXTURE(Texture, pC4 - dx).xyz; l1*=l1;
	ct = COMPAT_TEXTURE(Texture, pC4     ).xyz; ct*=ct;
	r1 = COMPAT_TEXTURE(Texture, pC4 + dx).xyz; r1*=r1;

	vec3 color2 = (wl1*l1 + wct*ct + wr1*r1)*wtt;
	l1*=l1*l1; l1*=l1; ct*=ct*ct; ct*=ct; r1*=r1*r1; r1*=r1;
	vec3 scolor2 = (wl1*l1 + wct*ct + wr1*r1)*wtt;
	scolor2 = pow(scolor2, vec3(1.0/6.0));
	scolor2 = mix(color2, scolor2, 1.25);
	
	float f1 = fp.y;
	float f2 = 1.0 - fp.y;
	float f3 = fract(tex.y * params.SourceSize.y); f3 = abs(f3-0.5);
	
	vec3 color;
	float t1 = st(f1);
	float t2 = st(f2);
	float wt = 1.0/(t1+t2);
	
// calculating scanlines

	float scan1 = mix(scanline1, scanline2, f1);
	float scan2 = mix(scanline1, scanline2, f2);
	
	vec3 sctemp = (t1*scolor1 + t2*scolor2)/(t1+t2);
	
	vec3 ref1 = mix(sctemp, scolor1, s_beam);
	vec3 ref2 = mix(sctemp, scolor2, s_beam);	
	
	vec3 w1, w2 = vec3(0.0);

	if (stype < 2.0)
	{
		w1 = sw1(f1, ref1, scan1);
		w2 = sw1(f2, ref2, scan2);
	}
	else
	{
		w1 = sw2(f1, ref1);
		w2 = sw2(f2, ref2);
	}

	color = w1*color1 + w2*color2;
	color = min(color,1.0);	
	
	vec3 ctemp = (t1*color1 + t2*color2)*wt;
		
	vec3 scan3 = vec3(0.0);
	float spos = floor((gl_FragCoord.x * 1.000001)/masksize); float spos1 = 0.0;

	vec3 tmp1 = 0.5*(sqrt(0.5*(ctemp+sctemp)) + sctemp);
	
	color*=mix(brightboost1, brightboost2, max(max(ctemp.r,ctemp.g),ctemp.b));
	color = min(color,1.0);	
	
	float mboost = 1.25;
	
	if (mask == 0.0)
	{
		spos1 = fract(spos*0.5);
		if      (spos1 < 0.5)  scan3.rb = color.rb;
		else                   scan3.g  = color.g;	
	}
	else
	if (mask == 1.0)
	{
		spos1 = fract(spos*0.5);
		if      (spos1 < 0.5)  scan3.rg = color.rg;
		else                   scan3.b  = color.b;
	}
	else
	if (mask == 2.0)
	{
		mboost = 1.0;
		spos1 = fract(spos/3.0);
		if      (spos1 < 0.333)  scan3.r = color.r;
		else if (spos1 < 0.666)  scan3.g = color.g;
		else                     scan3.b = color.b;
	}
	else
	if (mask == 3.0)
	{
		spos1 = fract(spos*0.25);
		if      (spos1 < 0.25)  scan3.r = color.r;
		else if (spos1 < 0.50)  scan3.rg = color.rg;
		else if (spos1 < 0.75)  scan3.gb = color.gb;	
		else                    scan3.b  = color.b;	
	}
	else	
	{
		spos1 = fract(spos*0.25);
		if      (spos1 < 0.25)  scan3.r = color.r;
		else if (spos1 < 0.50)  scan3.rb = color.rb;
		else if (spos1 < 0.75)  scan3.gb = color.gb;
		else                    scan3.g =  color.g; 
	}
	
	vec3 mixmask = tmp1;
	if (maskmode == 1.0) mixmask = vec3(max(max(tmp1.r,tmp1.g),tmp1.b)); else
	if (maskmode == 2.0) mixmask = color;
	
	color = max(mix( mix(color, mboost*scan3, maskdark), mix(color, scan3, maskbright), mixmask), 0.0);
	
	vec3 color1g = pow(color, vec3(1.0/2.1));

	if (stype != 1.0)
	{
		vec3 color2g = pow(color, vec3(1.0/gamma_out));			
		float mx1 = max(max(color1g.r,color1g.g),color1g.b) + 1e-12;	
		float mx2 = max(max(color2g.r,color2g.g),color2g.b);
		color1g*=mx2/mx1;		
	}
	
    FragColor = vec4(color1g, 1.0);
}

This version is about 10% slower in slang…but doesn’t matter too much i guess.

Image can get much smoother as before, for example:

5 Likes