Monday, January 24, 2011

XNA and bsnes

Download the code for this example here.

I miss my Super Nintendo.  My favorite console of all time also plays host to some of the best games ever made (at least, in the opinion of a guy who spent his adolescence playing video games in the mid-1990s).  A fuse near the power input of my SNES burned out years ago, and it hasn't worked since.  I should buy a replacement from eBay, but why, when there are so many excellent emulators available?

Excellent emulators, such as bsnes, developed by byuu.  As a developer, I greatly respect what byuu has accomplished: beautiful code which models the architecture of an SNES in a simple, logical fashion and doesn't circumvent quality in favor of slapdash development.  As a gamer, I appreciate the picture-perfect emulation that bsnes brings to any computer on which I care to play a "quick" game of Seiken Densetsu 3.

byuu has created a C-style interface to the bsnes library, called libsnes.  People have developed cool frontends to the bsnes backend in languages such as Python and C# using SlimDX.  I thought it would be fun to try my hand at an XNA-powered bsnes frontend.

Instead of using explicit P/Invoke to interop calls to libsnes, I opted to use C++/CLI.  It's a personal opinion, but C++/CLI helps me better understand where a problem lies when things go wrong with the tricky world of C++/C# interop.  That being said, Microsoft's support for C++/CLI in Visual Studio 2010 is pretty dismal.

I accomplished importation from the libsnes DLL by attaching the DLLImport attribute to each libsnes function call.  Since the libsnes assembly is compiled using MinGW GCC, it was necessary to specify the cdecl calling convention, seen below.

[DllImport("snes.dll", CallingConvention=CallingConvention::Cdecl)]
void snes_run(void);

The libsnes library uses function pointers to callbacks for various frame completion events.  However, the static ref class I use to expose libsnes to my XNA game is all managed C++, so its functions cannot be passed directly to native code.  To pass a function from the ref class to native code, it was necessary to define delegate types which included the cdecl calling convention on the delegates via the UnmanagedFunctionPointer attribute:

delegate static void SnesVideoRefresh(const unsigned short* data, unsigned int width, unsigned int height);

I used GetFunctionPointerForDelegate to pass managed functions which matched the delegate signatures to native libsnes:

template<typename functionPointerType, typename delegateType> static functionPointerType NativeFunctionPointer(delegateType managedFunction)
 auto pointer = System::Runtime::InteropServices::Marshal::GetFunctionPointerForDelegate(managedFunction);
 return static_cast<functionPointerType>(pointer.ToPointer());

_videoRefreshDelegate = gcnew SnesVideoRefresh(&LibSnes::OnVideoRefresh);

Additionally, I adapted the interface to be more C#-like, rather than a C-style translation.  This included the use of managed enums, properties, and events, for which I wrote custom event handler arguments that could send bsnes data to any event listeners in managed code.  The result was a concise interface for use with an XNA game that doesn't require unsafe codeblocks to accommodate C++ syntax.

public static class LibSnes
    public static Region CartridgeRegion { get; }
    public static uint LibraryRevisionMajor { get; }
    public static uint LibraryRevisionMinor { get; }
    public static uint SerializeSize { get; }

    public static event EventHandler<AudioRefreshEventArgs> AudioRefresh;
    public static event EventHandler InputPoll;
    public static event EventHandler<InputStateEventArgs> InputState;
    public static event EventHandler<VideoRefreshEventArgs> VideoRefresh;

    public static void CheatReset();
    public static void CheatSet(uint index, bool enabled, string code);
    public static byte[] GetMemoryData(Memory id);
    public static uint GetMemorySize(Memory id);
    public static void Init();
    public static void LoadCartridgeNormal(string xml_utf8, byte[] data, uint dataLength);
    public static void Power();
    public static void Reset();
    public static void Run();
    public static bool Serialize(byte[] data, uint size);
    public static void SetCartridgeBasename(string basename);
    public static void SetControllerPortDevice(Port port, Device device);
    public static void Term();
    public static void UnloadCartridge();
    public static bool Unserialize(byte[] data, uint size);

To translate bsnes audio and video data for use in an XNA game, I took a look at the existing code for other frontends.

Video was as simple as creating an array of Color data, copying it to a Texture2D, then drawing it with a SpriteBatch draw operation.  The XNA Creators Club has a sample for using effects with SpriteBatch drawing, which I adapted to draw the video using the HQ2X filter.

Audio data is sent in arrays of samples and played for each frame in bsnes.  This made it seem that it was going to be difficult to handle audio in XNA before I discovered a cool class, new to XNA 4.0: DynamicSoundEffectInstance.  Creating an instance of this class and calling Play allows you to submit buffers as you receive them, perfect for an application such as this.

Audio, Video, and Input were all handled using XNA GameComponent and DrawableGameComponent classes.  Each game Update calls Run on LibSnes, which advances the game one frame.  The game components receive raised events from LibSnes, and the video component draws updated bsnes video texture data using a SpriteBatch.

The XNA game accepts command line arguments for cartridge ROMs and runs at 60 FPS on my laptop.  I also tried out a very simple WPF UI based on my earlier post.  I implemented a view which has a load button, a pause button, and a combo box to switch filters.  The frame rate appears to be fill bound, but I haven't looked into the issue extensively.

I started with a view model for the game, which has an instance of the game class, a pause property, a filter property, and a property for the load command.  The view model implements INotifyPropertyChanged, used to communicate changes in the game to controls bound to the view model.  The load command launches a browse dialog and creates a new bsnes game instance.

I made some changes to my GamePanel this time.  The control was given Game, Pause, and Filter dependency properties, which have PropertyChangedCallback functions specified in their UIPropertyMetadata.  Rather than handle the game creation in the control's OnLoaded, the game is created whenever the DP changes.  Changes to the pause and filter state are communicated to the bound game instance.  I believe these changes make the code easier to understand.

Wednesday, January 12, 2011

An eventful December

After a long break from work and the passing of the holidays for the year 2010, I've finally caught up on enough sleep to share a post.

Marie and I were fortunate enough to be witness to all three wedding ceremonies for our friends Aswin and Raksha: a legal ceremony in Seattle, USA; a Hindu ceremony in Bangalore, India; and a Western ceremony, also in Bangalore.  Additionally, there was a reception for the newlyweds in the groom's hometown, near Mangalore.  They are a beautiful couple that deserves all the happiness they've found in each other, and I couldn't be more thrilled that they're finally married.

An added bonus to all the excitement of seeing my friends tie the knot was getting to see India in the days leading up to their official weddings.  We flew into New Delhi, and visited Jaipur, Jodhpur, and Udaipur in Rajasthan, before continuing on to Goa, and finally Bangalore and Mangalore in Karnataka.  We celebrated Christmas and New Year's in the country, and I "observed" my 30th birthday.  I've uploaded a few photos I took using my Canon 1Ds and a 50mm lens.

Those of you familiar with pretty much any book, painting, film or television show ever concerning the subject of India will notice the absence of a certain white, marble building from our itinerary.  This was not an intentional omission.  Our trip was originally two and a half weeks, but was abbreviated to one and a half weeks, due to the fact that my wife and I are "spoiled Americans" (as my friend Aswin jokingly referred to us) who didn't know we were required to obtain Indian visas prior to departure.

After a weekend "trapped" in NYC, we flew back to Houston and got our visas in a day.  We are very lucky to live 4.1 miles from the Southern USA's regional Indian visa outsourcing office, and could apply in person.  Still, many days of our vacation (and many dollars from our bank account) were lost.  Unfortunate, but not enough to ruin the experience.

Prior to this trip, we visited Scotland to celebrate the marriage of our friends Chris and Michelle.  Next stop, Mexico City, for the wedding of my friend Morten, from Frederiksv√¶rk, Denmark.  The World Wedding Tour continues!