It has been two months since the last time I wrote about my psp emulator, and I have duplicated the number of commits: r34 -> r77 since then.
It has been three pretty intense sessions split in several days.
People like me, that are working on several projects, tend to do these kind of projects in “sessions”.
In my case I usually spend a few days from time to time.
And it is somehow logical… Programming requires concentration. If you are coding something and you want to do it well, try to do “a bit” to a lot of projects all the days is unpractical.
Some people also wants to stop doing this. Stop working on all those projects for once and to be happy. I have been almost a year without working on some projects (not the PSP emulator) and I started to be a bit happier.
At any rate, sometimes it looks like this is the right moment for that, but it is just a mirage. I have to wait for that a bit longer.
And while waiting, I’m going to continue with those projects that fills a bit the void feeling from inside, but that also makes me learn a lot of things and to gain experience in several areas.
From time to time it is good to think about different things; in order to see the progress and to be aware of things learnt and improved. It is something that I have been doing for a long time and sometimes I write about it.
Things that I have been doing:
- I have added an standalone assembler to assemble external files without the need of recompiling. (The standalone compiler uses the same class that the emulator itself, so any improvement is included in both).
- I added support to insert bytes, halfs and words, in both decimal and hexadecimal.
- Using template mixins I replaced some utilities to some functional blocks to the end of the files so I can keep the important part of the code at the beginning.
- I changed several times the directory stricture (slightly). Trying to find a balance between folders and folders inside folders. No extreme is good even when some people defend any of them (C projects in a single project, or Java projects with zillions of folders / packages).
- I added dmd/setup.bat and utilities (7z.exe and httpget.exe). wget was too big for my simple needs. Windows has an API called WinHTTP that worked for me, and I created a small 4KB exe (compiled using TCC [c 1=”fuente” 2=”en” 3=”el” 4=”repository” language=”ódigo”][/c]) to download files. setup.bat downloads and installs in the dmd folder all the project dependencies. In a way that any person can compile the projects from svn without needing to manually download tons of utilities. It is enough to have TortoiseSVN, download the repo and launch setup.bat. Current dependencies: DMD, DFL, PSPSDK.
- At this point I haven’t executed any program. The loader was missing, and there wasn’t a GUI either. I started to prepare a basic GUI to execute programs specifying with a CLI argument the program to execute. A simple window with a 480x272 viewport with a OpenGL component (GLControlDisplay) that is in charge of doing the glDrawPixels with the current screen format obtaining the format and producing vBlank events using an IDisplay object. All this being executed in its own thread. Initially there was 3 threads: CPI thread, DLF thread with the window event processing, and a display thread. Later I added another thread for the GraphicEngine. In addition of being more logical to keep it in different threads, allows people with two or more processors to be able to better use their processors.
- I did an ELF loader and started to emulate the GE
- Fixed a few CPU instructions and started to do HLE emulation with the system modules
- I noticed that everything was slower than the first version. It was enough to enable optimizations in the compiler and I got the same performance as the first version again.
- I fixed more CPU instruction and added regression tests. Detect CPU emulation problems is really nasty. Fortunately there are other emulators (including the one I did) to compare results and do some testing. (I still keep my PSP, I will probably bring it back from the drawer soon).
- Started with the pending FPU instruction emulation and continued with the GE that was not working yet.
- Linked to the project the PSPSDK and created a folder with C programs. After that point, when the unittesting is launch, those programs were compiled to later verify that they were executed correctly. Enabled a few special syscalls allowing to send results to later be compared with the results specified in an .expected file. This was a great success, since it allows to try more complicated algorithms and generated by GCC that would be much more difficult to be tested from an assembler. And testing those manually, is not a very good idea.
- With all this new testing, I was able to detect a problem that occurred with printf* %f functions with the new version of newlib. It was a bad implemented instruction (SLIU) that treateed the immediate as unsigned, even whet it should be treated as signed.
- I made the GE to use opengl via off-rendering (CreateDIBSection & PFD_DRAW_TO_BITMAP)
- I started to implement kernel module functions and some things from the GE.
- GE was failing badly since it had a wrong implementation. I rewrote most of it. Before I was considering just a single line of extension. I made it to support a queue of DisplayLists (the correct behaviour).
- I separated all the dependencies from opengl and the GE and extracted it to a separate implementation. (To be able to make separate implementations: OpenGL, Software, DirectX…)
- When I wanted to implement textures on the GE, I noticed that the GE didn’t load the extended OpenGL functions. After tons of tries and a lot of time wasted, I noticed that it was because of PFD_DRAW_TO_BITMAP, that apparently was using a shabby OpenGL version without hardware support so it could have everything on the system’s memory. And this shabby OpenGL version only implemented the OpenGL basic 1.0 or 1.1 specification. So I checked other ways of doing offscreen rendering using OpenGL. In the end I had to create an invisible window, and to use PFD_DRAW_TO_WINDOW. Surprisingly I found the solution because I thought that that could be the solution and I googled wglCreateContext SW_HIDE. I had several performance problems after doing that, due to lots of glReadBuffer, but I was able to fix that without too much trouble.
- Original kernel module functions were had the same prototype. void func() and parameters were onbtained by using param(0). It was pretty cumbersome to do it like that, and documenting is pretty dirtly like that. D is pretty powerful, and I have seen in other places that it was possible to wrap functions and to obtain types at compilation time. Using templates, CTFE and mixings… I was able to allow to use the original prototype of the function. For further information: pspemu.hle.Module.
- Implemented a memory manager in the loader and the module pspemu.hle.kd.sysmem.
- I created a virtual file system API and implemented basic kernel functions.
Next steps:
- Implement input (buttons) (right now there is no user interaction)
- Implement threads and callbacks
- Implement the sound basic API (to try the right behaviour of threads and callbacks)
- More unittesting
- Code refactoring
- Make the sample programs to start working step by step
- Create test programs (testable using the new syscall emits) to try the right behaviour of stuff
- Create a nice GUI like the original version
Initially I got the minifire working. It struggled a bit, but I got it working in the end:
Even if I tried it after other stuff, probably it started to work almost after the minifire sample. It is a demo that paints directly on the video memory without using GE.
The lines sample. At the beginning lines were moving strangely (due to cpu bugs), and they were drawn even worse because GE was bad implemented (no queues for DisplayLists).
The typical sample of ortonormal projection (ortho). It was the next one I got working after the lines one. Colors and a simple geometry, modifying video memory via pspDebugScreenPrintf. With that sample I noticed that with later PSPSDK versions, there were some strange results with printf(“%f”). It took me a few days and a lot of madness to identify the problem. It was a bad implemented opcode: STLIU. That bug happened also in the first version of the emulador. I fixed in both.
Having the CPU with less bugs, I continued with the implementation. Now it was time of the textures. Right now there is no swizzled implemented (and lots of missing things), but it is a first step.
Almost after implementing textures, some more demos started to work almost magically:
The curted demo (sprites). No emulator (as far as I know) makes it to work as in the PSP. It is supposed that a GU_SPRITE primitive without transform2D, should be drawn orthogonally (supposedly).
This behaviour is not directly supported via OpenGL, so you have to do some “stuff”.
Maybe the key is to use this extension (when available):
- http://www.opengl.org/registry/#arbextspecs
- http://www.opengl.org/registry/specs/ARB/point_sprite.txt
- http://www.opengl.org/registry/specs/ARB/point_parameters.txt
- http://www.opengl.org/sdk/docs/man/xhtml/glPointParameter.xml
- http://www.informit.com/articles/article.aspx?p=770639&seqNum=7
- http://cirl.missouri.edu/gpu/glsl_lessons/glsl_geometry_shader/index.html
Lights sample (without lighting implemented). Ligths sample already have indices into account and this is the last thing I have implemented at this point.
Spanish
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):
- http://www.opengl.org/registry/#arbextspecs
- http://www.opengl.org/registry/specs/ARB/point_sprite.txt
- http://www.opengl.org/registry/specs/ARB/point_parameters.txt
- http://www.opengl.org/sdk/docs/man/xhtml/glPointParameter.xml
- http://www.informit.com/articles/article.aspx?p=770639&seqNum=7
- http://cirl.missouri.edu/gpu/glsl_lessons/glsl_geometry_shader/index.html
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.