⭅ Previous (Graphics Palette) | Next (Background graphics.) ⭆ |
Now that we know how to specify colors in the NES palette, lets use this to actually draw an image. First, we’ll take a look at objects or sprites, which are used to draw characters and other accent details on top of the background.
Also available on Youtube.
Note that for this article, we will cover a high level perspective of how sprite rendering works. This is all you need to know if you’re going to try writing your own NES games. Even if you are writing an emulator, this way of understanding sprites covers most of what is used in NES software.
There are some graphic effects that take advantage of extremely accurate timing. We will cover this “cycle-accurate” rendering model in the future. But in order to really understand how the NES PPU graphics chip generates images, you need to understand what it is trying to accomplish, as well as the other hardware in the system.
This high level view will make understanding PPU intricacies easier later on.
And with that disclaimer out of the way, lets see the sprite rendering features of the NES.
Sprites are individual tiles that can be positioned anywhere on the screen. The NES can store information about 64 sprites to draw at once. Due to some particulars in the implementation, 8 sprites can be drawn on any line. If more than 8 sprites were specified, only the leftmost 8 will be drawn.
Sprites can be drawn with various attributes including
We’ll cover all of these, starting with the simplest variation first.
Sprite drawing is handled by the PPU. In order for the PPU to know what sprite to draw, the CPU needs to send the appropriate commands to the PPU. Just as with the palettes, this is accomplished via MMIO.
Sprites information is controlled by a memory region within the PPU, called Object Attribute Memory. Information for 64 sprites, and 4 bytes per sprite, 256 bytes total is used for the OAM.
Since this is within the PPU, rather than residing in VRAM (like the palette and CHR data), new MMIO addresses are used to control this data. These are :
0x2003 : OAM Address (OAMADDR). Write only. Specifies which byte within OAM
to update, starting with 0.
0x2004 : OAM Data (OAMDATA). Read + Write. Writing here writes to the previously
set OAMADDR. Writing here also increments OAMADDR.
0x4014 : OAM DMA (OAMDMA). Write. Initiates a copy of the 256 bytes from CPU
memory page 0xYY00 into OAM. Faster than many writes to OAMDATA.
The OAMADDR and OAMDATA addresses behave very much like the PPUADDR and PPUDATA registers we saw for writing palette data. Since OAM is inside the PPU chip rather than in external memory, the PPU* addresses cannot be used.
Using these first two addresses is simple. Lets look at what sort of data the OAM holds, so we know what to write. We will revisit OAMDMA later in this article.
For each sprite in OAM, the NES stores 4 bytes with metadata.
- Byte 0 : Y position of top the top of the sprite.
- Byte 1 : Tile number for standard (8x8) sprites.
Tall sprites(8x16) are trickier and omitted for now.
- Byte 2 : Attributes. Each bit has significance according to:
76543210
VHB000PP
V: Flip vertically
H: Flip horizontally
B: Behind aka Priority. 0 above bg, 1 behind bg
0: unused, always reads as 0
P: Palette selection.
- Byte 3 : X position of left of the sprite.
More details on NESdev
Lets set up a simple sprite in the middle of the screen for a test. To center the sprite, we want
Y = (ScreenHeight - SpriteHeight) / 2
= (240 - 8) / 2 = 116
= 116 = 0x74
X = (256 - 8) / 2
= 124 = 0x7C
So byte 0 should be 0x74, and byte 3 should be 0x7C. Byte 1 selects the tile number. I’ve created a simple CHR rom which contains a non-symmetric arrow. I’ve put it in tile 1. The nice thing about this is that the initialized OAM will be using tile 0 for the other sprites, which is empty.
And initially, we’ll just use 0 for all the attributes. Afterwards we can experiment with the other various features.
This gives us:
0: 0x74
1: 0x01
2: 0x00
3: 0x7C
Now lets write a little program to test what we know about the OAM.
; Setup sprite 0
lda #$0
sta #$2003 ; OAMADDR = 0
; Updating OAM:
lda #$74
sta #$2004 ; OAM[0] = 0x74
lda #$01
sta #$2004 ; OAM[1] = 0x01
lda #$00
sta #$2004 ; OAM[2] = 0x00
lda #$7C
sta #$2004 ; OAM[3] = 0x7C
And to make sure the sprite uses non-default colors, we’ll want to set up palette 4 (the first fg palette) to have some color.
Here’s what it should look like:
This has been tested with fceux, laines, and our own emulator. BSD Licensed, as with the other test roms on this site.
Updating even a single sprite using OAMADDR and OAMDATA was very tedious. And though we haven’t discussed it in detail yet, OAM data has the same timing restriction as the palette data. Updating this data while the PPU is actively drawing the screen can lead to graphical corruption. So in practice, there is a narrow time window during which OAM data can be safely modified before the next frame.
Fortunately, the NES has a faster and more convenient way to write the entire OAM data. This is via OAMDMA. Writing a byte ZZ to OAMDMA results in a full page from ZZ00 to ZZFF getting copied into OAMDATA.
DMA stands for “Direct Memory Access”, and refers to the fact that the copy is performed without the CPU. After the transfer is started, the CPU is paused, and the PPU reads from memory directly.
This is functionally equivalent, though much faster, than repeated writes into OAMDATA. 64 sprites * 4 bytes = 256 bytes or 0x100, perfectly matching this copy.
In practice, most software for the NES uses this technique. A copy of the desired OAM bytes in memory, then using OAMDMA to copy everything at once.
Here’s what that same program would look like if OAMDMA were used instead:
; put address 0x2000 into zeropage starting at 0
lda #$00
sta 0
lda #$02
sta 1
; zero a page at 0x0200 for use as OAM
; (see full source for example)
; setup sprite 0 info in page 0x2XX
ldy #0
lda #$74
sta (0), y ; 0x0200 = $74
iny
lda #$01
sta (0), y ; 0x0201 = $01
iny
lda #$00
sta (0), y ; 0x0202 = $00
iny
lda #$7C
sta (0), y ; 0x0203 = $7C
; now can dma to draw all sprites
; OAMDMA
lda #$02
sta #$4014 ; DMA page 2 into OAM
If you want to experiment with the other attributes like flipping, you can try updating the attribute byte.
Thats all for sprites. Next we’ll take a look at how background images, used for most level maps, are stored and drawn.
⭅ Previous (Graphics Palette) | Next (Background graphics.) ⭆ |