Desde la última vez que escribí sobre el emulador de psp han pasado más de dos meses y he commiteado más de la mitad de las revisiones actuales: r34 -> r77.

Han sido 3 sesiones de varios días muy intensas.

La gente como yo, que estamos enzarzados en varios proyectos, solemos abarcar este tipo de proyectos en “sesiones”.

En mi caso suelo invertir unos cuantos días cada tiempo indeterminado.

Y no es casualidad… La programación requiere concentración. Si estás programando algo y quieres hacerlo bien, intentar hacer “algo” de muchos proyectos todos los días es altamente inviable.

La gente como yo, también busca acabar con esto. Dejar de lado todos estos proyectos de una vez por todas y ser feliz. He estado casi un año sin tocar ciertos proyectos (no me refiero al emulador de psp) y empezaba a ser feliz.

De todas formas, hay veces que parece que ha llegado el momento, pero es una mera ilusión. Todavía hay que esperar un poco más.

Y mientras haya que esperar, yo seguiré con estos proyectos que llenan un poco del vacío de mi interior, que me hacen aprender un montón de cosas y que me hacen ganar experiencia en diferentes ámbitos.

Cada cierto tiempo viene bien reflexionar sobre diferentes aspectos; para ver el progreso que se ha hecho y para ser consciente de las cosas que se han aprendido y mejorado. Es algo que llevo mucho tiempo haciendo y que de vez en cuando expreso.

Cosas que he ido haciendo:

  • Añadí un ensamblador standalone para ensamblar archivos externos sin necesidad de recompilar. (El standalone utiliza la misma clase que el emulador en sí, así que cualquier mejora se aplica a ambos).
  • Metí soporte para poder insertar bytes, halfs y words, diría que en decimal o en hexadecimal.
  • Usando template mixins recoloqué ciertas utilidades para ciertos bloques funcionales al final de los archivos para tener la parte importante al principio.
  • Cambié varias veces la estructura de directorios (ligeramente). Hay que buscar el equilibrio entre carpetas y archivos en carpeta. Ningún extremo es bueno pese a que haya gente que defienda cualquiera de ellos (proyectos en C con una sola carpeta, proyectos en Java con mil millones de carpetas).
  • Añadí dmd/setup.bat y utilidades  (7z.exe y httpget.exe). El wget era demasiado pesado para lo que necesitaba. Windows tiene un api llamada WinHTTP que me servía. Hice un pequeño exe de 4KB (compilado con TCC [c 1=”fuente” 2=”en” 3=”el” 4=”repositorio” language=”ódigo”][/c]) para bajar archivos. El setup.bat se baja e instala en la carpeta dmd, todas las dependencias del proyecto. De tal forma que cualquier persona, sin necesidad de instalar manualmente mil utilidades, puede compilar el proyecto del svn. Basta con tener el TortoiseSVN, bajarse el repositorio y lanzar setup.bat. Dependencias actuales: DMD, DFL, PSPSDK.
  • Hasta el momento no había llegado a ejecutar ningún programa. Faltaba el loader, y no había GUI. Empecé a preparar una GUI básica para ejecutar programas especificando como parámetro el programa a ejecutar. Una mera ventana con un viewport de 480x272 con un componente de OpenGL (GLControlDisplay) que se encarga de hacer un glDrawPixels con el formato de pantalla actual obteniendo el formato y produciendo eventos de vBlank usando un objeto IDisplay. Todo esto ejecutándose en su propio thread. Inicialmente habían 3 threads: thread de la CPU, thread de DLF con el procesado de eventos de windows, thread de display. Luego añadí otro thread para el GraphicEngine. Además de ser mas lógico tenerlo en diferentes threads, hace que las personas con 2 o más procesadores tengan más margen a la hora de ejecutar.
  • Hice un loader de ELF y empecé la emulación del GE
  • Corregí algunas instrucciones de la CPU y empecé con la emulación HLE ya con los módulos del sistema
  • Me di cuenta de que todo iba mucho más lento que en la primera versión. Bastó con activar el optimizador para la salida y ya tenía otra vez el rendimiento de la primera versión.
  • Fui corrigiendo más instrucciones de CPU y añadiendo tests de regresión. Detectar problemas en la emulación de la CPU es realmente horrible. Aunque por suerte hay otros emuladores (incluyendo el que hice) para comprobar resultados y hacer pruebas. (La PSP la tengo guardada, me tocará sacarla próximamente).
  • Empecé a implementar instrucciones restantes de la FPU y seguí con el GE que no estaba ni funcionando todavía.
  • Vinculé al proyecto el PSPSDK y creé una carpeta de programas en C. A partir de ese momento, cuando se lanzaba el unittesting, se compilaban dichos programas, y se verificaba que se ejecutasen correctamente. Habilité una serie de syscalls especiales que permitían enviar resultados para luego compararlos con los resultados especificados en un archivo .expected. Esto fue un gran acierto, ya que pude probar algoritmos más complejos y generados por GCC que habrían sido imposibles de probar con ensamblador. Y probarlos a base de ejecutar (testing manual) no es buena idea.
  • Con este testing nuevo, pude detectar un problema que ocurría con las funciones printf* %f con las versiones nuevas de newlib. Era una instrucción mal implementada (SLIU) que trataba el inmediato como unsigned, debiéndolo tratar como signed.
  • Hice que el GE usase opengl mediante off rendering (CreateDIBSection & PFD_DRAW_TO_BITMAP)
  • Fui implementando funciones de los módulos del kernel y cosas del GE.
  • El GE fallaba por todos los lados porque estaba mal implementado. Rehice gran parte. Antes consideraba una línea de ejecución sin más. Y hice que soportase una cola de DisplayLists (como debe de ser).
  • Separé todas las dependencias de opengl del GE y las saqué a una implementación. (Para poder hacer una implementación OpenGl, Software, DirectX…)
  • Cuando fui a implementar las texturas en el GE vi que el GE no había cargado las funciones extendidas de OpenGL. Tras montones de pruebas y mucho tiempo perdido, me di cuenta de que era por el PFD_DRAW_TO_BITMAP, que aparentemente usaba una versión cutre de opengl sin soporte por hardware para tenerlo todo siempre en la memoria del sistema. Y esa versión cutre de opengl tenía solo la especificación base 1.1 o 1.0. Así que estuve buscando otras formas de renderizado offscreen con OpenGL. Al final tocó crear una ventana invisible. Y usar PFD_DRAW_TO_WINDOW. Sorprendentemente la solución la encontré imaginándome que se haría así y buscando en google wglCreateContext SW_HIDE. Tuve problemas de rendimiento en cuanto hice eso, por numerosos glReadBuffer. Lo arreglé sin mayores contratiempos.
  • Las funciones de los módulos del kernel originalmente eran funciones con un mismo prototipo. void func(). y los parámetros se obtenían mediante param(0) y así. Es bastante engorroso trabajar así, y para documentar queda feo. D es muy potente, y ya había visto en otros sitios que se podían wrappear funciones y obtener información de la misma en tiempo de compilación. Con templates, CTFE, mixins… Conseguí hacer que se pudiese utilizar el prototipo original de la función. Para más detalles pspemu.hle.Module.
  • Implementé un gestor de reserva de memoria usado en el loader y en el módulo pspemu.hle.kd.sysmem.
  • Creé un api de sistema virtual de archivos e implementé las funciones de archivos básicas del kernel.

Siguientes pasos:

  • Implementar el input (botones) (ahora mismo no hay interacción con el usuario)
  • Implementar threads y callbacks
  • Implementar el API básica de sonido (para probar el correcto funcionamiento de threads y callbacks)
  • Más unittesting
  • Refactorización de código
  • Ir haciendo funcionar poco a poco los diferentes programas de ejemplo
  • Crear programas de prueba (testeable con emits) para probar el correcto funcionamiento de diferentes aspectos
  • Crear una GUI en condiciones como la de la primera versión

Primero vino el minifire. Se resistió un poco, pero acabó saliendo:

Aunque lo probé después de otras cosas, posiblemente empezase a funcionar poco después de minifire. Es una demo que pinta directamente sobre la memoria de vídeo sin usar el GE.

El ejemplo de líneas. Al principio las líneas se movían raro (por bugs en la cpu), y se pintaba todavía peor. Porque el GE estaba mal implementado (sin colas para las DisplayLists).

El famoso ejemplo de proyección ortonormal (ortho). Es el siguiente que hice funcionar después de las líneas.

Colores y una geometría simple, con una memoria de vídeo modificada ya por el pspDebugScreenPrintf.

Aquí fue cuando me di cuenta que con versiones posteriores del PSPSDK, salían valores raros con los printf(“%f”). Me llevó varios días y mucha locura identificar el problema. Era un opcode mal implementado: STLIU. El bug estaba también en la primera versión del emulador. Lo corregí en ambos.

Ya con la CPU con menos bugs, seguí con la implementación. Ahora le tocaba a las texturas. Por ahora no hay swizzling, ni un montón de cosas, pero es un primer paso.

Prácticamente después de implementar las texturas, empezaron a funcionar más demos por arte de magia:

La demo maldita (sprites). Ningún emulador del que tenga constancia, hace que funcione como en la PSP. Se supone que una primitiva de tipo GU_SPRITES sin el transform2D, debe pintarse ortogonalmente. (Se supone).

El comportamiento no está soportado por OpenGL directamente, así que hay que hacer “cosas”.

Quizá la clave esté en usar esta extensión (cuando esté disponible):

Ejemplo de lights (sin las luces implementadas). El ejemplo de lights ya hace uso de los índices para los vértices y es lo último implementado.