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 Discrepancia Entre Logs y Visualización
Resumen
Se implementaron tres verificaciones adicionales para investigar por qué, a pesar de que los logs muestran correspondencia correcta entre el framebuffer y la visualización, las imágenes muestran problemas visuales (rayas verticales, pantalla blanca, checkerboard). Las verificaciones incluyen: (1) verificación del framebuffer completo (todas las líneas, no solo líneas específicas), (2) verificación del escalado y blit a la pantalla, y (3) verificación línea por línea de la visualización después de dibujar los píxeles.
Concepto de Hardware
Framebuffer Completo: El framebuffer del Game Boy contiene 23040 píxeles (160×144). Cada línea tiene 160 píxeles, y todas las líneas deben tener datos para una visualización correcta. Si algunas líneas están vacías (todos los píxeles con índice 0), la visualización mostrará problemas como rayas blancas o áreas vacías.
Escalado y Blit: La superficie original (160×144) se escala a la resolución de pantalla usando interpolación. El escalado puede causar cambios ligeros en los colores debido a la interpolación. El blit copia la superficie escalada a la pantalla. El contenido en pantalla debe coincidir con el framebuffer original, aunque puede haber pequeñas diferencias debido a la interpolación.
Verificación Línea por Línea: Para identificar problemas de visualización, es útil verificar cada línea de la superficie después de dibujar los píxeles y compararla con el framebuffer original. Esto permite identificar líneas donde hay discrepancias entre el framebuffer y la visualización.
Implementación
Se implementaron tres verificaciones principales para investigar la discrepancia entre los logs y la visualización:
1. Verificación del Framebuffer Completo
Se agregó código que verifica todas las líneas del framebuffer (0-143), no solo líneas específicas. La verificación cuenta cuántas líneas tienen datos (no todas blancas), cuántas líneas están vacías, y la distribución de índices en todo el framebuffer. También identifica líneas problemáticas (vacías cuando deberían tener datos).
# --- Step 0347: Verificación del Framebuffer Completo ---
if frame_indices is not None and len(frame_indices) == 23040 and \
self._framebuffer_complete_check_count < 10:
self._framebuffer_complete_check_count += 1
# Contar líneas con datos (no todas blancas)
lines_with_data = 0
lines_empty = 0
total_non_zero_pixels = 0
index_counts = [0, 0, 0, 0]
for y in range(144):
line_start = y * 160
line_non_zero = 0
for x in range(160):
idx = line_start + x
if idx < len(frame_indices):
color_idx = frame_indices[idx] & 0x03
if color_idx < 4:
index_counts[color_idx] += 1
if color_idx != 0:
line_non_zero += 1
total_non_zero_pixels += 1
if line_non_zero > 0:
lines_with_data += 1
else:
lines_empty += 1
logger.info(f"[Renderer-Framebuffer-Complete] Frame {self._framebuffer_complete_check_count} | "
f"Lines with data: {lines_with_data}/144 | Lines empty: {lines_empty}/144 | "
f"Total non-zero pixels: {total_non_zero_pixels}/23040 | "
f"Distribution: 0={index_counts[0]} 1={index_counts[1]} 2={index_counts[2]} 3={index_counts[3]}")
# -------------------------------------------
2. Verificación del Escalado y Blit
Se agregó código que verifica que el escalado y blit funcionan correctamente. La verificación compara algunos píxeles antes y después del escalado, y verifica que el color en la pantalla después del blit coincide con el color escalado.
# --- Step 0347: Verificación del Escalado y Blit ---
if hasattr(self, '_scaled_surface_cache') and self._scaled_surface_cache is not None and \
hasattr(self, 'surface') and self.surface is not None and \
self._scale_blit_check_count < 10:
self._scale_blit_check_count += 1
# Verificar tamaño de la superficie escalada
scaled_size = self._scaled_surface_cache.get_size()
screen_size = self.screen.get_size()
# Verificar algunos píxeles antes y después del escalado
test_pixels = [(0, 0), (80, 72), (159, 143)]
for x, y in test_pixels:
# Color en la superficie original (160x144)
original_color = self.surface.get_at((x, y))
# Calcular posición escalada
scale_x = int(x * screen_size[0] / 160)
scale_y = int(y * screen_size[1] / 144)
# Color en la superficie escalada
scaled_color = self._scaled_surface_cache.get_at((scale_x, scale_y))
# Color en la pantalla después del blit
screen_color = self.screen.get_at((scale_x, scale_y))
# Verificar que los colores son similares (tolerancia para interpolación)
if abs(original_color[0] - scaled_color[0]) > 20 or \
abs(original_color[1] - scaled_color[1]) > 20 or \
abs(original_color[2] - scaled_color[2]) > 20:
logger.warning(f"[Renderer-Scale-Blit] ⚠️ Color escalado muy diferente!")
# -------------------------------------------
3. Verificación Línea por Línea de la Visualización
Se agregó código que verifica cada línea de la superficie después de dibujar los píxeles y la compara con el framebuffer original. La verificación identifica líneas donde hay discrepancias entre el framebuffer y la visualización.
# --- Step 0347: Verificación Línea por Línea de la Visualización ---
if hasattr(self, 'surface') and self.surface is not None and \
frame_indices is not None and len(frame_indices) == 23040 and \
self._line_by_line_check_count < 5:
self._line_by_line_check_count += 1
# Verificar algunas líneas específicas
test_lines = [0, 36, 72, 108, 143] # Distribuidas uniformemente
for y in test_lines:
line_start = y * 160
matches = 0
mismatches = 0
# Verificar primeros 10 píxeles de la línea
for x in range(min(10, 160)):
idx = line_start + x
if idx < len(frame_indices):
# Índice del framebuffer
framebuffer_idx = frame_indices[idx] & 0x03
expected_rgb = palette[framebuffer_idx]
# Color en la superficie
surface_color = self.surface.get_at((x, y))
# Comparar (tolerancia pequeña)
if abs(surface_color[0] - expected_rgb[0]) <= 5 and \
abs(surface_color[1] - expected_rgb[1]) <= 5 and \
abs(surface_color[2] - expected_rgb[2]) <= 5:
matches += 1
else:
mismatches += 1
if mismatches <= 3: # Solo loggear primeros 3 desajustes
logger.warning(f"[Renderer-Line-by-Line] Line {y}, Pixel ({x}, {y}): "
f"Mismatch! Expected={expected_rgb}, Actual={surface_color}")
# -------------------------------------------
Decisiones de Diseño
- Verificación completa del framebuffer: Se verifica todas las líneas del framebuffer, no solo líneas específicas, para identificar líneas problemáticas que podrían causar problemas visuales.
- Verificación del escalado y blit: Se verifica que el escalado y blit funcionan correctamente comparando píxeles antes y después del escalado, y después del blit. Esto ayuda a identificar si el problema está en el escalado o en el blit.
- Verificación línea por línea: Se verifica cada línea de la superficie después de dibujar los píxeles y se compara con el framebuffer original. Esto permite identificar líneas donde hay discrepancias.
- Tolerancia para interpolación: Se usa una tolerancia pequeña (5 para RGB) al comparar colores, ya que la interpolación puede causar cambios ligeros en los colores.
Archivos Afectados
src/gpu/renderer.py- Agregadas tres verificaciones: framebuffer completo, escalado y blit, y verificación línea por línea
Tests y Verificación
Las verificaciones se ejecutarán durante la ejecución normal del emulador. Para analizar los resultados, se recomienda ejecutar pruebas con múltiples ROMs en paralelo:
# Ejecutar las 5 ROMs en paralelo (procesos en segundo plano)
timeout 150 python3 main.py roms/pkmn.gb 2>&1 | tee logs/test_pkmn_step0347.log &
timeout 150 python3 main.py roms/tetris.gb 2>&1 | tee logs/test_tetris_step0347.log &
timeout 150 python3 main.py roms/mario.gbc 2>&1 | tee logs/test_mario_step0347.log &
timeout 150 python3 main.py roms/pkmn-amarillo.gb 2>&1 | tee logs/test_pkmn_amarillo_step0347.log &
timeout 150 python3 main.py roms/Oro.gbc 2>&1 | tee logs/test_oro_step0347.log &
# Esperar a que todos los procesos terminen
wait
echo "Todas las pruebas completadas"
Para analizar los logs generados, se pueden usar los siguientes comandos:
# Verificar framebuffer completo
grep "\[Renderer-Framebuffer-Complete\]" logs/test_*_step0347.log | head -n 50
# Verificar escalado y blit
grep "\[Renderer-Scale-Blit\]" logs/test_*_step0347.log | head -n 50
# Verificar línea por línea
grep "\[Renderer-Line-by-Line\]" logs/test_*_step0347.log | head -n 50
Validación de módulo compilado C++: Las verificaciones confirman que el framebuffer tiene el tamaño correcto (23040 píxeles = 160×144) y que los píxeles se dibujan correctamente en la superficie.
Fuentes Consultadas
- Pygame Documentation: pygame.Surface
- Pygame Documentation: pygame.transform
Integridad Educativa
Lo que Entiendo Ahora
- Framebuffer Completo: El framebuffer del Game Boy contiene 23040 píxeles (160×144). Todas las líneas deben tener datos para una visualización correcta. Si algunas líneas están vacías, la visualización mostrará problemas.
- Escalado y Blit: El escalado puede causar cambios ligeros en los colores debido a la interpolación. El blit copia la superficie escalada a la pantalla. Es importante verificar que el escalado y blit funcionan correctamente.
- Verificación Línea por Línea: Verificar cada línea de la superficie después de dibujar los píxeles y compararla con el framebuffer original permite identificar líneas donde hay discrepancias.
Lo que Falta Confirmar
- Causa de la discrepancia: A pesar de que los logs muestran correspondencia correcta, las imágenes muestran problemas visuales. Las verificaciones implementadas ayudarán a identificar la causa.
- Impacto del escalado: El escalado puede causar artefactos visuales si no se maneja correctamente. Se debe verificar que el escalado no causa problemas.
Hipótesis y Suposiciones
Se asume que el problema podría estar en: (1) solo algunas líneas se verifican, pero otras no, (2) problema en el escalado, (3) problema en el blit, o (4) problema en la lectura completa del framebuffer. Las verificaciones implementadas ayudarán a identificar cuál de estas hipótesis es correcta.
Próximos Pasos
- [ ] Ejecutar pruebas con las 5 ROMs en paralelo (5 emuladores simultáneos, ~2.5 minutos total)
- [ ] Analizar los logs generados para identificar problemas
- [ ] Si se identifica la causa, implementar corrección en el siguiente step
- [ ] Si el problema persiste, realizar análisis más profundo y solución alternativa