⚠️ Clean-Room / Educational

This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.

Enabling Real Rendering (Background C++)

Date:2025-12-19 StepID:0119 State: Verified

Summary

The C++ → Python video link works correctly (confirmed with gray screen and red dot at 60 FPS). Removed the diagnostic code (gray screen and red pixel) from the C++ PPU builder and enabled the actual rendering of the Background. The framebuffer is now initialized to blank (0xFFFFFFFF) and the `render_scanline()` method renders the actual game tiles from VRAM when the PPU enters H-Blank (Mode 0).

Hardware Concept

Scanline rendering on Game Boy:The PPU renders the screen line by line (scanline rendering). Each visible line (0-143) goes through 4 modes:

  • Mode 2 (OAM Search):0-79 cycles - PPU searches for sprites that intersect the current line
  • Mode 3 (Pixel Transfer):80-251 cycles - PPU transfers tile data from VRAM
  • Mode 0 (H-Blank):252-455 cycles - The PPU renders the entire line into the framebuffer
  • Mode 1 (V-Blank):Lines 144-153 - Vertical delay period, not rendered

Background Rendering:During H-Blank (Mode 0), the PPU reads the tilemap from VRAM (0x9800-0x9BFF or 0x9C00-0x9FFF), decodes the tiles from Tile Data (0x8000-0x8FFF or 0x8800-0x97FF), applies the scroll (SCX/SCY) and the BGP palette, and writes the 160 pixels of the current line to the framebuffer.

Framebuffer initialization:The framebuffer must be initialized to a default color (white) before the PPU starts rendering. If left uninitialized or initialized to a diagnostic color (gray), Games will display incorrect colors or test screens instead of actual content.

Fountain:Pan Docs - LCD Timing, Background, Tile Data, 2bpp Format

Implementation

The following changes were made to `src/core/cpp/PPU.cpp`:

1. Clearing the Diagnostic Code

Removed the diagnostic code that painted the screen gray and one pixel red:

  • Builder:Changed framebuffer initialization from gray (`0xFF808080`) to white (`0xFFFFFFFF`)
  • Builder:Removed the line that painted the diagnostic pixel red (`framebuffer_[0] = 0xFFFF0000`)

2. Rendering Logic Verification

Verified that the rendering logic works correctly:

  • `step()` method:Calls `render_scanline()` when the PPU enters Mode 0 (H-Blank) and the line is visible (LY< 144)
  • `render_scanline()` method:Render Background, Window and Sprites in the correct order
  • `render_bg()` method:Read VRAM, apply scroll (SCX/SCY), decode 2bpp tiles and apply BGP palette

Modified components

  • src/core/cpp/PPU.cpp- Builder: Initialization of framebuffer to blank, removal of diagnostic code

Design decisions

Initialization color:White (0xFFFFFFFF) was chosen as the default color because it is color 0 of the BGP palette standard (0xE4), which corresponds to "transparent" or "white" in most games. If a tile does not render correctly, It will appear white instead of gray, making it easier to debug.

No changes to rendering logic:The rendering logic was already implemented correctly in previous steps. Only the diagnostic code needed to be removed to trigger the actual rendering.

Affected Files

  • src/core/cpp/PPU.cpp- Clean diagnostic code, initialize framebuffer to blank

Tests and Verification

Rendering tests were run to ensure that cleaning the diagnostic code did not break functionality:

$ python -m pytest tests/test_core_ppu_rendering.py -v

============================= test session starts =============================
platform win32 -- Python 3.13.5, pytest-9.0.2, pluggy-1.6.0
collected 4 items

tests/test_core_ppu_rendering.py::TestCorePPURendering::test_bg_rendering_simple_tile PASSED [ 25%]
tests/test_core_ppu_rendering.py::TestCorePPURendering::test_bg_rendering_scroll PASSED [ 50%]
tests/test_core_ppu_rendering.py::TestCorePPURendering::test_window_rendering PASSED [ 75%]
tests/test_core_ppu_rendering.py::TestCorePPURendering::test_framebuffer_memoryview PASSED [100%]

============================== 4 passed in 0.12s ==============================

Compiled C++ module validation:All tests passed, confirming that:

  • Background rendering works correctly
  • Scroll (SCX/SCY) is applied correctly
  • The Window is rendered on top of the Background
  • Framebuffer is correctly exposed as memoryview (Zero-Copy)

Key test:`test_bg_rendering_simple_tile` verifies that a black tile renders correctly in the framebuffer when set to VRAM and tilemap. This test confirms that the actual rendering works.

Sources consulted

Educational Integrity

What I Understand Now

  • Scanline rendering:The PPU renders line by line during H-Blank (Mode 0), not frame by frame. This is critical for synchronization with the CPU.
  • Framebuffer initialization:The framebuffer must be initialized to a default color (white) before the PPU begins rendering. Diagnostic colors (gray, red) must be removed before triggering actual rendering.
  • Render Order:Background → Window → Sprites. This order determines which layer is drawn on top of another.

What remains to be confirmed

  • Sprite Rendering:The sprites render but the priority logic may need adjustment. Verify with real games.
  • Windows:The Window may have timing or positioning problems in some games. Check with Tetris and other games.

Hypotheses and Assumptions

Initialization color:White (0xFFFFFFFF) is assumed to be a safe color to initialize the framebuffer. If a game displays incorrect colors, it may be necessary to initialize to another color or check the BGP palette.

Next Steps

  • [ ] Test with real ROMs (Tetris, Mario) to verify that the rendering works correctly
  • [ ] Verify that sprites are rendered correctly (priority, transparency, flip)
  • [ ] Optimize rendering if necessary (tile caching, loop optimization)
  • [ ] Implement support for Game Boy Color (CGB palettes, VRAM Banks)