As a challenge, I am trying to write source code for a large number of 3.5” floppy disks. As of a few days ago, I completed my first floppy! It is an emulator for the CHIP-8 gaming computer. You can find the sources here. The game is also listed on the Floppy Challenge website if you just want the binaries.
Although hardware implementations have been made, the CHIP-8 was never meant to be a physical computer. Instead, it was one of the first virtual machines implemented to improve the ease of developing cross-platform computer games. At this it has succeeded, with the CHIP-8 becoming the go-to platform to implement as an aspiring emulation developer (one look at the r/emudev subreddit should prove enough).
I figured it would be good to write down some of my own thoughts on developing a (albeit very simple) CHIP-8 emulator. Do with it what you will, I hope at least it will help some people when they’re stuck in development 😄. Please do not use this as your only source though! I was helped by a collection of kind bloggers: this, this and this should help you along your travels.
Let’s start with the thing that messed with me the most: timing. Timing is something you have to deal with often when developing videogames. As computers are inherently discrete iterative machines (they transition from one binary state to another, in contrast to our continuous and analogue reality), we need to configure a variety of smoke and mirrors to make it seem like time is running continuously.
The CHIP-8 has the following timing requirements:
The key thing to note is how the processor is updated much more frequently than the timers and screen. A cool trick you can do (if you do not care about exact approximations) is evaluating 10 op-codes for every update to the timers and screen. This only adds a single line to your code, and your emulator should play games very close to their original behaviour.
If you’ve been programming for a while, you probably have heard of Test Driven Development (TDD) before. With TDD, your programming workflow goes something like so:
test
for this method: a simple function which evaluates the proper behaviour of the method.test suite
and notice how the test fails.In my experience, TDD is often viewed as the most boring way you could program. Most programmers are impatient, especially when they have a good idea of what they need to program and just “want to get to it already”. Like most things you’d rather not do, it benefits you in the long run to do them anyway. When it comes to developing the CHIP-8 emulator, it was especially beneficial to first develop a collection of tests that verify the expected behaviour of your op-code parser. You might find your expectations to be wrong (which is a bug in its own right), but it will really help you to hunt down the inevitable collection of bugs you will discover.
Directly manipulating pixels on the screen used to be common. At some point you could have a graphics card (GPU) installed, but this was optional. For a while you wouldn’t even use the graphics card for all draw operations, as it was faster to do it in software. These days things are quite the opposite.
On modern computers we draw graphics using an abstraction layer and remote graphics devices, where you push textures to the GPU and request these textures to be drawn within the confines of your window (something specific to your operating system). This has enabled developers to do amazing things. Especially 3D graphics greatly benefit from the speed gained when using a device specifically optimised for efficiently processing and rendering polygon data (as a neat example of device rendering, see how Quake II is modified to support hardware ray-tracing).
As with most advances in technology, we also lose some benefits by using this dedicated hardware. The steps to software rendering vary wildly depending on the language and platform you are developing for (JavaScript + HTML5 is the simplest in my experience), but most modern applications now require the following steps:
window
, this is often OS specific.render context
for this window, this often depends on your graphics API.frame buffer
for this window using the render context.frame buffer
.device
.render context
to show the changes.The fifth step is the tricky part. The GPU is best utilised when textures (e.g. local frame buffers) are pushed rarely, which is the exact thing we need to push often (about 60 pushes a second when it regards to CHIP-8). So make sure you determine the most efficient manner to push pixels on your platform.