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.
Implementación de Ventana y Fix de Instrumentación
Resumen
Este paso implementa dos mejoras críticas: (1) Movimiento de los monitores de diagnóstico (VBLANK-ENTRY, RESET-WATCH, POLLING-WATCH) al inicio de CPU::step() para evitar que el early return de interrupciones los oculte, y (2) Implementación completa de la lógica de renderizado de la Ventana (Window) en PPU::render_scanline() considerando los registros WY (0xFF4A) y WX (0xFF4B). La ventana se renderiza correctamente encima del Background pero debajo de los Sprites, respetando el bit 5 de LCDC (Window Enable) y usando el mismo sistema de direccionamiento de tiles que el Background según el bit 4 de LCDC.
Concepto de Hardware
Window (Ventana): La Game Boy tiene una capa de renderizado llamada "Window" que es una superficie opaca que se dibuja encima del Background pero debajo de los Sprites. A diferencia del Background, la Window no tiene scroll: siempre comienza desde el tile (0,0) del tilemap seleccionado. La posición de la Window se controla mediante dos registros:
- WY (0xFF4A): Coordenada Y de inicio de la Window. La Window solo se dibuja cuando LY >= WY.
- WX (0xFF4B): Coordenada X de inicio de la Window con un offset de 7 píxeles. WX=7 significa que la Window comienza en X=0 de la pantalla. La Window solo se dibuja si WX <= 166.
LCDC Bit 5 (Window Enable): Controla si la Window está habilitada. Si este bit está desactivado, la Window no se renderiza, independientemente de los valores de WY y WX.
LCDC Bit 6 (Window Tilemap): Selecciona qué tilemap usar para la Window (0x9800 o 0x9C00), independiente del tilemap del Background.
LCDC Bit 4 (Tile Data Addressing): Controla el direccionamiento de tiles tanto para Background como para Window. Bit 4=1 usa direccionamiento unsigned (0x8000, tile IDs 0-255), Bit 4=0 usa direccionamiento signed (0x8800, tile IDs -128 a 127).
Orden de Renderizado: Background → Window → Sprites. La Window sobrescribe el Background en las áreas donde se dibuja.
Fuente: Pan Docs - "Window", "LCDC Register", "Tile Data Addressing"
Implementación
Se realizaron tres cambios principales:
1. Movimiento de Monitores de Diagnóstico
Los monitores VBLANK-ENTRY, RESET-WATCH y POLLING-WATCH estaban ubicados después del switch de instrucciones, lo que causaba que no se ejecutaran cuando había interrupciones que provocaban early return. Se movieron al inicio de CPU::step(), justo después de capturar el PC original pero antes de handle_interrupts().
Cambio crítico: El PC original ahora se captura al inicio de step() y se reutiliza en todo el método, asegurando que los monitores se ejecuten incluso cuando hay interrupciones.
2. Implementación Completa de render_window()
Se completó la función render_window() en PPU.cpp con la lógica completa de renderizado:
- Verificación de LCDC bit 5 (Window Enable) y bit 7 (LCD Enable)
- Validación de condiciones WY <= LY y WX <= 166
- Selección de tilemap según LCDC bit 6
- Uso del mismo sistema de direccionamiento de tiles que Background (LCDC bit 4)
- Renderizado píxel por píxel desde window_x_start hasta SCREEN_WIDTH
- Aplicación de paleta BGP a los píxeles de la Window
3. Integración en render_scanline()
Se agregó la llamada a render_window() en render_scanline() después del renderizado del Background pero antes de los Sprites, respetando el orden correcto de capas.
Decisiones de diseño
- Reutilización de original_pc: Se captura una vez al inicio y se reutiliza en todo step() para evitar inconsistencias.
- Consistencia de direccionamiento: Tanto Background como Window usan la misma lógica de direccionamiento de tiles según LCDC bit 4, garantizando coherencia.
- Validación temprana: Se verifican todas las condiciones (LCD Enable, Window Enable, WY, WX) antes de iniciar el renderizado para optimizar rendimiento.
Archivos Afectados
src/core/cpp/CPU.cpp- Movimiento de monitores de diagnóstico al inicio de step()src/core/cpp/PPU.cpp- Implementación completa de render_window() e integración en render_scanline()
Tests y Verificación
La implementación se validó mediante:
- Compilación exitosa: El módulo C++ se compiló sin errores usando
python setup.py build_ext --inplace - Validación de módulo compilado C++: El módulo viboy_core se generó correctamente como .pyd
- Verificación de lógica: Revisión manual del código para asegurar que:
- Los monitores se ejecutan antes del early return de interrupciones
- La Window se renderiza solo cuando LCDC bit 5 está activo
- El direccionamiento de tiles es consistente entre Background y Window
- La Window respeta las condiciones WY <= LY y WX <= 166
// Ejemplo de verificación de condiciones en render_window()
if ((lcdc & 0x80) == 0 || (lcdc & 0x20) == 0) {
return; // LCD desactivado o Window deshabilitada
}
if (ly_ < wy || wx > 166) {
return; // Condiciones de posición no cumplidas
}
Fuentes Consultadas
- Pan Docs: "Window" - Descripción del sistema de ventana
- Pan Docs: "LCDC Register" - Bits de control del LCD
- Pan Docs: "Tile Data Addressing" - Sistema de direccionamiento de tiles
- Pan Docs: "Background and Window" - Orden de renderizado de capas
Integridad Educativa
Lo que Entiendo Ahora
- Window vs Background: La Window es una capa independiente sin scroll que siempre comienza desde (0,0) del tilemap. El Background tiene scroll mediante SCX/SCY.
- Offset de WX: WX tiene un offset de 7 píxeles porque el hardware de la Game Boy tiene esta peculiaridad. WX=7 significa posición X=0 en pantalla.
- Direccionamiento compartido: Tanto Background como Window comparten el mismo sistema de direccionamiento de tiles (LCDC bit 4), pero pueden usar tilemaps diferentes (LCDC bits 3 y 6).
- Monitores de diagnóstico: Para que los monitores capturen eventos críticos (como entrada a V-Blank), deben ejecutarse antes de cualquier early return, especialmente antes de handle_interrupts().
Lo que Falta Confirmar
- Comportamiento con WX < 7: Necesita validación en hardware real si la Window se renderiza parcialmente cuando WX < 7.
- Rendimiento: Verificar que el renderizado de la Window no afecte significativamente el rendimiento a 60 FPS.
Hipótesis y Suposiciones
Se asume que cuando WX < 7, la Window simplemente no se renderiza (window_x_start se ajusta a 0). Esto necesita validación con hardware real o ROMs de test.
Próximos Pasos
- [ ] Validar renderizado de Window con ROMs de test que usen ventanas
- [ ] Verificar que los monitores de diagnóstico capturan correctamente eventos de interrupción
- [ ] Optimizar render_window() si es necesario para mantener 60 FPS
- [ ] Implementar tests unitarios específicos para renderizado de Window