Emdev newbie - PPU woes

Discuss emulation of the Nintendo Entertainment System and Famicom.

Moderator: Moderators

Post Reply
InconspicuousBrick
Posts: 5
Joined: Fri Oct 18, 2019 9:44 am
Location: Netherlands

Emdev newbie - PPU woes

Post by InconspicuousBrick » Sat Oct 19, 2019 7:32 am

Hi all,

I have been trying my hand at developing a simple NES emulator for the past few weeks. Aside from the APU, I have implemented all the important parts of the NES. After lots of debugging, the CPU finally passes nestest with flying colors. But I have been stuck with the PPU for some time now.

My PPU code mostly works. Donkey Kong renders perfectly. Super Mario Bros, however, has some issues and I've not been able to track down the root cause.

I basically have two problems, and I am not even sure if they are separate or related:
1. The menu screen does not display correctly
2. After starting a new game, the game renders correctly, except there are weird scrolling artifacts (only) on the left side of the screen. And for some reason, some sprites render with a weird 1x4 pixel stripe artifact (see the goomba's in my second image). (The latter does not occur with any of Donkey Kong's sprites, or for example the player sprites in SMB.)

See the attached images below.

I have read that SMB menu issues are often caused by an incorrect $2007 reading implementation. However, I can't see any obvious mistake in my code. What else could be causing this? I feel as though there may be something wrong with my ppu scrolling implementation (based on this: http://wiki.nesdev.com/w/index.php/PPU_scrolling), but again, I can't spot the mistake, and I don't see how that could cause the menu issues.

I would be very grateful if any of you veterans can point me in the right direction. I'd be happy to share my code, although I don't think anyone is interested in reviewing ~1000 lines of ppu code written by someone who, before this, had no C++ experience whatsoever.
Attachments
smb2.jpg
smb.jpg

User avatar
Quietust
Posts: 1500
Joined: Sun Sep 19, 2004 10:59 pm
Contact:

Re: Emdev newbie - PPU woes

Post by Quietust » Sat Oct 19, 2019 11:06 am

Your rendering problem isn't limited to sprites - some of the background tiles are corrupted as well. Are you correctly write-protecting your CHR ROM data?
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.

InconspicuousBrick
Posts: 5
Joined: Fri Oct 18, 2019 9:44 am
Location: Netherlands

Re: Emdev newbie - PPU woes

Post by InconspicuousBrick » Sun Oct 20, 2019 8:18 am

Hi Quietust, thanks for your input! I would have never thought to consider that. It turns out my CHR ROM was indeed not write protected. I had left it accessible for writing for debugging purposes and never bothered to change it, because I assumed programs wouldn't try to write to it (not on mapper 000 anyway). Clearly I was mistaken.

Write protecting CHR ROM and PRG ROM seems to have fixed some of the weird stuff. The SMB logo is less broken, and the sprite artifacts appear to have gone. However, the menu is still not rendering correctly and the scrolling artifacts on the left side of the screen persist. Additionally, when the demo starts playing after waiting on the menu for a while, some background tiles do not render correctly, but this does not appear to be an issue after Mario walks further to the right.

Could this indicate an issue with my v-t register implementation or timings?
Attachments
smb5.jpg
smb4.jpg
smb3.jpg

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

Re: Emdev newbie - PPU woes

Post by Dwedit » Sun Oct 20, 2019 9:22 am

SMB title screen is a well known problem, you need to buffer the bytes read through $2007. The game will discard the first byte read out, then the second read will return the first value, the third read returns the second value, etc.

It also looks like you are going one pixel too low after the screen wraps, it's not a simple increment, it's a wrapping increment.

Also double check the nametable mirroring.
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!

InconspicuousBrick
Posts: 5
Joined: Fri Oct 18, 2019 9:44 am
Location: Netherlands

Re: Emdev newbie - PPU woes

Post by InconspicuousBrick » Sun Oct 20, 2019 9:53 am

Thanks, Dwedit! I had read about the need to buffer reading through $2007 and had already implemented it. After reading your comment, I checked my $2007 code again carefully. I was correctly buffering the read, but it appears I made very silly program flow mistake that caused PPUADDR to be incremented before reading from the PPU bus. Egg on my face.

The menu now renders in all its glory, with only the weird column on the left side on the screen remaining. I will double check my nametable mirroring implementation next, as you suggested.

Regarding the screen wrapping, are you referring to what's going on in the left column? It does appear to be one pixel too many down, but I would expect a mistake with scanline counting to affect the entire screen.

I have based my scanline counting code on this: http://wiki.nesdev.com/w/images/d/d1/Ntsc_timing.png

Code: Select all

dots++;

if (dots > 340)
{
	dots = 0;
	scanline++;

	if (scanline > 261)
	{
		scanline = 0;
	}
}
Attachments
smb6.jpg

InconspicuousBrick
Posts: 5
Joined: Fri Oct 18, 2019 9:44 am
Location: Netherlands

Re: Emdev newbie - PPU woes

Post by InconspicuousBrick » Sun Oct 20, 2019 11:07 am

I have checked my nametable mirroring implementation against the information on http://wiki.nesdev.com/w/index.php/Mirr ... _Mirroring, and I think it's correct.

For reference, I have implemented the nametable as a pair of 1024-byte arrays, and I do mirroring by selecting one of them depending on the type of mirroring used.

Code: Select all

if (MirrorMode == NametableMirrorType::Vertical)
{
	if (address >= 0x2000 && address <= 0x23FF)
		return VRAM.Nametable[0][address & 0x03FF];
	else if (address >= 0x2400 && address <= 0x27FF)
		return VRAM.Nametable[1][address & 0x03FF];
	else if (address >= 0x2800 && address <= 0x2BFF)
		return VRAM.Nametable[0][address & 0x03FF];
	else if (address >= 0x2C00 && address <= 0x2FFF)
		return VRAM.Nametable[1][address & 0x03FF];
}
else if (MirrorMode == NametableMirrorType::Horizontal)
{
	if (address >= 0x2000 && address <= 0x23FF)
		return VRAM.Nametable[0][address & 0x03FF];
	else if (address >= 0x2400 && address <= 0x27FF)
		return VRAM.Nametable[0][address & 0x03FF];
	else if (address >= 0x2800 && address <= 0x2BFF)
		return VRAM.Nametable[1][address & 0x03FF];
	else if (address >= 0x2C00 && address <= 0x2FFF)
		return VRAM.Nametable[1][address & 0x03FF];
}

User avatar
Quietust
Posts: 1500
Joined: Sun Sep 19, 2004 10:59 pm
Contact:

Re: Emdev newbie - PPU woes

Post by Quietust » Sun Oct 20, 2019 11:39 am

Regarding the left edge of the screen, it looks like all of the tiles are being shifted one pixel down and rotated (i.e. shifted with wrap-around) one pixel to the left. The first part could happen if you were doing the cycle 325-328 fetches with a explicitly incremented Y coordinate (to compensate it for being on the next scanline) despite having already incremented the Y coordinate at cycle 256, though if that were the case I'd have expected the "glitched" area to be twice as wide (i.e. 16 pixels).
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.

creaothceann
Posts: 214
Joined: Mon Jan 23, 2006 7:47 am
Location: Germany
Contact:

Re: Emdev newbie - PPU woes

Post by creaothceann » Sun Oct 20, 2019 11:59 am

InconspicuousBrick wrote:

Code: Select all

if (MirrorMode == NametableMirrorType::Vertical)
{
	if (address >= 0x2000 && address <= 0x23FF)
		return VRAM.Nametable[0][address & 0x03FF];
	else if (address >= 0x2400 && address <= 0x27FF)
		return VRAM.Nametable[1][address & 0x03FF];
	else if (address >= 0x2800 && address <= 0x2BFF)
		return VRAM.Nametable[0][address & 0x03FF];
	else if (address >= 0x2C00 && address <= 0x2FFF)
		return VRAM.Nametable[1][address & 0x03FF];
}
else if (MirrorMode == NametableMirrorType::Horizontal)
{
	if (address >= 0x2000 && address <= 0x23FF)
		return VRAM.Nametable[0][address & 0x03FF];
	else if (address >= 0x2400 && address <= 0x27FF)
		return VRAM.Nametable[0][address & 0x03FF];
	else if (address >= 0x2800 && address <= 0x2BFF)
		return VRAM.Nametable[1][address & 0x03FF];
	else if (address >= 0x2C00 && address <= 0x2FFF)
		return VRAM.Nametable[1][address & 0x03FF];
}
Note that this code is the same as this:

Code: Select all

const int bit0  = 1 <<  0;  const int bits0  = bit0  - 1;
// ...
const int bit8  = 1 <<  8;  const int bits8  = bit8  - 1;
const int bit9  = 1 <<  9;  const int bits9  = bit9  - 1;
const int bit10 = 1 << 10;  const int bits10 = bit10 - 1;
// ...


if (MirrorMode == NametableMirrorType::Vertical)  {
        if ((address >= 0b'1000'0000000000) && (address <= 0b'1000'1111111111))  return VRAM.Nametable[0][address & bits10];  else
        if ((address >= 0b'1001'0000000000) && (address <= 0b'1001'1111111111))  return VRAM.Nametable[1][address & bits10];  else
        if ((address >= 0b'1010'0000000000) && (address <= 0b'1010'1111111111))  return VRAM.Nametable[0][address & bits10];  else
        if ((address >= 0b'1011'0000000000) && (address <= 0b'1011'1111111111))  return VRAM.Nametable[1][address & bits10];
} else
if (MirrorMode == NametableMirrorType::Horizontal)  {
        if ((address >= 0b'1000'0000000000) && (address <= 0b'1000'1111111111))  return VRAM.Nametable[0][address & bits10];  else
        if ((address >= 0b'1001'0000000000) && (address <= 0b'1001'1111111111))  return VRAM.Nametable[0][address & bits10];  else
        if ((address >= 0b'1010'0000000000) && (address <= 0b'1010'1111111111))  return VRAM.Nametable[1][address & bits10];  else
        if ((address >= 0b'1011'0000000000) && (address <= 0b'1011'1111111111))  return VRAM.Nametable[1][address & bits10];
}
which can be simplified to:

Code: Select all

if ((address & 0b00'11'000000000000) = 0b00'10'000000000000)  {  // check bits 12 and 13
        if (MirrorMode == NametableMirrorType::Vertical  )  return VRAM.Nametable[(address >> 10) & bit0][address & bits10];
        if (MirrorMode == NametableMirrorType::Horizontal)  return VRAM.Nametable[(address >> 11) & bit0][address & bits10];
}
Might or might not speed things up.
My current setup:
Super Famicom ("2/1/3" SNS-CPU-GPM-02) → SCART → OSSC → StarTech USB3HDCAP → AmaRecTV 3.10

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

Re: Emdev newbie - PPU woes

Post by Dwedit » Sun Oct 20, 2019 1:05 pm

I've never seen Mirroring done with IF statements before, it's always been shifts and array lookups for me...

Given bits NNYYYYYXXXXX, you mask off NN, then shift right, and use that as an index into your nametable selection array, which is updated whenever mirroring changes.
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!

InconspicuousBrick
Posts: 5
Joined: Fri Oct 18, 2019 9:44 am
Location: Netherlands

Re: Emdev newbie - PPU woes

Post by InconspicuousBrick » Mon Oct 21, 2019 7:19 am

I managed to track down the cause of left edge weirdness! It appears to have been a combination of not correctly updating v/t on the pre-render line and an error in updating the background data shift registers. It looks good now!

Thanks again for your help and suggestions! I was completely stuck on these problems before, and I would have never figured it out on my own.

Regarding my approach to nametable mirroring, I just went with something that made sense to me. Code that makes heavy uses of bit shift magic often seems a little esoteric to me, but then again there's likely some conditional branching overhead involved with the use of IF statements. Performance was never really an issue though (modern CPUs are crazy fast). That said, I have implemented the optimization suggested by creaothceann and that does seem to make nametable lookups slightly faster (thanks!). That's probably a worthwhile optimization, given how often those lookups happen.
Attachments
success.jpg

creaothceann
Posts: 214
Joined: Mon Jan 23, 2006 7:47 am
Location: Germany
Contact:

Re: Emdev newbie - PPU woes

Post by creaothceann » Tue Oct 22, 2019 11:17 am

If the program is still evolving then it's perfectly fine to not spend too much time on optimizations.

At the end maybe you can even get the jackpot: clear code that after compilation is as fast as code that has been optimized to hell and back.
My current setup:
Super Famicom ("2/1/3" SNS-CPU-GPM-02) → SCART → OSSC → StarTech USB3HDCAP → AmaRecTV 3.10

Post Reply