New CRT shader from Guest + CRT Guest Advanced updates

Will do.

Seeing that the implementation in 2025-11-11-r1 worked very well in this scenario especially in the PC-Engine games I’ve tested and the latest version is quite good for the Genesis games you have tested, would it be possible going forward to have the ability to toggle between 2025-11-11-r1 and 2025-11-16-r1 implementations if you can’t find a “one size fits all solution” or maybe have the older implementation active when NTSC Phase Mode 5 is selected but the newer mode active when NTSC Mode 2 or Auto are active or have either (Font Preservation) implementation tied to whichever NTSC modes work best with each one.

Just thinking out loud here, I know you are the Grandmaster of CRT Shader Programming!

On another note, I can send some more PC-Engine font exmples if you wish. I know back in the day I saw some examples of blurry fonts in at least one of the Ys games.

3 Likes

The “Show original image” option is a useful addition to the shader. And the other features I haven’t tested yet look promising. Impressive! @guest.r

4 Likes

I hope you are playing that last game with the bug fix.

2 Likes

Yes with bugfix version 1.1 :grin:

1 Like

Would you mind sharing your shader settings? : )

2 Likes

Sure

-> Snes preset

shaders = "18"
shader0 = "shaders_slang/crt/shaders/guest/advanced/stock.slang"
alias0 = ""
wrap_mode0 = "clamp_to_border"
mipmap_input0 = "false"
filter_linear0 = "false"
float_framebuffer0 = "false"
srgb_framebuffer0 = "false"
scale_type_x0 = "source"
scale_x0 = "1.000000"
scale_type_y0 = "source"
scale_y0 = "1.000000"
shader1 = "shaders_slang/crt/shaders/guest/advanced/stock.slang"
alias1 = "StockPass"
wrap_mode1 = "clamp_to_border"
mipmap_input1 = "false"
filter_linear1 = "false"
float_framebuffer1 = "false"
srgb_framebuffer1 = "false"
scale_type_x1 = "source"
scale_x1 = "1.000000"
scale_type_y1 = "source"
scale_y1 = "1.000000"
shader2 = "shaders_slang/crt/shaders/guest/advanced/afterglow0.slang"
alias2 = "AfterglowPass"
wrap_mode2 = "clamp_to_border"
mipmap_input2 = "false"
filter_linear2 = "true"
float_framebuffer2 = "false"
srgb_framebuffer2 = "false"
scale_type_x2 = "source"
scale_x2 = "1.000000"
scale_type_y2 = "source"
scale_y2 = "1.000000"
shader3 = "shaders_slang/crt/shaders/guest/advanced/pre-shaders-afterglow.slang"
alias3 = "PrePass0"
wrap_mode3 = "clamp_to_border"
mipmap_input3 = "false"
filter_linear3 = "true"
float_framebuffer3 = "false"
srgb_framebuffer3 = "false"
scale_type_x3 = "source"
scale_x3 = "1.000000"
scale_type_y3 = "source"
scale_y3 = "1.000000"
shader4 = "shaders_slang/crt/shaders/guest/advanced/ntsc/ntsc-pass1.slang"
alias4 = "NPass1"
wrap_mode4 = "clamp_to_border"
mipmap_input4 = "false"
filter_linear4 = "false"
float_framebuffer4 = "true"
srgb_framebuffer4 = "false"
scale_type_x4 = "source"
scale_x4 = "4.000000"
scale_type_y4 = "source"
scale_y4 = "1.000000"
shader5 = "shaders_slang/crt/shaders/guest/advanced/ntsc/ntsc-pass2.slang"
alias5 = ""
wrap_mode5 = "clamp_to_border"
mipmap_input5 = "false"
filter_linear5 = "true"
float_framebuffer5 = "true"
srgb_framebuffer5 = "false"
scale_type_x5 = "source"
scale_x5 = "0.500000"
scale_type_y5 = "source"
scale_y5 = "1.000000"
shader6 = "shaders_slang/crt/shaders/guest/advanced/ntsc/ntsc-pass3.slang"
alias6 = ""
wrap_mode6 = "clamp_to_border"
mipmap_input6 = "false"
filter_linear6 = "true"
float_framebuffer6 = "false"
srgb_framebuffer6 = "false"
scale_type_x6 = "source"
scale_x6 = "1.000000"
scale_type_y6 = "source"
scale_y6 = "1.000000"
shader7 = "shaders_slang/crt/shaders/guest/advanced/custom-fast-sharpen.slang"
alias7 = "NtscPass"
wrap_mode7 = "clamp_to_border"
mipmap_input7 = "false"
filter_linear7 = "true"
float_framebuffer7 = "false"
srgb_framebuffer7 = "false"
scale_type_x7 = "source"
scale_x7 = "1.000000"
scale_type_y7 = "source"
scale_y7 = "1.000000"
shader8 = "shaders_slang/crt/shaders/guest/advanced/stock.slang"
alias8 = "PrePass"
wrap_mode8 = "clamp_to_border"
mipmap_input8 = "true"
filter_linear8 = "true"
float_framebuffer8 = "false"
srgb_framebuffer8 = "false"
scale_type_x8 = "source"
scale_x8 = "1.000000"
scale_type_y8 = "source"
scale_y8 = "1.000000"
shader9 = "shaders_slang/crt/shaders/guest/advanced/avg-lum-ntsc.slang"
alias9 = "AvgLumPass"
wrap_mode9 = "clamp_to_border"
mipmap_input9 = "true"
filter_linear9 = "true"
float_framebuffer9 = "false"
srgb_framebuffer9 = "false"
scale_type_x9 = "source"
scale_x9 = "1.000000"
scale_type_y9 = "source"
scale_y9 = "1.000000"
shader10 = "shaders_slang/crt/shaders/guest/advanced/linearize-ntsc.slang"
alias10 = "LinearizePass"
wrap_mode10 = "clamp_to_border"
mipmap_input10 = "false"
filter_linear10 = "true"
float_framebuffer10 = "true"
srgb_framebuffer10 = "false"
scale_type_x10 = "source"
scale_x10 = "1.000000"
scale_type_y10 = "source"
scale_y10 = "1.000000"
shader11 = "shaders_slang/crt/shaders/guest/advanced/crt-guest-advanced-ntsc-pass1.slang"
alias11 = "Pass1"
wrap_mode11 = "clamp_to_border"
mipmap_input11 = "false"
filter_linear11 = "true"
float_framebuffer11 = "true"
srgb_framebuffer11 = "false"
scale_type_x11 = "viewport"
scale_x11 = "1.000000"
scale_type_y11 = "source"
scale_y11 = "1.000000"
shader12 = "shaders_slang/crt/shaders/guest/hd/gaussian_horizontal.slang"
alias12 = ""
wrap_mode12 = "clamp_to_border"
mipmap_input12 = "false"
filter_linear12 = "true"
float_framebuffer12 = "true"
srgb_framebuffer12 = "false"
scale_type_x12 = "absolute"
scale_x12 = "800"
scale_type_y12 = "source"
scale_y12 = "1.000000"
shader13 = "shaders_slang/crt/shaders/guest/advanced/gaussian_vertical.slang"
alias13 = "GlowPass"
wrap_mode13 = "clamp_to_border"
mipmap_input13 = "false"
filter_linear13 = "true"
float_framebuffer13 = "true"
srgb_framebuffer13 = "false"
scale_type_x13 = "absolute"
scale_x13 = "800"
scale_type_y13 = "absolute"
scale_y13 = "600"
shader14 = "shaders_slang/crt/shaders/guest/hd/bloom_horizontal.slang"
alias14 = ""
wrap_mode14 = "clamp_to_border"
mipmap_input14 = "false"
filter_linear14 = "true"
float_framebuffer14 = "true"
srgb_framebuffer14 = "false"
scale_type_x14 = "absolute"
scale_x14 = "800"
scale_type_y14 = "absolute"
scale_y14 = "600"
shader15 = "shaders_slang/crt/shaders/guest/advanced/bloom_vertical.slang"
alias15 = "BloomPass"
wrap_mode15 = "clamp_to_border"
mipmap_input15 = "false"
filter_linear15 = "true"
float_framebuffer15 = "true"
srgb_framebuffer15 = "false"
scale_type_x15 = "absolute"
scale_x15 = "800"
scale_type_y15 = "absolute"
scale_y15 = "600"
shader16 = "shaders_slang/crt/shaders/guest/advanced/crt-guest-advanced-ntsc-pass2.slang"
alias16 = ""
wrap_mode16 = "clamp_to_border"
mipmap_input16 = "false"
filter_linear16 = "true"
float_framebuffer16 = "true"
srgb_framebuffer16 = "false"
scale_type_x16 = "viewport"
scale_x16 = "1.000000"
scale_type_y16 = "viewport"
scale_y16 = "1.000000"
shader17 = "shaders_slang/crt/shaders/guest/advanced/deconvergence-ntsc.slang"
alias17 = ""
wrap_mode17 = "clamp_to_border"
mipmap_input17 = "false"
filter_linear17 = "true"
float_framebuffer17 = "false"
srgb_framebuffer17 = "false"
scale_type_x17 = "viewport"
scale_x17 = "1.000000"
scale_type_y17 = "viewport"
scale_y17 = "1.000000"
bth = "1.000000"
PR = "0.000000"
PG = "0.000000"
PB = "0.000000"
AS = "0.070000"
agsat = "1.000000"
auto_res = "1.000000"
cust_artifacting = "0.000000"
cust_fringing = "0.000000"
ntsc_fields = "1.000000"
ntsc_phase = "3.000000"
ntsc_scale = "1.500000"
ntsc_gamma = "0.250000"
ntsc_taps = "9.000000"
ntsc_charp3 = "10.000000"
ntsc_cscale1 = "2.250000"
ntsc_ring = "1.000000"
GAMMA_INPUT = "2.800000"
gamma_out = "2.200000"
interm = "0.000000"
HSHARPNESS = "1.000000"
SIGMA_HOR = "0.450000"
S_SHARP = "0.000000"
HSHARP = "0.000000"
MAXS = "0.000000"
HARNG = "0.000000"
m_glow = "1.000000"
m_glow_cutoff = "0.000000"
m_glow_low = "5.400000"
m_glow_high = "0.000000"
m_glow_dist = "4.000000"
SIZEH = "50.000000"
SIGMA_H = "15.000000"
SIZEV = "50.000000"
SIGMA_V = "13.125000"
SIZEHB = "1.000000"
SIGMA_HB = "0.575000"
SIZEVB = "1.000000"
SIGMA_VB = "0.250000"
glow = "0.010000"
halation = "0.787000"
hmask1 = "0.000000"
brightboost = "4.700000"
brightboost1 = "3.000000"
gsl = "-1.000000"
scanline1 = "70.000000"
scanline2 = "70.000000"
beam_min = "0.650000"
beam_max = "1.125000"
beam_size = "0.000000"
scans = "0.000000"
scan_falloff = "2.000000"
scangamma = "2.200000"
csize = "0.060000"
shadowMask = "2.000000"
maskstr = "1.000000"
mcut = "1.000000"
masksize = "3.000000"
mask_zoom = "-3.000000"
mask_layout = "1.000000"
maskDark = "0.000000"
maskLight = "1.000000"
mask_gamma = "1.000000"
pr_scan = "0.000000"
textures = "SamplerLUT1;SamplerLUT2;SamplerLUT3;SamplerLUT4"
SamplerLUT1 = "shaders_slang/crt/shaders/guest/advanced/lut/trinitron-lut.png"
SamplerLUT1_mipmap = "false"
SamplerLUT1_linear = "true"
SamplerLUT1_wrap_mode = "clamp_to_border"
SamplerLUT2 = "shaders_slang/crt/shaders/guest/advanced/lut/inv-trinitron-lut.png"
SamplerLUT2_mipmap = "false"
SamplerLUT2_linear = "true"
SamplerLUT2_wrap_mode = "clamp_to_border"
SamplerLUT3 = "shaders_slang/crt/shaders/guest/advanced/lut/nec-lut.png"
SamplerLUT3_mipmap = "false"
SamplerLUT3_linear = "true"
SamplerLUT3_wrap_mode = "clamp_to_border"
SamplerLUT4 = "shaders_slang/crt/shaders/guest/advanced/lut/ntsc-lut.png"
SamplerLUT4_mipmap = "false"
SamplerLUT4_linear = "true"
SamplerLUT4_wrap_mode = "clamp_to_border"
4 Likes

Very nice preset! I’m quite fond of how a simple Mask Zoom adjustment makes it a very nice 1440p option without tweaking other values.

2 Likes

Thanks! :slightly_smiling_face: Yes the crt mask zoom function is very nice, (-4) and (-5) allowed me to obtain the same image quality, so it’s very useful for lower resolution screens.

2 Likes

It’s very useful. I also noticed that when testing stuff with non-native resolutions on my laptop that mask zoom can result in looks that don’t look totally screwed up as opposed to using a mask that has the equivalent amount of pixels by default. So I guess it fits the subpixel layout better?

2 Likes

It’s definitelly because of more tolerant horizontal mask distribution. If mask zoom would allow fractional value setups, then this could sync even better with sub-pixel patterns on non-native resolutions.

Non-integer mask widths are indeed a thing if a mask is big enough, like above 5.0. This is more or less a 4k thingie regardless…

5 Likes

Is there a way to get 100% strength just on the black pixel, using RRGGBBX? Or independent adjustment of the black pixel? Could allow for a much more realistic slot mask effect, I think.

edit: also, I have to manually set the slot mask width when using RRGGBBX to 7, not sure if that’s correct behavior.

3 Likes

New Release Version (2025-11-30-r1):

Notable changes:

  • Slotmask Auto / Manual width interaction bugfix.
  • A small change.

Download link:

https://mega.nz/file/QoI0wAoS#z53_-BJN5hw9QieusZzhjDAbeuYJqL4gCvBfsfnpFv4

12 Likes

Quick demo using an overlay to illustrate what I’m talking about, but I can’t get it to line up with the triads correctly- it’s like there’s a single pixel border at the edge of the screen, so the overlay gets shifted by a single pixel (is this something the shader is doing, or do I need I need to check my video output settings?)

No overlay

Add overlay, darken every 7th pixel. Mask low/high 75/100, Overlay 75

The overlay helps to preserve the slot shape and just looks nicer in general (especially closer to the screen). Unfortunately, it’s not lined up correctly with the triads (shifted by 1 pixel horizontally). Overlays are annoying, it’s better to do this with a shader :smiley:

Currently, the vertical black lines and horizontal black lines that comprise the slot triad get altered by different amounts when you adjust brightnesses, blooms, glows, mask strengths, etc. For better looking and more accurate results, the horizontal black lines and vertical black lines comprising the slot should be of equal strength. On a real CRT, they’d always be 100% black no matter how bright the phosphors are shining, but we have to mitigate this on an LCD. We can get better results if the black lines defining the slot triad are all of a roughly equal and consistent strength.

This might require an additional “mask” to be added, and would work with those masks containing a black pixel (MGX, RGBX, RYCBX, RRGGBBX). The code would simply be: darken every 3rd, 4th, 5th, or 7th pixel by 0-100%.

3 Likes

I think I got a solution, which would only complicate slotmask and lottes mask 2 combos.

With this solution slotmask strength controls are managed by mask strength controls instead. It would also save one parameter. Existing presets should not be affected at an disturbing level.

To summarize, instead of slotmask strength controlls there would be a slotmask ON/OFF switch, slotmask strength = mask strength.

I hope this would also improve the issues you are mentioning.

4 Likes

It’s worth a shot!

I found something else that might be improved:

What would be nice is something closer to this:

EDIT: Ok, so mask 12 is actually XRRGGBB (Not RRGGBBX as I had assumed) :smiley:

No biggie, but that’s what was throwing me off. I’ve got the overlay working now. Here’s what I’d like to accomplish (without the use of a clunky overlay):

3 Likes

Why is the red phosphor wider than the green and blue phosphors in this example?

I get that you would like to get this same result via the shader but without the “clunky” overlay but do you realize what you’ve done here?

You’ve basically perfectly abstracted the mask and slot wires for CRT Emulation. As a resut of this all of the vertical and horizontal slots are perfectly aligned and there are no compromises or ambiguity when it comes to at least the Mask. what’s probably missing is if you could have added the vertical lines between the phosphor colours themselves but I’m not sure if you have enough resolution for that at 4K since I’m assuming that you’re already at 1 subpixel wide for the black mask outline and horizontal slot.

The preset looks amazing at least when not zoomed in. We just have to do something to improve the realism of the scanlines and the phosphors.

Is that overlay your custom creation? Am I right to assume that it only works at a specific scale factor?

Great job on the preset so far though!

RetroArch Screenshot 2025.09.21 - 04.50.56.41

https://mega.nz/file/hYwyUByR#iEZoE1tcPPm8nFEqgc6S1lWGRZ4ZDvynKN0QPKw2gX8

Please excuse the jpeg compression artifacts in all of the screenshots.

Also, if we could have gotten rid of one of the vertical black mask lines then we wouldn’t have the double line when tiled and that should also help in the brightness department.

1 Like

The phosphor itself isn’t wider, it’s just bleeding into the black spaces of the slot mask which makes it appear that way. I think it’s a decent way to compensate for the brightness loss. You can also see this happening in this example:

It’s an easy pattern, but it will only work with XRRGGBB at 4k :smiley: This could be scale-dependent, I don’t know. I’ve only tested it with Nestopia, Core Provided, Overscale

That’s an interesting pattern, right off the bat the vertical lines separating the slots are a bit thick, though. The noise(?) is an interesting way to increase brightness.

I’ve been compromising a bit on that lately with my local dimming setups. Once you get used to local dimming, it’s really hard to go back.

That’s just a cropped section of larger image; the pattern itself doesn’t have a double black line (as you can see) :slight_smile:

This is 308 TVL already with a mask width of 7 pixels, to emulate the black space between phosphors would take at a minimum 14 pixels per triad if you want the proportions to be accurate (RRRXGGGXBBBXXX). So you’d need 8K resolution for the same TVL.

1 Like

Yes, they are and I only checked and have now noticed this because of your example. So in my case, this mask could probably benefit from a little trim if possible.

There was a particular slot mask preset combination that I had really liked, except for one thing, I used to see some light vertical lines that looked like a kind of moire pattern or artifact. It’s possible that it could be related to this double black vertical line.

It could be noise that you’re seeing but it could also be the jpeg compression artifacts that you’re seeing in addition to the noise. In general the level of noise that I typically use in my presets is barely noticeable but I did struggle a bit with the parameter range/sensitivity when I initially added the grain shader from the Image Mod shader to my shader stack and combined it with XBR-LEVEL-2.

For many of my presets, I manually tweaked them to where I liked the noise but some remained unchanged.

All of those pics are from lossy originals. JXR uses JPEG, then I convert JXR, which is relatively high bitrate to jpegs which fit within the 4096KB limit for sharing to the forum.

The PNG is a 3rd Gen of the original JXR.

Looking at the regularity of what you described as possible noise, I think at least some of it might be the scanlines gaps as well. So noise + scanline gaps + compression artifacts.

3 Likes

Slotmask and scanline alignments are tricky to do, since there are the usual modulo missalignments. From my experience with a slotmask based CRT TV the scanlines were quite mild, not comparable with a PVM etc.

There is also the usual issues with curvature, odd integer scaling (symmetric slotmasks are even). To cut it short it would make a lot of mess to cover all the original and viewport resolution combos, not even considering other possible circumstances.

As a mitigating fix to these situations there is an slotmask option to round things up a bit.

The only option i can think of right now is to allow slotmask vertical shifts. It also took much creativity to get a proper slotmask - mask alighment on various gpu drivers because of rounding discrepancies.

4 Likes

Here are some real-world comparisons of Composite and RGB showing the infamous “m” as well as showing how RGB messes up the nice shading of gradients and has a more aliased appearance for very little gain in my opinion. It also shows how well colour saturation can hold up in Composite vs RGB and how smoothly mixed the composite colour artifacts and fringing are spread across the Image.

In my opinion these hardly take away from the Image while adding a bit of character, charm and warmth.

I’ve also recently learnt that these composite colour artifacts as well as loss of sharpness and clarity of especially on things like fonts are less visible in real-time so pictures which attempt to capture them should be taken with a pinch of salt.

https://www.reddit.com/r/crtgaming/s/dlWY2QBsBv

I also find it interesting that 3-Phase with Merge Fields - On takes the Composite signal even closer to RGB.

1 Like