[My emulator] Graphics glitches - SuperMarioBros
Moderator: Moderators
I doubt it's a timing or OAM problem. Super Mario Bros completes all its PPU writes reasonably early within vblank time.
Double check the scrolling related code. When the status bar isn't on the screen, the game waits for a sprite 0 hit forever.
Log the timestamps and values of PPU writes to 2000, 2005, and 2006.
The last PPU write SMB1 makes is to 2001 to enable display, and that usually happens long before vblank time ends. So you can look at your P and T values at that time. If your T is incorrect, it will pick the wrong nametable for X scrolling, and crash.
Double check the scrolling related code. When the status bar isn't on the screen, the game waits for a sprite 0 hit forever.
Log the timestamps and values of PPU writes to 2000, 2005, and 2006.
The last PPU write SMB1 makes is to 2001 to enable display, and that usually happens long before vblank time ends. So you can look at your P and T values at that time. If your T is incorrect, it will pick the wrong nametable for X scrolling, and crash.
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!
Code: Select all
for (scanline = 241; ; scanline = scanline < 260 ? scanline + 1 : -1) {
...
}Oh. That is completely irrelevant. If just means that the reset state begins from vblank rather than from the pre-render line. The tests are designed to ignore that aspect.beannaich wrote:Code: Select all
for (scanline = 241; ; scanline = scanline < 260 ? scanline + 1 : -1) { ... }
solved
Two changes to the emulator made the game not crash:
- Forcing Sprite 0 hit to be always triggered at a certain position (such as X=0, scanline 32)
- Making Sprite 0 hit check disregard the background pixel color.
Both of these changes made the game not crash. However, the HUD blinks fro and to existence.
I traced the problem to be the following: At the beginning of the frame render, the nametable bitmask of T was set from Bits 0..1 of $2000. Apparently, it is only to be set when $2000 is written. The bits 0..1 of $2000 do not need to be stored anywhere permanently.
- Forcing Sprite 0 hit to be always triggered at a certain position (such as X=0, scanline 32)
- Making Sprite 0 hit check disregard the background pixel color.
Both of these changes made the game not crash. However, the HUD blinks fro and to existence.
I traced the problem to be the following: At the beginning of the frame render, the nametable bitmask of T was set from Bits 0..1 of $2000. Apparently, it is only to be set when $2000 is written. The bits 0..1 of $2000 do not need to be stored anywhere permanently.
Re: solved
Like writes to PPUSCROLL ($2005), writes to PPUCTRL ($2000) bits 1 and 0 change only the value in t (background base address), which gets copied to v (background current address) near the end of line -1.Bisqwit wrote:I traced the problem to be the following: At the beginning of the frame render, the nametable bitmask of T was set from Bits 0..1 of $2000. Apparently, it is only to be set when $2000 is written. The bits 0..1 of $2000 do not need to be stored anywhere permanently.
Thanks tepples.
As of this change, my emulator now replays Super Mario Bros. (JPN/USA PRG0) "warpless" in 18:41.7 by HappyLee successfully from begin to end without desync. (As does the real thing.) Happy days and jubilations!
EDIT: It also replays Wizards & Warriors (USA) in 12:14.93 by Cardboard until the very end. I have therefore reached my accuracy goal. (I followed the principle that better overshoot and find out that I have excess than to do badly and find I need to improve.) Now to add relevant missing features (APU is 0% done), and I'm set.
EDIT: Add Mega Man 2 (JPN) in 23:48.51 by aglasscage, FinalFighter, pirohiko, & Shinryuu to the list. This includes the very timing sensitive scrolling glitches which work by the coincidence of lag and NMI and mutual exclusion bugs in the game.
The above two are made to sync with movies made with FCEUX by preinitializing the RAM to a pattern where bit 2 of the RAM address is mirrored to all 8 bits of the RAM byte.
EDIT: Mega Man (JPN) in 12:23.34 by Shinryuu & FinalFighter still does not sync. It does not sync on the real thing, either. Depending on the number of idle frames inserted in the beginning, it desync in the second stage, where a combination of lag, object table overflow and NMI was used to invoke a buffer overflow glitch, or in the fourth stage, where the same thing was done.
EDIT: http://tasvideos.org/590M.html goes to the list of syncs.
EDIT: Here is a YouTube video showing my emulator... http://www.youtube.com/watch?v=XWgNquPq9LM
As of this change, my emulator now replays Super Mario Bros. (JPN/USA PRG0) "warpless" in 18:41.7 by HappyLee successfully from begin to end without desync. (As does the real thing.) Happy days and jubilations!
EDIT: It also replays Wizards & Warriors (USA) in 12:14.93 by Cardboard until the very end. I have therefore reached my accuracy goal. (I followed the principle that better overshoot and find out that I have excess than to do badly and find I need to improve.) Now to add relevant missing features (APU is 0% done), and I'm set.
EDIT: Add Mega Man 2 (JPN) in 23:48.51 by aglasscage, FinalFighter, pirohiko, & Shinryuu to the list. This includes the very timing sensitive scrolling glitches which work by the coincidence of lag and NMI and mutual exclusion bugs in the game.
The above two are made to sync with movies made with FCEUX by preinitializing the RAM to a pattern where bit 2 of the RAM address is mirrored to all 8 bits of the RAM byte.
EDIT: Mega Man (JPN) in 12:23.34 by Shinryuu & FinalFighter still does not sync. It does not sync on the real thing, either. Depending on the number of idle frames inserted in the beginning, it desync in the second stage, where a combination of lag, object table overflow and NMI was used to invoke a buffer overflow glitch, or in the fourth stage, where the same thing was done.
EDIT: http://tasvideos.org/590M.html goes to the list of syncs.
EDIT: Here is a YouTube video showing my emulator... http://www.youtube.com/watch?v=XWgNquPq9LM
Last edited by Bisqwit on Wed Oct 19, 2011 3:07 pm, edited 2 times in total.
It doesn't match because (afaik) FCEUX needs some work in the emulation. I don't remember of being able to get a pass in some of the test ROMs.
Zepper
RockNES author
RockNES author
This algorithm passes the ppu_vbl_nmi test
After spending a lot of CPU time trying to find the combination of timings that passes the ppu_vbl_nmi test, including test 7, in an automated manner, unsuccessfully, I dug up my earlier revision to try and discover what it was that I did earlier that actually worked.
Reworking the complicated algorithm to simpler pieces, I came up with the following, which passes all tests in ppu_vbl_nmi:
Pseudo-code 2, "$2000 write":Pseudo-code 3, "$2002 read":Pseudo-code 4, "enter vblank" (at the beginning of cycle where X=0, scanline=240, still after the generic code above):Pseudo-code 5, "exit vblank" (at the beginning of PPU cycle where X=0, scanline=-1 (pre-render), still after the generic code above):
I don't know what is correct. Test-based development has its limits, being mostly based on guess-work. These long idle cycle sequences between states -5 and 0 are nasty. Also, ppu_sprite_overflow_test still keeps telling me that my VBL timing is wrong.
Reworking the complicated algorithm to simpler pieces, I came up with the following, which passes all tests in ppu_vbl_nmi:
- For each CPU cycle, PPU is run for three cycles first. This is done by a call to tick(), which is automatically issued before any memory access, read or write.
- The CPU reads the global flag "nmi" before fetching the opcode byte. After fetching the opcode byte, if a raising edge was detected in the nmi flag, the read opcode is replaced with a BRK as explained before (with special NMI-related exceptions)
Code: Select all
switch(VBlankState)
{
case -5: VBlankState = -4; reg.reg2 = 0; break; /* This clears vblank & sprite-related flags */
case -4: VBlankState = -3; break;
case -3: VBlankState = -2; break;
case -2: VBlankState = -1; break;
case -1: VBlankState = 0; break;
case 0: CPU::nmi = reg.InVBlank && reg.NMIenabled; break;
case 1: VBlankState = 0; break;
case 2: VBlankState = 1; reg.InVBlank = true; break;
}Code: Select all
/* Nothing special happens, aside from storing to registers. */Code: Select all
if(VBlankState != -5) VBlankState = 0;Code: Select all
VBlankState = 2;Code: Select all
VBlankState = -5;EDIT:blargg's tests wrote:$ xvfb-run ./ppu_vbl_test.sh
01-vbl_basics
Passed
T+ 1 2
00 - V
01 - V
02 - V
03 - V
04 - -
05 V -
06 V -
07 V -
08 V -
02-vbl_set_time
Passed
00 V
01 V
02 V
03 V
04 V
05 V
06 -
07 -
08 -
03-vbl_clear_time
Passed
04-nmi_control
Passed
00 4
01 4
02 4
03 3
04 3
05 3
06 3
07 3
08 3
09 2
05-nmi_timing
Passed
00 - N
01 - N
02 - N
03 - N
04 - -
05 V -
06 V -
07 V N
08 V N
09 V N
06-suppression
Passed
00 N
01 N
02 N
03 N
04 N
05 -
06 -
07 -
08 -
07-nmi_on_timing
Passed
03 -
04 -
05 -
06 -
07 N
08 N
09 N
0A N
0B N
0C N
08-nmi_off_timing
Passed
00 01 01 02
09-even_odd_frames
Passed
08 08 09 07
10-even_odd_timing
Passed
$ xvfb-run ./ppu_sprite_overflow_test.sh
01-basics
Passed
02-details
Passed
PPU VBL timing is wrong
03-timing
Failed #3
Checks that second byte of sprite #10 is treated as its Y
04-obscure
Failed #2
05-emulator
Passed
Updated version here: http://bisqwit.iki.fi/src/nesemu1_vbl_t ... letonv2.cc (standard level requirement also dropped from C++11 to C++03).Bisqwit wrote:http://bisqwit.iki.fi/src/nesemu1_vbl_test_skeleton.cc
Here is a link to my V-Blank / NMI timing test skeleton, stripped of all features not related to V-Blank / NMI timing testing (370 lines remain). It can be used to run Blargg's tests. Note that it does not include any graphical / audio output. It outputs only to the console. Lacking any mapper functions, it only supports the "rom_singles" versions.
I'd just like to thank you guys for the responses to this post.
Based on some of the answers, I was able to fix my emulator to finally show SMB title screen.
I had already known about the 1 byte read delay, but for reasons I cannot fathom, I was taking the address and masking it to be 0x2000-0x3FFF instead of letting it stay 0x0000-0x3FFF. I'm guessing that I did that in the early days before I properly implemented mirroring, although I really dont know.
Now my SMB title screen works.
Thanks again
Al
Based on some of the answers, I was able to fix my emulator to finally show SMB title screen.
I had already known about the 1 byte read delay, but for reasons I cannot fathom, I was taking the address and masking it to be 0x2000-0x3FFF instead of letting it stay 0x0000-0x3FFF. I'm guessing that I did that in the early days before I properly implemented mirroring, although I really dont know.
Now my SMB title screen works.
Thanks again
Al
Was going to share this when I caught you on IRC again, but I'll post it here. This is how I pass all of blargg's ppu_vbl_nmi tests.
However, I still have to cache BG||OAM enable at X=337, rather than X=338, for the missing dot behavior. That may be correct, but it feels wrong. All other actions happen every two PPU ticks.
Code: Select all
if(status.ly == 240 && status.lx == 340) status.nmi_hold = 1;
if(status.ly == 241 && status.lx == 0) status.nmi_flag = status.nmi_hold;
if(status.ly == 241 && status.lx == 2) cpu.set_nmi_line(status.nmi_enable && status.nmi_flag);
//some symmetry here, we can replace rhs-values with 0 too
if(status.ly == 260 && status.lx == 340) status.nmi_hold = 0;
if(status.ly == 261 && status.lx == 0) status.nmi_flag = status.nmi_hold;
if(status.ly == 261 && status.lx == 2) cpu.set_nmi_line(status.nmi_enable && status.nmi_flag);Code: Select all
uint8 PPU::read(uint16 addr) {
uint8 result = 0x00;
switch(addr & 7) {
case 2: //PPUSTATUS
result |= status.nmi_flag << 7;
result |= status.sprite_zero_hit << 6;
result |= status.sprite_overflow << 5;
result |= status.mdr & 0x1f;
status.address_latch = 0;
status.nmi_hold = 0;
cpu.set_nmi_line(status.nmi_flag = 0);
break;Code: Select all
void PPU::write(uint16 addr, uint8 data) {
status.mdr = data;
switch(addr & 7) {
case 0: //PPUCTRL
status.nmi_enable = data & 0x80;
status.master_select = data & 0x40;
status.sprite_size = data & 0x20;
status.bg_addr = (data & 0x10) ? 0x1000 : 0x0000;
status.sprite_addr = (data & 0x08) ? 0x1000 : 0x0000;
status.vram_increment = (data & 0x04) ? 32 : 1;
status.taddr = (status.taddr & 0x73ff) | ((data & 0x03) << 10);
cpu.set_nmi_line(status.nmi_enable && status.nmi_hold && status.nmi_flag);
return;My understanding of this behavior is that the NMI is triggered immediately, but that the CPU might not immediately recognize it, hence the delay. What info are you basing this on?byuu wrote:Code: Select all
if(status.ly == 241 && status.lx == 2) cpu.set_nmi_line(status.nmi_enable && status.nmi_flag);
Just curious as I spent a bunch of time looking into this behavior recently (see my Castlevania bug thread), and wasn't able to find a definitive (or clear, at least) answer.