Porting the PCE emulator to the browser

Screenshot of MacPaint showing the 'woodblock' demo image

The Internet Archive recently added the original Macintosh to the list of classic computers of which they provide emulation, so you can run their archive of software titles in your browser, without installing anything. This is great because it provides the same level of accessibility and convenience to emulation as you'd expect of playing a media file or viewing a document.

When you start up the emulated computer on these pages of the Internet Archive, you're running the PCE emulator, originally a piece of software intended to run natively on desktop operating systems, which has been adapted and recompiled to run in your web browser. As I did the initial work of porting this emulator to the browser (back in 2013), I thought it would be worthwhile to provide a run-down of the tools and hacks which made this possible.

First, I had to get the emulator's C codebase to compile to ASM.js-compatible Javascript using Emscripten. This involved adjusting the project's GNU Autotools-based build system to use Emscripten's emcc compiler executable instead of gcc. Emscripten's wrapper for Autotools' configure, called emconfigure, does most of the work here. Emscripten also handles the mapping of native APIs to browser equivalents, so SDL rendering calls become Canvas API calls, browser input events become SDL events, etc.

Once the code compiled successfully and was able to start up in the browser without crashing, the next issue to deal with was 'yielding' to the browser event loop. In modern operating systems, native programs can run as one unbroken thread of execution. The program can rely on the operating system to manage the program's usage of the CPU, interrupting it periodically so that other programs can do some work. The program doesn't need to know when this will happen or do anything special to enable it. We call this 'preemptive multitasking'. However, Javascript code running in a web browser can't just run indefinitely, it must regularly yield control back to the browser so that I/O can be performed (updating the screen, triggering mouse and keyboard event handlers, etc). So I had to break the control flow of the emulator code up, so that it could perform a 'chunk' of work emulating the Macintosh CPU and other hardware, and then allow the browser to do it's thing before the next chunk of work. You could draw a comparison between this and a 'cooperative multitasking' operating system, such as Classic Mac OS itself.

The way I modified the existing code to achieve this sort of chunked execution was pretty blunt, but it worked. The emulator initializes normally, and then instead of running the emulated system in an infinite loop, it registers a callback function with the Emscripten runtime which, when called, will run a bunch clock cycles of the emulated computer's CPU. By 'a bunch', I mean a few thousand. Emscripten calls this callback many times a second. Ideally we could yield back to the browser after every single cycle of the CPU, so that we could collect the latest inputs from the mouse and keyboard, and update the screen if necessary. However, there is some fixed overhead each time we enter a task of Javascript work from the event loop that we have enqueued via a browser API call such as setTimeout or requestAnimationFrame, which means that to achieve reasonable performance of the emulator we need to run many emulated CPU cycles for each yield. How many cycles to run could be dynamically adjusted, much like a game timestep, but to keep things simple I just hard-coded and hand-tuned this, and found that ~10000 CPU cycles per yield gives a decent balance of speed and I/O responsiveness for the emulator.

Finally, there was the issue of 'mouse pointer integration', a feature provided by OS virtualization apps like VirtualBox. At this point, moving your mouse around the browser window resulted in the relative movement of the mouse being provided to Mac OS as emulated hardware mouse inputs. Mac OS then moves the mouse cursor by the same amount on the emulator's screen, but the resulting mouse position was not necessarily the same as your OS' real mouse pointer. I felt I could do better, so I added a neat/evil hack to actually update the emulated Mac OS mouse position to match your real mouse cursor's position on the screen. I realised that in classic Mac OS, the mouse position is stored in a few fixed absolute locations in the computer's memory, called 'low memory globals'. Basically, I directly write the mouse position value into the emulated computer's memory. Gross, right? But it works great, as you can see by drawing some stuff in Kid Pix. The mouse responds perfectly. You can read more about low memory globals in this folklore.org story.

I'm really glad Classic Mac emulation made its way onto archive.org, because I think everybody should have the opportunity to experience computing history, and the original Macintosh is an essential piece of that history.

If you're wondering about my rationale for porting emulators to the browser, have a read of my previous post on the subject.