Hello again, it's been a while! I'm sure many of you wondered what we've been up to since there's been somewhat of a radio silence around Mikage. Suffice to say the project is alive and kicking, with hundreds of hours spent on development since last progress report, and clearly too few on marketing :)
So what have we been up to? We achieved lots of user-visible progress such as fixed rendering issues and new functional games, and more importantly we sorted out a number of critical technical challenges under the hood.
Let's get to the gist most of you will care about: What content did we manage to get running in Mikage? Among various titles reaching title screens or at least booting up, two games - Nano Assault EX and Cave Story 3D - make it past the openings and are functionally playable now! We've released gameplay videos for both of these, so make sure to give them a watch if you haven't already.
There wasn't any one individual, big fix that made this possible, but it's rather various big groundwork items being finished (such as the Render Target Cache revamp mentioned below) plus the accumulation of many small iterations and bug fixes, such as for instance:
- Implementing more configurations of the GPU's pixel pipeline, e.g. alpha blend modes or depth testing functions
- Implementing texture combiner features such as the texture combiner buffer
- Supporting more FS features such as reading the game icon via the SelfNCCH archive or copying file handles ("link files")
- Adding 3DS configuration blocks for parental controls and EULA version
In fact, bringing up new games is straightforward in Mikage: Start the game, check the logs to see what functionality is used, and implement what's not yet emulated. The typical hour-long debugging sessions other emulator developers go through is largely unnecessary, partly because the 3DS platform is rather well understood by now, but I also like to attribute this success to good design in Mikage's emulation core: Instead of blindly accepting "weird" states of emulation, it'll loudly complain as soon as anything smells fishy. While sometimes a bit tedious, this prevents small inaccuracies from trickling through layers over layers of abstraction and causing unretraceable butterfly effects, and instead makes nailing down critical issues straightforward. This approach easily saved tons of hours of debugging work and allows us to focus on improving the emulator instead of fixing old, unresolved bugs!
There has been progress on other titles too, notably Super Mario 3D Land. We're now reaching the title screen with the characters rendering just fine. The landscape still has some issues that I need to look at - my suspicion is that missing stencil support is the culprit. In fact, back in August I did have the landscape geometry rendered albeit in a corrupted framebuffer, so it's just a matter of getting it presented to the screen properly!
Another title that's showing signs of life is Bravely Default: We can boot the game and even start it; unfortunately playing the game won't be much fun quite yet since the top screen just stays black. I haven't had time to dig into this, so it will be interesting to figure out what exactly is going on here.
Finally, it's not always about the latest and shiny games, but there's also many homebrew applications Mikage needs to support. Most 3ds-examples are now supported, and even portal3ds makes it to the title screen:
It's not always about getting new games to boot - often, fixing smaller bugs in games that already work uncovers subtle issues that would've been harder to debug in a game that outright refuses to render anything at all.
One issue that always bugged me out was the missing flame effect around the title screen logo of The Legend of Zelda: Ocarina of Time 3D. Thanks to Mikage's software renderer I knew the issue was with the Vulkan renderer. But it took me a rather lengthy debugging session to finally figure out the problem: The pixel shaders generated for the AddThenMultiply texture combiner operation were wrong! Instead of
(min(255, (rgb_in1 + rgb_in2)) * rgb_in3, it said
min(255, (rgb_in1 + rgb_in2) * rgb_in3) - note the misplaced parenthesis! The unmodified sum of
rgb_in2 entered the multiplication instead of having it clamped first! With that issue fixed, I finally got to see the flame effect work just fine and, as a pleasant surprise, it also made the intro animation in Nano Assault EX show up!
Another fun glitch are the intro logos in Cubic Ninja, which are supposed to fade in smoothly upon starting the game, but just instantly popped up when ran in Mikage. This issue affected even the software renderer, making it even more puzzling what was going on. On the other hand, reproducing the issue in the software renderer allowed me to very closely inspect every step of the rendering pipeline. Things just didn't add up: The emulator rendered things just like it was supposed to! Eventually, this was one of the few cases where comparing against Citra actually helped - running both Mikage's and Citra's software renderer in lockstep for individual pixels allowed me to catch unexpected differences in the logo rendering. Eventually, I found the root cause: The GPU register definitions for two alpha blending where off!
Mikage's Render Target Cache is responsible for matching raw framebuffer configurations to render targets used on the phone GPU. Ideally such a system avoids unnecessary round trips from GPU memory to emulated 3DS RAM, and this sort of caching is critical for getting any performance benefits out of hardware (Vulkan) rendering over software. Meanwhile, getting it right is essential to bug-free emulation without rendering glitches. In fact, Citra's early hardware renderer was plagued by caching issues like this, so it was important to me to address this early on.
Thanks to the work I put into the testing framework (see below), it wasn’t too stressful to come up with a reliable rewrite of this component. As a result, not only is the RenderTargetCache itself much more readable, but I was also able to resolve various compatibility issues: Nano Assault picked up an old render target previously, causing it to crash shortly after due to an invalid viewport setup. The both_screens homebrew example now renders the bottom screen as you’d expect:
Finally, the RenderTargetCache revamp removes the need for many annoying workarounds that harmed performance. Of course, the bottleneck is still largely CPU emulation, so don't expect this to do miracles to your frame rate yet. But at least the GPU now won’t hold us back anymore either.
Testing an emulator
Testing emulators is particularly interesting: On the one hand you want to verify your emulator is doing what it's supposed to do, and you want to ensure already verified behavior doesn't unintentionally break in a future change. This aspect of testing is absolutely critical to ensure emulator development keeps moving forward. Without tests, you'll run into a state where any fix you make to get a game running breaks 5 others because things become unpredictable. On the other hand, you also want a research platform to run against physical 3DS consoles to make sure your understanding of the emulated system is correct in the first place. Mikage's testing framework serves both of these requirements.
Unfortunately there's not much to show of the tests themselves - after all, for a correct emulator the whole point of tests is to just run through silently and pass. But if they do fail, such as when you're researching how a particular hardware feature works, this is the kind of output you get:
As you'll imagine, this sort of information is very helpful during development: Quickly pinning down what line of the test sources caused an issue, including information such as C++ exception information, allows for quick iteration cycles and makes writing tests a smooth experience.
One case I applied this powerful framework on is Mikage's OpenLinkFile implementation: Link files are used to open a second "view" onto an already opened file on the 3DS. This is a feature many games - notably Bravely Default and Nano Assault - use internally, and they won't let you get past boot unless you implement it. But doing so posed a bit of a challenge: I didn’t know what things games were allowed to do with them specifically, and so supporting link files properly could’ve required either a minor adjustment or a costly rewrite. And I didn't want to just support the bare minimum required to boot games just to find another game that'll have me rewrite it all a month later!
And this is where the testing framework came to the rescue: The first thing I found was that link files only really are supported in the SelfNCCH archive (as opposed to e.g. save data or SD card contents), hence massively reducing the scope of things to test. SelfNCCH can easily be accessed from 3DS homebrew, so writing tests was straightforward in the case, and in the process I also ported the existing FS tests (which previously only checked save file archives) to cover SelfNCCH, too.
Long story short: Having tested link files on actual hardware, I can now confidently say that a simple tweak is enough to support them properly. Basing this on a scientific research process feels much better than taking a leap of faith and crossing fingers nothing will break because of it!
A new homebrew toolchain
3DS homebrew is a valuable tool for emulator development - not only is it really easy these days to write your own 3DS application to test your emulator with, but there already is a plethora of demos to check your emulator works with! In particular, the 3ds-examples project covers a lot of the elementary 3DS GPU features, from basic 3D rendering to fragment lighting, up to procedural textures. Unfortunately though, not all is perfect.
Let me tell you the story of when I tried to build the 3ds-examples project for testing:
- I went to GitHub, downloaded the sources, and typed
maketo build the library. It doesn't work:
citro3d.h not foundis what it says
- Turns out it now depends on the citro3d library: So I go to GitHub again, download citro3d, type in
maketo build that library, and luckily that works out fine
- Going back to 3ds-examples though, the
makecommand still fails, now with errors related to citro3d: It turns out the project was written against an old version of citro3d, incompatible with the newer version I had just downloaded
- There's no mention about what version exactly was used, so I go back and make an educated guess; after downloading and attempting to compile the old version, well... it turns out the old citro3d version doesn't build with the latest 3DS toolchain
The bottom line is: To build 3ds-examples, you need to downgrade all your base libraries to versions nobody ever wrote down. It was at that point that I realized this system is too fragile to be used for Mikage!
Unfortunately, package management for C++ in general is rather meager: Every library may use its own build system, and there's no standardized way of declaring dependencies in a project. Luckily things are changing, and with Conan there is now a feasible candidate for a C++ package manager. It's already in use by Mikage to manage dependencies such as SDL and boost across Linux and Android with ease. So back in October I started an experiment: What if we could manage 3DS libraries using Conan?
Suffice to say it wasn't quite so easy; figuring out a good way to bootstrap the toolchain itself and bundle it as a Conan package wasn't quite as straightforward as I had hoped, because
binutils expect to be installed in a well-defined, global location, whereas Conan uses its own paths in the user's home folder based on hashes of the package configuration. Still though, eventually I managed to get something functional, and built on top of that. Among others, there's now packages for:
- a cross-compiling
gcctargeting the 3DS's ARM11 processor
- ctrulib, the base library for 3DS homebrew
- citro2d/citro3d to render using the GPU
- picasso, tex3ds, and other tools often used to build 3DS homebrew
- ctrtool, a program to inspect 3DS application images (CCI, CXI, etc)
- 3ds-examples, a suite of demo applications for 3DS functionality
For now this system is merely sitting on my hard disk for use in Mikage's internal 3DS tools, but these package recipes can be useful for 3DS homebrew development too; if there's sufficient community interest I'm definitely up for publishing those Conan packages in the future.
Open-sourcing Mikage's serialization framework
Serialization is the task of turning blocks of binary data into meaningful objects such as C++ structures, and vice-versa. This comes up when reading 3DS-specific file formats (such as CXI or CIA), when decoding parameters for high-level emulation of services, when interpreting command buffers for GPU emulation, and generally whenever the emulator core needs to access data structures contained in the emulated 3DS's memory.
Considering how common this task is, I grew tired of reinventing the wheel for every component it came up in. To automate all of this redundant work, I created blobify as part of Mikage. blobify uses advanced C++ techniques to automate serialization tasks as much as possible, and it has proven to be a very effective tool at that. To allow others to benefit from this, I decided to spend a couple of weeks on extracting blobify from Mikage's source code and turning it into a proper library that can be used in other projects, too.
I released the fruits of this work earlier today on GitHub. This reiterates my dedication to open-source development, and is a first (if small) step towards open-sourcing Mikage as a whole.
Stickers and 36c3
Hackers and security researchers gathered at the 36th Chaos Communication Congress (or 36c3 for short) at the end of December - and it's become somewhat of a tradition for emulator developers in Europe to meet up there too! It's been a blast to meet old and new friends alike, and I can't wait to go to 37c3 in 2020. If you haven't seen it in action yet, I definitely recommend trying it out! Plus, see what cool merch you're missing out on ;)
Despite all this progress, there's lots of work left to be done! As usual, I'm planning on extending the set of supported games: With the bulk of the essential 3DS features emulated by now, bringing up new games has become a task of a few days rather than a few weeks, so compatibility will improve a lot over the next few months.
With that base line, improving performance has become an increasingly important target. Mikage's internal Linux build is already running games at 60 FPS, but performance on Android is far from playable at the moment. Luckily, my experiments with a Just-in-Time recompiling CPU emulator have concluded, and I'm planning on boosting GPU performance using optimizations to the texture cache and the shader processor (akin to what Citra is doing but with some special sauce).
And with that, stay tuned for future updates!
Until next time!