[My emulator] Graphics glitches - SuperMarioBros

Discuss emulation of the Nintendo Entertainment System and Famicom.

Moderator: Moderators

beannaich
Posts: 207
Joined: Wed Mar 31, 2010 12:40 pm

Post by beannaich » Mon Oct 17, 2011 5:05 am

Does the timing change if you don't start at scanline 241? That's the only real difference I can see between your PPU and mine. I start at scanline -1.

Bisqwit
Posts: 248
Joined: Fri Oct 14, 2011 1:09 am

Post by Bisqwit » Mon Oct 17, 2011 5:17 am

beannaich wrote:Does the timing change if you don't start at scanline 241? That's the only real difference I can see between your PPU and mine. I start at scanline -1.
Start what?

User avatar
Dwedit
Posts: 4351
Joined: Fri Nov 19, 2004 7:35 pm
Contact:

Post by Dwedit » Mon Oct 17, 2011 5:19 am

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.
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!

beannaich
Posts: 207
Joined: Wed Mar 31, 2010 12:40 pm

Post by beannaich » Mon Oct 17, 2011 5:19 am

Code: Select all

for (scanline = 241; ; scanline = scanline < 260 ? scanline + 1 : -1) {
...
}

Bisqwit
Posts: 248
Joined: Fri Oct 14, 2011 1:09 am

Post by Bisqwit » Mon Oct 17, 2011 5:23 am

beannaich wrote:

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.

Bisqwit
Posts: 248
Joined: Fri Oct 14, 2011 1:09 am

solved

Post by Bisqwit » Tue Oct 18, 2011 4:56 am

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.

tepples
Posts: 22050
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: solved

Post by tepples » Tue Oct 18, 2011 5:01 am

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.
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
Posts: 248
Joined: Fri Oct 14, 2011 1:09 am

Post by Bisqwit » Tue Oct 18, 2011 5:06 am

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
Last edited by Bisqwit on Wed Oct 19, 2011 3:07 pm, edited 2 times in total.

User avatar
Zepper
Formerly Fx3
Posts: 3217
Joined: Fri Nov 12, 2004 4:59 pm
Location: Brazil
Contact:

Post by Zepper » Tue Oct 18, 2011 3:02 pm

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.

Bisqwit
Posts: 248
Joined: Fri Oct 14, 2011 1:09 am

This algorithm passes the ppu_vbl_nmi test

Post by Bisqwit » Mon Oct 24, 2011 3:23 am

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:
  • 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)
Pseudo-code 1, "generic code, executed in the beginning of every PPU cycle":

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;
        }
Pseudo-code 2, "$2000 write":

Code: Select all

/* Nothing special happens, aside from storing to registers. */
Pseudo-code 3, "$2002 read":

Code: Select all

if(VBlankState != -5) VBlankState = 0;
Pseudo-code 4, "enter vblank" (at the beginning of cycle where X=0, scanline=240, still after the generic code above):

Code: Select all

VBlankState = 2;
Pseudo-code 5, "exit vblank" (at the beginning of PPU cycle where X=0, scanline=-1 (pre-render), still after the generic code above):

Code: Select all

VBlankState = -5;
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.
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
EDIT:
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.
Updated version here: http://bisqwit.iki.fi/src/nesemu1_vbl_t ... letonv2.cc (standard level requirement also dropped from C++11 to C++03).

albailey
Posts: 177
Joined: Thu Jul 13, 2006 3:15 pm

Post by albailey » Mon Oct 24, 2011 6:59 am

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

Near
Founder of higan project
Posts: 1550
Joined: Mon Mar 27, 2006 5:23 pm

Post by Near » Mon Oct 24, 2011 10:57 am

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.

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;
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.

User avatar
James
Posts: 429
Joined: Sat Jan 22, 2005 8:51 am
Location: Chicago, IL
Contact:

Post by James » Mon Oct 24, 2011 12:58 pm

byuu wrote:

Code: Select all

  if(status.ly == 241 && status.lx ==   2) cpu.set_nmi_line(status.nmi_enable && status.nmi_flag);
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?

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.

Near
Founder of higan project
Posts: 1550
Joined: Mon Mar 27, 2006 5:23 pm

Post by Near » Mon Oct 24, 2011 1:22 pm

Conjecture and empirical evidence. It doesn't seem that anybody really knows the true breakdown of interactions between the CPU and PPU.

sleepy9090
Posts: 81
Joined: Fri Aug 22, 2008 10:04 am

Post by sleepy9090 » Wed Oct 26, 2011 4:31 pm

7 days? incredible

Post Reply