This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.
Investigation of Line Rendering and Framebuffer Status
Summary
Thorough investigation of line rendering and framebuffer status to identify why some framebuffer lines are empty when they should have data. Diagnostic logs were implemented to check if all lines are rendered (LY 0-143), if the framebuffer is cleared during rendering, if any lines are skipped, and the complete state of the framebuffer at the end of each frame. The logs revealed that all lines are rendered correctly and the framebuffer has consistent data across all lines.
Hardware Concept
Scanlines
The Game Boy has 144 visible lines (LY 0-143). Each line is rendered during H-Blank (Mode 0). After line 143, there is V-Blank (lines 144-153). The LY (Line Y) register is automatically incremented after each line and reset to 0 at the start of each frame.
Framebuffer
The framebuffer contains color indices (0-3) for each pixel on the screen. Size: 160×144 = 23,040 pixels. Each line occupies 160 pixels in the framebuffer. The framebuffer is generated line by line during the rendering of each scanline and is cleared at the start of the next frame (when LY is reset to 0).
Line Rendering
render_scanline()is called for each visible line (LY 0-143). Rendering must complete before Python reads the framebuffer. The framebuffer is cleared at the start of the next frame (LY=0), ensuring that Python always reads the framebuffer BEFORE it is cleared.
Fountain:Pan Docs - "LCD Status Register", "LY Register", "LCD Timing"
Implementation
5 blocks of diagnostic logs were implemented inPPU.cppto investigate line rendering and framebuffer status.
1. All Lines Rendering Check
Logs were added inPPU::render_scanline()at startup to verify that all lines are rendered (LY 0-143). The logs mark each line when it is rendered and check for lines that are not rendered.
- Array of rendered lines:Static array of 144 booleans that marks each line when rendered
- Periodic verification:Every 10 frames, check if all lines have been rendered
- Tag:
[PPU-LINES-RENDER]
2. Framebuffer Status Check Line by Line
Logs were added inPPU::render_scanline()at the end to check the state of the framebuffer after each line is rendered. The logs check specific lines (0, 72, 143) and some random ones.
- Index distribution:Counts how many pixels have each color index (0, 1, 2, 3) in each line
- Specific pixels:Check some specific pixels (x=0, 40, 80, 120, 159) to see what indices they contain
- Warnings:Warn if a line is completely empty after rendering
- Tag:
[PPU-FRAMEBUFFER-LINE]
3. Improved Framebuffer Cleanliness Check
Existing logs were improved inPPU::clear_framebuffer()to verify that the framebuffer is only cleared at the start of the frame (LY=0) and not during rendering (LY 1-143).
- Cleaning log:Logs every time the framebuffer is cleared with the current frame and LY
- Warning:Warn if the framebuffer is cleared during rendering (should not happen)
- Tag:
[PPU-CLEAR-FRAMEBUFFER]
4. Line Sequence Verification
Logs were added inPPU::step()when LY changes to verify that LY increments correctly (0, 1, 2, ..., 143, 144, ..., 153, 0) and no lines are skipped.
- Sequence verification:Check if LY increments correctly or if lines are skipped
- Periodic log:Log every 10 lines or important lines (0, 72, 143, 144)
- Tag:
[PPU-LY-SEQUENCE]
5. Verification of the Complete State of the Framebuffer at the End of the Frame
Logs were added inPPU::step()when LY reaches 144 (VBLANK_START) to check the full state of the framebuffer at the end of the frame.
- Lines with data:Count how many lines have data (not all white)
- Non-zero pixels:Counts how many pixels have non-zero indices (1, 2, 3)
- Global distribution:Shows the distribution of indices across the entire framebuffer
- Warnings:Warns if there are few lines with data or if the framebuffer is completely empty
- Tag:
[PPU-FRAMEBUFFER-COMPLETE]
Components created/modified
src/core/cpp/PPU.cpp: Added 5 diagnostic log blocks inrender_scanline(),clear_framebuffer()andstep()
Affected Files
src/core/cpp/PPU.cpp- Added diagnostic logs of Step 0339
Tests and Verification
Tests were run with the Pokémon ROM (pkmn.gb) for 2.5 minutes. The logs revealed valuable information about line rendering and framebuffer status.
Command Executed
timeout 150 python3 main.py roms/pkmn.gb 2>&1 | tee logs/test_pkmn_step0339.log
Result
The logs show that:
- All lines are rendered:144/144 lines have data in each frame
- The framebuffer has consistent data:11520 non-zero pixels out of 23040 total (50%), which is correct for the checkerboard
- The distribution is correct:Only indices 0 and 3, which is the checkerboard pattern
- The sequence of lines is correct:LY increments correctly (0, 10, 20, 30, ..., 143, 144)
- The framebuffer is only cleared at the start of the frame:No cleanups detected during rendering
Log Example
[PPU-FRAMEBUFFER-COMPLETE] Frame 1 | LY:144 (VBLANK_START) | Lines with data: 144/144 | Total non-zero pixels: 11520/23040 | Distribution: 0=11520 1=0 2=0 3=11520
[PPU-FRAMEBUFFER-LINE] Frame 1 | LY: 0 | Non-zero pixels: 80/160 | Distribution: 0=80 1=0 2=0 3=80
[PPU-LY-SEQUENCE] Frame 1 | LY: 0
[PPU-LY-SEQUENCE] Frame 1 | LY: 10
[PPU-LY-SEQUENCE] Frame 1 | LY: 20
...
[PPU-LY-SEQUENCE] Frame 1 | LY: 143
[PPU-LY-SEQUENCE] Frame 1 | LY: 144
Log Analysis
# Verificar si todas las líneas se renderizan
grep "\[PPU-LINES-RENDER\]" logs/test_*_step0339.log | head -n 30
# Verificar estado del framebuffer línea por línea
grep "\[PPU-FRAMEBUFFER-LINE\]" logs/test_*_step0339.log | head -n 50
# Verificar limpieza del framebuffer
grep "\[PPU-CLEAR-FRAMEBUFFER\]" logs/test_*_step0339.log | head -n 30
# Verificar secuencia de líneas
grep "\[PPU-LY-SEQUENCE\]" logs/test_*_step0339.log | head -n 50
# Verificar estado completo del framebuffer
grep "\[PPU-FRAMEBUFFER-COMPLETE\]" logs/test_*_step0339.log | head -n 30
Native Validation:C++ Compiled Module Validation
Findings and Conclusions
Main Findings
- ✅ All lines are rendered correctly:The logs show that 144/144 lines have data in each frame. No non-rendering lines were detected.
- ✅ The framebuffer has consistent data:Each line has 80 non-zero pixels out of 160 total (50%), which is correct for the checkerboard. The distribution of indices (only 0 and 3) is correct.
- ✅ The sequence of lines is correct:LY increments correctly from 0 to 144, without breaks or duplicate lines.
- ✅ The framebuffer is only cleared at the beginning of the frame:No cleanups were detected during rendering (LY 1-143). The framebuffer is cleared correctly only when LY is reset to 0.
- ✅ The complete state of the framebuffer is correct:At the end of each frame (LY=144), all 144 lines have data, with 11520 non-zero pixels out of 23040 total.
Conclusions
The logs reveal that line rendering is working correctly. All lines are rendered, the framebuffer has consistent data across all lines, and there are no issues with framebuffer cleanup or line sequencing. The "white screen with only top line" issue mentioned in Step 0338 is probably not related to line rendering, but rather something else (possibly Python display or framebuffer read timing).
Sources consulted
- Bread Docs:LCD Status Register, LY Register, LCD Timing
Educational Integrity
What I Understand Now
- Scanlines:The Game Boy has 144 visible lines (LY 0-143). Each line is rendered during H-Blank and the LY register is automatically incremented.
- Framebuffer:Contains color indices (0-3) for each pixel. It is generated line by line during rendering and cleared at the start of the next frame.
- Line rendering:
render_scanline()is called for each visible line. The framebuffer is cleared at the start of the next frame, ensuring that Python always reads the framebuffer BEFORE it is cleared.
What I confirmed
- All lines are rendered:The logs confirm that 144/144 lines have data in each frame.
- The framebuffer has consistent data:Each line has correct data (80 non-zero pixels out of 160 for the checkerboard).
- The sequence of lines is correct:LY increments correctly without jumps or duplicates.
- The framebuffer is only cleared at the start of the frame:No cleanups were detected during rendering.
Next Steps
Since line rendering works correctly, the "white screen with only top line" issue is probably somewhere else. Next steps should investigate:
- Framebuffer reading timing in Python
- Displaying the framebuffer in the Python renderer
- Synchronization between C++ rendering and Python reading
Next Steps
- [ ] Investigate framebuffer read timing in Python
- [ ] Check the display of the framebuffer in the Python renderer
- [ ] Investigate synchronization between C++ rendering and Python reading
- [ ] If the cause is identified: Implement correction in Step 0340