5
votes

I've been porting a scrolling shooter game I made in XNA over to Linux using MonoGame. Almost everything has gone smoothly, but I'm having an issue in a specific place with calls to SpriteBatch.Draw() crippling the framerate. Most of the game runs fine without any hiccups. It draws a good number of enemies and large number of bullets at once without any slowdown. The part that is causing the dropped frames, however, is a layered scrolling background. The relevant sections of code are here.

Level.cs:

public void Draw(SpriteBatch spriteBatch)
{
    foreach (ScrollingBackgroundLayer sbl in scrollingBackground)
    {
        sbl.Draw(spriteBatch);
    }
}

The "scrollingBackground" above is a list of ScrollingBackgroundLayers, the relevant sections of which are:

ScrollingBackgroundLayers.cs:

Vector2[] scrollingBackgroundImages;
public float DrawLayer;

public ScrollingBackgroundLayer(GameScene newScene, Texture2D texture, float scrollSpeed, Color color)
{
    layerColor = color;
    layerSpeed = scrollSpeed;
    layerTexture = texture;
    thisScene = newScene;

    Initialize();
}

public void Initialize()
{
    scrollingBackgroundImages = new Vector2[2];

    for (int i = 0; i < 2; i++)
    {
        scrollingBackgroundImages[i] = new Vector2(0, thisScene.ScreenArea.Height - (layerTexture.Height * i));
    }
}


public void Update(GameTime gameTime)
{
    for (int i = 0; i < scrollingBackgroundImages.Length; i++)
    {
        scrollingBackgroundImages[i].Y += (float)gameTime.ElapsedGameTime.TotalSeconds * layerSpeed;

        if (layerSpeed > 0 && scrollingBackgroundImages[i].Y >= thisScene.ScreenArea.Height)
        {
            scrollingBackgroundImages[i] = new Vector2(scrollingBackgroundImages[i].X, (scrollingBackgroundImages[i].Y - layerTexture.Height * 2));
        }
        else if (layerSpeed < 0 && scrollingBackgroundImages[i].Y + layerTexture.Height <= 0)
        {
            scrollingBackgroundImages[i] = new Vector2(scrollingBackgroundImages[i].X, (scrollingBackgroundImages[i].Y + layerTexture.Height * 2));
        }
    }
}

public void Draw(SpriteBatch spriteBatch)
{
    foreach (Vector2 sb in scrollingBackgroundImages)
    {
        spriteBatch.Draw(layerTexture, sb, null, layerColor, 0f, Vector2.Zero, 1f, SpriteEffects.None, DrawLayer);
    }
}

All of the framerate issues go away as soon as I comment out the call to ScrollingBackgroundLayer.Draw(), so that seems to be a pretty big hint that the issue is coming from the SpriteBatch's attempt to draw the scrolling layers. Obviously, I want to figure out why this is happening. I looked into some other issues with SpriteBatches, and the most common thing I found that might point toward an answer is the fact that a new SpriteBatch is created whenever the texture is changed, but even setting the SpriteSortMode to Texture gave no improvements.

The framerate decrease occurs on a stage of the game that has 7 different ScollingBackgroundLayers, each of which draws a separate texture that is around 700 pixels wide and 900 high (each layer should do this no more than 2 times to account for the scrolling effect). I feel like I have to be missing something obvious, because the game sometimes renders as many as 2-300 bullets using the exact same overload, so another 14 calls should be a drop in the bucket. Is this simply an issue with trying to draw textures too large for SpriteBatch to effectively handle? This is not an issue on the Windows version, so I'm wondering if there isn't some platform specific workaround to this, or if MonoGame's implementation of Draw is just inefficient. Any feedback would be welcome.

Full source for those interested:

Linux - https://github.com/cmark89/AsteroidRebuttal-Linux
Windows - https://github.com/cmark89/AsteroidRebuttal

Edit: I tried tinkering with it a bit more. I changed the call to Draw() to ensure that destination rectangle was equal to the drawable area of the screen, but there was no noticeable change. I swapped the texture to a very small one and that eliminated the lag, so I guess it does have something to do with the size of the texture.

Edit 2: Alright, I ended up just biting the bullet and cutting up the transparency layers I was using (like some kind of not-lazy guy). It solved the problem, presumably since it no longer has to render huge numbers of useless pixels, but it still leaves me wondering why this is only an issue on Linux. Even though I have solved my issue for the time being, I'm going to leave this open for a bit just in case anyone has any insight about what could be causing the drastic performance difference.

1
I can't really remember the performance costs for different things but forcing GPU to draw that many high-res textures is probably not a very good idea and something you should be able to design in a different way. If memory serves me correct those textures would end up being 1024*1024 textures (due to power of 2-requirement and remaking into that size during runtime) when sent to GPU.Daniel MesSer
The problem in this case is that each image of the background scrolls at a different speed or has a different level of transparency. Some of them I could definitely chop up and create layers that draw in smaller areas of the screen, but some of them (mostly transparency layers) are not so easy. I wasn't aware that the GPU resized non-power of two textures anyways; maybe I'll resize them and see if it changes anything. Irrespective of the optimizations I'm going to be looking into making, I'm really confused why this problem doesn't occur on Windows.cmark89

1 Answers

2
votes

In this situation, I would suggest you use a shader instead, in order to accomplish this. You will see your performance skyrocket by doing this. Take a look at the following:

http://www.david-gouveia.com/scrolling-textures-with-zoom-and-rotation/

Doing it this way, will alleviate a lot of CPU usage, and make your GPU do the bulk of the work.

You may also want to store your textures in powers of 2, instead of 700x900. (do something like 512x512). Your graphics pipeline has performance boost relating to textures that have a power of two.