sprite dma transfer pig

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
sprite dma transfer pig
by on (#123424)
I'm re-reading the nerdynights 4th tutorial, the one on sprites.
The first thing it does in the NMI is set the address to $0200 (because thats where his changable sprite data is).
It then automatically copies the whole page (256?). But what if I only have a couple sprites in my game, want to use $200-2FF as variables?
Seems like a waste of vblank timeto copy more than the couple sprites' bytes I need.
Can I update just one sprite?
Am I right that every frame the sprite data needs to be sent to the ppu or elese it deteroriates on a real nes?

Another thing I think I read before was about using the stack as variable ram, but just the low area, and make sure complicated functions dont push far down into it.
I should have tried to find where I read that, before asking if it's true.

Edit.
Now that I think of it, the ppu probably does the whole transfer in the background, not taking up vblank time/ cycles.
And I can probly use most of it as variable. I'll try.
Re: sprite dma transfer pig
by on (#123425)
You can write a few sprites byte by byte, but the problem is that when rendering is off the data in the OAM gradually starts to decay after a few milliseconds (it's safe for about the length of vblank).

It's quite difficult to write arbitrary data to all 256 bytes of OAM during a single vblank. There's just not a lot of time there to do this. The DMA makes this task a lot easier. If you just want to change a few bytes each frame, this is doable. As long as you can fit your OAM updates into vblank it will remain stable.

One thing you might consider, if you don't have a lot of sprite states and RAM is scarce, you can put your OAM table in ROM instead and DMA it from there.
Re: sprite dma transfer pig
by on (#123426)
To conserve RAM, put unused sprites offscreen (Y coordinate $F0-$FF). Then you can use the tile, attribute, X coordinate, and even the lower nibble of the Y coordinate of each unused OAM entry for other purposes.
Re: sprite dma transfer pig
by on (#123427)
Warning: This has been tested and is known working, but is one of the crankier corners of the NES.

The FC and NES have a bug—I think due to its lengthened M2 cycle—where any write to $2003 ("OAMADDR") causes corruption of sprite memory. In fact, not just CPU-mediated writes to OAMADDR, but most things that change it, including when it is reset to 0 at the start of rendering.

On first powerup, OAMADDR is 0, but for resets or brief power outages, it'll retain whatever was the last value when the NES last stopped executing code. So you can't rely on its value when your game starts. Additionally, OAM itself is full of random stuff, so you have to initialize that once when you first turn the NES on.

There is precisely one safe way to set OAMADDR to 0 without subsequently refreshing all 256 bytes using DMA or writes to $2004: let the NES render one complete frame.

So, now, in your NMI, after you've passed one frame with either background or sprite rendering enabled, you know that OAMADDR is 0. You can write values to OAMDATA safely, and know where in OAM they'll end up.

But there's another bug: if OAMADDR is 8 or higher when rendering starts, the eight bytes of memory starting at (OAMADDR & 0xF7) are copied to the first 8 bytes.

So you can safely write 7 bytes, allowing precisely one sprite to placed arbitrarily, and one to have everything but its X coordinate updated.


TL;DR: in boot code, or any time you disable rendering, enable background rendering for one frame. After NMI, use DMA to initialize all OAM. (Not all need to be offscreen)
For all subsequent frames, you can write these seven bytes: Y0 Tile0 Attr0 X0 Y1 Tile1 Attr1
You can never disable both background and sprite rendering without having to do this again, because OAM decays if rendering is disabled for longer than 22 scanlines or so, and if OAMADDR wasn't 0 when you disabled rendering, it'll damage OAM when you restart rendering.


That said, tepples's idea is better, unless those last 64 bytes matter or you need more time during vblank.
Re: sprite dma transfer pig
by on (#123430)
tsu wrote:
Can I update just one sprite?

Theoretically, yes. The PPU has 2 registers meant to manipulate OAM ($2003 and $2004) which are supposed to let you modify individual bytes (much like you use $2006/$2007 to update VRAM), but people found out that those registers aren't always 100% reliable, so the safest way to update sprites is indeed to use the DMA.

Quote:
Am I right that every frame the sprite data needs to be sent to the ppu or elese it deteroriates on a real nes?

AFAIK, the PPU itself will take care of refreshing OAM as long as rendering is enabled (it seems that the PPU reading from the OAM when evaluating and drawing sprites is enough to keep the bits from deteriorating). I'm pretty sure lots of games don't request a sprite DMA during lag frames, and they don't seem to have any sprite glitches because of that.

Quote:
Another thing I think I read before was about using the stack as variable ram, but just the low area, and make sure complicated functions dont push far down into it.

Sure you can use part of page 1 for variables. Look at the Atari 2600 (which also uses a 6502) for example: it has only 128 bytes of RAM, that are mapped at $0080-$00FF, and also mirrored at $0180-$01FF, meaning that ALL of its RAM is in the stack (and it doesn't even fill the whole stack!). So as long as you don't let the stack pointer reach your variables, you're safe. You don't even have to put the variables in the low area, since you could just as well initialize the stack pointer to $7F (instead of the usual $FF) for example, and use $180-$1FF for variables. Your stack can be as small as you want to, and anywhere in page 1, you just have to be sure to initialize the stack pointer to the top of the stack and never push more data than you have set aside for it.

Quote:
Now that I think of it, the ppu probably does the whole transfer in the background, not taking up vblank time/ cycles.

Nope, sprite DMA definitely takes up VBlank time (after the write to $4014, 513 or 514 CPU cycles are necessary to upload all 256 OAM bytes).
Re: sprite dma transfer pig
by on (#123431)
Thanks guys, that is stuff I wanted to know.

I wasn't sure how much nes cpu ram i had to work with. I was woried that if I start the dma transfer higher than $0200 it might read on into some other memory range it's not suposed to.
So I checked the nesdev wiki cpu memory map. It shows ram all the way to $0800.
That should be enough, shouldn't have to use stack or sprite sma area.
Is the sprite dma tied to $0200-$02ff?
Re: sprite dma transfer pig
by on (#123432)
No, the number you write to $4014 specifies the upper byte of the address that DMA reads from. (i.e. writing 2 means it transfers $0200 to $02ff; writing $81 means it transfers $8100 to $81ff, &c)
Re: sprite dma transfer pig
by on (#123433)
A lot of games use $0200-$02FF. A lot of other games use $0700-$07FF. Some games with extra RAM on the cartridge use one of the pages in $6000-$7FFF. Hot Seat Harry uses $0000-$00FF so that it can write to shadow OAM using shorter zero page instructions; it hides other zero page variables from the sprite processor with $Fx.