VGA vs. NTSC timings are not giving the same desired output
Posted: Sat May 10, 2025 6:04 pm
[This post includes source code and binaries to test on real hardware, see attachments]
Games need to have the same visuals no matter if they are displayed on VGA, NTSC, emulator, and hopefully future HDMI.
My game requires scroll registers to be latched at the desired scanlines, no matter if the display mode is VGA, or NTSC. And no matter what the scroll register is (changing scroll X for layer 1 might not be the same latch timings as changing scroll Y for layer 0). The latching process is hidden and unknown to programmers, but if timed right, MUST behave the same on every allowed video mode and computer hardware.
Just because most people recognizes horizontal scrolling as parallax doesn't mean that vertical scrolling shouldn't work the same way internally. Latching the vertical scroll register of a layer should be the same as latching the horizontal scroll register of another layer. Or at least the documentation should say priority/order of latching.
The bug is visually presented by writing this test program that can be run and easily modified. Using the backdrop palette color as test, it will give a more visual representation of the problem.
VGA is behaving as expected of the documentation. Setting a palette index such as the first (the backdrop) to a different color than set from vertical blank, during a specific scanline should change the color somewhere close to 1/3 of the screen's width of said scanline as seen in the screenshot from a capture card:
VGA
Note that 240p scaled content such as layers and sprites do snap to even pixels, but backdrop color won't since it is acting on the 480p interrupted scanline. How layer scroll values are affected by even and odd scanlines in 240p are unknown and when something works on VGA, it must also work exactly the same on NTSC (which it currently don't, play that Sonic demo on the SD card for proof).
To get a full scanline change of a backdrop color, one can use the timing of the CPU to delay the write until the raster beam has reached the end of the visible screen by inserting NOPs. In this case, 63 NOPs are inserted to give this result on VGA:
VGA
A few more NOPs and the write of the palette entry will happen in the hblank as desired. Normal game consoles allow such effects, no matter if it is NTSC or PAL (see Yoshi's Island for SNES). With these many NOPs, there are no time to run game logic between scanlines in 480p, but in 320p there are a few cycles to take advantage of.
Now to the NTSC 240p, Composite. The same code as before, the backdrop is affected much earlier. Looks like half the distance compared to VGA but that might be misleading:
Composite
When adding the same 63 NOPs to get the palette change in the next hblank, the palette change happens somewhere in the middle of the screen instead:
Composite
With these tests, we developers of games that want raster effects hope to see a unified result where a hblank is a hblank, no matter if it is VGA, Composite, S-video or HDMI.
*A palette entry is 12 bit and requires two bytes to be written to VRAM to make the desired color. If they are written during the active raster beam, colors might not show up correctly for a few pixels at times. That's why it is important we can get a chance to write these values, as well as scroll values for layers in the non-visible screen area (before registers are latched of course).
**Mouse cursor sprite graphics is glitched in NTSC at bottom of screen, but that has nothing to do with this bug report!
Source, binary:
Games need to have the same visuals no matter if they are displayed on VGA, NTSC, emulator, and hopefully future HDMI.
My game requires scroll registers to be latched at the desired scanlines, no matter if the display mode is VGA, or NTSC. And no matter what the scroll register is (changing scroll X for layer 1 might not be the same latch timings as changing scroll Y for layer 0). The latching process is hidden and unknown to programmers, but if timed right, MUST behave the same on every allowed video mode and computer hardware.
Just because most people recognizes horizontal scrolling as parallax doesn't mean that vertical scrolling shouldn't work the same way internally. Latching the vertical scroll register of a layer should be the same as latching the horizontal scroll register of another layer. Or at least the documentation should say priority/order of latching.
The bug is visually presented by writing this test program that can be run and easily modified. Using the backdrop palette color as test, it will give a more visual representation of the problem.
VGA is behaving as expected of the documentation. Setting a palette index such as the first (the backdrop) to a different color than set from vertical blank, during a specific scanline should change the color somewhere close to 1/3 of the screen's width of said scanline as seen in the screenshot from a capture card:
VGA
Note that 240p scaled content such as layers and sprites do snap to even pixels, but backdrop color won't since it is acting on the 480p interrupted scanline. How layer scroll values are affected by even and odd scanlines in 240p are unknown and when something works on VGA, it must also work exactly the same on NTSC (which it currently don't, play that Sonic demo on the SD card for proof).
To get a full scanline change of a backdrop color, one can use the timing of the CPU to delay the write until the raster beam has reached the end of the visible screen by inserting NOPs. In this case, 63 NOPs are inserted to give this result on VGA:
VGA
A few more NOPs and the write of the palette entry will happen in the hblank as desired. Normal game consoles allow such effects, no matter if it is NTSC or PAL (see Yoshi's Island for SNES). With these many NOPs, there are no time to run game logic between scanlines in 480p, but in 320p there are a few cycles to take advantage of.
Now to the NTSC 240p, Composite. The same code as before, the backdrop is affected much earlier. Looks like half the distance compared to VGA but that might be misleading:
Composite
When adding the same 63 NOPs to get the palette change in the next hblank, the palette change happens somewhere in the middle of the screen instead:
Composite
With these tests, we developers of games that want raster effects hope to see a unified result where a hblank is a hblank, no matter if it is VGA, Composite, S-video or HDMI.
*A palette entry is 12 bit and requires two bytes to be written to VRAM to make the desired color. If they are written during the active raster beam, colors might not show up correctly for a few pixels at times. That's why it is important we can get a chance to write these values, as well as scroll values for layers in the non-visible screen area (before registers are latched of course).
**Mouse cursor sprite graphics is glitched in NTSC at bottom of screen, but that has nothing to do with this bug report!
Source, binary: