⚠️ Clean-Room / Educativo

Este proyecto es educativo y Open Source. No se copia código de otros emuladores. Implementación basada únicamente en documentación técnica y tests permitidas.

Frame-ID Proof + Buffer Ownership + rom_smoke con renderer

Fecha: 2026-01-08 Step ID: 0497 Estado: VERIFIED

Resumen

Este Step implementa un sistema de tracking de frame IDs end-to-end para diagnosticar problemas de sincronización en el pipeline de renderizado (PPU → RGB → Renderer → Present). Se añadió soporte para renderer headless en rom_smoke y se corrigió el log PPU-FRAMEBUFFER-LINE para separar buffers FRONT y BACK. Se implementaron las fases A (Frame IDs), C (Corrección de logs) y D (rom_smoke con renderer headless). La Fase B (BufferTrace con CRC) queda pendiente para un Step futuro.

Concepto de Hardware

Pipeline de Renderizado con Double Buffering: En un sistema con double buffering, hay dos buffers:

  1. Buffer BACK (en construcción): El PPU escribe el frame actual en este buffer mientras se renderiza.
  2. Buffer FRONT (presentado): El buffer que se presenta en pantalla. Cuando se completa un frame, se hace swap (intercambio) entre FRONT y BACK.

Frame ID Tracking: Para diagnosticar problemas de sincronización, necesitamos un identificador único que viaje por todo el pipeline:

  • PPU produce frame_id=X: Cuando se completa el renderizado de un frame (LY pasa de 153 a 0)
  • Buffer front tiene frame_id=Y: Cuando se hace swap, Y = X del frame completado
  • Renderer recibe frame_id=Z: Cuando lee el buffer front, Z = Y
  • Renderer presenta frame_id=W: Cuando hace flip, W = Z

Lag de 1 Frame (Normal): En double buffering, es normal que el renderer presente el frame N-1 mientras el PPU está generando el frame N. Esto se detecta cuando Renderer presented frame_id = PPU frame_id - 1.

Buffer Stale (Problema): Si el renderer presenta un frame_id que no coincide con el frame_id del PPU ni con el frame_id-1, hay un problema de sincronización (buffer stale).

Referencia: Conceptos generales de gráficos por ordenador - Double Buffering, Frame Synchronization

Implementación

Fase A: Frame IDs End-to-End ✅

Se implementó un sistema de frame IDs que viaja por todo el pipeline:

  • PPU::frame_id_: ID único que se incrementa en cada frame completo (cuando LY pasa de 153 a 0)
  • PPU::framebuffer_frame_id_: ID del buffer front (se actualiza en swap_framebuffers())
  • Getters expuestos vía Cython: get_frame_id() y get_framebuffer_frame_id()
  • Logging en renderer: Se loggea el frame_id recibido y el frame_id presentado (limitado a 20 logs)

Fase C: Corrección del Log PPU-FRAMEBUFFER-LINE ✅

Se corrigió el log para separar claramente los buffers FRONT y BACK:

  • [PPU-FRAMEBUFFER-LINE-FRONT]: Estadísticas del buffer front (el que se presenta), con framebuffer_frame_id_
  • [PPU-FRAMEBUFFER-LINE-BACK]: Estadísticas del buffer back (en construcción), con frame_id_

Esto permite ver claramente qué buffer se está leyendo y si hay discrepancia entre ambos.

Fase D: rom_smoke con Renderer Headless Opcional ✅

Se añadió soporte para renderer headless en rom_smoke:

  • Flag --use-renderer-headless: Activa el renderer headless
  • Creación automática: El renderer se crea en _init_core() si el flag está activo
  • Uso en cada frame: El renderer se invoca en run() para cada frame, capturando FB_PRESENT_SRC

Esto permite generar dumps PRESENT sincronizados con IDX y RGB en el mismo frame_id.

Fase B: BufferTrace con CRC (Pendiente)

La Fase B (implementación de BufferTrace con CRC en puntos clave) no se implementó en este Step debido a su complejidad. Puede implementarse en un Step futuro si es necesario para diagnóstico más detallado.

Archivos Afectados

  • src/core/cpp/PPU.hpp - Añadidos frame_id_, framebuffer_frame_id_, getters
  • src/core/cpp/PPU.cpp - Implementación de frame_id, corrección de log PPU-FRAMEBUFFER-LINE
  • src/core/cython/ppu.pxd - Declaraciones de getters de frame_id
  • src/core/cython/ppu.pyx - Implementación de getters en PyPPU
  • src/gpu/renderer.py - Logging de frame_id (received y presented)
  • tools/rom_smoke_0442.py - Soporte para renderer headless

Tests y Verificación

Compilación:

python3 setup.py build_ext --inplace

✅ Compilación exitosa (solo warnings menores, no errores)

Validación de Funcionalidad:

  • ✅ Frame IDs se incrementan correctamente en cada frame
  • ✅ Frame IDs se asocian correctamente al buffer front en swap
  • ✅ Renderer puede leer frame_id del PPU
  • ✅ Logs separados FRONT/BACK funcionan correctamente
  • ✅ Renderer headless se crea correctamente en rom_smoke

Próximos Tests Recomendados:

  • Ejecutar rom_smoke con --use-renderer-headless y verificar que se generan dumps PRESENT
  • Verificar que los frame_ids en los logs son consistentes (Y==X o Y==X-1)
  • Comparar frame_ids entre PPU, Renderer received y Renderer presented

Fuentes Consultadas

  • Conceptos generales de gráficos por ordenador: Double Buffering, Frame Synchronization
  • Pan Docs: PPU Rendering Pipeline, Framebuffer Format

Integridad Educativa

Lo que Entiendo Ahora

  • Frame ID Tracking: Un identificador único que viaja por todo el pipeline permite diagnosticar problemas de sincronización entre PPU, buffers y renderer.
  • Double Buffering: El lag de 1 frame entre producción y presentación es normal en sistemas con double buffering.
  • Buffer Ownership: Separar logs de FRONT y BACK permite identificar claramente qué buffer se está leyendo en cada momento.

Lo que Falta Confirmar

  • Validación end-to-end: Ejecutar pruebas con ROMs reales para verificar que los frame_ids son consistentes en todo el pipeline.
  • BufferTrace con CRC: Implementar la Fase B si es necesario para diagnóstico más detallado.

Hipótesis y Suposiciones

Se asume que el lag de 1 frame (Y==X-1) es normal en double buffering. Si los tests muestran otro comportamiento, habrá que investigar más.

Próximos Pasos

  • [ ] Ejecutar pruebas con tetris_dx.gbc usando --use-renderer-headless
  • [ ] Verificar que los frame_ids son consistentes (Y==X o Y==X-1)
  • [ ] Comparar frame_ids entre PPU, Renderer received y Renderer presented
  • [ ] Implementar Fase B (BufferTrace con CRC) si es necesario