Static, semi-static, or dynamic allocation of OAM

Discussion of programming and development for the original Game Boy and Game Boy Color.
Post Reply
tepples
Posts: 22705
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Static, semi-static, or dynamic allocation of OAM

Post by tepples »

On the NES, I've gathered that it's considered best practice not to hardcode a particular game object's starting position in the display list. For example, don't always draw the main character using sprites 1-8, enemy 1 using sprites 9-14, enemy 2 using sprites 15-20, etc. Instead, NES games are supposed to reassign slots every frame, especially if there's a possibility that more than eight will be displayed on the same scanline. This also allows the game to make enemies intentionally Z-fight if they overlap.

But in the Game Boy community (gbdev.gg8.se and the related Discord server), it's common to hardcode OAM indices for actors or at least not shuffle them from frame to frame, and I'm trying to understand why they do that. The Game Boy sprite system differs from that of the NES as follows:
  • There is enough secondary OAM to draw 10 8-pixel-wide sprites per 160-pixel scanline. Thus sprites can cover nearly half of the screen's width rather than one-fourth like on the NES.
  • The PPU determines which sprites to draw by finding the 10 lowest-numbered sprites in OAM whose Y range overlaps each scanline, just as the NES PPU does. But then according to Pan Docs, the monochrome Game Boy sorts these frontmost 10 sprites by their X coordinate before displaying them. Sprites to the left are drawn in front, with position in OAM only breaking ties.
  • The memory controller responsible for OAM isn't a rushed, buggy mess. This means OAM can be randomly accessed at $FE00-$FE9F during blanking periods.
  • Horizontal blanking on the Game Boy is much longer than on the NES. OAM on the Game Boy can be accessed not only during vertical blanking but also during most of horizontal blanking.
On the Game Boy, with more relative overdraw and less control of sprite-to-sprite priority, would it be less of a bad practice to statically allocate OAM space? Does random access to OAM even during horizontal blanking have anything to do with it by reducing the need to always use OAM DMA?


Relationship to other topic: This one also includes the random access instead of DMA as an additional factor.
User avatar
rainwarrior
Posts: 8731
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Static, semi-static, or dynamic allocation of OAM

Post by rainwarrior »

I don't know why this was a whole new topic, but my previous response applies more or less equally here with a minor edit:
rainwarrior wrote:I would say it's quite common for new NES developers to hardcode OAM indices too. It's just that they probably pretty quickly get beat back by the problems that creates. On a system with wider tolerance you wouldn't necessarily create a problem at all.

Overlap is often a big problem in a platform game with a flat plane of play, since gravity brings everything to the same level. But... 4 sprites on one platform is usually a pretty busy situation in NES games already... 8 5 sprites (on a smaller screen) ? How crowded is this?
I think pretty much everyone's reply in that thread is still relevant here.
adam_smasher
Posts: 271
Joined: Sun Mar 27, 2011 10:49 am
Location: Victoria, BC

Re: Static, semi-static, or dynamic allocation of OAM

Post by adam_smasher »

tepples wrote:On the Game Boy, with more relative overdraw and less control of sprite-to-sprite priority, would it be less of a bad practice to statically allocate OAM space? Does random access to OAM even during horizontal blanking have anything to do with it by reducing the need to always use OAM DMA?
Yes, it's probably less of a "bad practice", mostly because of the first point in your list: visual glitches are less likely to happen, so why bother?

I don't understand what you're talking about w.r.t. hblank and DMA, but I doubt it's a factor. My understanding is that most games do use the shadow OAM/OAM DMA method (IIRC the official docs prescribe it). hblank on the GB is still incredibly short and not useful for much besides raster effects.

My engine doesn't shuffle OAM slots right now, but it's something I'd like for it to do eventually. I don't know off the top of my head what any commercial games do.
User avatar
rainwarrior
Posts: 8731
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Static, semi-static, or dynamic allocation of OAM

Post by rainwarrior »

adam_smasher wrote:I don't know off the top of my head what any commercial games do.
Teenage Mutant Ninja Turtles: Fall of the Foot Clan is one commercial game that appears to have OAM cycling:
https://youtu.be/V9g3abNwXO8?t=5m25s

I'm sure there are many others. There are also plenty of NES games that don't need to. I think we're just so used to NES games having "flicker" that it's an obvious feature. Less so on GB. Even less so on SNES.

The plant boss from Gradius III always made me suspect it might not cycle OAM. (In many other places overlap order appears to be fixed, as well.)
https://youtu.be/pjQ1_8hbp9I?t=21m40s
psycopathicteen
Posts: 3140
Joined: Wed May 19, 2010 6:12 pm

Re: Static, semi-static, or dynamic allocation of OAM

Post by psycopathicteen »

Dynamic OAM doesn't necessarily mean no flicker. My old Gunstar Heros demo used a dynamic OAM but no alternating flicker.
tepples
Posts: 22705
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Static, semi-static, or dynamic allocation of OAM

Post by tepples »

adam_smasher wrote:hblank on the GB is still incredibly short and not useful for much besides raster effects.
That's what I thought, until I found a reason to push 14 1bpp tiles over the course of 14 hblank lines. It's a variable width font (VWF) engine, and unlike on the NES, the 8.5 lines of vblank that remain after OAM DMA aren't long enough without stack abuse. Here's what I ended up doing, with no exceptions in BGB.
  1. Wait for LY not 153 or 0, to avoid a vblank followed immediately by OAM scan with no intervening hblank.
  2. Wait for LY not LYC - 2 or LYC - 1, to avoid the stat interrupt handler throwing off the timing of the following steps.
  3. Wait for STAT.D0 = 1, meaning vblank or draw.
  4. Wait for STAT.D1 = 0, meaning vblank or hblank; this loop has a 6-cycle granularity.
  5. You have about 44 cycles (for OAM) or 62 cycles (for VRAM) to write bytes. This should be enough to push one OAM entry, one 1bpp tile (alternating write and skip), half a 2bpp tile, or one group of four tiles in the tilemap.
Thus someone might be tempted to write a sprite update routine that pushes out changes immediately in hblank.
nitro2k01
Posts: 252
Joined: Sat Aug 28, 2010 9:01 am

Re: Static, semi-static, or dynamic allocation of OAM

Post by nitro2k01 »

I was going to write a reply about a routine I've written, but the post quickly became so long that I posted it in its own thread instead: 16 byte per line hblank copy routine
Post Reply