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

Medición Correcta Profiling + Comparación Headless/UI + Fix Mínimo

Fecha: 2026-01-02 Step ID: 0448 Estado: VERIFIED

Resumen

Corrección del sistema de profiling para medir correctamente stage_sum_ms, frame_wall_ms y pacing_ms por separado. Mejora del logging [UI-PATH] para incluir métricas del core (PC, VRAM_nonzero, LCDC, BGP, LY) y NonWhite calculado con muestreo serio (grid 16×16). Verificación post-blit con muestreo decente (64 puntos, grid 8×8). Creación de script de comparación headless vs UI para producir tabla comparativa y decidir con evidencia numérica si el problema es presenter/UI o core. Control de spam de logs con gating estricto.

Concepto de Hardware

Profiling Correcto: El profiling anterior medía tiempo acumulado incorrectamente, generando valores inconsistentes (TOTAL: 421ms vs suma de etapas: 5.88ms). El profiling correcto debe medir:

  • stage_sum_ms: Suma de las etapas individuales de render (frombuffer/reshape, blit_array, scale/blit, flip)
  • frame_wall_ms: Tiempo total del frame completo (wall-clock time desde inicio hasta fin)
  • pacing_ms: Tiempo de espera/sincronización (diferencia entre wall y stages: wall - stages)

Muestreo de NonWhite: El muestreo anterior era insuficiente (solo 3 píxeles). Para obtener estimaciones fiables:

  • Grid 16×16: 256 puntos de muestra para calcular NonWhite antes del blit
  • Grid 8×8: 64 puntos de muestra para verificar NonWhite después del blit

Comparación Headless vs UI: Para decidir si el problema es presenter/UI o core, se compara el framebuffer generado por el core (headless) vs el framebuffer presentado por la UI. Si headless tiene NonWhite > 0 pero UI before ~0, el problema está en cómo UI obtiene el framebuffer. Si headless tiene NonWhite > 0 y UI before > 0 pero UI after ~0, el problema está en el presenter/blit.

Implementación

Fase A: Arreglar Profiling

Archivo: src/gpu/renderer.py

Cambios:

  • Medir frame_wall_start al inicio del frame completo (antes de cualquier etapa)
  • Medir cada etapa individual: frombuffer_ms, blit_ms, scale_blit_ms, flip_ms
  • Calcular stage_sum_ms = suma de etapas individuales
  • Calcular frame_wall_ms = tiempo total desde frame_wall_start hasta final
  • Calcular pacing_ms = frame_wall_ms - stage_sum_ms
  • Log formateado: [UI-PROFILING] Frame N | stages=Xms (frombuf=A blit=B scale=C flip=D) | wall=Yms | pacing=Zms

Fase B: Mejorar Logging [UI-PATH]

Archivo: src/gpu/renderer.py, src/viboy.py

Cambios:

  • Añadir parámetro metrics opcional a render_frame()
  • Crear función helper _sample_vram_nonzero() en Viboy (muestreo cada 16º byte, igual que headless tool)
  • Recolectar métricas en viboy.py: PC, VRAM_nonzero, LCDC, BGP, LY
  • Pasar métricas a render_frame() desde todas las llamadas
  • Mejorar muestreo NonWhite: grid 16×16 = 256 puntos (antes era cada 64º píxel ≈ 3 puntos)
  • Log formateado: [UI-PATH] F4 | Path=cpp_rgb_view | PC=6152 | LCDC=E3 | BGP=FC | LY=90 | VRAMnz=2028 | NonWhite=23040 | Hash=abc12345 | wall=16.7ms

Fase C: Verificación Post-Blit Mejorada

Archivo: src/gpu/renderer.py

Cambios:

  • Reemplazar muestreo de 3 píxeles por grid 8×8 = 64 puntos
  • Muestrear desde self.surface.get_at() después del blit
  • Calcular nonwhite_after_total estimado (multiplicar por densidad)
  • Comparar nonwhite_sample (antes, grid 16×16) vs nonwhite_after_total (después, grid 8×8)
  • Detectar pérdida significativa: si before > 1000 y after < 100, emitir warning

Fase D: Script de Comparación Headless vs UI

Archivo: tools/compare_headless_vs_ui_0448.sh

Funcionalidad:

  • Ejecutar headless tool para cada ROM (Mario, Pokémon, Tetris, Zelda DX)
  • Ejecutar UI con timeout de 15s para cada ROM
  • Extraer métricas de ambos (NonWhite, VRAM_nonzero, PC_end)
  • Generar tabla comparativa: ROM | headless NonWhite | UI NonWhite_before | UI NonWhite_after | VRAMnz | PC_end
  • Permitir decisión automática basada en la tabla:
    • headless NonWhite > 0 y UI before > 0 pero UI after ~0 → bug presenter/blit
    • headless NonWhite > 0 y UI before ~0 → bug en cómo UI obtiene framebuffer
    • Ambos 0 pero VRAMnz alto → bug PPU/paleta
    • Ambos 0 y VRAMnz ~0 y PC estancado → bug CPU/ROM exec

Fase E: Control de Spam de Logs

Archivo: src/gpu/renderer.py

Verificación:

  • Gating estricto: should_log = (self._path_log_count < 5) or (self._path_log_count % 120 == 0)
  • VIBOY_DEBUG_UI default OFF (os.environ.get('VIBOY_DEBUG_UI', '0') == '1')
  • Logs [UI-PATH] y [UI-PROFILING] solo dentro de should_log o cuando FPS < 30

Archivos Afectados

  • src/gpu/renderer.py - Corrección de profiling, mejora de logging [UI-PATH], verificación post-blit mejorada
  • src/viboy.py - Función helper _sample_vram_nonzero(), pasar métricas a render_frame()
  • tools/compare_headless_vs_ui_0448.sh - Script de comparación headless vs UI (creado)

Tests y Verificación

Compilación:

python3 setup.py build_ext --inplace
# BUILD_EXIT=0 ✓

python3 test_build.py
# TEST_BUILD_EXIT=0 ✓

Validación Nativa: Módulo C++ compilado correctamente, interfaz Python-C++ funcional.

Script de Comparación: Script creado y ejecutable. Requiere ejecución manual con ROMs para generar tabla comparativa.

Fuentes Consultadas

  • Pan Docs: Memory Map, I/O Registers (para métricas VRAM, LCDC, BGP, LY)
  • Step 0442: Tool headless (tools/rom_smoke_0442.py) - Referencia para muestreo VRAM_nonzero

Integridad Educativa

Lo que Entiendo Ahora

  • Profiling Correcto: Para medir rendimiento correctamente, hay que separar el tiempo de trabajo real (stages) del tiempo total (wall), y el tiempo de espera/sincronización (pacing). Esto permite identificar si los cuellos de botella están en el render (stages alto) o en la sincronización (pacing alto).
  • Muestreo Representativo: Un muestreo de 3 píxeles no es suficiente para estimar NonWhite. Un grid 16×16 (256 puntos) o 8×8 (64 puntos) proporciona estimaciones mucho más fiables, especialmente cuando se comparan antes/después del blit.
  • Comparación Headless vs UI: Para diagnosticar problemas de presentación, es crucial comparar qué genera el core (headless) vs qué presenta la UI. Si headless tiene datos pero UI no, el problema está en el presenter. Si headless no tiene datos, el problema está en el core.

Lo que Falta Confirmar

  • Ejecución del Script de Comparación: El script está creado pero requiere ejecución manual con ROMs reales para generar la tabla comparativa y tomar decisiones basadas en evidencia numérica.
  • Diagnóstico Final: Una vez ejecutado el script de comparación, se podrá decidir con certeza si el problema es presenter/UI (blit, format, surface) o core (CPU/VRAM/PPU no produce imagen para esa ROM).

Próximos Pasos

  • [ ] Ejecutar script de comparación headless vs UI con ROMs reales (Mario, Pokémon, Tetris, Zelda DX)
  • [ ] Analizar tabla comparativa y tomar decisión basada en evidencia numérica
  • [ ] Si problema es presenter/UI: investigar y corregir bug en blit/format/surface
  • [ ] Si problema es core: investigar por qué PPU no produce imagen para esa ROM específica