The SNES has 128 independent sprites. The sprite definitions are stored in Object Attribute Memory, or OAM.
OAM consists of 544 bytes, organized into a low table of 512 bytes and a high table of 32 bytes. Both tables are made up of 128 records. OAM is accessed by setting the word address in register $2102, the "table select" in bit 0 of $2103, then writing to $2104 or reading from $2138. Since the high table is only 32 bytes long, only the low 4 bits of $2102 are significant for indexing this table.
The internal OAM address is invalidated during the rendering of a scanline; this invalidation is deterministic, but we do not know how or when the value is determined. Current theory is that it is invalidated more-or-less continuously and has something to do with the current OAM address and possibly which sprites are on the current scanline. The internal OAM address is reloaded from $2102/3 at the beginning of V-Blank, if this occurs outside of a force-blank period. The reload also occurs on a 1->0 transition of $2100.7.
Each read/write increments the address by one byte (the internal address has 10 bits, with bit 9 selecting the table and bits 0-8 indexing). Reads simply read the current byte. Writes to the low table go into a word-sized buffer, which is written to the appropriate word of OAM when the high byte of the word is written. Thus, if alternating reads and writes occur such that the high byte of the word is always read instead of written, none of the writes will actually affect OAM. If the alternation happens such that the writes always occur to the high byte, not only the high bytes but whatever garbage is left in the low byte will be written as well!
Pictorially: Start OAM filled with all zeros. Write 1, read, read, Write 2, read, write 3 => OAM is 00 00 01 02 01 03, rather than 01 00 00 02 00 03 as you might expect.
Writes to the high table, on the other hand, work exactly as expected.
The record format for the low table is 4 bytes:
byte OBJ*4+0: xxxxxxxx byte OBJ*4+1: yyyyyyyy byte OBJ*4+2: cccccccc byte OBJ*4+3: vhoopppN
The record format for the high table is 2 bits:
bit 0/2/4/6 of byte OBJ/4: X bit 1/3/5/7 of byte OBJ/4: s
The values are:
Xxxxxxxxx = X position of the sprite. Basically, consider this signed but see below. yyyyyyyy = Y position of the sprite.^ cccccccc = First tile of the sprite.^^ N = Name table of the sprite. See below for the calculation of the VRAM address. ppp = Palette of the sprite. The first palette index is 128+ppp*16. oo = Sprite priority. See below for details. h/v = Horizontal/Vertical flip flags.^^^ s = Sprite size flag. See below for details.
^Values 0-239 are on-screen. -63 through -1 are "off the top", so the bottom part of the sprite comes in at the top of the screen. Note that this implies a really big sprite can go off the bottom and come back in the top.
^^See below for the calculation of the VRAM address. Note that this could also be considered as ‘rrrrcccc’ specifying the row and column of the tile in the 16x16 character table.
^^^Note this flips the whole sprite, not just the individual tiles. However, the rectangular sprites are flipped vertically as if they were two square sprites (i.e. rows "01234567" flip to "32107654", not "76543210").
The sprite size is controlled by bits 5-7 of $2101, and the Size bit of OAM. $2101 determines the two possible sizes for all sprites. If the OAM Size flag is 0, the sprite uses the smaller size, otherwise it uses the larger size.
There are 8 16-color palettes available to sprites, starting at CGRAM index 128. Thus, the palette number ‘ppp’ in OAM indicates that colors 128+ppp16 through 128+ppp16+15 are available to this sprite. However, the first of these is always considered transparent, to allow for non-rectangular shaped sprites.
Only sprites with palettes 4-7 participate in color math.
Sprites have two 16x16 tile character tables in VRAM. Wrapping on these works much like for BG tilemaps: tile 0 is to the right of tile $0F and below tile $F0, tile $10 is below tile 0 and to the right of tile $1F, tile $FF is to the left of tile $F0 and above tile $0F, and so on. Which character table a sprite uses is determined by the N bit in OAM. So if you specify Tile=$ff, your 16x16 sprite is made of tiles $ff, $f0, $0f, and $00. Wrapping always occurs within the same character table the sprite belongs to. To emphasize this point with an example, an arbitrary 32x32 sprite with the N-bit set is specified to use tile $fe. This tile may be referred to as $1fe. This is to make it clear that this tile is not the same as $fe with the N-bit 0. Here is an example of the wrapping that will occur on the aforementioned sprite at $1fe:
As far as sprite table locations go, the first sprite table is at the address specified by the Name Base bits of $2101, and the offset of the second is determined by the Name bits of $2101. The word address in VRAM of a sprite’s first tile may be calculated as:
((Base<<13) + (cccccccc<<4) + (N ? ((Name+1)<<12) : 0)) & 0x7fff
See the section "BACKGROUNDS" below for details on the format of the character data.
There are two ‘priority’ concepts applicable to sprites. First, there are the priority bits in OAM, which control the priority of the sprites relative to the BGs. See the section "Backgrounds" for more details on this.
The second is the priority with relation to the other sprites. This is completely controlled by the sprite’s index and the priority rotation setting.
Priority rotation is set by bit 7 of $2103. If the bit is unset, Sprite 0 is always the first sprite. Otherwise, take the current internal OAM word address (not affected by OAM Address Invalidation) and give priority to the sprite number
(OAMAddr&0xFE)>>1. So if you set $2102/3 to $104, then write 4 bytes, sprite 3 will have priority for the next frame. However, OAM Address Reset will reset the internal OAM address to word $104, so sprite 2 will have priority for subsequent frames.
There is one major oddity: if you set $2102/3=A, then write
4n+2*(A&1)+1 bytes (e.g. so the next byte written would go to the last byte in the 4-byte sprite record), sprite
((OAMAddr>>1)+Y)&0x7F has priority (where Y is the current line as addressed by sprites). Thus, if you put all 128 8x8 sprites at Y=63, write $8000 to $2102/3, then read 3 bytes from $2138, you will see sprites 63-70 having priority on successive scanlines.
FirstSprite ends up on top of all other sprites, regardless of the priority bits in OAM.
FirstSprite+1 is on top of
FirstSprite+2 is on top of
FirstSprite+3 and so on until
FirstSprite+127 (wrapping of course from sprite 127 to sprite 0). Note that only the priority of the topmost sprite is considered relative to the backgrounds. Thus, if
FirstSprite+4 are identical except
FirstSprite+3 has priority 0 and
FirstSprite+4 has priority 3, they will both be hidden by any backgrounds that hide priority 0 sprites. This may seem counter-intuitive, since
FirstSprite+4 would normally go in front of these BGs, but many games depend on this behavior.
As with everything else on the SNES, sprites are drawn per-scanline. The process is basically as follows:
0) If any OBJ is at X=256 (or X=-256, same difference), consider it as being at X=0 when considering Range and Time. Note that this doesn’t mean you actually draw it at X=0.
1) Range: Starting with the
FirstSprite, determine the first 32 sprites on this scanline. Only those sprites with -size < X < 256 are considered in Range. If there are more than 32 sprites on the scanline, set bit 6 of register $213e.
2) Time: Starting with the last sprite in Range, load up to 34 8x8 tiles (from left-to-right, after flipping). If there are more than 34 tiles in Range, set bit 7 of $213e. Only those tiles with -8 < X < 256 are counted.
3) Associate with each tile in Range and Time its true X position (256/-256 should not be set to 0), palette, and priority for drawing.
See the section "Rendering the Screen" for details.