@NonWonderDog, I’m glad you’ve found it useful! I still have to look through your repo more thoroughly, but I have some comments and explanations. I apologize in advance for the wall of text here.
Wider resolution range (I’m sure there’s a good reason for scaling to 907 horizontal pixels, but by using half the display resolution instead I can use this shader for Darius or even SVGA.)
Basically, there is a tradeoff here. If you go too low, there can be 2 problems:
- Not having enough samples to properly represent the bandwidth of the input (i.e. dropping below or too close to the Nyquist frequency). This can cause aliasing artifacts. I’ll skip over this for now, because it requires some math for each different output type. 907 is more than enough for 15kHz TV/monitor content.
- Not having enough samples to properly estimate the integral for the scanlines. This is the bigger problem most of the time for 15kHz TV/monitor content (where the bandwidth is quite limited) and is why the default value isn’t lower. The smaller the spot/scanline is on the screen, the more samples will be needed. Basically, some number of samples must be in the borders of the spot, and so a higher sample density is required for smaller spots.
On the other hand, if you push the number of samples too high, the performance gets worse. For each pixel, the scanline code basically looks at any nearby samples that are close enough to affect the value of this pixel. If there are more samples overall, it looks at more samples for a proper estimation. This results in more lookups and more computation for each pixel. The number of lines and the spot size affect the performance as well. More lines and smaller spot sizes mean looking at fewer samples as a proportion of the total. So performance doesn’t actually suffer much if we only scale up the sample count with the line count.
The proper value for this parameter would be something like max(1.5 * 2 * Cutoff frequency * active line time, 3.5 * line count)
. The first value avoids problem 1, and the second value avoids problem 2. The 1.5 and 3.5 factors are kind of pulled out of my hat, but seem reasonable to me. Unfortunately, we can’t do this sort of math in .slangp
files, so I set it to a reasonable number for content up to 6MHz and 240-288 lines while aiming for good performance. If we could set it dynamically, it should work for any resolution from CGA to SVGA and beyond while maintaining good performance.
The downside of using half the viewport width is that for low resolution devices (I’m thinking of the Steam deck, handhelds, and mobile phones), the sample count may be too low. For slow, high resolution devices (like a laptop or NUC connected to a 4k TV), the performance may suffer for no visible quality gain.
VGA line-doubling mode for DOS games.
This is a great idea, and I may be able to come up with a VGA preset that does line doubling like this, sets the sample count appropriately, and adjusts the filter for the VGA timings being different from NTSC/PAL.
Constant brightness regardless of scanline width (just a scale factor of 1/(scanline width)^2; I don’t know how physically-justifiable it is).
This may result in some tonality changes to the image. Basically, it may crush the highlights as pixel values get clipped. Currently, the very center of a full brightness scanline will be at full brightness (e.g., 1.0 or rgb(255)) even with smaller scanline widths. So the default is basically the brightest the image can be without changing the shape of a full brightness scanline.
I think if I wanted the image to be brighter, I would try applying some gamma function like pow(rgb, MaxSpotWidth)
. I’d have to do some more thinking to figure out what exactly would be appropriate. This will still taper off the highlights and change the tonality of the image, but it should at least avoid clipping because it still maps 1.0 to 1.0.
A semi-fixed scanline width mode that uses a reference resolution instead of matching the input resolution, so games that switch resolution don’t also switch monitors. (It scales with the square root of vertical lines, and seems to work fine even if scanlines overlap.)
I think this is an interesting idea and should make systems that can change the line count work properly.
The scanlines overlapping will cause some issues with scanline shape and image tonality. There are two issues, really:
- Currently, only the 2 nearest lines are used for finding a pixel’s value. If the scanlines overlap more, it should really consider the nearest 3 lines. If they overlap even more, it should consider the nearest 4 lines, and so on.
- When scanlines overlap more, the pixel values will increase above 1.0. Some sort of compensation needs to be done to bring the values back into range to avoid clipping.
Both of these are solvable problems if there is enough desire for this sort of configurability. There could be a substantial performance impact to using 3 lines instead of 2, though. It would basically make that part of the shader 50% slower, and that’s already the slow part. 
Integration with the NTSC color fringing simulation from crt-guest-advanced-ntsc.
A new shader I’ve written to combine a Famicom palette generator with crt-guest-advanced-ntsc for proper artifact colors in NES games (I should probably figure out how to contribute it over there…).
I have been thinking about NTSC simulation. There are some things that Themaister’s shaders (and guest’s, which are based on them) don’t do, if I understand the code correctly (and I may not!).
- The chroma filter is a simple notch filter instead of a comb filter, so the artifacts are often more severe and of a different character than on nicer TVs from the 90’s.
- Those shaders are also sensitive to the input resolution. The FIR filter coefficients are hard-coded based on the input resolution and I think that can be avoided, which would allow more resolutions to be used.
- Some subtleties of certain systems aren’t simulated. For example, the extra pixel that the SNES outputs every other frame, which offsets the chroma phase.
@PlainOldPants has an interesting NTSC shader, although I will admit that I don’t understand the code yet. I think a fairly simple and accurate NTSC shader (with a comb filter but without color correction) could be done in 3 passes.
Presets for various systems, including one that looks really good for PC-98 games
I noticed that you are using the NTSC shader for an S-Video preset. Do you find that to look better than just flipping the composite parameter in crt-beans and adjusting the I and Q bandwidths?