The Great Porting Adventure: Day 7

In this episode, our hero does battle with the beasts known as music and vsync… and wins.

Last time, I wrote about how I had successfully transitioned to a SoundEffect-based audio system. This was true, but it was also not the end goal. I was playing my music as a SoundEffect as well, which had the unfortunate requirement of all the source files being in .wav format. This meant that the music in my game was taking up about 170MB of space. Yikes.

There are two ways you deal with this in XNA land: switch to XACT to get fancy xWMA compression (not an option) or use the Song API to load in MP3 or WMA files (which both end up as built WMA files). Simple, right?

So I switched up my audio system to use Songs whenever a music cue was played (and updated the pipeline and content loader accordingly to use Song objects instead of SoundEffects for music cues). Then I remembered some of the drawbacks to the Song API.

You can only have one Song playing at a time, so there’s no way to do cross-fading. What’s more, the Song is controlled through the MediaPlayer class which has ridiculous overhead doing simple things like changing Volume. That means it’s impractical to do fading out manually by adjusting Volume. It’s possible, but you take a big performance hit for something simple (on Xbox anyway).

So I reverted all of those changes and went back to using SoundEffect for music. I realized that you can actually use waves processed with a bit of compression (ADPCM) and still load them as SoundEffect objects. This was great, because it saved something like 70% filesize for “Medium” quality (which sounds fine for the chiptune-style music I have).

Next I had to solve the pesky problem of not being able to play multiple SoundEffectInstances of the same SoundEffect (a limitation of the current build of MonoGame). Fortunately, there is an outstanding pull request on the MonoGame github project that integrates OpenAL support along with a bunch of missing SoundEffect functionality. I had to download and build maccore and monomac from source first, but that was a simple case of cloning those repos and running “make” from inside the monomac folder.

This is a picture of the OpenAL logo, because otherwise this post would be mostly text.

After all that – it didn’t work! Turns out that OpenAL doesn’t seem to support ADPCM wave files, so I’m back to square one as far as music goes.

Or am I? OpenAL can read MP3 files. And the SoundEffect object doesn’t care what’s in its buffer. So I can actually load MP3 files as SoundEffects directly using MonoGame! Vanilla XNA can’t even do that!

I had one more bug where I wasn’t disposing of SoundEffectInstance objects properly (my code), but once that was fixed, everything seemed to be working perfectly. Yay!

Next on the docket was vsync. The screen tearing I was experiencing was unacceptable, so vsync was definitely required. Thankfully, @SoftSavage (the current MonoGame coordinator) pointed me in the right direction with some docs from Apple on the subject. It was a simple case of finding out where to get the OpenGLContext and then set up the SwapInterval. Since I had just built monomac from source, this was relatively easy to track down and then add it to Game.applyChanges() thusly (line 19):

internal void applyChanges()
{
    if (GraphicsDevice.PresentationParameters.IsFullScreen)
        _platform.EnterFullScreen();
    else
        _platform.ExitFullScreen();

    // FIXME: Is this the correct/best way to set the viewport? There
    // are/were several snippets like this through the project.
    var viewport = new Viewport();

    viewport.X = 0;
    viewport.Y = 0;
    viewport.Width = GraphicsDevice.PresentationParameters.BackBufferWidth;
    viewport.Height = GraphicsDevice.PresentationParameters.BackBufferHeight;

    GraphicsDevice.Viewport = viewport;

    _platform.Window.OpenGLContext.SwapInterval = graphicsDeviceManager.SynchronizeWithVerticalRetrace;
}

There was a bit of other housekeeping to get the above code to work, so I include that only to show you the general idea. There is one other odd and important thing thing: in monomac, the MonoMacGameView class (parent of the GameWindow your game runs in) looks like this:

public void Run (double updatesPerSecond)
{
    AssertValid ();
    if (updatesPerSecond == 0.0) {
        Run ();
        return;
    }

    OnLoad (EventArgs.Empty);

    // Here we set these to false for now and let the main logic continue
    // in the future we may open up these properties to the public
    SwapInterval = false;
    DisplaylinkSupported = false;

    // Synchronize buffer swaps with vertical refresh rate
    openGLContext.SwapInterval = SwapInterval;

    if (displayLinkSupported)
        SetupDisplayLink ();

        StartAnimation (updatesPerSecond);
    }

You can see that vsync will be disabled no matter what you do (line 13). Fortunately, this only occurs just before the first Update. It does mean that you have to setup vsync after your Game has called Initialize(). I just added the following to MacGamePlatform.StartRunLoop():


public override void StartRunLoop()
{
    _gameWindow.Run(1 / Game.TargetElapsedTime.TotalSeconds);

    //The first call to MonoMacGameView.Run disables vsync for some reason, so  give
    //the game a chance to re-enable it here
    this.Game.applyChanges();
}

And then everything was right as rain.

The game is really coming together now. I gave the app packager/installer a whirl too and that looks pretty straightforward, which is a breath of fresh air compared to making an installer on Windows. More work remains to be done key binds, controller support, my loading screens and some resolution/alt-tab support, but I’m getting close! Oh, and I suppose I’ll need to look into Mac Store requirements sooner rather than later too 🙂

Running total: 7 days, $112.87

Advertisements