TATE Shader - Hand Made (for FB Neo...)

I tried to do a tate crt shader ‘by hand’ and the results are nice.

Auto rotation must be turned off in the core options and some games use different rotations, so is must be adjusted with parameters.

Shaders go to crt/shaders/guest, preset into crt folder.

Only SLANG version is possible though.

d65-d50-tate.slang

#version 450

layout(push_constant) uniform Push
{
	vec4 SourceSize;
	vec4 OriginalSize;
	vec4 OutputSize;
	uint FrameCount;
   float WP;
   float wp_saturation;
   float fliph,flipv;
} params;

#pragma parameter fliph "Flip The Image Horizontally" -1.0 -1.0 1.0 2.0 
#pragma parameter flipv "Flip The Image Vertically" 1.0 -1.0 1.0 2.0 
#pragma parameter WP "Color Temperature %" 0.0 -100.0 100.0 5.0 
#pragma parameter wp_saturation "Saturation Adjustment" 1.0 0.0 2.0 0.05 


#define WP params.WP
#define wp_saturation params.wp_saturation
#define fliph params.fliph
#define flipv params.flipv

#define COMPAT_TEXTURE(c,d) texture(c,d)
#define TEX0 vTexCoord

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.yxzw * vec4(fliph, flipv, 1.0, 1.0);
   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;

const mat3 D65_to_XYZ = mat3 (
           0.4306190,  0.2220379,  0.0201853,
           0.3415419,  0.7066384,  0.1295504,
           0.1783091,  0.0713236,  0.9390944);

const mat3 XYZ_to_D65 = mat3 (
           3.0628971, -0.9692660,  0.0678775,
          -1.3931791,  1.8760108, -0.2288548,
          -0.4757517,  0.0415560,  1.0693490);
		   
const mat3 D50_to_XYZ = mat3 (
           0.4552773,  0.2323025,  0.0145457,
           0.3675500,  0.7077956,  0.1049154,
           0.1413926,  0.0599019,  0.7057489);
		   
const mat3 XYZ_to_D50 = mat3 (
           2.9603944, -0.9787684,  0.0844874,
          -1.4678519,  1.9161415, -0.2545973,
          -0.4685105,  0.0334540,  1.4216174);	

void main()
{
	vec3 color = COMPAT_TEXTURE(Source, TEX0.xy).rgb;
   
   color = normalize(pow(color + 1e-4, vec3(wp_saturation)))*length(color);
   
	float p = 2.4;
	
	color = pow(color, vec3(p));
	
	vec3 warmer = D50_to_XYZ*color;
	warmer = XYZ_to_D65*warmer;
	
	vec3 cooler = D65_to_XYZ*color;
	cooler = XYZ_to_D50*cooler;
	
	float m = abs(WP)/100.0;
	
	vec3 comp = (WP < 0.0) ? cooler : warmer;
	
	color = mix(color, comp, m);

	color = pow(color, vec3(1.0/p));	
	
	FragColor = vec4(color,1.0);
}

crt-guest-sm-tate.slang

#version 450

/*
   CRT - Guest - SM (Scanline Mask) Shader (TATE)
   
   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, cubic, 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.5 0.05
// bright area beam max -wide
#pragma parameter beam_max "Scanline bright" 1.10 0.5 2.5 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 1.0 10.0 0.25
// pixel sharpness
#pragma parameter cubic "Cubic Filtering" 0.0 0.0 1.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 cubic params.cubic
#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
#define SourceSizeO params.OriginalSize.yxwz

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(vec3(mx), res, 0.8);
}

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 mix(vec3(mx), res, 0.60);
}

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.00001;

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

	vec2 pC4 = (floor(OGL2Pos) + vec2(0.5)) * SourceSizeO.zw;	
	
	// Reading the texels
	vec2 dx = vec2(SourceSizeO.z,0.0);
	vec2 dy = vec2(0.0,SourceSizeO.w);
	vec2 x2 = dx+dx;
	float zero = mix(0.03, exp2(-h_sharp), cubic);
	
	float wl2 = 1.0 + fp.x;	
	float wl1 =       fp.x;
	float wr1 = 1.0 - fp.x;
	float wr2 = 2.0 - fp.x;

	wl2*=wl2; wl2 = exp2(-h_sharp*wl2);	float sl2 = wl2;
	wl1*=wl1; wl1 = exp2(-h_sharp*wl1);	float sl1 = wl1;
	wr1*=wr1; wr1 = exp2(-h_sharp*wr1);	float sr1 = wr1;
	wr2*=wr2; wr2 = exp2(-h_sharp*wr2);	float sr2 = wr2;
	
	wl2 = max(wl2 - zero, mix(0.0,mix(-0.15, -0.01, fp.x),cubic));
	wl1 = max(wl1 - zero, 0.0);
	wr1 = max(wr1 - zero, 0.0);	
	wr2 = max(wr2 - zero, mix(0.0,mix(-0.15, -0.01, 1.-fp.x),cubic));
	
	float wtt =  1.0/(wl2+wl1+wr1+wr2);
	float wts =  1.0/(sl2+sl1+sr1+sr2);

	vec3 l2 = COMPAT_TEXTURE(Texture, pC4 - dx).xyz; l2*=l2;
	vec3 l1 = COMPAT_TEXTURE(Texture, pC4     ).xyz; l1*=l1;
	vec3 r1 = COMPAT_TEXTURE(Texture, pC4 + dx).xyz; r1*=r1;
	vec3 r2 = COMPAT_TEXTURE(Texture, pC4 + x2).xyz; r2*=r2;
	
	vec3 color1 = (wl2*l2+wl1*l1+wr1*r1+wr2*r2)*wtt;
	
	vec3 colmin = min(min(l2,l1),min(r1,r2));
	vec3 colmax = max(max(l2,l1),max(r1,r2));
	
	if (cubic > 0.4) color1 = clamp(color1, 0.7*colmin, 1.35*colmax);
	
	l1*=l1*l1; l1*=l1; r1*=r1*r1; r1*=r1; l2*=l2*l2; l2*=l2; r2*=r2*r2; r2*=r2;
	vec3 scolor1 = (sl2*l2+sl1*l1+sr1*r1+sr2*r2)*wts;
	scolor1 = pow(scolor1, vec3(1.0/6.0));
	scolor1 = mix(color1, scolor1, 1.0);
	
	pC4+=dy;
	l2 = COMPAT_TEXTURE(Texture, pC4 - dx).xyz; l2*=l2;
	l1 = COMPAT_TEXTURE(Texture, pC4     ).xyz; l1*=l1;
	r1 = COMPAT_TEXTURE(Texture, pC4 + dx).xyz; r1*=r1;
	r2 = COMPAT_TEXTURE(Texture, pC4 + x2).xyz; r2*=r2;
	
	vec3 color2 = (wl2*l2+wl1*l1+wr1*r1+wr2*r2)*wtt;
	
	colmin = min(min(l2,l1),min(r1,r2));
	colmax = max(max(l2,l1),max(r1,r2));
	
	if (cubic > 0.4) color2 = clamp(color2, 0.7*colmin, 1.35*colmax);

	l1*=l1*l1; l1*=l1; r1*=r1*r1; r1*=r1; l2*=l2*l2; l2*=l2; r2*=r2*r2; r2*=r2;
	vec3 scolor2 = (sl2*l2+sl1*l1+sr1*r1+sr2*r2)*wts;
	scolor2 = pow(scolor2, vec3(1.0/6.0));
	scolor2 = mix(color2, scolor2, 1.0);
	
	float f1 = fp.y;
	float f2 = 1.0 - fp.y;
	
	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 = mix(ctemp, sctemp, 0.75);
	tmp1 = 0.5*(sqrt(tmp1) + tmp1);
	
	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);
}

crt-guest-sm-tate.slangp

shaders = 2

shader0 = shaders/guest/d65-d50-tate.slang
filter_linear0 = false
scale_type0 = viewport
scale0 = 1.0

shader1 = shaders/guest/crt-guest-sm-tate.slang

A screenie (FB Neo):

1 Like