This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.
Post-Double Buffering White Screen Investigation
Summary
Even though double buffering completely eliminated race conditions (0 warnings vs 7291 before), all ROMs still show completely white screens. Detailed checks were implemented in each render pipeline stage (write to framebuffer back, swap buffers, read in Python, render) to identify exactly where data is lost. Logs confirm that the exchange is working correctly and that Python reads data from the framebuffer, but normal rendering is not writing data to the framebuffer_back_ (all lines are empty). The 11520 non-white pixels that appear in the exchange appear to come from the checkerboard, not the normal rendering of tiles.
Hardware Concept
Full Rendering Pipeline in Hybrid Emulators
In a hybrid C++/Python emulator, the rendering pipeline has multiple stages that must work correctly:
- Rendering in C++ (PPU):
render_scanline()write pixels toframebuffer_back_during MODE_3_PIXEL_TRANSFER - Buffer exchange:When a frame is completed (LY=144), they exchange
framebuffer_front_andframebuffer_back_wearingstd::swap() - Reading in Python:Python reads the
framebuffer_front_via Cython (memoryview zero-copy) - Immutable copy:Python creates a
bytearraysnapshot of the framebuffer to protect it from changes - Rendering in Python:The renderer converts color indices (0-3) to RGB using the BGP palette and draws in Pygame
- Screen update:Pygame shows surface in window
Any problems at any of these stages will cause white screens or corrupted graphics.
Data Verification at Every Stage
It is critical to verify that the data remains correct at each stage:
- If framebuffer_back_ is empty:The problem is in C++ rendering (no data written during MODE_3)
- If the framebuffer_front_ is empty after the swap:The problem is in the exchange (although
std::swap()is atomic) - If Python reads zeros:The problem is in the reading or the Cython wrapper
- If the renderer receives zeros:The problem is in the data transfer (snapshot)
- If the surface is white:The problem is in Python rendering (RGB conversion or drawing)
Implementation
Implemented Verifications
Detailed checks were implemented at each stage of the pipeline:
- Verification of writing to framebuffer_back_:At the end of
render_scanline(), it is verified that non-white data was written in lines 0, 72 and 143 - Full frame verification:When LY=143 and mode=MODE_1_VBLANK, the framebuffer_back_ is verified to have data before swap
- Exchange Verification:In
swap_framebuffers(), non-white pixels are counted before and after swapping - Reading verification in Python:In
viboy.py, the contents of the framebuffer are checked before and after the copy - Rendering check:In
renderer.py, it is verified that the renderer receives data and that the surface has non-white pixels
Corrections Made
- Check move:Write verification moved to the end of
render_scanline()(after writing) instead of the beginning - Buffer fix:Fixed code that verified
framebuffer_front_when should i checkframebuffer_back_
Affected Files
src/core/cpp/PPU.cpp- Added verifications inrender_scanline(),swap_framebuffers()and full frame verificationsrc/viboy.py- Added framebuffer read check before and after copysrc/gpu/renderer.py- Added received data verification and surface verification after drawing
Tests and Verification
Tests were run with the 6 ROMs (TETRIS, Mario, Zelda DX, Gold, PKMN, PKMN-Amarillo) to analyze the verification logs:
Log Findings
- ✅ Exchange works correctly:Back buffer has 11520 non-white pixels (50%), front receives them correctly
- ✅ Python reads correctly:Reads 52 non-white pixels in the first 100 (data present)
- ❌ Normal rendering does not write data:All rendered lines are empty (0 non-white pixels)
- ❌ The 11520 non-white pixels appear to come from the checkerboard:Not normal tile rendering
Example Logs
[PPU-SWAP-DETAILED] Frame 1 | Back before: 11520 non-zero | Front before: 0 non-zero | Front after: 11520 non-zero
[PPU-SWAP-DETAILED] Front first 20 pixels: 3 3 3 3 3 3 3 3 0 0 0 0 0 0 0 0 3 3 3 3
[Python-Read-Framebuffer] Frame 1 | First 20 indices: [3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3] | Non-zero in first 100: 52/100
[Python-Read-Framebuffer] ✅ Correct copy: 52 non-zero pixels
Identified problem:Normal rendering is not writing data to the framebuffer_back_ during MODE_3_PIXEL_TRANSFER.
All lines are empty, suggesting thatrender_scanline()It is not executing the tile rendering logic,
or that the VRAM is empty when trying to render.
Sources consulted
- Bread Docs:LCD Controller (PPU)
- Implementation based on general knowledge of LR35902 architecture and double-buffered rendering systems
Educational Integrity
What I Understand Now
- Rendering pipeline:The entire pipeline from C++ to Pygame has multiple stages that must work correctly
- Data verification:It is critical to verify each stage to identify where data is lost
- Double buffering works:Buffer swapping works correctly, eliminating race conditions
- Rendering problem:Normal rendering is not writing data to framebuffer_back_, all lines are empty
What remains to be confirmed
- Why render_scanline() doesn't write data:I need to investigate if the VRAM is empty, if MODE_3 is not running, or if there is another problem
- Origin of the 11520 non-white pixels:Confirm if they come from the checkerboard or from another place
- Why screens are white:Although Python reads data, the screens are white, suggesting a problem in Python rendering
Hypotheses and Assumptions
Main hypothesis:Normal rendering is not writing data because VRAM is empty when trying to render, or because MODE_3_PIXEL_TRANSFER is not running correctly. The 11520 non-white pixels come from the checkerboard, which is triggered when VRAM is empty, but the checkerboard is not displaying on the screen (possible issue in Python rendering).
Next Steps
- [ ] Investigate why
render_scanline()does not write data during MODE_3_PIXEL_TRANSFER - [ ] Check if VRAM is empty when trying to render
- [ ] Check if MODE_3_PIXEL_TRANSFER is running correctly
- [ ] Investigate why screens are white even though Python reads data from the framebuffer
- [ ] Check if the problem is in the RGB conversion or in the drawing in Pygame