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
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_startal 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 desdeframe_wall_starthasta 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
metricsopcional arender_frame() - Crear función helper
_sample_vram_nonzero()enViboy(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_totalestimado (multiplicar por densidad) - Comparar
nonwhite_sample(antes, grid 16×16) vsnonwhite_after_total(después, grid 8×8) - Detectar pérdida significativa: si
before > 1000yafter < 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_UIdefault OFF (os.environ.get('VIBOY_DEBUG_UI', '0') == '1')- Logs
[UI-PATH]y[UI-PROFILING]solo dentro deshould_logo cuando FPS < 30
Archivos Afectados
src/gpu/renderer.py- Corrección de profiling, mejora de logging [UI-PATH], verificación post-blit mejoradasrc/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