Some days ago my psp emulator has 77 subversion commits. Today it has 99.
Now there are a few graphical demos working. A few more than the revision 77, but of course, still less than the version I did a few years ago. Also it doesn’t have a graphical debugger.
But what I can warrantee is that it is much better in terms of code (even if it requires dome refactoring, cleanups and documenting). Furthermore in this time I have learnt a lot of things about D and about programming patterns, so I was able to better implement typical problems.
After this version I’m a bit more calm. I have been a few days working super hard on it. I have done things that would have required a few weeks for a few persons to do. Maybe it is fue to spring that makes people more active, but at any rate, being motivated is something you should take advantage from, and I needed to disconnect from everything for a while.
I will continue with the emulator, but at a slower pace. At least on workdays :P
Regarding to the things I said I was going to fo, I have done almost everything I said.
The most interesting thing I have done (and relatively newer regarding to the last time) has been to implement threads and semaphores.
A few things:
- I have implemented input. Now interactive demos that use button pressing are working already. This time I have implemented button input in a way that I believe is more similar to the reality. The last time it just saved the latest state. Now from time to time I’m producing what is called a “frame” using a ringbuffer. The API allows to read the latest or a set of the latest input frames available.
- Did a few tries using SIMD (SSE). Initially I thought that vertex processing and matrices, especially in the case of GU_SPRITES should be done via cpu. So I created a few structures/clases Matrix and TVector(Type, int Size = 4) -> Vector!(float, 4). From one hand so I could try to create structures created dynamically via D templates, and in the other hand to be able to create specialized classes/structures. Vector!(float, 4) specialization, makes the vector operations to be executed using SSE extensions. I implemented internal and external operations. External: opAdd/opSub/opMul /opDiv(float), Internas:opAdd/opSub/opMul /opDiv(Vector). I implemented Matrix multiplication against Vector in order to make transformations. But in the end it seems that maybe it won’t be necessary the implementation I did, since Im going to use Geometry Shaders to create the two remaining vertices (tesselation). This is something relatively new that I haven’t tried yet, and this is a great opportunity to try it, in addition to have a huge performance boost. At any rate having Vector and Matrix classes implemented is something useful to have. It served to make some tries in the case sometime I make a software-based implementation of the GE. In fact I have a prototype already created.
- Started audio support. Right now it works partially. It generates the audio wave, but doesn’t play right.
There are a lot of pauses and it sounds pretty bad. But at any rate, it is a good initial approach.
At the beginning it didn’t work at all, even if I had implemented threads. Didn’t know why.
Threads were being executed, but the wave wasn’t generated.
After that I saw that there was a problem with the
sceKernelStartThread
implementation. It happened thatsceKernelStartThread
makes the switch to that newly created thread immediately. And making use of that feature, the audio thread received an argument to a volatile parameter that was changed from the caller thread immediately. That parameter was at the stack of the parent thread, and it was even being changed inside a for. Since before another thread switch there are a bunch of instructions that will be executed before, the first thing done is to copy the passed parameter to a local variable. My implementation was not executing that thread immediately, but placed it in the thread queue waiting to be executed by theThreadManager
, that way the thread was executed later and when executed the variable already changed its value. In this case it happend that all the created threads were processing the channel 4, instead of 0, 1, 2, 3 respectively :P For curious people:pspsdk\src\audio\pspaudiolib.c
- Implemented interrupts. I changed how the vblank handling worked, that was a bit dirty. Now it happens in a registered event happening with the VBLANK interruption. That stabilized a lot fps. Before that some frames were doing weird stuff.
- Regarding to OpenGL, the implementation. Now I have implemented texture swizzling and there are a few more opcodes implemented.
For example the
LOP
opcode. I’m not taking too much attention to the GPU just yet. The important part is the CPU that now works more or less fine and the kernel functions, specially the ones using threads and modules that are the ones that are breaking most of the stuff. - I added a
ModuleManager
. Before now HLE modules were being loaded statically, that prevented being well tested from one hand, and on the other hand, new binaries at runtime. Or hacing several executions. - Embedded the
psplibdoc.xml
inside the executable and made it to show information about modules and not implemented NIDs. - Implemented a pretty important part of the file handling module. Using as base the
VirtualFileSystem
I created to map physical folders, allowing to create proxy entries, remapping other things, or in general creating virtual file systems. It is something I already did in the last version, but now I have implemented it much better. (I haven’t still implemented the ISO implementation for the VFS). After that moment, some programs that used files, have started to work. (IncludingSDL
). Though I implemented this before threads, and SDL demos were crashing as hell because theSDL_Init
function was not even finishing. - Created a utility to detect infinite loops in functions that were working for other components. This kind of functions should be avoided favoring callbacks, but since it was easier and we have to increase complexity little by little, right now it does its work. Specially in components that are being executed in different threads that are THE PAIN.
- I added a debug dump key (
F5
) that in addition to showing registers and dumping instructions near the current PC, it shows the threads and active semaphores. - Changed the executable generation system. Before you had to use
.bat
files. I used PHP. This PHP in addition detects dependencies and compiles necessary files without having to specify them manually. This kind of things are being done by other utilities like BUD/REBUILD and DSSS. In the end I will probably use DSSS, but for now I wanted to had a more fine-grained control and an alternative option. - I med a lot of refactorings and simplifications to the codebase. But I have to still improve a lot in this regard. There are things that are screaming for refactorings and cleanups.
- I fixed a lot of things, added a window menu, an icon and a way to hot load new programs.
Some screenshots of the latest versions:
Texturized cube from one of the NeHe tutorials ported to PSP. It uses texture swizzling, and cube is being manually rotated. It reports between 120 and 200 FPS.
http://www.psp-programming.com/code/doku.php?id=c:pspgu-neheport-lesson6
PSPONG Mini-Game.
It works perfectly, but super slow. (5~10 fps)
It must be doing lots of operations via CPU instead of rendering via GPU.
That was expected, since current emulator implementation is interpreted.
JPCSP runs this demo faster, due to its dynamic decompilation and probably because it identify functional blocks and replaces them with native functions (memcpy, memset, etc.).
At this point I don’t care about speed. Once interpreted mode is working fine and I have a solid base of executables tests, I will start exploring the rest of the stuff.
On the other hand real games make use of the GE, where this emulator shines and works better than the ones implemented on Java and C# (and GE is still unoptimized).
SDL Demo I found on psp.scenebeta (http://psp.scenebeta.com/tutorial/tutorial-04-mostrar-un-archivo-bmp-en-pantalla).
This demo required a lot of time to get it working since it used SDL. The first version of the emulator was unable to get it working. Here it started to work after I correctly implemented Threads and Semaphores.
Demo loads a PNG file using SDL and SDL_Image.
Spanish
Hace unos días el emulador iba por la revisión 77 de subversion. Hoy la he dejado en la 99.
El estado actual en la revisión 99 es que funcionan unas cuantas demos gráficas. Unas pocas más que en la revisión 77, pero sin llegar a las de la versión que hice hace varios años. No tiene tampoco debugger gráfico.
Pero puedo afirmar que además de ser muchísimo mejor a nivel de código (aunque hay que refactorizar, limpiar y documentar muchas cosas). Además en este tiempo he aprendido un montón de cosas de D y técnicas de programación, con lo que he podido abarcar mejor muchos problemas típicos.
Con esta revisión, me quedo un poco más tranquilo. He estado unos cuantos días de “frenesí programantil” que llamo yo. He hecho una burrada de cosas en unos días que posiblemente habrían llevado semanas a varias personas. Igual es la primavera que la sangre altera, pero en cualquier caso la motivación hay que aprovecharla. Y necesitaba desconectar de todo un poco.
Seguiré con el emulador, pero ahora con más calma. Al menos entre semana :P
Respecto a las cosas que dije que iba a hacer, he hecho casi todo lo comentado.
Lo más interesante (y relativamente nuevo con respecto a la última vez) que he hecho, ha sido implementar threads y semaphores.
Cosas:
- Actualmente he implementado input. Ahora las demos interactivas que usan como entrada la pulsación de botones de psp ya funcionan. Esta vez he implementado la entrada de botones creo que más acorde a cómo funciona en realidad. La última vez simplemente guardaba el último estado. Ahora de vez en cuando produzco lo que se llama aquí un “frame” del controlador usando un ringbuffer. El api permite leer el último o últimos frame/s de entrada.
- Hice pruebas con SIMD (SSE). Inicialmente pensaba que el procesado de vértices y matrices, especialmente en el caso de los GU_SPRITES, tendría que hacerlo manualmente desde cpu. Así que me hice unas estructuras/clases Matrix y TVector(Type, int Size = 4) -> Vector!(float, 4). Por una parte para probar la creación de estructuras dinámicas mediante templates en D, y en otra par poder probar especialización de esas clases/estructuras. La especialización de Vector!(float, 4), hace que las operaciones vectoriales se hagan mediante SSE. Implementé operaciones internas y externas. Externas: opAdd/opSub/opMul /opDiv(float), Internas:opAdd/opSub/opMul /opDiv(Vector). Implementé la multiplicación de Matrix por Vector para poder hacer transformaciones. El caso es que posiblemente al final no hagan falta en la implementación de opengl que es la que estoy haciendo porque voy a usar Geometry Shaders para la creación de los dos vértices restantes (teselación). Era algo realmente nuevo que no había probado y es una buena ocasión para probar, además de que debería aumentar el rendimiento una barbaridad. En cualquier caso la creación de las clases Matrix y Vector nunca está de mal. Ha servido para hacer pruebas y puede ser por si alguna vez se hace implementación del GE por software (de hecho, ya tengo la clase prototipo ahí creada).
- Empecé el soporte para audio. Ahora mismo funciona parcialmente. Genera la onda, pero no se reproduce bien. Hay muchas pausas y se oye mal, vaya. Pero en cualquier caso es una primera aproximación. Al principio no iba y eso que ya había implementado threads. No entendía por qué. Se ejecutaban los threads, pero no se generaba la onda. Luego vi que era por un problema en la implementación de sceKernelStartThread. Resulta que el sceKernelStartThread hace un switch al thread en cuestión inmediatamente. Y haciendo uso de esa característica se le pasaba al thread de audio de la demo que estaba probando un parámetro volátil. Un parámetro que estaba en la pila del thread padre y que además se estaba cambiando en un for. Pero aprovechando que hay unas cuantas instrucciones de margen, el thread de audio creado, lo primero que hace es copiar el parámetro pasado a una variable local. Mi implementación no ejecutaba el thread inmediatamente sino que lo dejaba en la cola, en espera de ser ejecutado por el ThreadManager, así que el thread se ejecutaba a posteriori y la variable que se le había pasado cambiaba de valor. En este caso resultaba que todos los threads creados se pensaban que estaban procesando el canal 4 en vez del 0, 1, 2, 3 respectivamente :P. Para los curiosos: pspsdk\src\audio\pspaudiolib.c
- Implementé interrupts. Y con ello cambié el handling del vblank, que se hacía a lo chapuza. Ahora puse que se hiciese mediante un evento registrado cuando se produce una interrupción de VBLANK. Con eso se ha estabilizado bastante los fps. Antes en algún frame hacía alguna cosa rara.
- Respecto a opengl, la implementación. Ahora hay swizzling de texturas y hay algún opcode más implementado. LOP por ejemplo. No le estoy dando mucha importancia a la GPU por ahora. Lo importante es la CPU que ya funciona mas o menos bien y las funciones del kernel, especialmente referente a threads y módulos que son las que más cosas rompen.
- Añadí un ModuleManager. Hasta el momento los módulos HLE se cargaban de forma estática, e impedian testearse bien por una parte y por otra cargar nuevos binarios en tiempo de ejecución. O tener varias ejecuciones.
- Embebí en el psplibdoc.xml en el ejecutable e hice que mostrase información sobre los módulos y NIDs no implementados.
- Implementé una parte importante del módulo de gestión de archivos. Usando como base la utilidad VirtualFileSystem que creé que permite mapear directorios físicos, crear entradas proxy, remapear otras, o en general crear sistemas virtuales de archivos. Es algo que ya hice en la anterior versión, pero ahora lo he implementado bastante mejor. (Todavía no he portado la implementación de ISO para el VFS.). A partir de ese momento, algunos programas que hacían uso de archivos, han empezado a funcionar. (SDL inclusive). Aunque esto lo implementé antes que los threads, y las demos de SDL cascaban como un demonio porque no llegaba ni a terminar el SDL_Init.
- Creé una utilidad para detectar bucles infinitos en functiones que esperan a otros componentes. En principio este tipo de funciones se deben evitar en pro de callbacks. Pero como es más sencillo y hay que complicarlo poco a poco, por ahora hace su función. Especialmente en componentes que se ejecutan en threads diferentes que son THE PAIN.
- Hice que el dump de debug (F5) además de mostrar registros y dump de las instrucciones colindantes al PC actual, mostrase los threads y los semaphores activos.
- Cambié el sistema de generación de ejecutables. Antes eran .bats a pelo. Lo hice en PHP. El PHP además detecta las dependencias y compila los archivos necesarios sin tener que especificar manualmente. Este tipo de cosas ya la hacen algunas utilidades como BUD/REBUILD y DSSS. Posiblemente acabe usando DSSS, pero por ahora quería tener un control más fino y tener “alternativas”.
- Hice bastantes refactorizaciones y simplificaciones del código. Pero todavía hay MUCHO que hacer al respecto. Hay cosas que piden una refactorización y limpieza a gritos.
- Arreglé muchas más cosas, añadí un menú a la ventana, un iconcito y sobretodo añadí la posibilidad de cargar en caliente nuevos programas.
Algunos screenshots de las últimas versiones:
Cubo texturizado de uno de los tutoriales de NeHe portado a PSP. Usa swizzling de texturas y el cubo se rota manualmente. Saca unos FPS entre 120 y 200.
http://www.psp-programming.com/code/doku.php?id=c:pspgu-neheport-lesson6
Mini juego PSPONG.
Funciona perfectamente, pero muy lento. (5~10 fps)
Debe hacer un montón de operaciones en la cpu en vez de hacer rendering de gpu.
La implementación actual del emulador es interpretada, con lo que no es de extrañar.
En jpcsp esta demo funciona mas rápida, por la recompilación dinámica y posiblemente por la identificación de bloques funcionales y reemplazo por funciones nativas (memcpy, memset, etc.).
A mí por ahora la velocidad no me preocupa. Cuando esté funcionando bien en modo intérprete y tenga una base sólida de tests autoejecutables, ya me aventuraré a lo demás.
Por otra parte los juegos de verdad suelen hacer uso del GE, donde en general este emulador funciona mejor que los implementados en java y C#. (Y todavía está sin optimizar el GE).
Demo SDL que encontré en psp.scenebeta (http://psp.scenebeta.com/tutorial/tutorial-04-mostrar-un-archivo-bmp-en-pantalla).
Esta demo costó bastante en hacerla funcionar porque usa SDL. En la primera versión del emulador no funciona directamente. Aquí empezó a funcionar cuando implementé correctamente threads y sempahores.
La demo carga un archivo PNG usando SDL y SDL_Image.