Porting the Chip8 emulator to the web
Running the emulation in the application’s main thread was as simple as moving the
step() emulation function and the OpenGL rendering code into a single
run_frame() function. The only difference is that now the game speed is dependent on the FPS the emulator is running at. However, since the Chip8 was never a real physical console, it doesn’t have a defined speed to be run at and most games will require different speed settings anyway.
Assuming a frame rate of 60fps, running at 10 instructions per frame (or a little higher) works well for most Chip-8 games tested, but Connect4 needs to run at only 1 instruction per frame to be playable. Super-Chip games generally need to be run a bit faster - somewhere in the 20-60 range seems to work well.
Now that the emulator runs in a single thread, we can tell Emscripten what it should run as its main loop:
WebGL is based on OpenGL ES, which supports approximately a subset of the features of full OpenGL, so some slight modifications were needed to the OpenGL API calls and shaders.
In newer versions of OpenGL, the
varying keywords have been replaced by
out, with the choice of replacement depending on whether we’re working on a vertex or fragment shader. To make our shaders work with WebGL, we’ll have to downgrade them to use the older syntax. Additionally, WebGL requires that we specify a precision that we want to work with in the fragment shader.
out-> gl_FragColor builtin
Old vertex shader
WebGL-compatible vertex shader
Old fragment shader
WebGL-compatible fragment shader
WebGL runs on a different set of version numbers to OpenGL, so I’ve just removed them entirely to make life easier.
Aside from some unnecessary OpenGL calls that could just be removed, only
glTexImage2D had compatibility issues. This function tells the graphics card how to interpret the block of memory representing the Chip8’s display output.
The Chip8 can only display two colours - black and white - so doesn’t need to use the more standard
GL_RGBA formats. In OpenGL the pair
GL_LUMINANCE works to use a single byte as a greyscale colour output, but this doesn’t work in WebGL. After some experimentation with different format combinations, the pair
GL_LUMINANCE was found to work in WebGL.
Unfortunately there doesn’t seem to be a format which is compatible between OpenGL and WebGL, so we’ll leave it up to the C++ preprocessor to pick which to use:
Starting with the default HTML file Emscripten generates, we need to make a few changes so that our emulator’s main function gets called with the arguments it needs.
First off, we’ll say that we don’t want the emulator to start as soon as the page is loaded by adding the following line in the definition of Emscripten’s
This code uses Emscripten’s file system API to save the chosen ROM into an in-memory filesystem, before calling the emulator’s main function with the ROM’s file path. This is necessary since the emulator expects to load ROMs from disk.
Controlling game speed
In order to make all games playable, we need to provide a way of letting the user pick what speed they should run at.
Try it out!