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

Investigación de Renderizado de Líneas y Estado del Framebuffer

Fecha: 2025-12-29 Step ID: 0339 Estado: VERIFIED

Resumen

Investigación exhaustiva del renderizado de líneas y el estado del framebuffer para identificar por qué algunas líneas del framebuffer están vacías cuando deberían tener datos. Se implementaron logs de diagnóstico para verificar si todas las líneas se renderizan (LY 0-143), si el framebuffer se limpia durante el renderizado, si hay líneas que se saltan, y el estado completo del framebuffer al final de cada frame. Los logs revelaron que todas las líneas se renderizan correctamente y el framebuffer tiene datos consistentes en todas las líneas.

Concepto de Hardware

Scanlines (Líneas de Escaneo)

La Game Boy tiene 144 líneas visibles (LY 0-143). Cada línea se renderiza durante H-Blank (Mode 0). Después de la línea 143, hay V-Blank (líneas 144-153). El registro LY (Line Y) se incrementa automáticamente después de cada línea y se resetea a 0 al inicio de cada frame.

Framebuffer

El framebuffer contiene índices de color (0-3) para cada píxel de la pantalla. Tamaño: 160×144 = 23,040 píxeles. Cada línea ocupa 160 píxeles en el framebuffer. El framebuffer se genera línea por línea durante el renderizado de cada scanline y se limpia al inicio del siguiente frame (cuando LY se resetea a 0).

Renderizado de Líneas

render_scanline() se llama para cada línea visible (LY 0-143). El renderizado debe completarse antes de que Python lea el framebuffer. El framebuffer se limpia al inicio del siguiente frame (LY=0), asegurando que Python siempre lee el framebuffer ANTES de que se limpie.

Fuente: Pan Docs - "LCD Status Register", "LY Register", "LCD Timing"

Implementación

Se implementaron 5 bloques de logs de diagnóstico en PPU.cpp para investigar el renderizado de líneas y el estado del framebuffer.

1. Verificación de Renderizado de Todas las Líneas

Se agregaron logs en PPU::render_scanline() al inicio para verificar que todas las líneas se renderizan (LY 0-143). Los logs marcan cada línea cuando se renderiza y verifican si hay líneas que no se renderizan.

  • Array de líneas renderizadas: Array estático de 144 booleanos que marca cada línea cuando se renderiza
  • Verificación periódica: Cada 10 frames, verifica si todas las líneas se han renderizado
  • Tag: [PPU-LINES-RENDER]

2. Verificación del Estado del Framebuffer Línea por Línea

Se agregaron logs en PPU::render_scanline() al final para verificar el estado del framebuffer después de renderizar cada línea. Los logs verifican líneas específicas (0, 72, 143) y algunas aleatorias.

  • Distribución de índices: Cuenta cuántos píxeles tienen cada índice de color (0, 1, 2, 3) en cada línea
  • Píxeles específicos: Verifica algunos píxeles específicos (x=0, 40, 80, 120, 159) para ver qué índices contienen
  • Advertencias: Advierte si una línea está completamente vacía después de renderizar
  • Tag: [PPU-FRAMEBUFFER-LINE]

3. Verificación Mejorada de Limpieza del Framebuffer

Se mejoraron los logs existentes en PPU::clear_framebuffer() para verificar que el framebuffer solo se limpia al inicio del frame (LY=0) y no durante el renderizado (LY 1-143).

  • Log de limpieza: Loggea cada vez que se limpia el framebuffer con el frame y LY actual
  • Advertencia: Advierte si el framebuffer se limpia durante el renderizado (no debería pasar)
  • Tag: [PPU-CLEAR-FRAMEBUFFER]

4. Verificación de Secuencia de Líneas

Se agregaron logs en PPU::step() cuando LY cambia para verificar que LY incrementa correctamente (0, 1, 2, ..., 143, 144, ..., 153, 0) y no se saltan líneas.

  • Verificación de secuencia: Verifica si LY incrementa correctamente o si se saltan líneas
  • Log periódico: Loggea cada 10 líneas o líneas importantes (0, 72, 143, 144)
  • Tag: [PPU-LY-SEQUENCE]

5. Verificación del Estado Completo del Framebuffer al Final del Frame

Se agregaron logs en PPU::step() cuando LY llega a 144 (VBLANK_START) para verificar el estado completo del framebuffer al final del frame.

  • Líneas con datos: Cuenta cuántas líneas tienen datos (no todas blancas)
  • Píxeles no-cero: Cuenta cuántos píxeles tienen índices no-cero (1, 2, 3)
  • Distribución global: Muestra la distribución de índices en todo el framebuffer
  • Advertencias: Advierte si hay pocas líneas con datos o si el framebuffer está completamente vacío
  • Tag: [PPU-FRAMEBUFFER-COMPLETE]

Componentes creados/modificados

  • src/core/cpp/PPU.cpp: Agregados 5 bloques de logs de diagnóstico en render_scanline(), clear_framebuffer() y step()

Archivos Afectados

  • src/core/cpp/PPU.cpp - Agregados logs de diagnóstico del Step 0339

Tests y Verificación

Se ejecutaron pruebas con la ROM de Pokémon (pkmn.gb) durante 2.5 minutos. Los logs revelaron información valiosa sobre el renderizado de líneas y el estado del framebuffer.

Comando Ejecutado

timeout 150 python3 main.py roms/pkmn.gb 2>&1 | tee logs/test_pkmn_step0339.log

Resultado

Los logs muestran que:

  • Todas las líneas se renderizan: 144/144 líneas tienen datos en cada frame
  • El framebuffer tiene datos consistentes: 11520 píxeles no-cero de 23040 totales (50%), lo cual es correcto para el checkerboard
  • La distribución es correcta: Solo índices 0 y 3, que es el patrón del checkerboard
  • La secuencia de líneas es correcta: LY incrementa correctamente (0, 10, 20, 30, ..., 143, 144)
  • El framebuffer solo se limpia al inicio del frame: No se detectaron limpiezas durante el renderizado

Ejemplo de Logs

[PPU-FRAMEBUFFER-COMPLETE] Frame 1 | LY: 144 (VBLANK_START) | Lines with data: 144/144 | Total non-zero pixels: 11520/23040 | Distribution: 0=11520 1=0 2=0 3=11520
[PPU-FRAMEBUFFER-LINE] Frame 1 | LY: 0 | Non-zero pixels: 80/160 | Distribution: 0=80 1=0 2=0 3=80
[PPU-LY-SEQUENCE] Frame 1 | LY: 0
[PPU-LY-SEQUENCE] Frame 1 | LY: 10
[PPU-LY-SEQUENCE] Frame 1 | LY: 20
...
[PPU-LY-SEQUENCE] Frame 1 | LY: 143
[PPU-LY-SEQUENCE] Frame 1 | LY: 144

Análisis de Logs

# Verificar si todas las líneas se renderizan
grep "\[PPU-LINES-RENDER\]" logs/test_*_step0339.log | head -n 30

# Verificar estado del framebuffer línea por línea
grep "\[PPU-FRAMEBUFFER-LINE\]" logs/test_*_step0339.log | head -n 50

# Verificar limpieza del framebuffer
grep "\[PPU-CLEAR-FRAMEBUFFER\]" logs/test_*_step0339.log | head -n 30

# Verificar secuencia de líneas
grep "\[PPU-LY-SEQUENCE\]" logs/test_*_step0339.log | head -n 50

# Verificar estado completo del framebuffer
grep "\[PPU-FRAMEBUFFER-COMPLETE\]" logs/test_*_step0339.log | head -n 30

Validación Nativa: Validación de módulo compilado C++

Hallazgos y Conclusiones

Hallazgos Principales

  • ✅ Todas las líneas se renderizan correctamente: Los logs muestran que 144/144 líneas tienen datos en cada frame. No se detectaron líneas que no se rendericen.
  • ✅ El framebuffer tiene datos consistentes: Cada línea tiene 80 píxeles no-cero de 160 totales (50%), lo cual es correcto para el checkerboard. La distribución de índices (solo 0 y 3) es correcta.
  • ✅ La secuencia de líneas es correcta: LY incrementa correctamente de 0 a 144, sin saltos ni líneas duplicadas.
  • ✅ El framebuffer solo se limpia al inicio del frame: No se detectaron limpiezas durante el renderizado (LY 1-143). El framebuffer se limpia correctamente solo cuando LY se resetea a 0.
  • ✅ El estado completo del framebuffer es correcto: Al final de cada frame (LY=144), todas las 144 líneas tienen datos, con 11520 píxeles no-cero de 23040 totales.

Conclusiones

Los logs revelan que el renderizado de líneas funciona correctamente. Todas las líneas se renderizan, el framebuffer tiene datos consistentes en todas las líneas, y no hay problemas con la limpieza del framebuffer o la secuencia de líneas. El problema de "pantalla blanca con solo línea superior" mencionado en el Step 0338 probablemente no está relacionado con el renderizado de líneas, sino con otro aspecto (posiblemente la visualización en Python o el timing de lectura del framebuffer).

Fuentes Consultadas

Integridad Educativa

Lo que Entiendo Ahora

  • Scanlines: La Game Boy tiene 144 líneas visibles (LY 0-143). Cada línea se renderiza durante H-Blank y el registro LY se incrementa automáticamente.
  • Framebuffer: Contiene índices de color (0-3) para cada píxel. Se genera línea por línea durante el renderizado y se limpia al inicio del siguiente frame.
  • Renderizado de líneas: render_scanline() se llama para cada línea visible. El framebuffer se limpia al inicio del siguiente frame, asegurando que Python siempre lee el framebuffer ANTES de que se limpie.

Lo que Confirmé

  • Todas las líneas se renderizan: Los logs confirman que 144/144 líneas tienen datos en cada frame.
  • El framebuffer tiene datos consistentes: Cada línea tiene datos correctos (80 píxeles no-cero de 160 para el checkerboard).
  • La secuencia de líneas es correcta: LY incrementa correctamente sin saltos ni duplicados.
  • El framebuffer solo se limpia al inicio del frame: No se detectaron limpiezas durante el renderizado.

Próximos Pasos

Dado que el renderizado de líneas funciona correctamente, el problema de "pantalla blanca con solo línea superior" probablemente está en otro lugar. Los próximos pasos deberían investigar:

  • El timing de lectura del framebuffer en Python
  • La visualización del framebuffer en el renderizador Python
  • La sincronización entre el renderizado C++ y la lectura Python

Próximos Pasos

  • [ ] Investigar el timing de lectura del framebuffer en Python
  • [ ] Verificar la visualización del framebuffer en el renderizador Python
  • [ ] Investigar la sincronización entre el renderizado C++ y la lectura Python
  • [ ] Si se identifica la causa: Implementar corrección en Step 0340