clownmdemu – frontend v0.5

Yesterday was this blog’s third birthday, and what better way to celebrate than with a big blog post about one of my biggest projects!

I was hoping to release this update after I finished adding support for the Mega CD, but progress on that has ground to a halt. Instead, the focus of this update will be the frontend! From features to refactoring to an entire port, here’s what’s new in clownmdemu!

Frame Advance

Frame Advance is a fairly mundane feature for typical users but a very handy tool for developers: it allows for advancing the game by only a single frame, which can be useful for examining a bug or verifying that animations occur correctly. It is activated by pressing the fast-forward hotkey while the emulator is paused, and can be combined with the rewind key to advance to the previous frame instead of the next.

At first, I figured that this would be an awkward feature to add, for code-flow reasons. However, the opposite turned out to be true: it couldn’t have been more convenient to add!

Debug Logging Window

Previously, the frontend would output debug information to the terminal (known to you Windows users as the command prompt), but when emulation goes catastrophically wrong, so much debug information is output to the terminal that it causes the entire emulator to slow to a crawl and becomes unresponsive. To avoid this, debug information is now output to a Dear ImGui window. By default, logging is disabled, minimising the performance impact of the emulator issuing debug information in the background. Even when enabled, however, performance is still far better than before, with instances where the window is flooded with debug information still resulting in a responsive emulator. If, for whatever reason, information absolutely must be output to the console, then an option is provided to reenable it.

There is one downside to this: the logging window is very RAM hungry. However, to avoid crashes, the logging window will detect when RAM has ran out and clear the log, freeing much of the RAM and allowing the emulator to continue running normally.

Disassembler

In a Mega Drive cartridge or Mega CD disc, code is stored as machine code, which is essentially just a bunch of hexadecimal numbers.

This is Sonic 1’s boot-code in machine code form.

This is very hard for a person to read, so the more practical way of viewing code is in assembly form. The process of converting machine code into assembly is called ‘disassembly’. My emulator’s frontend is now capable of automatically performing disassembly, allowing the code of the emulated game to be viewed with ease!

This is Sonic 1’s boot-code in assembly form.

The 68000 disassembler is actually an off-shoot of my CPU interpreter library – clown68000. This is because, long ago, I made an effort to split the interpreter into two parts: the interpreter itself, and a kind of database library that describes the functionality of the 68000. The idea behind this was to minimise the work that would have to be redundantly repeated every time the interpreter was rewritten, or a new 68000-related tool was made. This plan ended up paying off, as the database library was not only used to automatically generate part of the interpreter, but now components of it are used by the disassembler. In particular, the instruction and operand decoding were made trivial by the library, allowing a quick-and-dirty prototype to be made in just one day. Being closely related to each other, the two now share a Git repository.

While this will surely be useful for the developers of ROM-hacks and homebrew, I’m hoping to use this for reverse-engineering games that don’t work correctly in my emulator. In particular, I’m hoping that this will be the key to getting Sonic CD working.

Frontend Refactor

Originally, the frontend was written in ANSI C, but it became C++98 when Dear ImGui was integrated, as that was the language that it (and, more importantly, its API) were written in. Later, Dear ImGui switched to C++11 and the frontend switched with it. Throughout all of this, however, the frontend never made much use of C++’s features, remaining very C-style. This has finally changed, as now the frontend has been converted to proper C++: ‘NULL’ has been replaced with ‘nullptr’, references are used instead of pointers, casts are now C++-style, and many components has been split off into their own classes. The migration to modern C++ features should improve the frontend’s code hygiene, such as through the more-explicit nature of C++-style casting.

The frontend has also been refactored to split much of the code from ‘main.cpp’ to separate files. ‘main.cpp’ was getting excessively large, which was making it hard to navigate, so I began splitting whatever I could to other files. This was done long ago with the debug menus, but poor encapsulation made them a bit of a mess. In this new wave, the audio stream was split to its own self-contained class, as was the window subsystem, the emulator wrapper, and the file dialogs.

68000 Interpreter Refactor

It is possible for an emulator to be excessively accurate: an emulator could recreate the exact behaviour of a console on the transistor level, but the game running on the emulator would behave exactly the same as it would on an emulator that recreates the console’s behaviour on a logic-gate level. The only difference is that one emulator is far more performance-intensive than the other. There comes a point where additional accuracy does not benefit emulation, as the external behaviour is already perfectly accurate, even if the internal behaviour is not.

This same issue applies to CPU emulation. You may have heard the term ‘cycle-accurate’ before; this refers to the accuracy of the emulator’s timing: on a given cycle (a fixed time-step that things like CPUs operate on), a cycle-accurate emulator’s state (RAM, CPU registers, etc.) will be the exact same as the original hardware on the same cycle. Neither of the CPU emulators in my Mega Drive emulator are cycle-accurate: the closest one is my Z80 emulator, which is accurate only during the cycles on which an instruction ends. This is because instructions are executed over the course of multiple cycles on a real Z80, while my emulator completes every instruction instantly in a single cycle, and simply does nothing for the remaining cycles. This may sound like an unimportant technical detail, but this does affect when certain operations (such as accesses to the VDP’s ports) occur. This creates a difference in behaviour that could cause the emulated game to behave differently. This is imperfect emulation.

To correct this, I could rework my CPU emulators to be ‘truly’ cycle-accurate. That is, on each cycle, the emulator does the exact same tasks as a real CPU. However, like described earlier, this would be overkill: many of the tasks done per cycle are strictly internal to the CPU, and are completely invisible to anything outside of it. Since a game exists outside of the CPU running it, this means that these internal behaviours are completely irrelevant to a game running exactly the same as it does on a real console. One example of such an invisible task is adding two numbers together using the CPU’s registers. In contrast, a task that would not be invisible is sending the result of that addition to RAM, since the RAM exists outside of the CPU.

Since cycle-accuracy is too accurate, and being accurate only on the cycles that instructions end is not accurate enough, I have settled on the perfect in-between: being accurate only on the cycles that a bus event occurs on. A bus event is a transfer of data between the CPU and the rest of the system, making it the fundamental external task of a CPU. Hopefully you can tell where I am going with this: by making bus event behaviour accurate, I make all external behaviour accurate, and therefore make the behaviour of software running on the CPU emulator 100% identical to a real console (at least when putting aside other emulated components of the console that can influence code execution such as the bus and VDP).

With that massive preamble out of the way, I have been refactoring the 68000 emulator in preparation for making it accurate on a bus-event level. This involved splitting the instructions into multiple steps, so that they can be completed over the course of multiple bus events instead of all at once as they did before. Each step is made into a unique function, then pointers to these functions are grouped into lists, one per instruction, and then, when an instruction needs to be ran, it is done by calling each function in the instruction’s list, with bus events occurring between each function call.

In hindsight, by doing all of this, I have implemented microcode into my 68000 emulator. While the emulator is still not yet accurate on a bus event level, all of the preparatory refactoring is now out of the way, leaving me with far less work to do to finally make the switch.

Improved High-DPI Support

On Windows, the emulator’s frontend already enjoys high-DPI support, making its fonts as crisp as possible! I assumed that the code for this would work on other platforms too, but I heard from a Mac user that it was horribly broken!

Without access to a Mac, and no understanding of what could possibly be going wrong, I was unable to fix this. Around the same time, I noticed that the DPI scaling was overkill on Linux, causing the font to be far larger than it needed to be. With all of these issues in mind, I disabled high-DPI on any platform that wasn’t Windows. This left Mac and Linux with blurry fonts (and blurry rendering in general), which I wasn’t happy with.

High-DPI is one of the SDL2 library’s shortcomings: while its support for the feature on Linux and Mac is good, Windows support was entirely missing for the longest time. More recently, it gained some support for Windows, but it had to be activated in a different manner to the other platforms and came with a bunch of caveats and edge-cases, defeating the whole point of SDL2 being a platform abstraction library.

I wanted to try again to implement high-DPI support on Linux, so I enabled the dormant original high-DPI code to see what would go wrong. When the emulator window appeared, I was surprised to see that it exhibited the exact same bugs as on macOS. As it turns out, on Linux (with Wayland, at least), SDL2 uses the same high-DPI mechanism as it does on macOS. This meant that, if I could fix high-DPI on Linux, then high-DPI on macOS would be fixed too!

At first, I tried supporting high-DPI the same way that Dear ImGui’s SDL2 example does, but it was terrible: all it does is force SDL2 to convert the rendering coordinates from pixel coordinates to ‘device coordinates’, to match the mouse movement events and window size, which are also measured in device coordinates. Device coordinates remain the same across DPIs, whereas pixel coordinates get smaller and smaller, which is why using device coordinates universally is one way to support high-DPI. The problem with making Dear ImGui render using device coordinates manifests when using a DPI that is not a multiple of 100%, such as 150%: in this situation, the font and other native-resolution graphics will not be aligned with the screen’s pixels as they are rendered, causing them to appear blurry, and also aliasing is introduced to the clip rectangle logic, causing letters to occasionally be cut off at an edge. The end result is extremely ugly rendering.

Left: Good Rendering – Right: Garbage Rendering

Appalled by this, I instead tried the opposite approach: rather than make the coordinates consistent by converting the renderer to device coordinates, I converted the mouse movement events and window size to pixel coordinates. This did result in awkward aliasing in mouse positions because SDL2 brilliantly only exposes mouse coordinates as integers instead of floating-point numbers, but otherwise this works perfectly. This does create more work for me though, as all of the calculations for rendering the frontend must be upscaled in accordance with the DPI because a higher DPI means more pixels. However, all of the work for this was already done when high-DPI support was added for the Windows port, since everything was already measured in pixel coordinates on that platform. So, in the end, all I did was make Linux and macOS behave like Windows, and everything magically worked! Gee, if only there was a library called SDL2 that did that… that would be great…

Web Browser Port

One of the main features of my emulator is its portability: the core emulator is written in portable ANSI C, the frontend is written in C++11 and only dependant on cross-platform libraries such as SDL2 and Dear ImGui, and the build system is CMake. In addition, while the build script supports linking system libraries, it is able to build these libraries manually if they are absent. All of this combined made the emulator almost immediately compatible with Emscripten!

For those that do not know, Emscripten is a toolchain that allows C and C++ to be compiled to either JavaScript or WebAssembly, instead of the usual CPU-specific assembly. JavaScript and WebAssembly can be embedded into a website, allowing it to be ran in a web browser. Emscripten also exposes various APIs to C and C++, allowing code to interact with the web browser to do things like render graphics and receive keyboard and mouse input. SDL2, which serves as a platform abstraction layer, supports Emscripten’s APIs, granting software that is built upon it compatibility with Emscripten.

The ‘main’ Function

There is, however, one incompatibility between typical C/C++ code and Emscripten: the ‘main’ function. Normally, the ‘main’ function only returns when the program closes. For a constantly-running program like an emulator, this means that there needs to be an infinite loop inside the function. But, with Emscripten, infinite loops are not allowed. Because of this, software that targets Emscripten must instead use the function to register a callback, which Emscripten will then run on the function’s behalf a specified number of times per second. This callback can be used to iterate the program in the same way that the function’s infinite loop would have.

Because of this incompatibility, I did need to refactor my emulator’s frontend slightly. It resulted in some nice decoupling, however: now, the emulator frontend and the ‘main’ function are entirely separate, with the frontend occupying its own source file and exposing an interface that allows for initialising, updating, and deinitialising the frontend. The ‘main’ function is the sole occupant of the ‘main.cpp’ source file, calling the frontend’s iteration function in an infinite loop in standard builds, and registering it as a callback in the Emscripten build.

File IO

Another one of SDL2’s shortcomings as a platform abstraction library is that it does not provide any way to create a file dialog to allow the user to select a file for the program to load or save. Because of this, my emulator’s frontend manually implements these file dialogs for each platform, using the Win32 API for Windows, and Zenity and kdialog on Linux and the BSDs. Since Emscripten is compatible with none of these, it requires its own platform-specific code.

I was able to find a handy MIT-licensed single-header-file library that implements Emscripten file dialogs, however, its API was much higher-level than what the frontend was designed for. The reason for this is that, due to running in a web browser, software suffers from strict limitations, with one such limitation being that files on the user’s computer cannot be directly accessed. Instead, software must ask the browser to access the file on its behalf.

Because of this, the library does not simply return the path of the file that the user selected: instead, for loading a file, the library makes the browser read the whole file into a memory buffer and then returns a pointer to it, and, for saving a file, the library takes a pointer to a memory buffer and makes the browser write its contents to the file.

While, previously, the tasks of reading and writing data to and from a file were the responsibility of the frontend, they now had to become the responsibility of a library. To achieve this, file IO had to be abstracted to operate purely on sending and receiving buffers instead of file paths. This abstraction layer would be implemented as a C++ class – the ‘FileUtilities’ class – which acts as a wrapper around the ’emscripten-browser-file’ library in Emscripten builds, and standard file IO in non-Emscripten builds.

The Result

With all of this work complete, my browser is able to load and run games within the confines of a web browser!

What’s great about this is that I can link to it directly from this blog’s Projects page, allowing anyone that is interested in the emulator to try it out! It would be nice to do this with some of my other software too, such as ClownMapEd. With some work, I could even make this auto-load my various ROM-hacks, allowing them to be tried-out in the browser too!

If you’re interested in trying this port out, you can find it at clownmdemu.clownacy.com.

Vastly-Improved Audio Playback

I have never been happy with the frontend’s audio delivery system: due to emulation being synchronous, the frontend is not simply able to render audio on command, instead needing to wait for audio to be generated by the emulator before it is able to pass it on to the operating system to be played through the user’s speakers. In order for there to not be gaps in the audio, audio must be generated as fast as it is played. However, the audio-generation process is extremely complicated (involving generating the audio output of two different sound chips – FM and PSG – at two entirely different sample rates – 53kHz and 224kHz – and then resampling them both to a common sample rate – typically 48kHz – and then mixing them together), meaning that audio is never exactly the correct size. Because of this, it’s possible for audio to be generated slightly faster than it is played. When this happens, a gradual build-up of audio occurs, making the audio more and more delayed. For the longest time, this was worked-around by detecting when too much audio was being generated and simply throwing away some of the excess. This kept the audio from ever exceeding a certain amount and becoming latent, but also caused the audio to audibly ‘skip’, producing irritating popping and crackling sounds.

This problem came to a head when adding support for 50Hz (PAL emulation) to the Emscripten port: the popping was so annoying that I simply had to do something about it. I decided that I would make the frontend resample (stretch or squash) its audio in accordance with how much audio build-up there was. This way, there would be no skips in the audio, making playback as smooth as could be. I spent the next few days experimenting with different methods of making the audio playback speed self-adjusting. It was a demoralising few days, as everything I tried failed in one way or another: either the self-adjustment would be too weak and it would fail to respond to fluctuations in framerate, causing audio skips or latency, or it would be too aggressive, causing the audio to constantly warble as the pitch was greatly increased, only to be greatly decreased to prevent too much audio being generated, only to be greatly increased again to prevent too little audio being generated, and so on and so forth.

On the verge of giving up, I remembered that somewhere in RetroArch’s documentation was a paper that described a method of ‘dynamic rate control’ that was just what I needed. I found it, implemented its formula, and was happy to hear it work perfectly! The frontend now adjusts the speed of its audio to always maintain a certain amount of generated audio at all times, preventing both skipping and latency. One of the RetroArch formula’s features is that the speed adjustment is kept to a minimum, preventing the change in pitch from being audible. At last, the frontend’s audio system is something that I can be proud of!

Unicode File Path Support on Windows

To end things off, here is a feature that I implemented just now! The frontend no longer has any problem with accessing files and folders whose names contain non-ANSI characters. This means that if you have a file whose name is written in Japanese in a folder whose name contains emojis, then the frontend will be able to load it! Previously this would just fail, as the internal way of encoding strings was incompatible.

Unfortunately, the font system does not support the entirety of Unicode, so many characters will appear as question marks when viewed in the frontend itself, like in the ‘Recent Software’ list.

Closing

So here it is – one big update! How typical of me to want to implement Mega CD support, only to end up doing a whole bunch of other things instead. Hopefully these new additions will be enough to tide people over until then!

Source code: https://github.com/Clownacy/clownmdemu-frontend
Windows executable: https://github.com/Clownacy/clownmdemu-frontend/releases/tag/v0.5
Try it in your web browser: clownmdemu.clownacy.com

Leave a comment

Design a site like this with WordPress.com
Get started