⚠️ 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 Pantallas Blancas Post-Doble Buffering

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

Resumen

A pesar de que el doble buffering eliminó completamente las condiciones de carrera (0 advertencias vs 7291 antes), todas las ROMs siguen mostrando pantallas completamente blancas. Se implementaron verificaciones detalladas en cada etapa del pipeline de renderizado (escritura al framebuffer back, intercambio de buffers, lectura en Python, renderizado) para identificar exactamente dónde se pierden los datos. Los logs confirman que el intercambio funciona correctamente y que Python lee datos del framebuffer, pero el renderizado normal no está escribiendo datos al framebuffer_back_ (todas las líneas están vacías). Los 11520 píxeles no-blancos que aparecen en el intercambio parecen venir del checkerboard, no del renderizado normal de tiles.

Concepto de Hardware

Pipeline de Renderizado Completo en Emuladores Híbridos

En un emulador híbrido C++/Python, el pipeline de renderizado tiene múltiples etapas que deben funcionar correctamente:

  1. Renderizado en C++ (PPU): render_scanline() escribe píxeles al framebuffer_back_ durante MODE_3_PIXEL_TRANSFER
  2. Intercambio de buffers: Cuando se completa un frame (LY=144), se intercambian framebuffer_front_ y framebuffer_back_ usando std::swap()
  3. Lectura en Python: Python lee el framebuffer_front_ a través de Cython (memoryview zero-copy)
  4. Copia inmutable: Python crea un bytearray snapshot del framebuffer para protegerlo de cambios
  5. Renderizado en Python: El renderizador convierte índices de color (0-3) a RGB usando la paleta BGP y dibuja en Pygame
  6. Actualización de pantalla: Pygame muestra la superficie en la ventana

Cualquier problema en cualquiera de estas etapas causará pantallas blancas o gráficos corruptos.

Verificación de Datos en Cada Etapa

Es crítico verificar que los datos se mantienen correctos en cada etapa:

  • Si el framebuffer_back_ está vacío: El problema está en el renderizado C++ (no se escriben datos durante MODE_3)
  • Si el framebuffer_front_ está vacío después del intercambio: El problema está en el intercambio (aunque std::swap() es atómico)
  • Si Python lee ceros: El problema está en la lectura o el wrapper de Cython
  • Si el renderizador recibe ceros: El problema está en la transferencia de datos (snapshot)
  • Si la superficie está blanca: El problema está en el renderizado Python (conversión RGB o dibujo)

Implementación

Verificaciones Implementadas

Se implementaron verificaciones detalladas en cada etapa del pipeline:

  1. Verificación de escritura al framebuffer_back_: Al final de render_scanline(), se verifica que se escribieron datos no-blancos en las líneas 0, 72 y 143
  2. Verificación de frame completo: Cuando LY=143 y mode=MODE_1_VBLANK, se verifica que el framebuffer_back_ tiene datos antes del intercambio
  3. Verificación de intercambio: En swap_framebuffers(), se cuenta píxeles no-blancos antes y después del intercambio
  4. Verificación de lectura en Python: En viboy.py, se verifica el contenido del framebuffer antes y después de la copia
  5. Verificación de renderizado: En renderer.py, se verifica que el renderizador recibe datos y que la superficie tiene píxeles no-blancos

Correcciones Realizadas

  • Movimiento de verificación: La verificación de escritura se movió al final de render_scanline() (después de escribir) en lugar del principio
  • Corrección de buffer: Se corrigió código que verificaba framebuffer_front_ cuando debería verificar framebuffer_back_

Archivos Afectados

  • src/core/cpp/PPU.cpp - Agregadas verificaciones en render_scanline(), swap_framebuffers() y verificación de frame completo
  • src/viboy.py - Agregada verificación de lectura del framebuffer antes y después de la copia
  • src/gpu/renderer.py - Agregada verificación de datos recibidos y verificación de superficie después de dibujar

Tests y Verificación

Se ejecutaron pruebas con las 6 ROMs (TETRIS, Mario, Zelda DX, Oro, PKMN, PKMN-Amarillo) para analizar los logs de verificación:

Hallazgos de los Logs

  • ✅ Intercambio funciona correctamente: Back buffer tiene 11520 píxeles no-blancos (50%), front los recibe correctamente
  • ✅ Python lee correctamente: Lee 52 píxeles no-blancos en los primeros 100 (datos presentes)
  • ❌ Renderizado normal no escribe datos: Todas las líneas renderizadas están vacías (0 píxeles no-blancos)
  • ❌ Los 11520 píxeles no-blancos parecen venir del checkerboard: No del renderizado normal de tiles

Logs de Ejemplo

[PPU-SWAP-DETAILED] Frame 1 | Back before: 11520 non-zero | Front before: 0 non-zero | Front after: 11520 non-zero
[PPU-SWAP-DETAILED] Front first 20 pixels: 3 3 3 3 3 3 3 3 0 0 0 0 0 0 0 0 3 3 3 3
[Python-Read-Framebuffer] Frame 1 | First 20 indices: [3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3] | Non-zero in first 100: 52/100
[Python-Read-Framebuffer] ✅ Copia correcta: 52 non-zero pixels

Problema identificado: El renderizado normal no está escribiendo datos al framebuffer_back_ durante MODE_3_PIXEL_TRANSFER. Todas las líneas están vacías, lo que sugiere que render_scanline() no está ejecutando la lógica de renderizado de tiles, o que la VRAM está vacía cuando se intenta renderizar.

Fuentes Consultadas

  • Pan Docs: LCD Controller (PPU)
  • Implementación basada en conocimiento general de arquitectura LR35902 y sistemas de renderizado con doble buffering

Integridad Educativa

Lo que Entiendo Ahora

  • Pipeline de renderizado: El pipeline completo desde C++ hasta Pygame tiene múltiples etapas que deben funcionar correctamente
  • Verificación de datos: Es crítico verificar cada etapa para identificar dónde se pierden los datos
  • Doble buffering funciona: El intercambio de buffers funciona correctamente, eliminando condiciones de carrera
  • Problema en renderizado: El renderizado normal no está escribiendo datos al framebuffer_back_, todas las líneas están vacías

Lo que Falta Confirmar

  • Por qué render_scanline() no escribe datos: Necesito investigar si la VRAM está vacía, si MODE_3 no se ejecuta, o si hay otro problema
  • Origen de los 11520 píxeles no-blancos: Confirmar si vienen del checkerboard o de otro lugar
  • Por qué las pantallas están blancas: Aunque Python lee datos, las pantallas están blancas, lo que sugiere un problema en el renderizado Python

Hipótesis y Suposiciones

Hipótesis principal: El renderizado normal no está escribiendo datos porque la VRAM está vacía cuando se intenta renderizar, o porque MODE_3_PIXEL_TRANSFER no se está ejecutando correctamente. Los 11520 píxeles no-blancos vienen del checkerboard, que se activa cuando VRAM está vacía, pero el checkerboard no se está mostrando en la pantalla (posible problema en el renderizado Python).

Próximos Pasos

  • [ ] Investigar por qué render_scanline() no escribe datos durante MODE_3_PIXEL_TRANSFER
  • [ ] Verificar si la VRAM está vacía cuando se intenta renderizar
  • [ ] Verificar si MODE_3_PIXEL_TRANSFER se está ejecutando correctamente
  • [ ] Investigar por qué las pantallas están blancas aunque Python lee datos del framebuffer
  • [ ] Verificar si el problema está en la conversión RGB o en el dibujo en Pygame