Sunday, February 6, 2011

Porting bsnes to C#: Part One

Browse the code repository for this post here.

An Idea and a Motivation

As I mentioned in my previous post, I'm a lifelong Super Nintendo (SNES) fan, and I miss the simple fun of being able to sit on my couch and play my old games.  There are excellent emulators available, but the experience of going through the PC isn't the same as powering up a good ol' fashioned console in the living room.

With that in mind, I began an experiment that I thought would be fun, in the spirit of bringing bsnes to a larger audience.  Since I'm interested in XNA game development, I wanted to port bsnes to run using XNA.  It was my hope to have an SNES emulator that would be playable on the Xbox, and perhaps even an nth generation Windows Phone.  It's easy to imagine all the fun things that could be accomplished using these devices, such as network play for multi-player games.  Neither of these platforms allow deployment of indie games written in native code, so to achieve this goal, I had to port the entire bsnes codebase from C++ to C#.

Lost in Translation

Although the finished product contains well over 10,000 LOC, this task sounds more daunting than it actually was.  Development was eased due to the clean, well-designed code that byuu and his peers have written.  I approached the port from the top-down, fleshing out the member function signatures and variables before implementing each function itself.  In the process, I learned quite a bit about the differences in C++ and C#, as well as a few things I didn't know about C++.  I'll discuss these in more detail in an upcoming post.

After a month's work translating functions and classes, I got my C# codebase - which I'm calling SnesBox (yes, I missed my calling in marketing) - to the point where it could be run without immediately throwing a System.NotImplementedException.  At this point, it was necessary to compare execution of bsnes to SnesBox, preferably in an IDE.  This was the most effective way to see where I had made the inevitable errors in such a large port.  To accomplish this, I installed Ubuntu in VirtualBox on my Windows machine, where I could debug bsnes in NetBeans, side-by-side with SnesBox in Visual Studio 2010.

bsnes already implements custom state serialization, which I had originally opted to forgo in favor of .NET serialization.  I later realized this serialization was my key to the most effective inter-OS, inter-IDE, inter-language debugging tool I could think of: by writing identically serialized states to VirtualBox's shared folders at key points during execution, I could use a full-featured diff tool to track down the points at which the states of bsnes and SnesBox began to diverge.  Usually this meant placing a state file "write" each time the emulated processors would yield to one another, then seeing which variable in the diff contained the offending difference.  By knowing which variable contained the difference, it was usually as simple as seeing where it was used in code and finding which of several "usual suspect" translation errors (bad cast, wrong primitive type, incorrect logic, etc.) had taken place.

After another month's work, I had a running XNA-based bsnes emulator, written in C#.

SnesBox running Super Mario World at 30 FPS on my laptop.
Now, if you don't think this game is the greatest game ever: I will fight you.  That's no lie.
Broken Threads

If I had discussed the idea of a managed solution with a hardware-level emulation developer before I began, I knew I'd immediately be laughed at for na├»vely attempting such a computationally intense task with the .NET framework.  And, they would have been correct...but for different reasons than I initially suspected.  Ultimately, my C# emulation does not run at the necessary speed of 60 FPS, for reasons I'll explain.

Early in my porting effort, I encountered libco, a cooperative multithreading library that byuu had developed.  Multithreading is used in bsnes to provide each emulated processor with its own execution context.  When a processor has determined it is time to hand off work to the next processor, its stack is preserved until execution is manually returned to the point where the processor was suspended.  bsnes contains three execution profiles: accuracy, compatibility, and performance.  One of the ways the performance profile runs faster is by eliminating the use of a separate thread for one of the emulated processors.

I knew the port for libco would not be straightforward, since it contained OS-specific fiber routines, some of which were written in machine code; talk about obfuscation!  Unfortunately, the .NET framework does not support fibers at this time.  To complicate things further, the manual threading functions which .NET does contain, Suspend and Resume, have been deprecated.  This left me to use heavyweight threads in a manner that is avoided on Windows and impossible on the Xbox.  My horribly awkward thread switching function makes use of these features, implemented as:


public static void Switch(Thread thread)
{
    var previous = _active;
    _active = thread;
    if (_active.ThreadState == ThreadState.Unstarted)
    {
        _active.Start();
    }
    else
    {
        WaitForCurrentThreadToFinishSuspending();
        _active.Resume();
    }
    previous.Suspend();
}

private static void WaitForCurrentThreadToFinishSuspending()
{
    while (_active.ThreadState != ThreadState.Suspended) { }
}

It was sufficient for debugging purposes, but not for a deliverable.  After running Visual Studio performance analysis on SnesBox, the percentage of execution time spent switching threads was between 81% for the accuracy profile, and 57% for the performance profile.

I did make an attempt to circumvent the threading issue, using an underutilized feature of the C# language: the yield statement.  I'll discuss this in much greater detail in a future post, but the results of the experiment can be found in the "yielded" branch of the SnesBox code repository.  Bottom line: it removes the usage of .NET threads, but generates so much additional code for state preservation in the process that execution speed is further reduced.

Conclusions

I have ported bsnes to C# and can run SNES games (which don't have any on-board co-processors) using the .NET framework and XNA.  Like so many projects that are completed on a developer's whim, I had an absolute blast working on this, even if it did not produce a shippable product.  In the process, I learned a lot about the two programming languages involved, and would feel confident taking on another C++ to C# porting project.  I even uncovered a couple of minor bugs in bsnes code, which I've identified in the repository with TODO comments.  C++ may not care if you overrun an array, but C# certainly throws an exception or two.

I have made the code repository available on Google Code, distributed under the MIT License.  I have been lead to believe this is the most "public domain-y" of the licenses Google Code offers.  Under the project issues, I have listed and prioritized some remaining problems I previously identified.  If anyone can solve the .NET fiber issue, I would be excited to work with them to create something we can all have fun playing.

My thanks to byuu and the other developers working on bsnes: you guys have created an amazing product, and it shows.  Look for my next post concerning the SnesBox effort, where I identify some of the issues I had to overcome in my C++ to C# port, and further explain co-routines as "fibers" in the .NET framework.

2 comments:

  1. The Mono runtime supports fibers and coroutines directly. You can embed it in a tiny C++ launcher to simplify deployment.

    ReplyDelete
  2. Very cool; I've looked at Mono before, but never actually used it. I'll give it a try to see if it removes the bottleneck from the Windows version, at least. Thanks for the tip!

    ReplyDelete