New CRT shader from Guest + CRT Guest Advanced updates

Also different manufacturers had slightly different color spaces/tunings.

4 Likes

IIRC there were 2 different types of color space in phosphor sets of CRTs in the 90s.

These CRTs made in 90s early, used P22 phosphors, which either less-saturated or over-saturated look - also most people called “nostalgia” in now days - for example: all before Sony PVM-M2 (including M2) models used P22; and the CRTs made in 90s later, used SMPTE-C phosphors - :PVM-M4, L4, L5 and BVM-D, the colors are petty much get closed to sRGB.

Most CRT TVs (especially those cheap ones) still using P22 phosphors in the whole 90s, SMPTE-C phosphors only used in high-end CRTs.

4 Likes

Great work mate .I liked mask 8 , it has a way of interpreting color graduate.

Is it possible that you can implement a feature for internal resolution . This will help with flycast ,dolphin and the upcoming PCSX2.

Hyllian has CRT shader that does that “3d Hyllian”

2 Likes

As we know, RGB/SCART (and svideo) was definitely a thing, though the vast majority of users would be using composite/RF, so I think it’s a solid guess that they tried to pack as much detail in as possible–to benefit the users with high-quality equipment/connections–without muddying the overall presentation to the detriment of those without (not unlike what we’re trying to do with the CRT shaders at 720p/1080p/4K/+ :slight_smile: ).

In the specific case of Super Metroid, the NTSC blending/smearing turns a lot of the visible-via-RGB small details into depth and contour, which is a real achievement, IMO. That is, making pixel art that’s among the best ever produced on both levels simultaneously.

3 Likes

The hires and ntsc versions already support internal resolutions, but not 2D/3D selective shading. I really didn’t plan to add the latter, since it would double the shaders and presets.

thanks for replying , it does indeed work

1 Like

I just discovered that the white pixel on my LG C9 is a ‘boost’ that can be enabled if you turn on “peak white”. Otherwise it is not used. But that means I still have BGR pixel setup. Which mask should I use? I notice that Mask 0, 7, and 8 look greyscale to me – no RGB like grids while others do. What should I make of this? BTW the fact that peak white is optional explains why it’s been said that LG was able to increase brightness without sacrificing resolution. @guest.r

4 Likes

Mask 7 is BW indeed. Masks 0 and 5 should have even sub-pixel spacing even on a BGR/RBG at 1080p, not minding W. Masks 1-4 should do quite fine. The only RGB specific mask is mask 8. It’s good to know that using mask size of 2.0 breaks sub-pixel distancing basically on all displays, even if a mask complies at size of 1.0.

3 Likes

I was wondering, would it be possible to add an option to rotate the mask you choose 90 degrees to either side? I know that there is TATE mode but that also flips the scanlines as if I have my display set in portrait mode.

I ask because I have a TV that is a Vertical BGR subpixel layout, and because of this it makes it very difficult to find a mask that lines up properly with my TV’s subpixel layout. I thought it would be a good idea after reading up on hunterk’s blog on CRT Shader Masks, and I think it would make it a lot easier to find a mask that looks right on my TV, even if it isn’t accurate to how CRT Masks actually looked.

2 Likes

Since i’m avoiding too many parameters and i’m even keen to remove some, this won’t be included in general i think.

But you can still try with a little fix in crt-guest-dr-venom.slang - line number 740 and some:

float smask = (notate) ? SlotMask(gl_FragCoord.xy * 1.000001, mx) : SlotMask(gl_FragCoord.yx * 1.000001, mx);

cmask*= (notate) ? Mask(gl_FragCoord.xy * 1.000001, mx) : Mask(gl_FragCoord.yx * 1.000001, mx);

You can try switching the swizzle on the gl_FragCoord, which means .xy -> .yx etc.

This will calculate the mask vertically. OTOH Mask 7 is black-white, it should be put to a decent use, if the effect is too slim, increase the mask size to 2.0.

2 Likes

@c9f5fdda06

That’s good news about the white subpixel.

Replace the relevant code with this. Another good option that requires no alteration to the code is the guest dr venom slotmask; set CRT mask to -1.00 when using it.

@guest.r is correct that you still get “regular” subpixel spacing using RGB subpixel masks with BGR subpixels, but the active subpixels are all adjacent to each other and this results in a definite loss of image quality and phosphor definition, in my experience.

Mask 0:

// Phosphor.
else if (shadowMask == 0.0)
{
	pos.x = fract(pos.x*0.5);
	if (pos.x < 0.5) { mask.r = mc; mask.g = 1.0; mask.b = mc; }
	else { mask.r = 1.0; mask.g = mc; mask.b = 1.0; }
}

For mask 8:

// 4k mask
else 
{
	mask = vec3(mc);
	pos.x = fract(pos.x * 0.25);
	if      (pos.x < 0.2)  mask.b = 1.0;
	else if (pos.x < 0.4)  mask.bg = 1.0.xx;
	else if (pos.x < 0.7)  mask.gr = 1.0.xx;	
	else                   mask.r = 1.0;	
}     

return mask;
}
2 Likes

speaking of masks, changing the black pixel in the red,green,blue,black mask to white keeps the brightness up, though I don’t know how it’ll affect your contrast ramp, @Nesguy

Making it a 2-row staggered pattern:

red, green, blue, white
blue, white, red, green

makes a pretty nice lower-TVL shadowmask alternative to the magenta/green checkerboard (works with a black pixel, too, but it makes it pretty dark).

latter on the left, former on the right:

7 Likes

@hunterk

I’m going to give this a try, but how do I say “black” and “white” in slang?

1 Like

Ye, I don’t really understand how to set color patterns with the masks myself tbh.

I found a sweet spot I enjoy between PVM like really visible scanlines while keeping adjacent pixels blending, comparable to what crt-geom does.
Clarity is great while still rounding corners to suit the intended drawings (like circle shapes).

shaders = "9"
shader0 = "shaders_slang/misc/ntsc-colors.slang"
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 = "crt-guest/shaders/guest/crt-gdv-new/stock.slang"
filter_linear1 = "false"
wrap_mode1 = "clamp_to_border"
mipmap_input1 = "false"
alias1 = "StockPass"
float_framebuffer1 = "false"
srgb_framebuffer1 = "false"
scale_type_x1 = "source"
scale_x1 = "1.000000"
scale_type_y1 = "source"
scale_y1 = "1.000000"
shader2 = "crt-guest/shaders/guest/crt-gdv-new/afterglow0.slang"
filter_linear2 = "false"
wrap_mode2 = "clamp_to_border"
mipmap_input2 = "false"
alias2 = "AfterglowPass"
float_framebuffer2 = "false"
srgb_framebuffer2 = "false"
scale_type_x2 = "source"
scale_x2 = "1.000000"
scale_type_y2 = "source"
scale_y2 = "1.000000"
shader3 = "crt-guest/shaders/guest/crt-gdv-new/pre-shaders-afterglow.slang"
filter_linear3 = "false"
wrap_mode3 = "clamp_to_border"
mipmap_input3 = "false"
alias3 = "PrePass"
float_framebuffer3 = "false"
srgb_framebuffer3 = "false"
scale_type_x3 = "source"
scale_x3 = "1.000000"
scale_type_y3 = "source"
scale_y3 = "1.000000"
shader4 = "crt-guest/shaders/guest/crt-gdv-new/avg-lum.slang"
filter_linear4 = "true"
wrap_mode4 = "clamp_to_border"
mipmap_input4 = "true"
alias4 = "AvgLumPass"
float_framebuffer4 = "false"
srgb_framebuffer4 = "false"
scale_type_x4 = "source"
scale_x4 = "1.000000"
scale_type_y4 = "source"
scale_y4 = "1.000000"
shader5 = "crt-guest/shaders/guest/crt-gdv-new/linearize.slang"
filter_linear5 = "true"
wrap_mode5 = "clamp_to_border"
mipmap_input5 = "false"
alias5 = "LinearizePass"
float_framebuffer5 = "true"
srgb_framebuffer5 = "false"
scale_type_x5 = "source"
scale_x5 = "1.000000"
scale_type_y5 = "source"
scale_y5 = "1.000000"
shader6 = "crt-guest/shaders/guest/crt-gdv-new/blur_horiz2.slang"
filter_linear6 = "true"
wrap_mode6 = "clamp_to_border"
mipmap_input6 = "false"
alias6 = ""
float_framebuffer6 = "false"
srgb_framebuffer6 = "false"
scale_type_x6 = "absolute"
scale_x6 = "800"
scale_type_y6 = "source"
scale_y6 = "1.000000"
shader7 = "crt-guest/shaders/guest/crt-gdv-new/blur_vert2.slang"
filter_linear7 = "true"
wrap_mode7 = "clamp_to_border"
mipmap_input7 = "false"
alias7 = "GlowPass"
float_framebuffer7 = "false"
srgb_framebuffer7 = "false"
scale_type_x7 = "absolute"
scale_x7 = "800"
scale_type_y7 = "absolute"
scale_y7 = "600"
shader8 = "crt-guest/shaders/guest/crt-gdv-new/crt-guest-dr-venom2.slang"
filter_linear8 = "true"
wrap_mode8 = "clamp_to_border"
mipmap_input8 = "false"
alias8 = ""
float_framebuffer8 = "false"
srgb_framebuffer8 = "false"
scale_type_x8 = "viewport"
scale_x8 = "1.000000"
scale_type_y8 = "viewport"
scale_y8 = "1.000000"
AS = "0.000000"
CP = "-1.000000"
glow = "0.000000"
gamma_c = "1.100000"
brightboost1 = "1.200000"
gsl = "2.000000"
scanline2 = "32.000000"
spike = "0.000000"
h_sharp = "3.600000"
s_sharp = "1.000000"
shadowMask = "-1.000000"
textures = "SamplerLUT1;SamplerLUT2;SamplerLUT3"
SamplerLUT1 = "crt-guest/shaders/guest/lut/sony_trinitron1.png"
SamplerLUT1_linear = "true"
SamplerLUT1_wrap_mode = "clamp_to_border"
SamplerLUT1_mipmap = "false"
SamplerLUT2 = "crt-guest/shaders/guest/lut/sony_trinitron2.png"
SamplerLUT2_linear = "true"
SamplerLUT2_wrap_mode = "clamp_to_border"
SamplerLUT2_mipmap = "false"
SamplerLUT3 = "crt-guest/shaders/guest/lut/other1.png"
SamplerLUT3_linear = "true"
SamplerLUT3_wrap_mode = "clamp_to_border"
SamplerLUT3_mipmap = "false"
8 Likes

@guest.r

Looking at the CRT shots you posted I noticed one of the effects of the shadow mask is that it seems to “dither” the outside of the scanlines. Since all current mask simulation has the large drawback of darkening the image, I was thinking about only simulating that dithering effect on the outside of scanlines.

A naive and simple approach would be to shift Y column up by 1 pixel every other pixel / column . From normal viewing distance this creates the illusion of dithering on the outside of the scanlines, like real mask on CRT does, with the big win that brightness is not affected.

I’m sure better looking implementations of this concept can be thought of, this is purely a paint program mockup to demonstrate the basic idea and effect. Since it uses only 1 pixel shift this is -not- for 4K (it would need bigger size / shift of course :wink: ) Watch at true size from normal viewing distance on 1080p / 1440p.

metal slug - 1 pixel y shift effect

2 Likes

It could be also normal blending instead of dithering, but i get the idea. There are more downsides though related to low-res pixel fetching and attributing it in high res, otherwise i’d use something similar to smooth the scanlines at non-integer scaling etc.

An after-pass could do it, but i’m avoiding it in general. Scanline smoothing for example went very well with extra pass, not minding the speed loss. There is currently no afterpass for scanline smoothing, although it works nice, because the performance degradation.

1 Like

I’m not sure if normal blending is the same in this situation.

I got the idea by the “produced by” in the CRT shot, see cutout below. If you look at the top row of white pixels of the “produced by”, it’s as if every other pixel / column is shifted “one pixel” up (if we would talk in terms of shader output size) .

This effect makes the scanline very different from high TVL PVM, the scanline is not as flat as BVM, but has “an illusion” bulging pixel pattern to it. That’s where my idea came from :slight_smile:. As mentioned I think the big win could be that part of real mask is simulated without any brightness loss.

shifting y 1 pixel every other column

Shifting Y column up by some amount every other column would produce a similar effect with the shader, but we would need to load up that raw screenshot of turrican in the image viewer to see if applying this effect would produce similar "dithering " pattern on the top pixel line of “produced by” as below from real CRT.

5 Likes

It’s a bit similar to the ‘coarse’ trinitron mask in crt-guest-sm, but, as i said, is easily achievable by an extra pass. The screenshot jaggies though are mask related to deconvergence and unequally fading phosphors, since they are more saturated. I can post the pass you can try it a bit later.

Shader, last pass, Scale1x, Filtering Linear

#version 450

layout(push_constant) uniform Push
{
	vec4 SourceSize;
	vec4 OriginalSize;
	vec4 OutputSize;
	uint FrameCount;
	float dsize;
} params;

#pragma parameter dsize "Scanline dithering size" 1.0 0.0 2.0 0.10
#define dsize         params.dsize     // Dithering size

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;

void main()
{

   vec2 tex = vTexCoord;
   vec2 pos = tex * params.OutputSize.xy;
   vec2 dy  = dsize*vec2(0.0, params.OutputSize.w);
   float mixer = floor(mod(pos.x,2.0));
   tex = mix(tex + 0.0*dy, tex - dy, mixer);
   
   FragColor = vec4(texture(Source, tex).rgb, 1.0);
}
6 Likes

Looks very good, similar to my maxDR preset before I broke it. Thanks for sharing your settings!

edit: I think it’s safe to increase bright boost a bit, here it is with 1.80/1.80. I also added grade and set white point to ~7500K


@hunterk are we still waiting on github permissions to be restored? :rofl:

Also, what’s slang for “white” and “black”? I know how to say red, green and blue, but white and black still elude me. I want to try that mask!