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

Frame-ID Proof + Buffer Ownership + rom_smoke with renderer

Date:2026-01-08 StepID:0497 State: VERIFIED

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:

  1. BACK Buffer (under construction): The PPU writes the current frame to this buffer while rendering.
  2. 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 inswap_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), withframebuffer_frame_id_
  • [PPU-FRAMEBUFFER-LINE-BACK]: Buffer back statistics (under construction), withframe_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 inrun()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_, getters
  • src/core/cpp/PPU.cpp- Implementation of frame_id, correction of PPU-FRAMEBUFFER-LINE log
  • src/core/cython/ppu.pxd- frame_id getter declarations
  • src/core/cython/ppu.pyx- Implementation of getters in PyPPU
  • src/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:

  • Executerom_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 withtetris_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