This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.
Fix PPU: white framebuffer with high TileData (render_bg/render_window/swap)
📋 Executive Summary
⚠️ REVERSED ATTEMPT
This Step attempted to correct the gating criteriavram_has_tiles_which was too strict for CGB games with high tiledata but low diversity of unique tile IDs. The changes were reverted because they did not completely resolve the white framebuffer issue.
Identified Problem:
- Games like
tetris_dx.gbcandzelda-dx.gbcthey reportedtiledata_effectivehigh (56.6% and 79.0%) butfb_nonzero=0/23040(completely white framebuffer). - The criterion of
vram_has_tiles_required diversity >= 5 unique tile IDs, butzelda-dx.gbcI only had 1 unique tile ID.
Tried Solution:
- Relax criteria to allow render when
tiledata_effective >= 200althoughunique_tile_idsbe low (>= 1) in CGB mode. - Conditional override:
cgb_high_tiledata_override = (tiledata_effective >= 200) && (unique_tile_ids >= 1).
Result:
- ✅
tetris_dx.gbc: Significant improvement -vram_has_tiles_activates correctly (Frame 676). - ⚠️
zelda-dx.gbc:vram_has_tiles_is activated (Frame 13) but framebuffer is still blank. - ❌ The changes were reverted because the problem persists (requires further investigation).
🔧 Hardware Concept
Tiles Detection Criteria in VRAM
Fountain:Pan Docs - "Video RAM", "Tile Data", "Background Tile Map"
The PPU needs to determine when there are valid tiles in VRAM to decide whether to render the background or display a checkerboard. In real games, especially during initialization or screen transitions, there may be situations where there is tile data loaded but with low diversity of unique tile IDs in the tilemap.
Original Problem:
The previous criterion required both useful data (tiledata_nonzero >= 200eithercomplete_tiles >= 10) ANDdiversity in the tilemap (unique_tile_ids >= 5). This caused false negatives in CGB games likezelda-dx.gbcwhich have high tiledata but only 1 unique tile ID.
Attempted Solution (Reversed):
Relax criteria for CGB mode: yestiledata_effective >= 200andunique_tile_ids >= 1, allow rendering even if diversity is low.
// Original criterion (strict)
vram_has_tiles_ = has_tiles_data && has_tilemap_diversity;
// Tried criterion (relaxed for CGB) - REVERSED
bool cgb_high_tiledata_override = (tiledata_effective >= 200) && (unique_tile_ids >= 1);
vram_has_tiles_ = has_tiles_data && (has_tilemap_diversity || cgb_high_tiledata_override);
Why it was Reverted:
Although the change improved the activation ofvram_has_tiles_, the framebuffer kept going blank onzelda-dx.gbc. This suggests that the problem is not just gating but that there is another problem in the rendering pipeline (possibly tile routing, framebuffer swapping, or incorrect cleanup).
💻 Implementation
Modified Files (Reverted Changes):
src/core/cpp/PPU.cpp- Lines ~1559-1578 (functionrender_scanline())
Attempted Change:
// Calculation of tiledata_effective for CGB
int tiledata_bank1 = count_vram_nonzero_bank1_tiledata();
int tiledata_effective = (tiledata_nonzero > tiledata_bank1) ? tiledata_nonzero : tiledata_bank1;
// Override for CGB with high tiledata
bool cgb_high_tiledata_override = (tiledata_effective >= 200) && (unique_tile_ids >= 1);
// New criteria
vram_has_tiles_ = has_tiles_data && (has_tilemap_diversity || cgb_high_tiledata_override);
🧪 Tests and Verification
Compilation:
$python3 setup.py build_ext --inplace
✅ Compilation successful (exit code: 0)
Parallel Suite (2 minutes, all ROMs):
$ export VBC_SUITE=1
$ # Parallel execution of 8 ROMs
✅ Suite completed (exit code: 0)
Log Evidence:
tetris_dx.gbc:
[VRAM-STATE-CHANGE] Frame 676 | has_tiles: 0 -> 1 | tiledata: 4032/4032 | tiles: 252/384 | unique_ids: 185
[VIDEO-SUMMARY] Frame 700 | tiledata=56.6% | tiles: 252/384 | unique_ids: 185 | fb_nonzero: 15360/23040 (66.7%)
[CGB-RGB-CHECK] Frame 700 | Non-white pixels: YES (66.7%)
✅ Significant improvement: vram_has_tiles_activates correctly, framebuffer has non-white pixels.
zelda-dx.gbc:
[VRAM-STATE-CHANGE] Frame 13 | has_tiles: 0 -> 1 | tiledata: 5632/5632 | tiles: 352/384 | unique_ids: 1
[VIDEO-SUMMARY] Frame 700 | tiledata=79.0% | tiles: 352/384 | unique_ids: 1 | fb_nonzero: 0/23040 (0.0%)
[CGB-RGB-CHECK] Frame 700 | Non-white pixels: NO (all white or black)
⚠️ Problem persists: vram_has_tiles_activates but framebuffer is still completely white (0%).
🔍 Post-Mortem Analysis
Key Findings:
- Gating is not the only problem: Although
vram_has_tiles_activates correctly inzelda-dx.gbc, the framebuffer is still white. - Low diversity may be valid:
zelda-dx.gbchas 352 full tiles but only 1 unique tile ID (possibly loads the same tile on the entire screen during initialization). - Deeper problem: Inline rendering does not use
vram_has_tiles_like gating (the for loop inrender_scanline()doesn't check this flag), so there must be another problem:- Incorrect tile addressing
- Framebuffer swap not working correctly
- Incorrect framebuffer cleanup
- problem in
get_tile_color_for_bg()
Why it was Reverted:
- The change improves the behavior of
tetris_dx.gbcbut it doesn't solvezelda-dx.gbc. - The relaxed criteria could cause false positives in other games.
- A deeper investigation of the rendering pipeline is needed before modifying the gating.
Suggested Next Steps (for future Steps):
- Instrument the inline rendering loop in
render_scanline()to check what color values are written to the framebuffer. - Verify that
swap_framebuffers()correctly swaps front/back pointers. - Audit
get_tile_color_for_bg()with real datazelda-dx.gbc. - Consider whether the problem is specific to CGB (VRAM banks, CGB paddles).
🎯 Conclusion
This Step documented an attempt to correct the criteria ofvram_has_tiles_which was reverted because it didn't completely solve the white framebuffer issue. The changes were beneficial fortetris_dx.gbcbut insufficient forzelda-dx.gbc.
The analysis revealed that the white framebuffer problem is not just the gating ofvram_has_tiles_but rather there is a deeper issue in the rendering pipeline that requires additional investigation.
Final State:DRAFT/REVERT - Code changes were reverted but documentation is maintained for traceability.