⚠️ 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.

Blank Screen Triage - Framebuffer vs Palettes vs Blit

Date:2026-01-05 StepID:0488 State: VERIFIED

Summary

Step 0488 implements complete instrumentation to diagnose the emulator blank screen issue. Framebuffer statistics (FrameBufferStats) and palettes (PaletteStats) are added to rom_smoke snapshots, framebuffer dump to PPM is implemented for visual evidence outside of SDL, and a unit test is created that validates that the PPU can produce color diversity when VRAM has data. The results show that the problem is different depending on the mode: in DMG (tetris.gb) the framebuffer is completely white (bug in fetch/decode), while in CGB (tetris_dx.gbc) the framebuffer DOES have diversity but the window is still white (bug in blit/presentation).

Hardware Concept

The PPU (Picture Processing Unit) of the Game Boy renders frames of 160x144 pixels. The rendering process includes:

  1. Tiles Fetch: Reading tile data from VRAM (0x8000-0x97FF)
  2. Decode 2bpp: 2bpp data conversion to color indices (0-3)
  3. Palette application: Mapping of indices to shades (DMG: BGP) or RGB colors (CGB: CGB palettes)
  4. Write to framebuffer: Writing pixels to the display buffer
  5. Blit/Presentation: Transfer framebuffer to SDL texture for display

The blank screen issue can occur in any of these steps. To diagnose it, we need:

  • FrameBufferStats: Check if the PPU is generating a framebuffer with more than 1 color/pattern
  • PaletteStats: Check if the paddles are correctly configured
  • PPM Dump: Visual evidence outside of SDL to rule out blit/presentation issues

Reference: Pan Docs - LCD Control Register (FF40 - LCDC), Background Palette (FF47 - BGP), CGB Palettes (FF68-FF6B).

Implementation

5 phases of instrumentation are implemented to diagnose the blank screen problem:

Phase A: FrameBufferStats

Structure is addedFrameBufferStatstoPPU.hppwith:

  • fb_crc32: Framebuffer hash to detect changes
  • fb_unique_colors: Number of unique indexes (0-3) present
  • fb_nonwhite_count: Pixels with index != 0
  • fb_nonblack_count: Pixels with index != 3
  • fb_top4_colors: The 4 most frequent indices
  • fb_top4_colors_count: Count of each index
  • fb_changed_since_last: Indicates if the framebuffer changed since the last frame

The functioncompute_framebuffer_stats()runs afterswap_framebuffers()and is crawlingVIBOY_DEBUG_FB_STATS=1. It is exposed to Python via the Cython wrapper.

Phase B: PaletteStats

Added pallet statistics collection inrom_smoke_0442.py:

  • CGB vs DMG mode detection
  • DMG records: BGP, OBP0, OBP1 and derived index→shade mapping
  • CGB Registries: BGPI, BGPD, OBPI, OBPD
  • Non-white input counters on CGB palettes

getters are added inMMUto access CGB palette data (get_cgb_bg_palette_data(), get_cgb_obj_palette_data()).

Phase C: Dump PPM

It is implemented_dump_framebuffer_to_ppm()in Python that:

  • Read the index framebuffer from the PPU
  • Convert indices to RGB using BGP (DMG) or CGB palettes
  • Writes a PPM file (Netpbm P6 format) to the specified path
  • Crawled byVIBOY_DUMP_FB_FRAMEandVIBOY_DUMP_FB_PATH

Phase D: Unit Test

It is createdtest_ppu_framebuffer_diversity_0488.pythat:

  • Create a tile checkerboard in VRAM (alternates indices 0 and 3)
  • Configure tilemap and activate LCD/BG
  • Executes entire frames explicitly waiting foris_frame_ready()
  • Verify thatfb_unique_colors >= 2andfb_nonwhite_count > 0

Result: ✅ The test passes, confirming that the PPU can produce diversity when VRAM has data.

Phase E: rom_smoke Execution and Report

It runsrom_smoke_0442.pywith:

  • VIBOY_SIM_BOOT_LOGO=0(clean baseline)
  • VIBOY_DEBUG_FB_STATS=1(activate statistics)
  • VIBOY_DUMP_FB_FRAME=180(dump at frame 180)
  • ROMs:tetris.gb(DMG) andtetris_dx.gbc(CGB)

Report is generated indocs/reports/reporte_step0488.mdwith snapshot tables, PPM analysis, and decision tree.

Affected Files

  • src/core/cpp/PPU.hpp- StructureFrameBufferStatsand methodget_framebuffer_stats()
  • src/core/cpp/PPU.cpp- Implementation ofcompute_framebuffer_stats()and call afterswap_framebuffers()
  • src/core/cython/ppu.pxd- Cython statementFrameBufferStats
  • src/core/cython/ppu.pyx- Python wrapperget_framebuffer_stats()
  • src/memory/mmu.py- Gettersget_cgb_bg_palette_data()andget_cgb_obj_palette_data()
  • tools/rom_smoke_0442.py- Integration of FrameBufferStats and PaletteStats in snapshots, feature_dump_framebuffer_to_ppm()
  • tests/test_ppu_framebuffer_diversity_0488.py- Unit test that validates framebuffer diversity
  • docs/reports/reporte_step0488.md- Complete report with analysis and decision tree

Tests and Verification

Unit test: test_ppu_framebuffer_diversity_0488.py::test_ppu_produces_multiple_colors_when_vram_has_pattern

def test_ppu_produces_multiple_colors_when_vram_has_pattern(self):
    """Test that verifies that PPU produces >1 color when VRAM has a pattern."""
    # Create tile checkerboard in VRAM
    # Configure tilemap and activate LCD/BG
    # Execute full frames waiting for is_frame_ready()
    # Check fb_unique_colors >= 2 and fb_nonwhite_count > 0
    assert fb_stats['fb_unique_colors'] >= 2
    assert fb_stats['fb_nonwhite_count'] > 0

Result: ✅ HAPPENS(1 passed in 0.05s)

C++ Compiled Module Validation: The test validates that the PPU compiled in C++ can produce color diversity when VRAM contains a known pattern.

Running rom_smoke:

  • tetris.gb (DMG): 240 frames executed, PPM generated at frame 180
  • tetris_dx.gbc (CGB): 240 frames executed, PPM generated at frame 180

Sources consulted

Educational Integrity

What I Understand Now

  • PPU Rendering Pipeline: The complete process from fetching tiles to rendering, and where each step can go wrong.
  • FrameBufferStats: How to measure framebuffer diversity without relying on SDL visualization.
  • PaletteStats: How to verify that the palettes are set correctly in both modes (DMG and CGB).
  • Dump PPM: How to obtain visual evidence outside the presentation system to isolate blit/presentation problems.

What remains to be confirmed

  • Bug in fetch/decode DMG: Why the framebuffer is completely white in DMG mode even though the tilemap has data.
  • Bug in blit/CGB presentation: Why SDL window shows white screen when framebuffer has diversity (4 unique colors).
  • Tiles loading timing: If there are VRAM access restrictions that prevent the correct loading of tiles into real ROMs.

Hypotheses and Assumptions

Main hypothesis: The problem is different depending on the mode:

  • DMG: PPU is not rendering correctly (empty framebuffer). Possible causes: bug in fetch/decode, VRAM access restrictions, or incorrect initialization.
  • CGB: PPU IS rendering (framebuffer has diversity), but blit/presentation fails. Possible causes: incorrect SDL texture format, incorrectly configured pitch/stride, or whiteout after rendering.

Next Steps

Step 0489 - Mode Specific Diagnostics:

  • For tetris.gb (DMG):
    • Instrumentrender_scanline()to check fetch/decode in DMG mode
    • Compare with tetris_dx.gbc which DOES work (CGB mode)
    • Check VRAM access restrictions during PPU modes in DMG
  • For tetris_dx.gbc (CGB):
    • Instrument before and after the blit (source buffer hash vs texture uploaded buffer hash)
    • Check SDL texture format (RGBA vs BGRA)
    • Check framebuffer pitch/stride
    • Verify that the framebuffer is not being whitened after rendering