March's big news: Mikage can now run Super Mario 3D Land! Perhaps you've already seen our gameplay video about it; what the video didn't show is just how much sophisticated work went into that title alone, so we figured it's a good time to show you the various steps of iteration from error messages over to garbled rendering to almost perfect game visuals.
In the Beginning, there was Nothing
The first sign of life Super Mario 3D Land showed in Mikage was back in July 2019: A title logo with just-kinda the right colors. One fix in the code that handles RGB565 framebuffer data transfers ("display transfers") later, it's rendering fine:
While this achievement looks simple on the surface, getting a game to draw anything is really the biggest hurdle from a development perspective: After all, how do you debug a game that simply doesn't do anything?
A Title Screen
Of course, a blocker was found quickly, and so the game didn't make it past the initial setup dialogs. There were some obvious and some not-so-obvious features not implemented in Mikage. Eventually though, we succeeded and got this beauty of a title screen:
All the rendering glitches notwithstanding, what's really cool about this picture is what's happening on the top screen behind the game logo: Look closely, it's actually rendering the 3D graphics properly! For instance the geometry of the block in the top right corner is completely fine.
Debugging issues with 3D geometry can be a royal pain, so seeing it just work meant that fixing the rendering for good was within close reach. But there was another roadblock that had to be dealt with first...
Command Processor Fixes
Shortly after entering the title screen, the game would randomly freeze up for no apparent reason. This hadn't always been the case, but at some point it just started doing so.
SM3DL likes to keep the 3DS GPU busy while doing processing on the CPU, and hence schedules a lot of work using command lists that call other command lists. Technically there's just one a primary list that calls smaller, secondary lists, which then return back to the primary one after processing. However, since there's no equivalent of a call stack, all return offsets are hardcoded in the command lists themselves, and hence it's equivalently just one huge pile of nested command lists calls.
My first command processor implementation struggled with this: While handling secondary command lists using a C++ function call each worked for most games, for SM3DL it leads to such deep recursion that the game eventually ran out of stack space.
Luckily, this was the only thing that caused unpredictable crashes in the game - after fixing my implementation to use a non-recursive algorithm, the game was rock-solid!
A couple of weeks later, many more fixes had landed, most notably the Render Target Cache Revamp. Some of the graphics fixes were fixed to the point you could recognize the 3D geometry better now:
Super cool! Somehow the geometry is missing colors though, and we somehow lost all environment graphics along the way. A couple of days later I at least managed to make the characters render properly:
So what's up with the missing environment graphics? Luckily, I stumbled upon a Citra issue from back in the day that shows a screenshot resembling Mikage's output very closely. As the issue points out, the background will be rendered in grey unless a feature called stencil testing is implemented.
Adding support for stencil testing was a bit involved, taking a full day or two. But it is a good example of how one change can make the difference between almost nothing working at all and an essentially playable game:
Meanwhile, on the Bottom Screen
You'd think with the top screen graphics being essentially perfect at this point, the 2D bottom screen graphics would just work easily, but nope. Due to missing support for decoding textures in the RG8 format, the UI elements displayed in a weird overlaid fashion. After extending the texture decoder, the bottom screen renders fine too!
We're getting there; the title screen displays just fine, Mario is running across the screen, all the objects show up just fine. But then you press A to select a save file, and argh - the game freezes up for no apparent reason! What's worse, the prototyping branch of Mikage's source code worked just fine.
When your work-in-progress fixes things it wasn't meant to, ironically that often uncovers yet more work to do: What fixed the issue, and why? Is it a proper fix or did I just accidentally change emulation behavior? What's left for a proper solution? Granted, at least it gave me a starting point for debugging the lockups, and eventually I found the culprit: Emulation of Nintendo's applet system.
On the 3DS, the applet system manages the various applications that run on the system (primarily the active game, but also the Home Menu and the browser), and it also manages the transition from one to another (such as when Home Menu launches a title). Game emulation requires only a small subset of this system, but it turns out Mikage's early implementation was a bit too simple. Given this opportunity for sorting things out, I decided to move a full rewrite on the priority list to fully support applets.
... which wasn't such an easy task, it turned out: Info on the applet system is scattered among many different places, each of which only contains a piece of the picture: Between 3dbrew, libctru sources, and Citra, no source does a really good job of conveying the underlying ideas. Piecing it all together into a coherent architecture took a lot of work, but luckily also means that Mikage's implementation is rock-solid now and won't run into major issues anytime soon. Meanwhile, the applet system also paves the way for other exciting work I'll be addressing in a future Progress Report.
Of course, that wasn't all - at any point of progress, there's a "boring" period of watching Mikage try to run the game and fail quickly due to the game using a feature not yet implemented in the emulator. This happens a lot: Mikage tries really hard to detect "unexpected" configurations, and immediately errors out instead of "hoping for the best" like many other projects do.
So really, sometimes it's just all about running the game and implementing whatever feature Mikage is complaining about. If you look at the changelogs, many of the fixes listed there are about this. A missing system module interface, a graphics rendering configuration, or an unimplemented CPU instruction.
Granted, tackling these things one by one instead of implementing features all in one go usually doesn't make a difference in 90% of the games I test, but for the other 10% it easily saves days of debugging time. And it gives me confidence that the supported features are "correct enough", meaning there are no glaring issues that could require a major rewrite.
Of course, there's the occasional exception to ensure Mikage keeps visibly progressing, such as for purely cosmetic features like fragment lighting, as required by Super Mario 3D Land.
Finally, we're done: The game is functionally playable! It's always a thrill to watch the intro cutscene, crossing fingers that the emulator doesn't hit yet another missing feature, and eventually make it through for good. This is the point where it's time to take a break, pat yourself on the back, and just enjoy the game for a moment.
Oh no, more Problems!
Of course, the job is never really done. While the game has been functional since January, not all is perfect:
... and then there's this hilarious problem with the ground geometry ...
... and let's just not talk about this one ...
Of course, I knew releasing a video with these glaring issues wouldn't be particularly impressive. Hence stay tuned for the next Progress Report to see how I fixed them ;)
I hope you enjoyed this little digression from the usual program. As usual, thanks for your support and stay safe!