This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.
PPU Phase E - Sprite Rendering
Summary
This Step implements the rendering of Sprites (OBJ - Objects) in the C++ PPU. Until now, the PPU could only render the Background, but with the DMA working (Step 0251), OAM memory (`0xFE00-0xFE9F`) now contains valid character data and game objects. This Step completes the rendering pipeline allowing the sprites to are drawn on top of the background, respecting transparency, priority and attributes (X/Y flip, palette).
Hardware Concept
TheSprites (OBJ - Objects)They are moving objects that are drawn on top of the Background and the Window. On real hardware, the Game Boy can display up to 40 sprites in total, but only 10 per scan line.
OAM (Object Attribute Memory) Structure
The OAM memory (`0xFE00-0xFE9F`) contains 160 bytes organized into 40 entries of 4 bytes each:
- Byte 0 (Y Position): Y position on screen + 16. If Y = 0, the sprite is hidden.
- Byte 1 (X Position): Position X on screen + 8. If X = 0, the sprite is hidden.
- Byte 2 (Tile ID): Tile index in VRAM (0-255). Sprites always use unsigned addressing from `0x8000`.
- Byte 3 (Attributes): Sprite attributes:
- Bit 7: Priority (0=above background, 1=behind background except color 0)
- Bit 6: Y-Flip (0=normal, 1=vertically flipped)
- Bit 5: X-Flip (0=normal, 1=horizontally flipped)
- Bit 4: Palette (0=OBP0, 1=OBP1)
- Bits 0-3: Not used (reserved for CGB)
Sprite Size
The size of the sprites is controlled by bit 2 of the LCDC register:
- Bit 2 = 0: 8x8 pixel sprites (single tile)
- Bit 2 = 1: 8x16 pixel sprites (two vertical tiles, tile_id and tile_id+1)
Priority and Transparency
Sprites have special rendering rules:
- Color 0 = Transparent: Color 0 in a sprite is always transparent and is not drawn.
- Priority (Attributes Bit 7):
- If Priority = 0: The sprite is always drawn on top of the background.
- If Priority = 1: The sprite is drawn behind the background, except if the background is color 0 (transparent).
Hardware Limit
On real hardware, only 10 sprites can be drawn per scan line. If there are more than 10 sprites that intersect a line, only the first 10 (in OAM order) are drawn. The others They silently ignore.
Fountain:Pan Docs - OAM, Sprite Attributes, Sprite Rendering
Implementation
Completed implementation of `render_sprites()` method in `PPU.cpp` and integrated into `render_scanline()` so that the sprites are drawn after the Background.
Modified components
- `src/core/cpp/PPU.cpp`:
- Completed `render_sprites()` implementation with all rendering logic.
- Integrated call to `render_sprites()` in `render_scanline()` after rendering the Background.
Rendering Logic
The `render_sprites()` method implements the following algorithm:
- Enablement Verification: Verifies that sprites are enabled (LCDC bit 1).
- Height Determination: Reads bit 2 of LCDC to determine if the sprites are 8x8 or 8x16.
- Iteration over OAM: Iterates over all 40 sprites in OAM (`0xFE00-0xFE9F`).
- Filter by Visibility:
- Check that Y and X are not 0 (hidden sprite).
- Verify that the sprite intersects the current line (LY).
- Attribute Decoding: Extracts priority, Y-Flip, X-Flip and palette.
- Sprite Line Calculation: Calculates which line of the sprite is being drawn, applying Y-Flip if necessary.
- Handling 8x16 Sprites: If the sprite is 8x16, determines which tile to use (top or bottom).
- Tile Decoding: Use `decode_tile_line()` to decode the tile line from VRAM.
- Pixel Rendering: For each pixel of the sprite:
- Apply X-Flip if necessary.
- Verify that the pixel is within the screen boundaries.
- Check transparency (color 0 = do not draw).
- Respect priority: if priority = 1 and the background is not color 0, do not draw.
- Writes the color index to the framebuffer.
Design Decisions
- Limit of 10 Sprites per Line: Implemented a `sprites_drawn` counter that limits rendering to 10 sprites per line, respecting the behavior of real hardware.
- Fund Priority: The background color is checked at each pixel before drawing the sprite. If the sprite has priority and the background is not transparent, the sprite is not drawn.
- Transparency: Sprite color 0 is always transparent, regardless of priority.
- Pallette: Color indices (0-3) are saved in the framebuffer. The application of the palette (OBP0/OBP1) is done in Python when rendering.
Affected Files
src/core/cpp/PPU.cpp- Completed implementation of `render_sprites()` and integration into `render_scanline()`
Tests and Verification
The implementation will be validated by running commercial ROMs that use sprites extensively:
- Pokémon Red: Should show the "POKÉMON" logo and the Gengar and Jigglypuff (or Nidorino) sprites on the title screen.
- Super Mario Deluxe: Should show Mario and other sprites on screen.
- Tetris: If it manages to boot, it should display the pieces (tetrominos) as sprites.
Test command:
python main.py roms/pkmn.gb
Expected validation:The sprites must appear above the background, respecting transparency and priority.
Sources consulted
- Bread Docs:OAM (Object Attribute Memory)
- Bread Docs:Sprite Attributes
- Bread Docs:Sprite Rendering
Educational Integrity
What I Understand Now
- OAM and Sprites: The OAM memory contains the attributes of the 40 possible sprites. Each sprite has position, tile ID and attributes (priority, flip, palette).
- Fund Priority: Sprites with priority (bit 7 = 1) are drawn behind the background, except if the background is color 0 (transparent). This allows for effects such as sprites passing "behind" background objects.
- Transparency: Color 0 in sprites is always transparent, allowing for irregular shapes and overlay effects.
- 8x16 Sprites: Large sprites use two consecutive tiles (tile_id and tile_id+1), one for the top half and one for the bottom half.
What remains to be confirmed
- Render Order: On real hardware, sprites are drawn in reverse order (sprite 39 first, sprite 0 last), causing sprites with lower index to appear on top. This current implementation draws in direct order, which could cause visual ordering problems.
- Limit of 10 Sprites: The limit of 10 sprites per line has been implemented, but it has not been verified whether the hardware actually stops rendering after 10 sprites or simply ignores the others.
Hypotheses and Assumptions
Render Order: For simplicity, direct order rendering was implemented (sprite 0 to sprite 39). If there are visual ordering problems (sprites appearing in the wrong order), it will be necessary to reverse the iteration order or implement a priority ordering system.
Next Steps
- [ ] Run `python main.py roms/pkmn.gb` and verify that the sprites appear correctly.
- [ ] If there are visual ordering problems, implement rendering in reverse order (sprite 39 to sprite 0).
- [ ] Verify that background priority works correctly (sprites passing behind objects).
- [ ] Implement Window rendering (if necessary to complete the rendering pipeline).