This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.
Frame-ID Proof + Buffer Ownership + rom_smoke with renderer
Summary
This Step implements an end-to-end frame ID tracking system to diagnose synchronization problems in the rendering pipeline (PPU → RGB → Renderer → Present). Added support for headless renderer inrom_smokeand the log was correctedPPU-FRAMEBUFFER-LINEto separate FRONT and BACK buffers. Phases A (Frame IDs), C (Log Correction) and D (rom_smoke with headless renderer) were implemented. Phase B (BufferTrace with CRC) is pending for a future Step.
Hardware Concept
Rendering Pipeline with Double Buffering: In a double buffered system, there are two buffers:
- BACK Buffer (under construction): The PPU writes the current frame to this buffer while rendering.
- Buffer FRONT (featured): The buffer that is presented on the screen. When a frame is completed, a swap is made between FRONT and BACK.
Frame ID Tracking: To diagnose synchronization problems, we need a unique identifier that travels through the entire pipeline:
- PPU produces frame_id=X: When the rendering of a frame is completed (LY goes from 153 to 0)
- Buffer front has frame_id=Y: When swapping, Y = X of the completed frame
- Renderer receives frame_id=Z: When reading the front buffer, Z = Y
- Renderer presents frame_id=W: When it flips, W = Z
1 Frame Lag (Normal): In double buffering, it is normal for the renderer to present frame N-1 while the PPU is generating frame N. This is detected whenRenderer presented frame_id = PPU frame_id - 1.
Buffer Stale (Issue): If the renderer presents a frame_id that does not match the PPU frame_id or the frame_id-1, there is a synchronization problem (buffer stale).
Reference: General concepts of computer graphics - Double Buffering, Frame Synchronization
Implementation
Phase A: Frame IDs End-to-End ✅
A frame ID system was implemented that travels throughout the pipeline:
- PPU::frame_id_: Unique ID that is incremented in each complete frame (when LY goes from 153 to 0)
- PPU::framebuffer_frame_id_: Front buffer ID (updated in
swap_framebuffers()) - Getters exposed via Cython:
get_frame_id()andget_framebuffer_frame_id() - Logging in renderer: The received frame_id and the presented frame_id are logged (limited to 20 logs)
Phase C: Correction of the PPU-FRAMEBUFFER-LINE Log ✅
Fixed the log to clearly separate the FRONT and BACK buffers:
- [PPU-FRAMEBUFFER-LINE-FRONT]: Statistics of the front buffer (the one presented), with
framebuffer_frame_id_ - [PPU-FRAMEBUFFER-LINE-BACK]: Buffer back statistics (under construction), with
frame_id_
This allows you to clearly see which buffer is being read and if there is a discrepancy between the two.
Phase D: rom_smoke with Optional Headless Renderer ✅
Added support for headless renderer inrom_smoke:
- Flag
--use-renderer-headless: Activate the headless renderer - Automatic creation: The renderer is created in
_init_core()if the flag is active - Use in each frame: The renderer is invoked in
run()for each frame, capturing FB_PRESENT_SRC
This allows generating synchronized PRESENT dumps with IDX and RGB on the same frame_id.
Phase B: BufferTrace with CRC (Pending)
Phase B (implementation of BufferTrace with CRC at key points) was not implemented in this Step due to its complexity. It can be implemented in a future Step if necessary for more detailed diagnosis.
Affected Files
src/core/cpp/PPU.hpp- Addedframe_id_,framebuffer_frame_id_, getterssrc/core/cpp/PPU.cpp- Implementation of frame_id, correction of PPU-FRAMEBUFFER-LINE logsrc/core/cython/ppu.pxd- frame_id getter declarationssrc/core/cython/ppu.pyx- Implementation of getters in PyPPUsrc/gpu/renderer.py- Frame_id logging (received and presented)tools/rom_smoke_0442.py- Headless renderer support
Tests and Verification
Compilation:
python3 setup.py build_ext --inplace
✅ Successful compilation (only minor warnings, not errors)
Functionality Validation:
- ✅ Frame IDs are incremented correctly on each frame
- ✅ Frame IDs are correctly associated with the front buffer in swap
- ✅ Renderer can read frame_id from PPU
- ✅ Separate FRONT/BACK logs work correctly
- ✅ Headless renderer is created correctly in rom_smoke
Next Recommended Tests:
- Execute
rom_smokewith--use-renderer-headlessand verify that PRESENT dumps are generated - Verify that the frame_ids in the logs are consistent (Y==X or Y==X-1)
- Compare frame_ids between PPU, Renderer received and Renderer presented
Sources consulted
- General Computer Graphics Concepts: Double Buffering, Frame Synchronization
- Pan Docs: PPU Rendering Pipeline, Framebuffer Format
Educational Integrity
What I Understand Now
- Frame ID Tracking: A unique identifier that travels throughout the pipeline allows diagnosing synchronization problems between PPU, buffers and renderer.
- Double Buffering: The lag of 1 frame between production and presentation is normal in systems with double buffering.
- Buffer Ownership: Separating FRONT and BACK logs allows you to clearly identify which buffer is being read at any given time.
What remains to be confirmed
- End-to-end validation: Run tests with real ROMs to verify that the frame_ids are consistent throughout the pipeline.
- BufferTrace with CRC: Implement Phase B if necessary for more detailed diagnosis.
Hypotheses and Assumptions
It is assumed that 1 frame lag (Y==X-1) is normal in double buffering. If the tests show other behavior, further investigation will be necessary.
Next Steps
- [ ] Run tests with
tetris_dx.gbcwearing--use-renderer-headless - [ ] Verify that the frame_ids are consistent (Y==X or Y==X-1)
- [ ] Compare frame_ids between PPU, Renderer received and Renderer presented
- [ ] Implement Phase B (BufferTrace with CRC) if necessary