PlainOldPants's Shader Presets

And here’s another with no grade, default settings for guest-advanced. I have no idea what changed. Using the version from “p68k-fast-multipass-2025-11-07”

Edit: It seems to be only the ntsc colors preset that’s working, the others all result in the washed out image

shaders = "21"
feedback_pass = "0"
shader0 = "shaders_slang/p68k-fast-multipass-2025-11-07/p68k-fast-mp-pre-color-only.slang"
alias0 = ""
wrap_mode0 = "clamp_to_border"
mipmap_input0 = "false"
filter_linear0 = "false"
frame_count_mod0 = "2"
float_framebuffer0 = "true"
srgb_framebuffer0 = "false"
scale_type_x0 = "source"
scale_x0 = "1.000000"
scale_type_y0 = "source"
scale_y0 = "1.000000"
shader1 = "shaders_slang/p68k-fast-multipass-2025-11-07/p68k-fast-mp-post-color-only.slang"
alias1 = ""
wrap_mode1 = "clamp_to_border"
mipmap_input1 = "false"
filter_linear1 = "false"
frame_count_mod1 = "2"
float_framebuffer1 = "true"
srgb_framebuffer1 = "false"
scale_type_x1 = "source"
scale_x1 = "1.000000"
scale_type_y1 = "source"
scale_y1 = "1.000000"
shader2 = "shaders_slang/stock.slang"
alias2 = ""
wrap_mode2 = "clamp_to_border"
mipmap_input2 = "false"
float_framebuffer2 = "false"
srgb_framebuffer2 = "false"
scale_type_x2 = "viewport"
scale_x2 = "1.000000"
scale_type_y2 = "viewport"
scale_y2 = "1.000000"
shader3 = "shaders_slang/crt/shaders/guest/advanced/stock.slang"
alias3 = ""
wrap_mode3 = "clamp_to_border"
mipmap_input3 = "false"
filter_linear3 = "false"
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/stock.slang"
alias4 = "StockPass"
wrap_mode4 = "clamp_to_border"
mipmap_input4 = "false"
filter_linear4 = "false"
float_framebuffer4 = "false"
srgb_framebuffer4 = "false"
scale_type_x4 = "source"
scale_x4 = "1.000000"
scale_type_y4 = "source"
scale_y4 = "1.000000"
shader5 = "shaders_slang/crt/shaders/guest/advanced/afterglow0.slang"
alias5 = "AfterglowPass"
wrap_mode5 = "clamp_to_border"
mipmap_input5 = "false"
filter_linear5 = "true"
float_framebuffer5 = "false"
srgb_framebuffer5 = "false"
scale_type_x5 = "source"
scale_x5 = "1.000000"
scale_type_y5 = "source"
scale_y5 = "1.000000"
shader6 = "shaders_slang/crt/shaders/guest/advanced/pre-shaders-afterglow.slang"
alias6 = "PrePass0"
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/ntsc/ntsc-pass1.slang"
alias7 = "NPass1"
wrap_mode7 = "clamp_to_border"
mipmap_input7 = "false"
filter_linear7 = "false"
float_framebuffer7 = "true"
srgb_framebuffer7 = "false"
scale_type_x7 = "source"
scale_x7 = "4.000000"
scale_type_y7 = "source"
scale_y7 = "1.000000"
shader8 = "shaders_slang/crt/shaders/guest/advanced/ntsc/ntsc-pass2.slang"
alias8 = ""
wrap_mode8 = "clamp_to_border"
mipmap_input8 = "false"
filter_linear8 = "true"
float_framebuffer8 = "true"
srgb_framebuffer8 = "false"
scale_type_x8 = "source"
scale_x8 = "0.500000"
scale_type_y8 = "source"
scale_y8 = "1.000000"
shader9 = "shaders_slang/crt/shaders/guest/advanced/ntsc/ntsc-pass3.slang"
alias9 = ""
wrap_mode9 = "clamp_to_border"
mipmap_input9 = "false"
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/custom-fast-sharpen.slang"
alias10 = "NtscPass"
wrap_mode10 = "clamp_to_border"
mipmap_input10 = "false"
filter_linear10 = "true"
float_framebuffer10 = "false"
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/stock.slang"
alias11 = "PrePass"
wrap_mode11 = "clamp_to_border"
mipmap_input11 = "true"
filter_linear11 = "true"
float_framebuffer11 = "false"
srgb_framebuffer11 = "false"
scale_type_x11 = "source"
scale_x11 = "1.000000"
scale_type_y11 = "source"
scale_y11 = "1.000000"
shader12 = "shaders_slang/crt/shaders/guest/advanced/avg-lum-ntsc.slang"
alias12 = "AvgLumPass"
wrap_mode12 = "clamp_to_border"
mipmap_input12 = "true"
filter_linear12 = "true"
float_framebuffer12 = "false"
srgb_framebuffer12 = "false"
scale_type_x12 = "source"
scale_x12 = "1.000000"
scale_type_y12 = "source"
scale_y12 = "1.000000"
shader13 = "shaders_slang/crt/shaders/guest/advanced/linearize-ntsc.slang"
alias13 = "LinearizePass"
wrap_mode13 = "clamp_to_border"
mipmap_input13 = "false"
filter_linear13 = "true"
float_framebuffer13 = "true"
srgb_framebuffer13 = "false"
scale_type_x13 = "source"
scale_x13 = "1.000000"
scale_type_y13 = "source"
scale_y13 = "1.000000"
shader14 = "shaders_slang/crt/shaders/guest/advanced/crt-guest-advanced-ntsc-pass1.slang"
alias14 = "Pass1"
wrap_mode14 = "clamp_to_border"
mipmap_input14 = "false"
filter_linear14 = "true"
float_framebuffer14 = "true"
srgb_framebuffer14 = "false"
scale_type_x14 = "viewport"
scale_x14 = "1.000000"
scale_type_y14 = "source"
scale_y14 = "1.000000"
shader15 = "shaders_slang/crt/shaders/guest/hd/gaussian_horizontal.slang"
alias15 = ""
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 = "source"
scale_y15 = "1.000000"
shader16 = "shaders_slang/crt/shaders/guest/advanced/gaussian_vertical.slang"
alias16 = "GlowPass"
wrap_mode16 = "clamp_to_border"
mipmap_input16 = "false"
filter_linear16 = "true"
float_framebuffer16 = "true"
srgb_framebuffer16 = "false"
scale_type_x16 = "absolute"
scale_x16 = "800"
scale_type_y16 = "absolute"
scale_y16 = "600"
shader17 = "shaders_slang/crt/shaders/guest/hd/bloom_horizontal.slang"
alias17 = ""
wrap_mode17 = "clamp_to_border"
mipmap_input17 = "false"
filter_linear17 = "true"
float_framebuffer17 = "true"
srgb_framebuffer17 = "false"
scale_type_x17 = "absolute"
scale_x17 = "800"
scale_type_y17 = "absolute"
scale_y17 = "600"
shader18 = "shaders_slang/crt/shaders/guest/advanced/bloom_vertical.slang"
alias18 = "BloomPass"
wrap_mode18 = "clamp_to_border"
mipmap_input18 = "false"
filter_linear18 = "true"
float_framebuffer18 = "true"
srgb_framebuffer18 = "false"
scale_type_x18 = "absolute"
scale_x18 = "800"
scale_type_y18 = "absolute"
scale_y18 = "600"
shader19 = "shaders_slang/crt/shaders/guest/advanced/crt-guest-advanced-ntsc-pass2.slang"
alias19 = ""
wrap_mode19 = "clamp_to_border"
mipmap_input19 = "false"
filter_linear19 = "true"
float_framebuffer19 = "true"
srgb_framebuffer19 = "false"
scale_type_x19 = "viewport"
scale_x19 = "1.000000"
scale_type_y19 = "viewport"
scale_y19 = "1.000000"
shader20 = "shaders_slang/crt/shaders/guest/advanced/deconvergence-ntsc.slang"
alias20 = ""
wrap_mode20 = "clamp_to_border"
mipmap_input20 = "false"
filter_linear20 = "true"
float_framebuffer20 = "false"
srgb_framebuffer20 = "false"
scale_type_x20 = "viewport"
scale_x20 = "1.000000"
scale_type_y20 = "viewport"
scale_y20 = "1.000000"
pf_hdr_enable = "2.000000"
textures = "SamplerLUT1;SamplerLUT2;SamplerLUT3;SamplerLUT4"
SamplerLUT1 = "shaders_slang/crt/shaders/guest/advanced/lut/trinitron-lut.png"
SamplerLUT1_mipmap = "false"
SamplerLUT1_wrap_mode = "clamp_to_border"
SamplerLUT2 = "shaders_slang/crt/shaders/guest/advanced/lut/inv-trinitron-lut.png"
SamplerLUT2_mipmap = "false"
SamplerLUT2_wrap_mode = "clamp_to_border"
SamplerLUT3 = "shaders_slang/crt/shaders/guest/advanced/lut/nec-lut.png"
SamplerLUT3_mipmap = "false"
SamplerLUT3_wrap_mode = "clamp_to_border"
SamplerLUT4 = "shaders_slang/crt/shaders/guest/advanced/lut/ntsc-lut.png"
SamplerLUT4_mipmap = "false"
SamplerLUT4_wrap_mode = "clamp_to_border"
3 Likes

@PlainOldPants apologies if you already discussed this somewhere but I was wondering if you have done gamut analysis on more modern TV color enhancement features like on an LCD. It would be interesting if it is at all related to what consumer CRT TVs are doing with color.

I have not done that sort of analysis on any modern displays yet. I have a couple modern TV sets, which don’t have great image quality, but I seldom use them. They’re mainly used by other family members that I live with.

At least, I have tried playing around with settings. I was able to turn off a couple settings that had been “enhancing” the image by oversaturating it and causing details to get clamped and disappear. One thing that stuck out to me was that an LG set had a “Dynamic Color” feature that could be set to Off, Low, Mid, or High, and it was keeping colors near the red/yellow/green boundary in place while moving other colors towards blue. I might try to get pictures of this, and maybe get rough measurements with the X-Rite i1Display 2.

As for now, I don’t have the time to be working on anything here, but I’ve been checking on these forums every once in a while. I have too many other things going on.

2 Likes

It’s been too long since I last posted here. I just made this set of NES palettes and 128x128x128 LUTs based on consumer CRTs. The Panasonic, Toshiba, and RCA ones are the ones I actually own and have measured myself, whereas the Sony ones are solely based on data found online. All of this is generated using Chthon’s tool gamutthingy, with my own modifications to have a straight 2.2 power law gamma instead of Rec. 1886, and to manually adjust contrast to fit the colors better. That’s unlike my previous posts where I took colorimeter samples directly, which was not good because of how uncalibrated the CRTs were. For more information, see the included text document, but it’ll probably only leave you more confused.

https://drive.google.com/drive/folders/1Nww5ce7xO7j9_2M-_DUmCY1QuVU7jzng Using Google Drive instead of MediaFire to avoid dealing with ads.

Edit: I’ve just realized, the NES palettes are all a little bit wrong because I forgot to set the correct black levels. They are all set so that $1d is black. I’ll edit this post again later once I’ve uploaded palettes with the correct black levels, at 7.5 IRE for US palettes and at 0 IRE for JP palettes. The LUTs also have the CRT’s black level adjusted to match the console’s black level, but I’ve heard that consoles often output the same black level voltage for all regions, meaning either US TVs got crushed blacks, or JP TVs had lifted blacks. As for PAL, I don’t have any decent PAL CRT data yet.

3 Likes

I can be somewhat helpful with black levels: All CRT-era game consoles before circa 2000, except U.S. model Playstations, had black at 0 IRE. There’s citations for this in the gamutthingy readme.

U.S. users could uncrush blacks by turning up the brightness knob on their CRT.

U.S. developers may or may not have baked crushed blacks into their pixel artwork, depending on how the televisions hooked up to their dev kits had their brightness knob set.

Aside from eyeballing, you might be able to sort out if a given game has baked-in crushed blacks by checking its artworks’ palettes for colors that would crush if black was at 7.5 IRE. Presumably such colors would not have been used if they didn’t produce a visible difference on the artist’s screen. (This method is not useful for NES though, since the luma step size was too big.)

1 Like

Thank you. I’ll look at the sources from the gamutthingy readme.

I doubt that the NES has its black level exactly at 0 IRE or 7.5 IRE. That is why I want to either take a video capture of my NES or use my colorimeter to sample an actual CRT to determine this. The Genesis/MegaDrive 240p test suite claims to have a black level of about 6 IRE.

Temporarily, I might as well pretend that these consoles are all exactly 0 or 7.5 IRE.

Figuring out the artist’s intent is a complex problem that requires looking across each game independently. Because of that complexity, I’m not trying to do the artist’s intent yet.

Instead, my goal for now is to get to what a typical end-user would see on these displays, which is a much simpler problem because, like myself, these end users don’t know the artist’s intent either. In that case, the end user would adjust their CRT for live broadcasts and leave it unchanged for video games. For specifics on how I did this, read the readme file that I included in that google drive link with the other LUTs and NES palettes.

For that purpose, it makes sense to leave the blacks crushed, even though it’s possible to fix using the brightness control.

There are some other ways I might try to set the black level and tint/color settings. I just haven’t gotten around to doing them yet.

One way that I’ve been meaning to try is watching YouTube videos (of real life) through ShaderGlass with the CRT color shaders and adjusting the settings by eye that way, including the black level. Maybe I could upload the shaders online too and crowd-source the settings. Doing this with my real 1989 RCA ColorTrak Remote is an option too, but it’ll be a bit wrong because the grayscale is slightly off from its intended point.

The other way is through a CRT’s default settings. Consumer-grade CRTs (but not professional ones like PVMs) starting in the late 80s typically have the contrast/brightness/tint/color/sharpness settings in an on-screen display with a “reset” button to get the defaults. As a kid (in the 2000s), I never knew that these settings existed, so chances are the average consumer didn’t ever touch them. Unfortunately, none of my CRTs have their default black level intact. I should at least be able to approximate the default tint/color from the 2000 Panasonic CRT, but not the default black level.

The black level of video is defined as 0 IRE (aside from systems using setup) by definition. This is how IRE works. It’s not tied to a particular voltage or nit value. The higher voltage of blanking is taken as the voltage for 0 IRE. 100 IRE is 714 mV above blanking. So this means consoles may have a white level slightly off 100 IRE but the black level will be 0 as long as black is the same color as the high blanking level.

The NES uses the same palette color for high blanking and black, so you can treat it as 0 IRE.

With 7.5 IRE setup, blanking level is still 0 and 100 is still always 714 mV. This means setup might not actually be perfectly at 7.5 IRE.

For a number of reasons, you can’t precisely measure IRE off a CRT with a colorimeter. You can get a rough estimate but it’s not going to be precise enough for what you need. Use an oscilloscope instead with 75 Ohm termination. Once you have voltage levels, you can reliably measure luminance off the CRT (say at 10 IRE steps). Plug that into one of the BT.1886 equations, and you should be able to get something close to a 2.4 power function (it doesn’t need to be perfectly linear, it will likely fluctuate all over from under 2 to 3). If you do, you know your IRE measurements are in the ballpark.

Also there is no ‘default’ black level. CRTs are analog. They’re not digital. Two identical TVs could have different black levels off the belt. Manufacturers did a very basic factory calibration to get in suitable range for TV but that would go out of date quickly as the components age rapidly within the first 100 hours of use.

1 Like

The part I don’t understand is how the voltages for black and blanking are meant to be detected and kept consistent by the CRT display when switching between different input devices or TV channels, if 0 IRE is not consistently at the same voltage.

And yes, by “defaults” I meant the factory calibration, which is displayed to the user as all settings being centered except contrast (unless the TV only had physical knobs for settings, unlike later ones which had an OSD), but if you’re right about how much this black level varies across identical TVs and ages in 100 hours of use, then it’s surely not worth it to recover this, even if I were to average several CRTs.

About measuring IRE off a CRT, what I was planning to do was something similar to my nesdev forum post about the Toshiba Blackstripe color correction, where I sample up the grayscale from my HDMI-to-composite convertor at 3 IRE (or so) intervals to create a regression function, and then invert that function to convert from sampled NES grays back to IRE levels. As you’ve said, this wouldn’t be precise. I’ve tried using a Dazzle DVC100 to capture the video before, but I remember it having some kind of issues that I couldn’t diagnose.

If you’re confused about why I had a paragraph about picking a black level by eye while watching actual videos, that goes back to my main goal of matching what an end user would see on average, even if it’s not necessarily correct or precise.

The idea is to set the TV’s settings based on actual video, and then see what happens when I switch to channel 3 or 4 or to a composite input to play games. This is why I need to understand what “0 IRE” truly is when switching between different video sources, so I appreciate your help and your patience.

Until I get data using an oscilloscope, the best idea may be to use the simple rule of 0 IRE and 7.5 IRE for crushing or lifting blacks, and assume every console’s max white is 100 IRE when doing that.

Edit: I had to check and make sure I’m not crazy, but yes, even late 1980s TVs can have digital on-screen controls and a “reset” feature that maxes out contrast and centers everything else. https://crtdatabase.com/crts/sony/sony-kv-19-ts20 The manual for this one shows the year 1989.

Video signals are what we call AC-coupled signals, meaning we only care about the AC part. This was intentionally designed so we can ignore the DC bias, something much more difficult to control when RF transmission is involved. So when we talk about voltage of a video signal, it’s always relative to that inherit DC bias that exists on every system. The TV has what we call a clamping circuit (not to be confused with what clamp in GLSL does, we would call that ‘hard limiting’) that removes the DC bias relative to the TV (i.e. it sets the DC bias to whatever the TV’s DC bias for video signals is). That clamping circuit operates on the ‘back porch’ portion of the video signal, which is defined as 0 IRE. This is why 0 IRE is defined as the high blanking level. The hardware literally uses that level as its own voltage reference, so it makes sense to define 0 IRE in the same way.

I can give you some history here. The reason we have calibration is because of response changes in systems over time. In any business, whether it’s a TV studio or some research laboratory, only work that needs to get done gets done. Calibration is only done on things needing it. So the habit of calibrating screens came from that need. LCDs can be calibrated far less frequently. If CRTs had all come from the factory looking the same and never changed their response, that culture of calibration would never have developed in the first place.

I am just warning you that this will take a lot of time and will probably leave you unsatisfied with the results.

I’m curious how much this differs as well. You need a strong baseline for one side of the transmission line to do this, which is why I recommend a scope. If you’re just doing luminance measurements, something like a high-quality DVD player with 0 IRE black mode and a test DVD can be good enough as the reference. Then you can measure gray window steps on different inputs/channels and see how the levels change.

1 Like

@PlainOldPants I am trying to use your shader with Genesis games (specifically Sonic 3D Blast) to create my own preset that is combined with a gamutthingy LUT and koko-aio. However, I cannot keep the color per-line and per-frame offsets at zero and retain a picture on screen. It is my understanding that there are 8/15 chroma periods per pixel and zero excess chroma subcarrier periods per scanline and field with respect to Genesis. Does the parameter setting differ in some way? I see the source code would encounter a division-by-zero and am confused as to why the fullOffset calculation would involve dividing by an offset.

1 Like

To get zero excess chroma per scanline and field, set the offsetLine, offsetFrame, and offsetFrameMod settings all to 1.0. The offset amount is the reciprocal of that multiplied by the subcarrier’s wavelength, which becomes 360 degrees in this case. That way, you use 1.0 for Genesis/MegaDrive and Master System, 2.0 for PSX or PC-Engine (“2-phase”), or 3.0 for NES and SNES (“3-phase”) (but offsetFrameMod=2.0 except Battletoads-like games). I think 8.0 and 15.0 are the correct numerator and denominator values as well, but I forget which is which. You’re required to set the default resolution to 320 pixels (or else your custom color carrier wavelength setting will get ignored), and if you’re using anything other than BlastEm, don’t forget to do the BlastEm correction. Since you’re using a gamutthingy LUT, you also need to change my video decoder/region setting to 0.0 which disables the color corrections that are in p68k-fast-mp-post-color-deinterlace.inc…

Although I didn’t implement parameters to control the filters in the encoder, they’re easy to find and edit in the code. What’s missing is a Y notch, though.

Here’s a quick copy of the code I currently have in my shaders folder right now, which I’ve already added an encoder Y notch setting into that you might find useful. The chroma bandpass (or lowpass in this implementation) still has to be controlled by directly editing the source code yourself, but the numbers are in an obvious spot that you can’t miss. This isn’t a fully prepared update, just a version that I’ve been using and gradually making small edits to over the months while playing my PS1 games casually, so I can’t guarantee that there isn’t going to be some bug that breaks a feature I wasn’t using regularly, or any inconsistency between files that are supposed to be consistent. https://drive.google.com/file/d/1xBkci0AlL-C-JuXxqzVA7a5VPLSowQmk/view

Edit: Also, in this version, you might want to turn off the “Replace Y Notch with C remod” setting to be able to control sharpness in the decoder.

Edit 2: You’ll also want to edit the preset to have a simple 1x or 2x multiplier for horizontal resolution instead of forcing 640. In other words, change “absolute” to “source”, and change 640 to 1 or 2, whatever your computer can handle. This horizontal resolution multiplied by 4 is the number of video signal samples, so to minimize aliasing artifacts, you need to make sure this number times 4 is always an integer multiple of the game’s resolution.

The problem with aliasing does negatively impact the FIR filters’ accuracy more than I originally realized months ago, and fixing this would require rewriting much of the shader code from scratch.

Edit 3:

That changes things. I haven’t tried ares, but I’ve tried ShaderGlass and found that my shaders fail to compile on it because it’s trying and failing to unroll several of my while-loops. To make them unroll successfully, I had to convert the while-loops to for-loops with a fixed number of iterations. A couple loops needed to have their inside code shortened or changed too. I’ll upload this fixed version for you soon, but it might not fix everything for Ares.

Even with that fix, some of my shaders’ features, like deinterlacing, didn’t work with ShaderGlass because they require referencing passes out of order and getting feedback from previous frames, neither of which seemed to be supported by ShaderGlass. When either a previous pass or a previous frame is referenced in ShaderGlass, the result is always black, so I suspect that might be why Ares is only outputting a black screen for you.

With ares, your mileage may vary, since I haven’t tried it with ntsc-p68k-fast-mp yet. I wish you luck with this.

Okay. That makes sense now. I was totally thrown off by the reciprocal and the usage of offset in the parameters. I was thinking how do I get this to work with a modulo and should it be “1 plus or minus offset in the denominator”.

I have a decent working start on October 2025 version of the shader. I may stick with that if I find the newest a little too unfitting for my existing koko-aio settings. I’m definitely doing a different kind of preset pack than what’s already out there. So, I’m trying to watch anything newly introduced to maintain what I got going as koko-aio requires input at 1x scale, and gamutthingy LUT is not the “normal” generated one but the “postgammaunlimited”.

As far as ares goes, I’m in touch with librashader’s author and know how to report issues to its GitHub. So, we have a system in place. librashader is independent of the other shader display tools and thus will have different kinds of problems.

I have only used Shaderglass lightly and noticed problems with shaders not matching. I might make an issue on its GitHub someday for some shaders, but librashader is my main method of using shaders.

I have been hacking a way at this and have a few questions:

  1. I assume the chroma lowpass variables are lowpassWindowStdNarrowSize/lowpassWindowStdBaseSize and lowpassSincStdNarrowRate/lowpassSincStdBaseRate. Is that correct? If so, do they only need to be changed within the encode slang file, or does that include the decode slang file as well?

  2. Regarding the preset editing with respect to the horizontal resolution, there is only one instance of “absolute” from the ntsc shader set in the .slangp file. I only change that to “source” and the corresponding horizontal resolution to 2x the game’s horizontal resolution, right? Then, the shader code is what does the multiply by 4? Is there any diminishing return to increasing the “source” number beyond 2x?

  3. The NES preset does not work well with my LUT. Do you intend to have a way to incorporate the hue and luma skews without the use of the raw palette?

  4. Is the noise shader near final, or should I avoid trialing it?

  5. I had to use the color initial offset for Genesis (about 0.29 for the value). What is that value supposed to represent in hardware terms?

Other than the questions, the results are coming in quite well.

Addendum to point 3:

I think NES emulation is a little strange with the usage of palettes. Also, I don’t quite understand why emulators do not cover more of the hardware (for all consoles in general) leading up to the cables. Therefore, I find the question about the skews out of place given the responsibility should seemingly fall upon the emulators.

Addendum to point 5:

I realize the Genesis comes with a bevy of image-altering configurations (region, revision, encoder choices, bad wiring, etc.), but I’m not sure if that explains the need for the initial offset. So, I suppose I’m just looking for something to equate the figure to in order to have confidence about the results and not illogically worry that something errant will show.

Those are correct, and you should only modify them in the encoder. You only have to modify one of those pairs of variables, and pick it in the shader parameters. The default values in there are based on the original NTSC standard, so they need to be modified to approximate the Genesis/MegaDrive’s bandpass filter from its schematic diagram. If the filter is too lenient, that shows up as too much rippling in the decoded luma signal, and if the filter is too heavy, the decoded chroma signal becomes too blurry.

The original circuit also has a notch filter on luma, and you’ll need the version I posted last week to do that. Without that filter, or with the filter set too lenient, you’ll have too much rainbow artifacts. With it set too heavy, you’ll have too little rainbow artifacts, and the resulting luma will be too blurry.

Anikom15’s scanline classic shader has the filters set up based on the original Genesis hardware for you out of the box. I haven’t done that for mine yet, so the best we can do with mine currently is adjust the filtering manually by eye, much like I used to do for Genesis in 2024. This problem of approximating the Genesis’s analog filters from the schematic diagram is the sole reason why I have not posted any Genesis-specific presets in over a year now.

That’s right, and I apologize for being unclear about that. Each horizontal pixel/fragment corresponds to four samples of the video signal. Increasing the number of samples increases accuracy but quickly drags down the performance.

I have to stick with the raw palette. The main problem actually is with how NES palettes in an emulator aren’t able to include negative numbers. (Edit to be more accurate and less confusing: The process of getting an NES palette from decoding the NES’s video signal is not reversible.) The reason why my shader isn’t looking correct with your gamutthingy LUT is probably because my shader is clamping off negative numbers at the end, which does affect the output of the NTSC color correction.

I’ll try to come back to this later today or tomorrow. I might have to do a couple small fixes to make the NES’s colors work well with an LUT, and while I’m at it, I may as well make the encoder’s filter parameters accessible in the settings. Until then, the easy alternative for NES is to skip the gamutthingy LUT and to set my “Video decoder / region” setting to 6 (Japanese NTSC color correction) instead of 0 (uncorrected color).

You should skip the noise for now, since not meant to be truly hardware accurate, only meant to be done by eye, and still unfinished. At least, I do think it’s better than the alternative noise shaders that I’ve looked at in the slang-shaders repo so far, since it’s made to follow the standard NTSC timings to recreate the scrolling patterns in the noise I’ve seen on a real CRT connected over RF, as opposed to using a simple random number generator on each pixel. It’s just not accurate and not quite finished, since I still haven’t put it side-by-side with my real CRT yet to pick the exact settings.

For the Genesis, that’s the chroma subcarrier’s angular offset at the left edge of each line. 0.29 * 2 * pi = ~1.822 radians. It won’t stay consistent if you change your emulator’s settings for cropping overscan horizontally.

I don’t know where to get an exact correct number for this, but I think I remember seeing some documented timings in BlastEm and/or Genesis Plus GX, possibly (Edit: They probably didn’t have this specific thing in there, so please don’t waste your time searching for this information).

EDIT 3 hours later:

I forgot to directly address this. Unfortunately, I have nothing to equate the number to, or anywhere to get it. The solution probably is to look at the output of a real console using an oscilloscope and derive these numbers from that. Otherwise, it’s the same thing as with the Y/C filtering inside the console: The only solution in my shaders is to eyeball it, or you can use anikom15’s scanline classic shaders which I can only guess have this taken care of already.

The NES’s color palette can be decoded from its video output, but you can’t convert an NES palette back into the original video signal. Decoding the NES signal into a palette is not a reversible process for several reasons (for instance, because it involves clamping RGB values between 0 and 255), and even if it were reversible, each palette has decoded and color-corrected the NES signal using different methods from each other, so our shaders would have to be told exactly which palette is in use to be able to invert its process back to the original video signal, which would then limit the shader to only support a selection of palettes. Some palettes are just done by guesswork or by eye, so there’s no way to emulate video signal artifacts accurately from that regardless.

The only way is to use the raw palette to approximately create the NES signal from scratch on our own, and decode and color-correct that palette in our own way.

I’m used to emulation where the colors are generated from the voltages themselves. Also, before switching to patchy-ntsc, I generated a palette from gamutthingy and used that in addition to my LUT. That seemed to work acceptably. I don’t know if that palette is able to avoid the clamping or not. All that I am looking for is a way to apply the luma and hue skews on top since not all NES emulators have palette support because they already handle the color correctly with voltages.

Are you open to the idea of moving this conversation over to NesDev if I start a thread there? I have a good rapport with lidnariq, and I think a lot of the hardware pieces to the “shader equation” can be cleared up from the group over there who are well versed in baseband signals and video output from consoles.

Not your fault, but I’m not sure what you mean by this. Rereading question 3 from your last post, I’m confused now as to what exactly you were asking about or what you meant. The idea of incorporating the hue and luma skews without using the raw palette doesn’t make sense to me since that step is already completed and baked into the NES palettes, like the one you had output by gamutthingy, and it’s part of my video signal emulation process with the raw palette too.

I must’ve been very tired or distracted when I answered you last, too, because that tangent I went off on doesn’t really directly answer the question you asked. The raw palette is required to emulate the signal, and using measured voltages is a required part of that process too. Since the process of making an NES palette as RGB values is only based on decoding the signal in non-invertible ways, it’s not possible to take an NES palette (other than the raw palette) and construct a video signal based on it. That’s even if the emulator outputs the palette “correctly using voltages” which isn’t invertible and still has multiple different “correct” ways based on the same voltages.

My shaders are using lidnariq’s measured voltage levels to generate the NES video signal as a perfect square wave as outlined in the nesdev wiki. I did the row hue skew in a hacky way temporarily (which makes de-emphasized colors get skewed wrongly). (The slight luma increase on the de-emph columns is missing, however.) All we can do is try to make a new NES palette from this output. There’s a prependable “test pattern” shader with an option to show the whole NES raw palette as a grid of differently colored rectangles, so that might be useful for this. All I have left to do is make my shaders compatible with the gamutthingy LUT. (I believe this should be doable, unless I’m not thinking things through. Negative values clamping shouldn’t be a real problem if I make my shaders compatible with the postgammaunlimited mode properly.)

On the subject of the NES, I just realized there’s a small fix I need to do with the black pedestal crush too (though I believe that isn’t in the October version that you have.). So much cleaning up that I need to do with this shader code now that I’m coming back to it.

I’m fine with that. This might hopefully help me get some direction on how to go about implementing the Genesis signal properly.

So, both higan and ares generate their own palette based on hardware voltages (I’m pretty sure they are from lidnariq’s numbers). Anyway, I think what I’m looking at is finding out first if the emulator is not incorporating the hue and luma skews into its calculations. I think there is good reason for the emulator (and others with similar approach) to handle this. Then, I can make presets according to who uses the raw palette and who does not. So, basically, I’m trying to cover all of the scenarios that I can. I don’t want to neglect Nestopia or Mesen users when my daily driver is ares. I want to be fair to most of the commonly used active emulators.

I know postgammaunlimited mode (applied through grade) is a bit unusual for RetroArch shaders, but it works really well with koko-aio. I can reach a point of physiological nostalgia with how well the LUT and koko-aio work together. Adding the proper NTSC effects just further solidifies that feeling.

I opened up a topic under “Other Retro Dev”. It should appear once it has been approved. I’m hoping it will be beneficial to everyone in getting the puzzle completed.

Lastly, I’m hanging onto the “color initial offset” for Genesis. I want to get my preset pack out, and I can worry about corrections later. With the color initial offset in place, the lowpassWindowStdBaseSize came out to 0.525, and the lowpassSincStdBase Rate was 0.800. That’s where my unorthodox, nostalgia-guiding approach took me. I believe chroma bandwidth is normally 1.5 MHz. So, I’m not sure if those variable values align well with the bandwidth or not.

Pants, what shader can I use for the Genesis and 5th gen consoles from your presets?

@kev4cards The problem I have found with Mega Drive is that if you ask five different people what it should look like you will get five different answers. Going by the schematic was the only reasonable choice I could make for it. I generally follow schematics for all the presets to the best I can find them. Most other consoles are more consistent.

The Famicom voltage outputs are fundamentally different from later consoles that output RGB and then convert to composite in the analog domain. The Famicom outputs direct composite voltages digitally. If the emulator wants to hold to the ‘output voltage directly’ ideal than it would have to output the dot clock pattern directly, not just the 256x240 framebuffer they do now.

Scanline Classic reconstructs the analog composite signal by first reconstructing the dot clock signal and then applying the appropriate cosine functions to chroma part of the signal. For Famicom/NES I would do the same thing but map chroma to a digital pulse pattern instead of multiplying chroma by a cosine. This is why there are no NES/Famicom presets yet (in fact there’s nothing earlier than the PC Engine yet).

1 Like