Graphics and event management for a retro-gaming project

I am currently working in a project that I have written out in OCaml, and I need to do some graphics. This project is a small emulator for the space invaders arcade machine (following emulator101.com).

Current state of affairs

The emulator is almost working now, but one thing is left though: I need to be able to interract with the game, as of right now, the game will launch and the cut scenes will play, but I haven’t been able write the interaction part. Up to this point, I was using the Graphics library to manage printing and that was working OK, it was very convenient to use for a specific reason: I am using a ROM dump of the game which, during the emulation encodes the entire screen to display as a bit-map as a location in the memory. Glossing a bit over the details, what I was doing until now was to read this bit map and convert it into a variable bitmap : color array array, and then simply call draw_image (make_image bitmap) 0 0 every time I wanted to refresh my screen.

One minor problem with this solution is that apparently the function make_image is a bit slow, and while profiling the app, I found out that 20% of the computing time is dedicated to it.

Adding the controls

I now want to add the controls, but I am running into a bigger problem here: the events management of the Graphics library is extremely limited, and in particular there is no way of telling if two keys are pressed at the same time, and there is no way to poll for key releases. That’s problematic, firstly for the space invaders machine because I don’t want the ship to need to stop for shooting, and also because I made all project modular enough to hopefully accomodate other machines working on the same processor fairly easily, and in which being able to detect simultaneous key presses may be more crucial. So after some googling I found two ways of interacting with SDL using OCaml: The OcamlSdl library and the Tsdl library. So I decided to try and leverage all the power of the SDL library, to get finer events managements and maybe also solve the efficiency problem I was having. For some reason the OcamlSdl library was causing trouble at the compilation, so I decided to settle for the Tsdl.

Messing around with SDL

Ok, so now I am back at trying to get the graphics done, I should now say that I have no previous experience with SDL, so I had to spend quite some time trying to understand how things work, and this is what I now understand (I am stating these facts here because I am not so confident about it, so a confirmation of this would be most welcome): There are two versions of SDL: SDL1.2 and SDL2. As far as I understand in SDL1.2, one use to define and print Surfaces, which are basically a huge block of pixels, with their color, this way of doing things is now considered deprecated, since SDL2. In SDL2, the canonical way of doing things is to use textures, as they allow to manipulate the graphical objects separately and move all the computations over to the GPU instead of relying on the CPU to compute on surfaces. So it is my understanding that given the nature of my project, I really should use the old way, even though it is deprecated, as I am emulating a program that does compute a bitmap at 60 hertz using the CPU. I have absolutely no interest in manipulating textures for this project, as nothing will ever change on the CPU.

Printing a bitmap using Surfaces with the Tsdl library

Here is where I am getting confused now, using the Tsdl library, I want to use the function Sdl.create_rgb_surface_from which seems to me to correspond to the make_image of the Graphics library. The issue is that once I call this function (and pattern-match to handle the error case separately), I get an object new_surface:Sdl.surface, which I don’t know how to set to my window. I know that I can also get the surface associated to my window by calling Sdl.get_surface(window). It seems that in standard SDL (in C) these two functions give pointers (of type SDL_Surface * ), so if I was in C, I would just have to do SDL_GetSurface(window) = new_surface, but of course that’s not how OCaml works.

So maybe there is a very easy trick that I am not seeing to account for the fact that the OCaml types really are pointers, but this all looks very confusing to me. I can see that the surface type in OCaml behaves like a pointer, as I can call Sdl.fill_rect surface rectangle color which will fill a given rectangle of a given color in the surface. I feel like I am missing a function that takes a surface and sets in to another surface. It is my understanding that I could use Sdl.blit_surface for this purpose, but that would mean unnecessarily copying the content of the first surface into the second instead of simply modifying the value of a pointer.

Questions

I have two questions related to what I have said:

  • Am I using the right technology for this project? I am very happy to have written the CPU emulator in OCaml, I think that I could save a significant amount of work with the powerful design choices it offers and I am very satisfied on how it turned out. So I really want to stick with OCaml, but I have no clue if the libraries I am using for graphics and events are well suited to my purposes.
  • Assuming that Tsdl is a reasonnable choice to perform what I do, how do I do the equivalent of draw_image (make_image bitmap) 0 0 of the Graphics library?

What if you try something like let img = make_image bitmap in ... outside of your loop and then simply call draw_image img 0 0 when you want to refresh your screen ?

So I really want to stick with OCaml, but I have no clue if the libraries I am using for graphics and events are well suited to my purposes.

Yes tsdl is fine for what you want to do.

Assuming that Tsdl is a reasonnable choice to perform what I do, how do I do the equivalent of draw_image (make_image bitmap) 0 0 of the Graphics library?

Here’s some pseudo-ocaml-code which should help you.

With a renderer and textures :

let w = create_window ...
let renderer = create_renderer w ...
let surface = load_bmp "file.bmp" ...
let texture = create_texture_from_surface surface renderer ...
let () = render_copy renderer texture ...
let () = render_present renderer ...

With surface only :

let w = create_window ...
let surface_w = get_window_surface w ...
let surface = load_bmp "file.bmp" ...
let () = blit_surface ~src:surface ~dst:surface_w ...

The real code would be much more verbose as you will have to check the result of almost each tsdl function but that’s quite straightforward.

EDIT: Ah, I missed the following part encodes the entire screen to display as a bit-map as a location in the memory, so my first advice won’t help at all.

Thanks a lot for your reply it is very helpful

Ah I see, so either I blit one surface onto the other, or I encapsulate the whole thing into a renderer and print it. What’s the difference, is there one of the two solutions that is better that the other?

As a side note, even thought that wasn’t my original question, I am not sure how to build the surface from an int array array as I just noticed that the Sdl.create_rgb_surface_from takes as parameter an ('a, 'b) bigarray which seems to be defined in the Ctype library, but I have just started looking it up. The load_bmp that you use in your example doesn’t do the job for me, since I have an the data stored in an array and not in a bmp file (but I think it wasn’t part of your answer)

EDIT: I just found out that bigarrays were a thing from OCaml, and not Ctypes.

What’s the difference, is there one of the two solutions that is better that the other?

It’s generally better to use a renderer and textures, see this stackoverflow answer.

I am not sure how to build the surface from an int array array as I just noticed that the Sdl.create_rgb_surface_from takes as parameter an ('a, 'b) bigarray which seems to be defined in the Ctype library, but I have just started looking it up.

Then you would just use a bigarray instead of a vanilla array, or convert it before passing it to create_rgb_surface_from. Also bigarray is not from ctypes but from the stdlib, see its interface.

Hi, nice project!
my advice is also that SDL2 is a good choice. I suggest using streaming textures, and using SDL_LockTexture each time you want to update your screen: http://wiki.libsdl.org/SDL_LockTexture. It will give you a bigarray where you can dump your data

Thanks a lot! I got the project to work!

I tried both modifying the texture with SDL_LockTexture and creating a texture from a surface, both options worked great, but I opted for the first one since avoiding lets me avoid creating and freeing surfaces.

The code is now running fine, I am pretty happy with the result. I uploaded it on github (https://github.com/thibautbenjamin/OcaEmu), if you are interested in what it looks like. I need to seriously reorganize the project and add the readme to specify the usage, but I am planning to do that shortly.

For now though, I still find that it runs extremely slowly, and I am not completely sure why. Upon some investigation with a profiler, I found that probably the way I am handling the events is in cause (85% of the computation time is dedicated to this). For now, inside the game loop at a frequency of 2MHz (the frequency of the processor), I run the loop

while (Sdl.poll_event(event)) do ... done

in order to completely clear the event queue. Somewhere in the process, it seems that there is the function SDL_WaitEventTimeout_REAL which causes a huge bottleneck in the performances of my program.

I understand that maybe I am polling for events way too often, but I thought it was essentially free. Do you know if I should decrease the frequency of events polling or if I should change the function I use for polling (or maybe both). I could see polling at a frequency of 120 Hz, as the machine sends an interruption to the processor at this frequency.

essentially free

I wouldn’t assume polling for an event is free. Ideally it would be, but I could also imagine it’s implemented in a way given your particular hardware and OS that it selects a driver that involves a system call or context switch. Modern CPUs are fast but I don’t think they’re able to handle the OS waking up at 2 million times a second fast.

did you try https://wiki.libsdl.org/SDL_FlushEvent ?

Thanks for the pointer, I don’t think that was was I am looking for, I need to handle the events that have happened and not just throw them away.

But I managed to solve the speed problem in a way that I found satisfying: instead of polling for the events on a regular cycle, I poll for the events whenever the emulated CPU executes the IN command that reads one of its entry pins. I think this is way more robust, as no matter what the ROM code is, I will always get the right events, but at the same time I don’t keep polling 2 million times a second.

I could probably poll at a much slower frequency, like maybe 120, and I suspect that it wouldn’t make that much of a difference since I believe the code I execute only calls the IN command whenever an interrupt is fired, which happens at 120Hz. But that relies on the ROM being a certain way, and might fail if it is another way. Maybe the frequency is high enough that even if the code asks for inputs more often, a human user wouldn’t see the difference, but I would rather emulate the real behaviour of the system.