New CRT shader from Guest + CRT Guest Advanced updates

This thread is about two shaders, namely crt-guest-sm and crt-guest-advanced. The first part of the thread is about crt-guest-sm, it’s latest versions are available in the official libretro repositories.

The end of the thread contains updates and feedback on my continued development of the later crt-guest-advanced (former crt-guest-dr-venom(2)) - slang shaders.

The latest crt-guest-advanced versions and presets are now in the official libretro shaders/shaders_slang/crt/ folder.

Anyway, get the former official version of crt-guest-advanced here (2022-07-27- release_1):

https://mega.nz/file/t4JlzISC#8FqqbBnVexxQDhk-o6THW_6US32zSun3cwfws7m2EbU

Newest version (03.27.2024):

https://mega.nz/file/1kogSL5Z#XpnDLAEJdx_yPApXPklk6MinukdhGW4LumEgr5-EqUI

Prior version still containing hires, ntsc-fast presets, dr-venom and SM versions:

https://mega.nz/file/JsZ12Y6D#ERhg_0ZZhtl3WahqvLAlg_5DlE3z5ZZ6WGweaE34sV8

Latest changelogs:

  • Double internal resolution support added (i.e. 224.0 -> 448.0), replaces 240p, which can be respectively achieved with further internal resolution dividers.
  • Performance optimizations, up to 10% speed boost
  • Compatibility mode added to fastest version, should now work with D3D drivers
  • Substractive options added to bloom passes for better shaping of bloom and halation effect
  • Stronger filtering mode with interlaced mode implemented (standard, fast versions)
  • “mclip” parameter now defaults 0.0
  • added a new option to clean up / adjust mask-scanline interaction in brighter tones when mask effects is too dominant between two of them
  • mask boost feature is now ‘edge’ aware
  • some scanline parameter range adjustments
  • mask bloom overhaul, some minor changes
  • scanline saturation has now 2 operating modes (one for positive, other for negative values), current presets aren’t affected by changes.
  • substractive sharpness parameter value increased to 2.0 (standard, fast, fastest versions), the filtering with this mode is improved.
  • Mask gamma now affects ordinary bloom, contrast improvements.
  • Magic Glow added, mask 2&6 fixes for slotmask alignment.
  • Magic Glow improvements, mask control.
  • Slotmask interaction mitigation implemented.
  • Brightness adjustments for interlaced modes.
  • Chroma scaling options added (ntsc), maximum sharpness parameters added to HD and ntsc versions.
  • slotmask automatic width implemented, support for 16-size luts added
  • mask zoom feature added
  • mask zoom overhaul
  • much better no-scanline mode
  • mask “shrink” implementation, more fixes
  • Base Mask strength feature added
  • ShadowMask options changed, new NTSC phase mode added
  • New functionality under ‘negative’ mask bloom parameter. Fine bloom sampling option added.
  • Mask strength controls for bloom and halation added.
  • Options for thin dark scanlines, per-channel gamma correction and negative bloom distribution added.
  • New functionality “Clip Saturated Color Beams” added.
  • Smart Sharpen Scanlines feature added. New ‘negative’ mask bloom.
  • Scanline saturation functionality overhaul.
  • Lite version added, grade shader update.
  • Mask bloom added to the fastest version.
  • Mask Zoom sharpen option added.
  • VGA Single/Double scan mode added.
  • Mask Zoom and Clip Beams added to the fastest version.
  • Fastest version FF mode fix.
  • Fastest version overhaul, NTSC version improvements.
  • Deblur added to NTSC version.
  • NTSC version scaler support added, various improvements.
  • High Resolution Scanlines option added to HD and NTSC versions.
  • High Resolution Scanlines added to standard and fast versions. Fine Glow Sampling added.
  • Rainbow effect added to the ntsc shaders.
  • Improved corner functionality.
  • Smart downsampling mode added (under interlacing options).
  • Ntsc # of taps and chroma fall-off implemented.
  • Ntsc pass-2 reformation.
  • Magic Glow alternate mode added (2.0).
  • Ntsc taps improvements, halation+ improvements.
  • Preserve mask strength feature re-implemented.

Here is an older version before some overhaul, for comparison, convenience etc…(2022-02-01-r1)

https://mega.nz/file/YkIwiJRJ#-_r40CXGhaL1yuhZqO8uL7PjI5vWigdmrc-N8bVaOvg

And a link to the (newest) ntsc-adaptive slang shaders, which go into the “ntsc/shaders/ntsc-adaptive” folder:

https://mega.nz/file/wpQBFQiY#Eqs9yIzHtd96t-_woH0kKRwBjmeFy2Z5R8nOAUUwwBc

Current ReShade versions ported by @DevilSingh:

Newest ReShade versions.

@DevilSingh’s ReShade port of crt-guest-dr-venom (with mask options updated)

https://mega.nz/file/NlY2HajS#15tz7rifdJ9y3ygRZ5PyHKn7QGIH2aviEw5t0iMUHYs

@DevilSingh’s ReShade port of crt-guest-advanced (with mask options updated)

https://mega.nz/file/s9B2gLID#cNN5v83P7DYiTxc4ttyYCktq0HDEAQyC2axxhNGf9BI

crt-guest-sm:

Nesguy’s post brought me to an idea, for a new shader look. Been working on it this evening and would like some feedback. :nerd_face:

Do you think it’s a good addition to the shader (crt-guest-dr.-venom)?

45 Likes

Could you elaborate what the changes are? I’m assuming that it has to do either with the mask or hdr. Probably the mask though, lol.

The image does look nice on mobile, even when I zoom in.

4 Likes

The idea is to calculate “scanlines” for individual phosphors and then gradually merge them with regular scanlines. No masks are needed and i think the real CRT behaviour is emulated even better.

9 Likes

Hmmmm this raises a multitude of questions for me.

Are the scanline styles still a thing with this (0-2)?

Are we still able to to choose mask styles and strength for them? Seems like the mask styles are gone from what you said.

It does look pretty nice though, it’d be interesting to see it on a monitor/tv.

3 Likes

Do it! :heart_eyes: Sounds awesome

3 Likes

This is a similar concept to crt-caligari, I think.

4 Likes

Very excited to try this, I think it looks fantastic! Love the concept, too! Above shot looks great on my 6 year old TN panel with my backlight maxed out.

A few things to make it complete:

  1. Can you adjust the phosphors so that pure white appears as 100% magenta/green and the dark lines (“scanlines”) have a certain amount of black added to them? That would get us even closer to how phosphors worked on a CRT (pure white is 100% R, G and B). Normally, this results in an absurdly dark image, but CRT-Aperture does some kind of magic that preserves the brightness and color when increasing the mask strength this way.

  2. I’d also like to see the different phosphor arrangements described in this thread.

  3. a “switch phosphor colors” option that replaces magenta/green with yellow/blue for displays that have RBG subpixels (instead of the usual RGB arrangement)

  4. Options to adjust the scanlines by adjusting phosphor bloom (like on a CRT when you adjust contrast, but without actually altering the phosphor intensities)

  5. A separate option to adjust phosphor intensity independent of phosphor bloom

  6. sharpness adjustment (ideally, you would have a range here with 0 being not quite as blurry as bilinear filter and max being nearest neighbor)

  7. gamma and white balance adjustments

Then it would be my favorite shader :smiley:

3 Likes

@hunterk

Can you please split this into a new topic starting with this post? Don’t want this important discussion to get buried!

Please show off what crt shaders can do!

1 Like

Done. This should be a better place to discuss this shader.

3 Likes

Now i was quite motivated to create a proper shader for this thread. :sweat_smile:

I used a different template, much faster and simpler, GLSL only, but it should be enough for couriosity sake.

crt-guest-test.glsl (goes in crt/shaders)

/*
   CRT - Guest - Test
   
   Copyright (C) 2019 guest(r) - [email protected]

   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.
   
*/

// Parameter lines go here:
#pragma parameter brightboost "Bright boost" 1.20 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 h_sharp "Horizontal sharpness" 2.0 1.0 5.0 0.05
#pragma parameter mask "CRT Mask" 0.0 0.0 1.0 1.0
#pragma parameter maskstr "Raw CRT Mask Strength" 0.15 0.0 1.0 0.05
#pragma parameter gamma_out "Gamma Out" 2.20 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 brightboost;
uniform COMPAT_PRECISION float scanline;
uniform COMPAT_PRECISION float beam_min;
uniform COMPAT_PRECISION float beam_max;
uniform COMPAT_PRECISION float h_sharp;
uniform COMPAT_PRECISION float mask;
uniform COMPAT_PRECISION float maskstr;
uniform COMPAT_PRECISION float gamma_out;
#else
#define brightboost  1.20     // 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 h_sharp      1.25     // pixel sharpness
#define mask         0.00     // crt mask
#define maskstr      0.15     // raw crt mask strength
#define gamma_out    2.20     // 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);
}

void main()
{
	vec2 OGL2Pos = TEX0.xy * 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(0.166666666666667));
	vec3 scolor2 = w*(dr*lx + dl*rx); scolor2 = pow(scolor2, vec3(0.166666666666667));	
	
// 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.4);
	vec3 cref2 = mix(scolor2, sctemp, 0.4);
	
	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); float spos1 = 0.0;
	vec3 tmp1 = 0.5*(ctemp+sqrt(ctemp));
	
	if (mask == 1.0)
	{
	spos1 = fract(spos/3.0);
	if      (spos1 < 0.333)  scan3.x = w1.x*color1.r + w2.x*color2.r;
	else if (spos1 < 0.666)  scan3.y = w1.y*color1.g + w2.y*color2.g;
	else                     scan3.z = w1.z*color1.b + w2.z*color2.b;
	}
	else
	{
	spos1 = fract(spos/2.0);
	if      (spos1 < 0.5)  scan3.xz = w1.xz*color1.rb + w2.xz*color2.rb;
	else                   scan3.y  = w1.y* color1.g  + w2.y* color2.g;	
	}
	
	color = mix(1.2*scan3, color, (1.0-maskstr)*tmp1)*(1.0 + 0.2*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 + 0.3*mask)));		
    FragColor = vec4(color, 1.0);
} 
#endif

And the preset (goes to crt…):

shaders = 1

shader0 = shaders/crt-guest-test.glsl 

I also thought about Nesguy’s recommendations and he’s definetely very experienced not only with shaders, but with real crt technology also. I’m somewhat limited with 1080p and 60Hz / SDR atm., my HW changes are incomming, but haven’t decided yet what to buy. 4k would make things very interesting, but i’m leaning into an 1440p fast refresh rate purchase. Thankfully there are some geared and talented shader coders around also…:face_with_monocle:

Edit: nice shader update.

8 Likes

This looks great so far! Just needs the other options I mentioned earlier and it’ll be complete :smiley:

Going back to my first recommendation, is it possible to get white to appear as 100% magenta/green? This would more closely match what a CRT does (where white is actually created with 100% red, blue and green phosphors). CRT-Aperture with mask strength at 100% does this, but it looks like it also does something to maintain color/brightness/contrast when you do this; otherwise, you wind up wrecking the image quality. It’s recommended that you max out the backlight when doing this; a bright backlight and full array local dimming are also recommended.

I don’t know how essential that particular phosphor arrangement is to the shader, but the ability to switch between different phosphor patterns is also a must-have, IMO.

Not half as experienced as some of the people around here, but thanks! I’ve spent way too much time looking at CRTs and shaders up close. :sweat_smile:

If you want to do backlight/mask tricks, I’d check out the Samsung QLEDs. They’re the brightest displays you can get aside from models made for outdoor use; I actually looked into those and none of them are great as gaming displays due to input latency. The Samsung QLEDs all support 120 Hz modes so you can do black frame insertion to eliminate motion blur, and they also have less than 1 frame of input lag, although not all models support 4K @ 120 Hz, so that’s something to be aware of.

This is probably the best one. It’s definitely on the higher-end, price wise. Some of the other options are around $1,000 or less, though.

2 Likes

I won’t get many opportunities to say this, but man that is a good lookin’ Rash lol

3 Likes

Thanks for the kind words…i definitely took them into consideration.

The shader posted above got updated, now the default mask is magenta/green, but the other still hangs around too. Some redundant code improoved too…

Some screenshots with different mask:

Mask 0:

Mask 1:

4 Likes

Just tried the updated shader, looking very nice so far! Thanks for your efforts thus far. The magenta/green mask is a big improvement from the standpoint of accuracy since phosphors on a CRT were evenly spaced (and the magenta/green mask gives you even spacing of the LCD subpixels). The second mask option gives you uneven subpixel spacing which makes the blue “phosphors” less visible. Some may prefer it though because it results in a lower TVL (360 as opposed to 540 for the magenta/green mask, on a 1080p display).

I’m still not able to get the correct/desired behavior for very bright phosphors though. It seems like the phosphors disappear altogether over bright colors.

Here’s a closeup showing the correct behavior (correct being what a CRT actually does). This is at 5x vertical integer scale (5 pixels per scanline).

For this to look right you need to max out the LCD backlight and you also need some kind of correction for brightness/contrast/color/etc. CRT-Aperture handles this nicely but I don’t know how it works because I don’t know code.

Also, is there a way to get this concept to work with any phosphor arrangement, or is it limited to aperture grille? The magenta-green slotmask pattern and magenta-green dotmask pattern should be options, if it’s something that can be easily added.

Edit: the sharpness adjustment is excellent! Very CRT-like.

3 Likes

Sounds like a good suggestion. I’ve added a parameter “Raw CRT mask strength” which, at full capacity, produces pure RGB/magenta-green mask pictures (updated version is posted above).

About other dotmasks, pure horizontal ones are the prefered ones with this shader, as it emulates specific phosphor dot sizes per scanline (more or less…:grin:).

Slotmask can be added as a seperate function, but i try to keep the code more or less lightweight. It can be tried with crt-guest-dr-venom, but it likes high vertical display resolutions.

4 Likes

Very nice, thank you! This is perfect! :smiley:

In terms of looks, ease of use and realism, this now rivals CRT-Aperture, IMO. I really like the concept behind this one, though. This one might actually be slightly easier to understand/use, as well. This is now my new favorite shader!

Can you also throw in a parameter for white balance adjustment? That’s about the only other thing I can think of.

I think I can sort of almost understand the reason why it won’t work with dotmask; but it depends on how a “scanline” is defined and I don’t know code so I don’t really know how you’ve done it.

If a “scanline” is one row of pixels being output by the emulator, you could take the average intensity of all the pixels in that row, do some kind of calculation to determine a phosphor size adjustment based on that, and then apply that adjustment to all the pixels in that row when you scale the image.

Am I close at all? :stuck_out_tongue:

That would explain why it wouldn’t work with dotmask… but then you say it would work with slotmask, so I’m confused :upside_down_face: There’s a good chance I wouldn’t understand even if you tried explaining it, so no worries!

Very high vertical resolutions :smile:. You actually need 8K for the slotmask to produce something close to a realistic result, so slotmask emulation isn’t really an immediate concern but something to keep in mind for the future as 8K and higher displays become more common. When we’re old men we should be able to do a fairly convincing slotmask emulation, lol.

edit: I’ve been playing around with the shader some more and I’ve noticed that it’s possible to fine-tune this even more than CRT-Aperture; looks like the granularity of the adjustments is greater. Compared to CRT-Aperture, I also think I slightly prefer the way your new shader looks after playing around with the parameters a bit; looks like the scanlines taper in a way that is more natural. Threw a bunch of different games at it and everything looked fantastic. Very nice work!

2 Likes

Raw CRT mask strength @ 50%, and a few other tweaks. Must be viewed @ 1080p and at full size or the mask won’t scale correctly. Also helps to crank up the backlight.

alias0 = ""
beam_max = "1.200000"
beam_min = "1.600000"
brightboost = "1.000000"
float_framebuffer0 = "false"
gamma_out = "2.400000"
h_sharp = "2.000000"
mask = "0.000000"
maskstr = "0.500000"
mipmap_input0 = "false"
parameters = "brightboost;scanline;beam_min;beam_max;h_sharp;mask;maskstr;gamma_out"
scanline = "8.000000"
shader0 = "C:\Program Files\RetroArch\shaders\shaders_glsl\crt\shaders\crt-guest-test.glsl"
shaders = "1"
srgb_framebuffer0 = "false"
wrap_mode0 = "clamp_to_border"![xmvsf-191214-170512|685x500]
6 Likes

@guest.r I was wondering if if it was maybe possible to get other scanline (modes) like in crt-guest-dr-venom? The only reason I’m bringing it up is scanline code seems similar-ish in places so I was thinking it may not be too hard to do it.

1 Like

I can do that, sure. I included my standard shader for this, you can get a warm or a cool touch with the colors, plus saturation is included.

Pretty close. :smile: It’s based on a fact, that CRT phosphors don’t share the same coordinates on the display plane. So each “emulated” phosphor has a size depending on the color component strength and the mask effect is the result. But we got to mix it with regular “LCD” beams, because of the tricks optics play on us when we look at CRT’s from a greater distance.

Slotmask can be added seperately, it doesn’t have to be a part of the regular composition. But, as you mentioned, it gets better with high resolution displays with HDR.

Thanks indeed. :partying_face: I think it’s even better than my older trinitron masks (5&6).

Scanline code is a bit heavy (so i’m hesitating to add more of it), but it should od well if you adjust the mask-dark value below 1.0 and adjust saturation, gamma and brightboost…

Nevertheless, i think the shader is complete now. There are minor changes in naming and in which folder it should go. Last but not least, i added smart Y integer scaling which works very nice for me and got used to it. :crazy_face:

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.20 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 h_sharp "Horizontal sharpness" 2.0 1.0 5.0 0.05
#pragma parameter mask "CRT Mask (2,3,4 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.20 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 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.20     // 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 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.20     // 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(0.166666666666667));
	vec3 scolor2 = w*(dr*lx + dl*rx); scolor2 = pow(scolor2, vec3(0.166666666666667));	
	
// 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.2*scan3, color, (1.0-maskstr)*tmp1)*(1.0 + 0.2*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 + 0.2*mask)));		
    FragColor = vec4(color, 1.0);
} 
#endif

crt-guest-sm.glslp (goes into crt folder):

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: updated shader with mask size param and proper masks.

5 Likes

A little off topic but would it be possible to add the curvature x/y and corner size parameters to the dr venom fast shader as well?

2 Likes