PPU / 02
From a tile to your first NES background pixel
How the PPU turns sixteen bytes of tile data into an image of 8 by 8 pixels.
Getting the CPU working feels good, but eventually you want it to draw something. This is where the NES gets interesting: writing a color to an arbitrary screen position isn't how its background works.
The PPU builds the image from tiles, a map of tile numbers and palettes. We'll follow one pixel through that process. You don't need a game ROM or a graphics editor; the example generates a small cartridge with its own graphics.
Change a bit and run the ROM
Each tile is 8 by 8 pixels. The editor below changes two bytes in one row of a tile. Click the bits, run it, and watch what the PPU draws. The enlarged image is cropped from the WebAssembly emulator's framebuffer.
With row 3 selected, the initial bytes are $58 and $38. Their combined pixel indices are 0 1 2 3 3 0 0 0, from left to right. Change just the leftmost bit in the low plane. It changes the leftmost pixel of that row.
Run a small NES program
The result is rendered by the same WebAssembly emulator as the home page.
Calculated pixel indices, left to right
; CHR tile 32, row 3
; low plane: $58
; high plane: $38
; pixel = (highBit << 1) | lowBitReady. Change the inputs or run the defaults.
The ROM includes startup and display code around the example. ROM source
Each run rebuilds the cartridge with your CHR bytes and starts the emulator again. We're editing graphics stored in CHR-ROM, rather than having the program write into ROM while it's running. Download the generated .nes file if you want to try it in the full emulator too.
The nametable chooses a tile
A background nametable contains a grid of 32 by 30 tile numbers. At 8 pixels per tile, that covers the 256 by 240 pixel screen. The tile number isn't a color. It selects a pattern from a pattern table.
Our little program puts tile 32 at the top-left of the nametable. It selects the pattern table starting at PPU address $0000, so the first byte of that tile is at 32 * 16, or $0200 in PPU memory.
That address belongs to the PPU's address space. It isn't the CPU's RAM address $0200 from the CPU guide. The two chips have different memory maps, which is worth keeping in mind before debugging the wrong array for an hour.
Two bits, kept apart
A tile takes 16 bytes. The first eight bytes contain one bit per pixel for each of its eight rows. The next eight contain a second bit for those same pixels. These are the low and high bitplanes.
For row 3, the low byte is at tileBase + 3, and the high byte is at tileBase + 3 + 8. For column x, we read bit 7 - x from each byte. Bit 7 is the leftmost pixel.
Here's the operation in Crystal, using the same approach as the book:
bit = 7 - x
low_bit = (low_plane >> bit) & 1
high_bit = (high_plane >> bit) & 1
pixel = (high_bit << 1) | low_bit
For $58 and $38, the two rows of bits look like this:
low: 0 1 0 1 1 0 0 0
high: 0 0 1 1 1 0 0 0
index: 0 1 2 3 3 0 0 0
At column 2, the low bit is 0 and the high bit is 1. That gives us binary 10, or index 2. At column 3, both bits are 1, giving index 3. No multiplication by an RGB color, no drawing API. Just two bits combined into a small number.
An index still isn't a color
The background has four palettes. An attribute table selects the palette for each 16 by 16 pixel region, which is a two-by-two block of tiles. Our program leaves the attributes at zero, so it uses background palette 0.
For nonzero pixel indices, the palette and the two-bit index choose an entry in palette RAM. That entry holds one of the PPU's 64 color codes. In this example, indices 1, 2 and 3 use $21, $16 and $30.
Index 0 has a special role: a background pixel with index 0 lets the backdrop show through. With no sprites in this example, it displays the universal background color at $3F00, set to $0F.
The exact RGB appearance of a NES color depends on the palette used by the emulator or display. The thing we're testing here is which palette entry gets selected.
Following the whole path
For one background pixel, our simplified walkthrough is:
- Find its tile position in the nametable.
- Read that tile number.
- Read the low and high bytes for the row from the pattern table.
- Extract one bit from each and combine them into index 0 to 3.
- Use the attribute-selected palette, or the backdrop for index 0, to get a color.
The actual PPU fetches and shifts data on a schedule, rather than running this list as a neat function for every pixel. Scrolling also changes which addresses it uses. This is the data path we're trying to understand before adding those details.
Something to try
Pick any row and clear all its bits in both planes. Then set only bit 7 of the high plane. Which pixel changes, and which index does it use?
Check the result
The leftmost pixel changes to index 2. The other seven pixels stay at index 0. If you also set bit 7 in the low plane, that first pixel becomes index 3.
Where this fits in the book
Chapter 5 introduces the PPU's tiles, nametables, attributes and palettes. Chapter 5.3 connects them to the background renderer in Crystal, then adds the scroll behavior needed by games. This example keeps one tile and one palette so you can see the bitplane part clearly.
References: PPU pattern tables, PPU memory map, PPU rendering, the book's source code.
Related book chapters: 5 and 5.3. Crystal.