New CRT shader from Guest + CRT Guest Advanced updates

A couple additional mask options you should consider adding for different display types:

for 4K displays:

  1. Red, Green, Blue, Black
  2. Red, Yellow, Cyan, Blue

At 4K, the regular magenta/green mask is very high TVL / dotpitch; 1080 TVL to be precise. That’s fine if you’re trying to emulate a BVM, but not so great if you want to emulate a lower TVL monitor like a PVM. These two masks bring the TVL back down to 540 @ 4K.

Also, you might want to consider adding an option to switch mask colors from magenta/green to yellow/blue because some displays have RBG subpixels instead of RGB.

With RBG subpixels you get even subpixel spacing with yellow/blue:

RxGxBxRxGxBx… etc

Edit: I also think it would be helpful to have some comments within the shader related to the different mask types and what resolutions they should be used with, along with a disclaimer explaining how the mask works on the lcd subpixels and has to be used in a particular way (fullscreen at native resolution) for it to work properly.

Editedit: What does “smart y integer scaling” do?

@hunterk can you please add this to the repo when you get a chance? :smiley:

3 Likes

Caution! Adjusting the mask size (if it’s the same way that most shaders do it) will cause the mask to no longer map to the lcd subpixels correctly. If you do two magenta pixels and then two green, you get:

RxBRxBxGxxGxRxBRxBxGxxGx… etc.

I think mask_size adjustment is better left out, for the sake of accuracy.

Basically, for these masks to work correctly, you have to display them at original scale and the display has to be displaying its native resolution. You’re stuck with a particular TVL / dotpitch for the type of display being used. However, 4K displays can lower the TVL / pitch with one of the previously mentioned patterns (if that makes sense).

2 Likes

Works fine here, but i’ve seen cases of HW where something went wrong. Now it should be identicall to crt-guest-dr-venom.

Hey there! Glad you liking the fast version. Unfortunatelly it’s gradual x/y scaling to display resolution does not allow classic curvature, since “scanlines” become choppy. Didn’t test the corner function, but it’s a major slowdown in the last pass. Hope the regular version is fast enough…:art:

For even scanlines, it does smart vertical integer scaling (sometimes underscan, sometimes overscan), depending which one is more appropriate. Very nice for 1080p and most contents. :grinning:

Otherwise, i added the “kinky” Red, Yellow, Cyan, Blue mask…:wink:

3 Likes

It maps to the LCD pixels fine, but it doesn’t map to the subpixels correctly when you adjust the mask size, something you can’t see with a screen capture.

The intended effect/primary advantage of the magenta/green masks is to have even LCD subpixel spacing, so that the subpixels are arranged as much like CRT phosphors as possible. Adjusting the mask size basically breaks the effect (see previous post). The magenta-green masks have to be used at original scale and with the display’s native resolution. Same goes for the “kinky” 4K mask(s). :slight_smile:

The only mask (included in the shader so far) where this doesn’t matter would be the red/green/blue aperture grille pattern. With this mask, you have uneven subpixel spacing to start with:

RxxxGxxxBRxxxGxxxBRxxxGxxxB… etc

IMO, this mask type and the mask size parameter should be left out if we’re going for pure accuracy at the finest level of detail possible (i.e., the level of the subpixels).

Very cool! I look forward to trying this out.

I think it’s fine to leave out the alternative 4K mask (red, green, blue, black) since it results in a huge amount of lost brightness compared to the Red, Yellow, Cyan, Blue mask.

pixels: Red, Green, Blue, Black
subpixels: RxxxGxxxBxxx 

That’s a 75% reduction in brightness at full mask strength!

pixels: Red, Yellow, Cyan, Blue
subpixels: RxxRGxxGBxxB

That’s a much more manageable 50% reduction in brightness.

So, my only suggested changes at this point are:

  1. remove red-green-blue aperture grille pattern

  2. remove “mask size” parameter

  3. add switch mask colors option for displays with RBG subpixels (instead of the more standard RGB). This would swap magenta with yellow and swap green with blue, giving you:

    pixels: Yellow, Blue

    subpixels: RxGxBx

No worries…:upside_down_face:

The increased mask size is only viable at 4k. For a proper mask effect (0 and 1) these users would have to revert back to 1080p, which would also mean worse scanlines. CRT-Aperture also has the mask size parameter. It’s simply a must have for 4k. :smiley:

I also think there might be a missunderstanding with pixels and subpixels, since there is no interpolation or an advanced logic involved in most of the masks around. The LCD techology does not support a perfect solution, which leaves us with discrete modulo 2,3,4… masks.

pixels: Red, Yellow, Cyan, Blue
subpixels: RxxRGxxGBxxB

It’s always RYCBRYCBRYCB…or GMGMGMGM or RGBRGBRGB… (in output resolution). Increased mask size (2) makes GMGM into GGMMGGMM etc.

I know this is far from perfect if we want to emulate CRT masks, but any sort of interpolation makes things only worse.

I appologize in advance if you meant something else.

I think we’re talking about different things :sweat_smile:

By “subpixels” I’m referring to the LCD’s physical subpixels.

An LCD looks like this in extreme close up:

image

One LCD pixel:

image

One LCD subpixel:

image

In my replies where I’ve said something like this:

pixels: Red, Yellow, Cyan, Blue
subpixels: RxxRGxxGBxxB

I’m using “x” to represent a black/inactive subpixel. This is assuming “raw CRT mask strength” at 100% (the most accurate setting).

If we want to recreate CRT phosphor behavior at the finest level of detail possible, the “active” subpixels should be evenly spaced. …hopefully that helps! :smiley:

If we want even spacing of the “active subpixels”, it places a few restrictions on us: you have to be using the display’s native resolution and you can’t alter the mask with the mask size parameter because then you get the following:

pixels: GGMMGGMM 
subpixels: xGxxGxRxBRxBxGxxGxRxBRxB

edit: hopefully, the above also helps explain the need for the switch mask colors option that I talked about it in earlier posts. :slight_smile: With some LCDs, the subpixels are actually arranged in R, B, G order.

2 Likes

masks are pretty easy to add/remove. not a big deal either way, IMO.

For curvature, there’s always royale’s last pass, which can add curvature/tilt and rounded corners to pretty much anything :slight_smile:

3 Likes

I think in general I’m trying to push the idea that cgwg hinted at in his write-up on shaders, that masks should take into account the LCD subpixel spacing to be as accurate as possible at the finest level of detail.

It’s the whole “more options vs ease of use and/or accuracy” debate.

Maybe the solution would be to write something like a user’s guide for each of the masks within the shader comments. I could do that if you think it’s a good idea. @guest.r

2 Likes

I’ve added the RBG mask option to the setup (mask 2). Looks a bit different, but that’s the point i guess. But i see bright sides of the situation too… I’m quite sure it’s nothing to worry about if we use a moderate mask strength.

There was a discussion on Discord a while ago about pixel crawl. Usually, the sharp-bilinear shader can be used to get a sharp upscale without any pixel crawl. However, this is obviously not usable when also using a CRT shader. It seems each and every shader should really incorporate sharp-bilinear into its own code when upscaling.

Do you think this is something you could add to this shader? It would provide a good example for other shader authors on how to get a sharper result without pixel crawl and (probably?) give much better results when not using integer scaling. Most shaders will look quite worse without integer scaling, but if they incorporated sharp-bilinear into their internal upscaling steps, the results should be in theory indistinguishable between integer and non-integer scaling.

The sharp-bilinear algorithm seems to be extremely simple and fast as well, so hopefully it’s easy to incorporate.

2 Likes

Ah, sorry but I think I didn’t make myself clear. Again, I think we’re missing each other when it comes to the meaning of “pixels” and “subpixels.” It will be easier to explain with pictures :slight_smile:

That RBG mask isn’t what I was describing; see numbers 3 and 4 below for clarification.

Here are the mask types I’m describing and what they look like at 100% strength if you zoom in to where you can see the LCD subpixels. First image is the “mask” pattern and the second image shows what it looks like at the LCD subpixel level if you were to zoom in that far.

(zoomed in to 100x size):

image

This looks like this when you zoom in to the LCD’s physical subpixels (assuming the subpixels are the standard RGB arrangement):

image

This pattern is useful on a 4K display to lower the TVL / dot pitch; this pattern lowers it to 540 TVL compared to the above mask @ 4K, which results in 1080 TVL.

image

zoom in to where you can see the LCD subpixels, it looks like this:

image

Some LCDs use a nonstandard RBG arrangement for the subpixels. For such displays we want a yellow/blue pattern:

image

With RBG subpixels, it will look like this when you zoom in to see the subpixels:

image

Finally, we need a 4th pattern for 4K displays that have RBG subpixels:

image

Looks like this when you zoom in to the subpixels:

image

4 Likes

Horizontally, there should be no problems, many nice algorithms exist that get the job done, for example gaussian, lanczos, quilez, polynomial…even some sort of sharp bilinear.

In order to calculate scanlines it’s important that the sum of brightness is very similar, even if they consist from a different number of “subpixels”. The hard way to achieve this (minding additional parameters) is to use a complex function, which makes sure the above is happening. The easy way is to use some sort of integer scaling, a higher resolution display or vertical supersampling… Using algorithms like sharp-bilinear for scanline calculation is out of it’s scope since it doesn’t consider brightness. Or better said, it can’t calculate scanlines. :neutral_face:

It’s a very fine algorithm though.

2 Likes

To simplify, here are the patterns that should definitely be included in the shader. If you can add these, it will be complete. The other patterns (the three-pixel wide red, blue, green and red, green, blue patterns) will result in uneven spacing of the LCD subpixels, but I guess they can still be included just for the sake of having more options (doesn’t really affect performance). For a more detailed explanation, see my previous post. Thanks again for this great shader! :slight_smile:

image

image

image

image

3 Likes

Done, the shader is updated, but it’s better checked for possible errors. :smiley: Too bad i don’t have a 4k display at hand to test the mask. They sure look great.

I took a liberty to include the “aperture” RGB mask, sure it has it’s fans…

3 Likes

All the masks look great!

This is just nitpicking, but I might change the label

“(2, 3, 4 4k masks)”

to

“(3, 4 4k masks)”

Since mask 2 is really intended for 1080p displays that have the less common RBG subpixel arrangement.

And I might swap mask 1 for mask 2 for better ordering.

Then you could write in the shader comments something like this:

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.

Or you could put what is currently mask 1 where mask 4 is, change 4 to 3 and change 3 to 2; then change the above accordingly. Again, just some nitpicking over the organization :stuck_out_tongue:

2 Likes

Sure and thanks for the detailded explanation, it’s now included in the shader. :smiley:

To summarize the efforts, i’ll post the shader again, along with the preset. Also some redundancies were corrected.

crt-guest-sm.glsl (goes into crt/shaders/guest ):

/*
   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 brightboost "Bright boost" 1.15 0.5 2.0 0.05
#pragma parameter scanline "Scanline adjust" 8.0 1.0 12.0 1.0
#pragma parameter beam_min "Scanline dark" 1.35 0.5 2.0 0.05
#pragma parameter beam_max "Scanline bright" 1.05 0.5 2.0 0.05
#pragma parameter s_gamma "Scanline gamma" 2.4 1.5 3.0 0.05
#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 maskstr "Raw CRT Mask Strength" 0.15 0.0 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 brightboost;
uniform COMPAT_PRECISION float scanline;
uniform COMPAT_PRECISION float beam_min;
uniform COMPAT_PRECISION float beam_max;
uniform COMPAT_PRECISION float s_gamma;
uniform COMPAT_PRECISION float h_sharp;
uniform COMPAT_PRECISION float mask;
uniform COMPAT_PRECISION float maskstr;
uniform COMPAT_PRECISION float masksize;
uniform COMPAT_PRECISION float gamma_out;
#else
#define brightboost  0.00     // smart Y integer scaling
#define brightboost  1.15     // adjust brightness
#define scanline     8.00     // scanline param, vertical sharpness
#define beam_min     1.35     // dark area beam min - narrow
#define beam_max     1.05     // bright area beam max - wide
#define s_gamma      2.40     // scanline gamma
#define h_sharp      1.25     // pixel sharpness
#define mask         0.00     // crt mask
#define maskstr      0.15     // raw crt mask strength
#define masksize     1.00     // crt mask size
#define gamma_out    2.40     // gamma out
#endif

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

vec3 sw(float x, vec3 color)
{
	vec3 tmp = mix(vec3(2.75*beam_min),vec3(beam_max), color);
	tmp = mix(vec3(beam_max), tmp, pow(vec3(x), color+0.3));
	vec3 ex = vec3(x)*tmp;
	return exp2(-scanline*ex*ex)/(0.65 + 0.35*color);
}

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 = 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);
	
	vec3 color1 = w*(ur*lx + ul*rx);
	vec3 color2 = w*(dr*lx + dl*rx);


	ul*=ul*ul; ul*=ul;
	ur*=ur*ur; ur*=ur;
	dl*=dl*dl; dl*=dl;
	dr*=dr*dr; dr*=dr;	
	
	vec3 scolor1 = w*(ur*lx + ul*rx); scolor1 = pow(scolor1, vec3(s_gamma*(1.0/12.0)));
	vec3 scolor2 = w*(dr*lx + dl*rx); scolor2 = pow(scolor2, vec3(s_gamma*(1.0/12.0)));	
	
// calculating scanlines
	
	float f = fp.y;

	float t1 = st(f);
	float t2 = st(1.0-f);
	
	vec3 color = color1*t1 + color2*t2;
	vec3 scolor = scolor1*t1 + scolor2*t2;
	
	vec3 ctemp = color / (t1 + t2);
	vec3 sctemp = scolor / (t1 + t2);
	
	vec3 cref1 = mix(scolor1, sctemp, 0.35);
	vec3 cref2 = mix(scolor2, sctemp, 0.35);
	
	vec3 w1 = sw(f,cref1);
	vec3 w2 = sw(1.0-f,cref2);
	
	color = color1*w1 + color2*w2;
	color = min(color, 1.0);
	
	vec3 scan3 = vec3(0.0);
	float spos = floor((gl_FragCoord.x * 1.000001)/masksize); float spos1 = 0.0;
	vec3 tmp1 = 0.5*(ctemp+sqrt(ctemp));

	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)
	{
		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;
	}
	
	color = mix(1.15*scan3, color, (1.0-maskstr)*tmp1)*(1.0 + 0.15*maskstr);
	
	color*=brightboost;
	float corr = (max(max(color.r,color.g),color.b) + 0.0001);
	if (corr < 1.0) corr = 1.0;
	color = color/corr;

	color = pow(color, vec3(1.0/gamma_out));
    FragColor = vec4(color, 1.0);
} 
#endif

crt-guest-sm.glslp:

shaders = 2

shader0 = shaders/guest/d65-d50.glsl
filter_linear0 = false
scale_type0 = source
scale0 = 1.0

shader1 = shaders/guest/crt-guest-sm.glsl

Edit: some last tweaks, corrected default gamma a bit more.

7 Likes

Hello,

Thank you for this new shader

Would it be possible to add the glow parameter, I would like to test a parameter similar to this with your new shader

https://forums.libretro.com/t/please-show-off-what-crt-shaders-can-do/19193/914

This new combination is much less complex than crt-guest-dr-venom (2 shaders combination) so it would seem much easier to add the GTU shader

Are the scan lines with the spike mitigation scanline as you mentioned in another thread?

Thanks

2 Likes

6 posts were split to a new topic: Big crt mask function for copypaste

@guest.r

Just wanted to say that the smart y integer scaling is fantastic! I had the same idea a while back but didn’t have the skills to make it happen. Very handy for PSX games that use multiple resolutions! Tried it with FFVII last night. You don’t even notice the resolution changing when you bring up the menu screen or switch to the overworld/map, because it happens so quickly. Perfect! :+1:

Christmas came early this year :smiley:

When is GLSL going to have 2d arrays? If that’s coming soon, I’d say just wait…

Speaking of slang, how do I get glcore? Will this be automatically added if I update my drivers, if the graphics card supports it?

1 Like

I made a nice setup for you with glow support below. The name of the shader must be considered though in order to work with the preset.

It’s best added as first passes, so it won’t break any other effects.

Yes, it’s included in this shader also. Otherwise it wouldn’t look properly for me any more.

crt-guest-sm-glow.glsl (goes into crt/shaders/guest ):

/*
   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 brightboost "Bright boost" 1.15 0.5 2.0 0.05
#pragma parameter scanline "Scanline adjust" 8.0 1.0 12.0 1.0
#pragma parameter beam_min "Scanline dark" 1.35 0.5 2.0 0.05
#pragma parameter beam_max "Scanline bright" 1.05 0.5 2.0 0.05
#pragma parameter s_gamma "Scanline gamma" 2.4 1.5 3.0 0.05
#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 maskstr "Raw CRT Mask Strength" 0.15 0.0 1.0 0.05
#pragma parameter masksize "CRT Mask Size" 1.0 1.0 2.0 1.0
#pragma parameter glow "Glow Strength" 0.05 0.0 0.5 0.01
#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;
uniform sampler2D PassPrev4Texture;
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 brightboost;
uniform COMPAT_PRECISION float scanline;
uniform COMPAT_PRECISION float beam_min;
uniform COMPAT_PRECISION float beam_max;
uniform COMPAT_PRECISION float s_gamma;
uniform COMPAT_PRECISION float h_sharp;
uniform COMPAT_PRECISION float mask;
uniform COMPAT_PRECISION float maskstr;
uniform COMPAT_PRECISION float masksize;
uniform COMPAT_PRECISION float glow;
uniform COMPAT_PRECISION float gamma_out;
#else
#define brightboost  0.00     // smart Y integer scaling
#define brightboost  1.15     // adjust brightness
#define scanline     8.00     // scanline param, vertical sharpness
#define beam_min     1.35     // dark area beam min - narrow
#define beam_max     1.05     // bright area beam max - wide
#define s_gamma      2.40     // scanline gamma
#define h_sharp      1.25     // pixel sharpness
#define mask         0.00     // crt mask
#define maskstr      0.15     // raw crt mask strength
#define masksize     1.00     // crt mask size
#define glow         0.05     // glow strength
#define gamma_out    2.40     // gamma out
#endif

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

vec3 sw(float x, vec3 color)
{
	vec3 tmp = mix(vec3(2.75*beam_min),vec3(beam_max), color);
	tmp = mix(vec3(beam_max), tmp, pow(vec3(x), color+0.3));
	vec3 ex = vec3(x)*tmp;
	return exp2(-scanline*ex*ex)/(0.65 + 0.35*color);
}

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 = 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(PassPrev4Texture, pC4                         ).xyz; ul*=ul;
	vec3 ur = COMPAT_TEXTURE(PassPrev4Texture, pC4 + vec2(SourceSize.z,0.0)).xyz; ur*=ur;
	vec3 dl = COMPAT_TEXTURE(PassPrev4Texture, pC4 + vec2(0.0,SourceSize.w)).xyz; dl*=dl;
	vec3 dr = COMPAT_TEXTURE(PassPrev4Texture, 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);
	
	vec3 color1 = w*(ur*lx + ul*rx);
	vec3 color2 = w*(dr*lx + dl*rx);


	ul*=ul*ul; ul*=ul;
	ur*=ur*ur; ur*=ur;
	dl*=dl*dl; dl*=dl;
	dr*=dr*dr; dr*=dr;	
	
	vec3 scolor1 = w*(ur*lx + ul*rx); scolor1 = pow(scolor1, vec3(s_gamma*(1.0/12.0)));
	vec3 scolor2 = w*(dr*lx + dl*rx); scolor2 = pow(scolor2, vec3(s_gamma*(1.0/12.0)));	
	
// calculating scanlines
	
	float f = fp.y;

	float t1 = st(f);
	float t2 = st(1.0-f);
	
	vec3 color = color1*t1 + color2*t2;
	vec3 scolor = scolor1*t1 + scolor2*t2;
	
	vec3 ctemp = color / (t1 + t2);
	vec3 sctemp = scolor / (t1 + t2);
	
	vec3 cref1 = mix(scolor1, sctemp, 0.35);
	vec3 cref2 = mix(scolor2, sctemp, 0.35);
	
	vec3 w1 = sw(f,cref1);
	vec3 w2 = sw(1.0-f,cref2);
	
	color = color1*w1 + color2*w2;
	color = min(color, 1.0);
	
	vec3 scan3 = vec3(0.0);
	float spos = floor((gl_FragCoord.x * 1.000001)/masksize); float spos1 = 0.0;
	vec3 tmp1 = 0.5*(ctemp+sqrt(ctemp));

	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)
	{
		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;
	}
	
	color = mix(1.15*scan3, color, (1.0-maskstr)*tmp1)*(1.0 + 0.15*maskstr);
	
	color*=brightboost;
	float corr = (max(max(color.r,color.g),color.b) + 0.0001);
	if (corr < 1.0) corr = 1.0;
	color = color/corr;

	vec3 Bloom = COMPAT_TEXTURE(Texture, tex).xyz;	
	
	color+= Bloom*glow;
	
	color = pow(color, vec3(1.0/gamma_out));
    FragColor = vec4(color, 1.0);
} 
#endif

crt-guest-sm-glow.glslp

shaders = 5

shader0 = shaders/guest/d65-d50.glsl
filter_linear0 = false
scale_type0 = source
scale0 = 1.0

shader1 = shaders/guest/linearize.glsl
filter_linear1 = false
scale_type1 = source
scale1 = 1.0
float_framebuffer1 = true

shader2 = shaders/guest/blur_horiz.glsl
filter_linear2 = false
scale_type2 = source
scale2 = 1.0
float_framebuffer2 = true

shader3 = shaders/guest/blur_vert.glsl
filter_linear3 = true
scale_type3 = source
scale3 = 1.0
float_framebuffer3 = true 

shader4 = shaders/guest/crt-guest-sm-glow.glsl
filter_linear4 = true 

Hey man, thanks. :smiley: Some content’s really look better with something like this… The story started with the overscan shader for some PSX games and luckily i toyed with a (homemade) crt shader that simply needed integer scaling.

4 Likes