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

Depuración del Framebuffer

Fecha: 2025-12-18 Step ID: 0036 Estado: Verified

Resumen

Después de optimizar el renderizado con PixelArray, el emulador mostraba pantalla negra sin logs visibles. Se diagnosticó y corrigió el problema: el PixelArray no se estaba cerrando correctamente antes de hacer blit a la pantalla, bloqueando la superficie. Se cambió a usar un context manager (with) para asegurar el cierre correcto. Además, se añadió un heartbeat que imprime cada 60 frames (≈1 segundo) el PC y FPS para confirmar que el emulador está vivo, incluso cuando el logging está en modo DEBUG.

Concepto de Hardware

Bloqueo de Superficie en Pygame: Cuando se crea un PixelArray sobre una superficie de Pygame, la superficie queda "bloqueada" para escritura directa. Esto significa que mientras el PixelArray está activo, la superficie no puede ser usada para otras operaciones como blit o transform.scale. Si intentas hacer estas operaciones con la superficie bloqueada, Pygame puede fallar silenciosamente o no dibujar nada.

Context Manager Pattern: En Python, un context manager (usando with) garantiza que un recurso se libere correctamente, incluso si ocurre una excepción. Para PixelArray, usar with pygame.PixelArray(buffer) as pixels: asegura que el array se cierre automáticamente al salir del bloque, desbloqueando la superficie y permitiendo que operaciones posteriores como blit funcionen correctamente.

Heartbeat (Latido del Sistema): Un heartbeat es un mecanismo de diagnóstico que imprime periódicamente el estado del sistema para confirmar que está vivo y funcionando. En este caso, cada 60 frames (aproximadamente 1 segundo a 60 FPS), se imprime el Program Counter (PC) y los FPS actuales. Esto es especialmente útil cuando el logging está en modo DEBUG y no se muestran mensajes normales, permitiendo verificar que el emulador está ejecutándose correctamente.

Implementación

Se realizaron dos cambios principales:

1. Context Manager para PixelArray

En src/gpu/renderer.py, se cambió el uso de PixelArray de:

pixels = pygame.PixelArray(self.buffer)
# ... código de renderizado ...
del pixels

A usar un context manager:

with pygame.PixelArray(self.buffer) as pixels:
    # ... código de renderizado ...
    # El array se cierra automáticamente al salir del bloque

Esto garantiza que el PixelArray se cierre correctamente antes de intentar escalar o hacer blit del buffer.

2. Heartbeat en el Bucle Principal

En src/viboy.py, se añadió un contador de frames y un heartbeat que imprime cada 60 frames:

frame_count = 0

# En el bucle principal, cuando se detecta V-Blank:
if in_vblank and not self._prev_vblank:
    frame_count += 1
    
    # Heartbeat: cada 60 frames (≈1 segundo), mostrar estado
    if frame_count % 60 == 0:
        pc = self._cpu.registers.get_pc()
        fps = self._clock.get_fps() if self._clock is not None else 0.0
        logger.info(f"Heartbeat: PC=0x{pc:04X} | FPS={fps:.2f}")

El heartbeat usa logger.info() para que siempre se muestre, incluso cuando el logging está en modo DEBUG.

Verificación del "Hack del Bit 0"

Se verificó que el "Hack del Bit 0" de LCDC sigue presente y funcionando correctamente (líneas 239-258 de renderer.py). Este hack permite que juegos CGB como Tetris DX que escriben LCDC=0x80 (bit 7=1 LCD ON, bit 0=0 BG OFF) puedan mostrar gráficos, ignorando el bit 0 cuando el LCD está encendido.

Archivos Afectados

  • src/gpu/renderer.py - Cambiado PixelArray a usar context manager (with)
  • src/viboy.py - Añadido contador de frames y heartbeat que imprime cada 60 frames

Tests y Verificación

Verificación Manual: Se ejecutó el emulador con Tetris DX para verificar que:

  • La pantalla ya no está negra (se muestra el fondo del juego)
  • El heartbeat aparece cada segundo en la consola: INFO: Heartbeat: PC=0xXXXX | FPS=59.XX
  • El framebuffer se renderiza correctamente sin bloqueos

Validación del Context Manager: Se verificó que el código compila correctamente y que el PixelArray se cierra antes de hacer blit, evitando el bloqueo de la superficie.

Nota sobre Tests Unitarios: Los tests existentes en tests/test_gpu_scroll.py usan @patch('src.gpu.renderer.pygame.draw.rect'), pero ahora el código usa PixelArray en lugar de draw.rect. Estos tests necesitarán actualizarse en el futuro para reflejar el nuevo método de renderizado, pero no afectan la funcionalidad del emulador.

Fuentes Consultadas

  • Pygame Documentation: PixelArray - Context manager y bloqueo de superficies
  • Python Documentation: Context Managers - Patrón de gestión de recursos

Integridad Educativa

Lo que Entiendo Ahora

  • Bloqueo de Superficies: PixelArray bloquea la superficie subyacente mientras está activo. Esto es necesario para permitir escritura directa a memoria, pero requiere que se cierre antes de usar la superficie para otras operaciones.
  • Context Managers: El patrón with en Python garantiza la liberación de recursos de forma segura, incluso si ocurre una excepción. Es la forma recomendada de usar PixelArray.
  • Heartbeat para Diagnóstico: Cuando el logging está en modo DEBUG, un heartbeat periódico permite verificar que el sistema está vivo sin necesidad de cambiar el nivel de logging.

Lo que Falta Confirmar

  • Tests Unitarios: Los tests de scroll necesitan actualizarse para reflejar el uso de PixelArray en lugar de draw.rect. Esto se hará en un paso futuro.
  • Rendimiento: Aunque el context manager añade un pequeño overhead, el beneficio de garantizar el cierre correcto supera cualquier impacto en rendimiento. Se puede medir en el futuro si es necesario.

Hipótesis y Suposiciones

Se asume que el bloqueo de superficie era la causa principal de la pantalla negra. Si el problema persiste después de este cambio, podría ser necesario investigar otros aspectos como la paleta de colores o el contenido de VRAM.

Próximos Pasos

  • [ ] Actualizar tests de scroll para reflejar el uso de PixelArray
  • [ ] Verificar que Tetris DX muestra correctamente el fondo y los gráficos
  • [ ] Si la pantalla sigue negra, investigar paleta de colores y contenido de VRAM
  • [ ] Considerar añadir más diagnósticos si es necesario (p.ej. verificar que VRAM tiene datos)