NES: Reimplement PAL forced OAM refresh according to new research#137
Merged
Conversation
On PAL, vblank is so long that OAM may decay during it. The PPU works around this by incrementing the OAM1 address every odd dot (delayed by 1, so happening on the following even dot) during scanlines 265-310. The PPU reads from OAM1 on every dot during vblank/fblank, so by repeatedly visiting all the addresses, it reads every row and keeps OAM fresh. When implementing this, I found that OAM was corrupting reliably. This is because the forced refresh normally leaves the OAM1 address set to $8C when rendering begins, which triggers the 2C02E+ OAM corruption bug. However, all 3 types of OAM corruption are actually fixed on PAL, so this change also restricts OAM corruption to the NTSC console region. All tests continue to pass with this change. blargg's oam_read test fails as it should, but not as severely as it does on real hardware. I'm not clear on why, but certainly part of it is that this change allows OAM1 to be incremented twice on a single dot. Adding hacky support for limiting this to just 1 increment makes it fail a little more severely, but still not as bad as on hardware (and that change is out of scope for this commit). My best guess right now is that it has to do with the precise timing of the $2004 writes, as the OAM write and increment delays depend on the timing of the end of the CPU write.
SourMesen
approved these changes
May 24, 2026
Based on my testing, frame-start (2C02E+ style) and mid-frame rendering toggle (NTSC style) OAM corruption happen on Dendy, but not $2003 write OAM corruption. So, this changes corruption emulation to match that. Tested with oam_flicker_test, oam_flicker_test_reenable, oam_read, oam_stress, and Huge Insect.
ff9667c to
e0ac5f1
Compare
Member
Author
|
I did some Dendy testing on a UM6561AF-2. I did not detect anything that looks like forced OAM refresh anywhere in the frame, so it does not appear to be doing refresh between the start of vblank and NMI. I did find that it is susceptible to 2 of the 3 sources of OAM corruption. $2003 writes do not corrupt, but rendering toggle or starting the frame with an OAM1/OAM2 row mismatch do corrupt. $2003 corruption was already limited to NTSC, but I've updated this change to only exempt PAL from the remaining corruption. With these changes, MesenCE matches what I'm seeing on hardware for Dendy. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
On PAL, vblank is so long that OAM may decay during it. The PPU works around this by incrementing the OAM1 address every odd dot (delayed by 1, so happening on the following even dot) during scanlines 265-310. The PPU reads from OAM1 on every dot during vblank/fblank, so by repeatedly visiting all the addresses, it reads every row and keeps OAM fresh.
When implementing this, I found that OAM was corrupting reliably. This is because the forced refresh normally leaves the OAM1 address set to $8C when rendering begins, which triggers the 2C02E+ OAM corruption bug. However, all 3 types of OAM corruption are actually fixed on PAL, so this change also restricts OAM corruption to the NTSC console region.
All tests continue to pass with this change. blargg's oam_read test fails as it should, but not as severely as it does on real hardware. I'm not clear on why, but certainly part of it is that this change allows OAM1 to be incremented twice on a single dot. Adding hacky support for limiting this to just 1 increment makes it fail a little more severely, but still not as bad as on hardware (and that change is out of scope for this commit). My best guess right now is that it has to do with the precise timing of the $2004 writes, as the OAM write and increment delays depend on the timing of the end of the CPU write.