Video summary
Gameboy Emulator Development - Part 11
Main summary
Key takeaways
Main focus: Starting PPU emulation (tiles, OAM/OEM, VRAM) and visual debugging
The video starts after verifying CPU emulation with standard CPU test ROMs, then transitions into PPU (Pixel Processing Unit) work. The goal is to implement PPU memory read/write behavior—specifically OAM/OEM and VRAM—and to render Game Boy tile data in a debug window so correctness can be visually confirmed.
1) Sprite attribute memory (OAM/OEM) structure and layout
- OAM RAM stores sprite metadata for up to 40 sprites.
- The code treats addresses
>= 0xFE00specially as the OAM base region in the Game Boy address map. - Each sprite entry is 4 bytes:
- Y position
- X position
- Tile index
- Attribute flags
Sprite attribute flags are bit-packed. The presenter highlights components such as:
- Palette number (3 bits)
- (Color Game Boy only) VRAM tile bank / palette-related bits (not fully used yet)
- X flip / Y flip
- Background priority
Implementation detail
- A C
structis introduced to represent an OAM entry, with either:- a single byte of flags, or
- bitfields for individual flag components.
2) PPU context: implementing memory read/write through the bus
A PPU context is introduced that contains:
- OAM RAM (40 entries)
- VRAM (0x2000 bytes)
Functions to implement:
ppu_oam_read/write- Addressed with the
0xFE00offset subtracted
- Addressed with the
ppu_vram_read/write- Uses the
0x8000offset logic
- Uses the
Next, the emulator’s bus integration is updated so PPU memory accesses go through these PPU functions.
Sanity checking
- After refactoring, the presenter rebuilds and runs the zero/one special test ROM to ensure no regressions.
3) Tile data in VRAM: how tiles are stored
Key tile storage rule:
- Tile data starts at VRAM
0x8000 - Each tile occupies 16 bytes
- Each tile is 8x8 pixels, using 4 possible colors (shades of gray)
Tile indexing behavior is discussed:
- Tile IDs 0–127 and 128–255, with behavior influenced by flags (not fully implemented yet).
4) Debug rendering: displaying all tiles from VRAM
To validate tile decoding, the presenter adds an SDL debug window that renders decoded VRAM tiles.
- A debug texture is created for a 16x8 tile grid, scaled by a configurable
scale(default mentioned: 4). - A function like
ui_update()is added:- Clears the screen to dark gray
- Iterates through all tiles in VRAM (referred to as 384 tiles)
- Reads from
0x8000as the tile data start - Calls a helper function to render each tile
display_tile implementation (tile decoding logic)
For a given tile:
- For each scanline, the code reads two bytes (because an 8x8 row needs 16 bytes total across the tile):
- One byte contains the high bitplane
- One byte contains the low bitplane
- For each pixel (bit 7 down to bit 0):
- Extract one bit from the high plane and one from the low plane
- Combine them into a 2-bit color index (0–3)
- Render the pixel via SDL
fill_rectusing the chosen color
Observation
- The debug window shows tiles correctly “loaded into memory,” but some games (e.g., Tetris) remain blank initially because sprite/OAM writing behavior isn’t implemented as real hardware does it yet.
5) Implementing DMA for OAM transfers (needed for games like Tetris)
Typical games populate OAM via DMA.
DMA module
A DMA module is added with:
dma_start(value)triggered by writes to0xFF46(DMA start register)dma_tick()called during CPU timing- A DMA context tracking:
activeflag- current
byteindex start_delayset to 2 cycles before transfer begins
DMA behavior
- DMA reads source from memory:
- Source base derived from the written value, described as
0x100 + currentByteIndex
- Source base derived from the written value, described as
- Copies 160 bytes total
- Stops when the byte index reaches
0xA0(or related range)
- Stops when the byte index reaches
- Completion logs a “done” message for debugging.
Timing integration
The emulator’s cycle loop is refactored:
- There is a nested per-cycle loop for timer ticks
- DMA tick is performed once per CPU cycle (not the timer’s 4x rate).
Bus/PPU read restrictions during DMA
Rules are added during active DMA:
- If DMA is transferring, OAM reads return
0xFF - If DMA is transferring, OAM writes are ignored (disallowed during DMA)
6) Progress and debugging stops
After DMA is implemented, the presenter runs Tetris again:
- Initially it still doesn’t work fully due to incomplete emulated registers/hardware behavior.
- To get more visibility, pragmatic hacks are introduced, such as a temporary response for LCD-related reads (e.g., LY via
FF44). - Eventually logs show:
- “dma done”
- signs that Tetris intro screen data is being transferred/processed
The video concludes by positioning this as a foundation toward a full PPU, to be covered next.
Key deliverables / “tutorial steps” covered
- Define and implement OAM/OEM sprite attribute storage
- 40 sprites, 4-byte entries
- Implement PPU context memory for OAM and VRAM, including correct offset handling:
- OAM offset around
0xFE00 - VRAM offset around
0x8000
- OAM offset around
- Integrate PPU read/write into
bus.c - Decode VRAM tile format
- 16 bytes per 8x8 tile
- 2-bitplanes → 2-bit color indices → 4 colors
- Add an SDL debug tile viewer to visually validate decoding
- Implement DMA to OAM via register
0xFF46, including:- source selection
- DMA timing via
dma_tick - read/write restrictions during active DMA
Main speakers / sources
- Main speaker/source: The single presenter from the YouTube channel (referred to as “Low-level Devil” / “low-level dev…” in the intro).