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.
Integración del Core C++ en el Frontend
Resumen
Se completó la integración del núcleo C++ (CPU, MMU, PPU, Registros) en el frontend Python, reemplazando los componentes lentos de Python con las versiones nativas compiladas. El sistema ahora puede ejecutar código máquina directamente, alcanzando velocidades de miles de FPS potenciales. El renderer se adaptó para usar el framebuffer de C++ mediante Zero-Copy (memoryview), eliminando el cálculo de tiles en Python y permitiendo un blit directo desde el framebuffer nativo a Pygame.
Concepto de Hardware
En un Game Boy real, todos los componentes (CPU, MMU, PPU) están implementados en hardware y se comunican directamente mediante buses de memoria y señales de control. No hay "overhead" de interpretación: cada instrucción se ejecuta en ciclos de reloj precisos.
En un emulador, tradicionalmente se implementa todo en un lenguaje de alto nivel (Python, JavaScript, etc.) que es fácil de entender pero lento porque cada operación pasa por múltiples capas de abstracción. La solución es migrar el "bucle crítico" (el código que se ejecuta millones de veces por segundo) a un lenguaje compilado (C++) que se ejecuta directamente en la CPU sin overhead.
Zero-Copy Integration: El framebuffer de la PPU C++ se expone como un memoryview de NumPy, que es una vista directa a la memoria C++ sin copias. Pygame puede leer directamente desde esta memoria, eliminando el paso intermedio de calcular tiles en Python. Esto es crítico para el rendimiento: en lugar de decodificar 23,040 píxeles en Python cada frame, simplemente hacemos un blit del framebuffer ya renderizado en C++.
Implementación
Se modificó src/viboy.py para detectar y usar el módulo viboy_core
cuando está disponible. El sistema mantiene compatibilidad hacia atrás: si el core C++ no está
compilado, usa los componentes Python como fallback.
Componentes creados/modificados
- src/viboy.py: Integración híbrida que detecta y usa componentes C++ o Python según disponibilidad
- src/gpu/renderer.py: Adaptado para usar framebuffer C++ mediante Zero-Copy cuando está disponible
- tests/test_integration_cpp.py: Test de integración completo que valida el sistema con core C++
Decisiones de diseño
Compatibilidad hacia atrás: El sistema detecta si viboy_core está disponible
y usa los componentes C++ si es posible, pero mantiene el código Python como fallback. Esto permite
que el proyecto funcione incluso si la compilación falla o no se ha ejecutado.
Zero-Copy Framebuffer: El framebuffer de C++ se expone como memoryview de NumPy,
que es compatible con Pygame mediante pygame.surfarray. Esto elimina copias innecesarias
y permite transferir 23,040 píxeles (160x144) directamente desde C++ a la GPU sin pasar por Python.
Timer y Joypad aún en Python: Por ahora, Timer y Joypad siguen en Python porque no son cuellos de botella críticos. El Timer se actualiza cada instrucción, pero su lógica es simple. El Joypad solo se lee cuando hay eventos de teclado. Estos componentes se pueden migrar más adelante si es necesario.
Archivos Afectados
src/viboy.py- Integración híbrida C++/Python con detección automáticasrc/gpu/renderer.py- Adaptado para usar framebuffer C++ con Zero-Copytests/test_integration_cpp.py- Test de integración completo (7 tests)
Tests y Verificación
Se creó un test de integración completo que valida todos los aspectos del sistema con core C++:
- Test de inicialización: Verifica que Viboy se inicializa correctamente con componentes C++
- Test de carga de ROM: Valida que la ROM se carga correctamente en MMU C++
- Test de ejecución de CPU: Ejecuta 1000 instrucciones sin errores
- Test de sincronización PPU: Valida que PPU se sincroniza correctamente con CPU
- Test de acceso a framebuffer: Verifica que el framebuffer es accesible y tiene el tamaño correcto
- Test de acceso a registros: Valida lectura/escritura de registros C++
- Test de ciclo completo: Ejecuta 100 ciclos completos (CPU + PPU) sin errores
Resultado: Todos los tests pasan (7/7) en 25.06 segundos.
$ python -m pytest tests/test_integration_cpp.py -v
============================= test session starts =============================
platform win32 -- Python 3.13.5, pytest-9.0.2, pluggy-1.6.0
collecting ... collected 7 items
tests/test_integration_cpp.py::TestIntegrationCPP::test_viboy_initialization_with_cpp_core PASSED
tests/test_integration_cpp.py::TestIntegrationCPP::test_load_rom_into_cpp_mmu PASSED
tests/test_integration_cpp.py::TestIntegrationCPP::test_execute_cpu_instructions PASSED
tests/test_integration_cpp.py::TestIntegrationCPP::test_ppu_synchronization PASSED
tests/test_integration_cpp.py::TestIntegrationCPP::test_framebuffer_access PASSED
tests/test_integration_cpp.py::TestIntegrationCPP::test_registers_access PASSED
tests/test_integration_cpp.py::TestIntegrationCPP::test_full_cycle_execution PASSED
============================= 7 passed in 25.06s ==============================
Validación de módulo compilado C++: El test verifica explícitamente que los componentes son instancias de las clases Cython (PyCPU, PyMMU, PyPPU, PyRegisters) y no de las clases Python.
Fuentes Consultadas
- Pan Docs: Game Boy Pan Docs - Referencia técnica general
- Cython Documentation: Cython Documentation - Memoryviews y Zero-Copy
- NumPy Documentation: NumPy Documentation - Memoryviews y arrays
Nota: La integración Zero-Copy se basa en conocimiento de Cython y NumPy, no en código de otros emuladores. La arquitectura híbrida es una decisión de diseño propia del proyecto.
Integridad Educativa
Lo que Entiendo Ahora
- Zero-Copy Integration: Los memoryviews de NumPy permiten que Python acceda directamente a la memoria C++ sin copias. Esto es crítico para el rendimiento cuando se transfieren grandes cantidades de datos (como un framebuffer de 23,040 píxeles) cada frame.
- Compatibilidad Híbrida: Es posible mantener ambos sistemas (Python y C++) en el mismo código, detectando automáticamente cuál usar. Esto permite desarrollo incremental y debugging más fácil.
- Rendimiento Real: El core C++ puede ejecutar miles de instrucciones por segundo sin problemas. El cuello de botella ahora está en el renderizado y la sincronización de FPS, no en la emulación misma.
Lo que Falta Confirmar
- Rendimiento en producción: Necesitamos medir FPS reales con una ROM completa para confirmar que alcanzamos 60 FPS estables. Los tests unitarios no miden rendimiento gráfico.
- Sprites en C++: Los sprites aún se renderizan en Python. Necesitamos migrar el renderizado de sprites a C++ para completar el motor gráfico (Paso 114).
- Timer C++: El Timer sigue en Python. Aunque no es crítico, podría beneficiarse de la migración para mantener consistencia.
Hipótesis y Suposiciones
Suposición: El framebuffer ARGB32 de C++ se convierte correctamente a RGBA para Pygame. La conversión usa operaciones vectorizadas de NumPy, que deberían ser rápidas, pero no hemos medido el overhead real de esta conversión.
Suposición: El sistema funciona correctamente sin Timer C++ porque el Timer solo se actualiza cada instrucción y su lógica es simple. Si hay problemas de precisión de RNG en juegos como Tetris, necesitaremos migrar el Timer.
Próximos Pasos
- [ ] Paso 114: Implementar renderizado de Sprites en C++
- [ ] Medir rendimiento real con ROMs completas (FPS, uso de CPU)
- [ ] Optimizar conversión ARGB32 → RGBA si es necesario
- [ ] Considerar migración de Timer a C++ si hay problemas de precisión
- [ ] Implementar Joypad C++ si es necesario para completar la migración