Efficient way to draw Particles in a 2D game.

Hello there,

i am currently working on a 2D RTS game. I have wrote a 2d Particle Engine for this game and the Effects like Flamethrower looks really nice. A Flamethrower just needs 300-400 Particle to look good and here comes the problem.
When a lot of Units fight on the screen, i have to draw 10000 - 20000 Particles each Frame and the Framerate drops significantly. The main problem lies in the drawing function, because each particle use one spritebatch.draw();
I know this is the wrong way, but all i found are some 3D tutorials and i have no experience with 3D game creation.

Pseudo code:

    spriteBatch.Begin(SpriteSortMode.Texture, BlendState.Additive);

    for (int i = 0; i < 10000; i++)
    {
        spriteBatch.Draw(ParticleBase, tmpRectangle, null, Color.White, 180, Vector2.Zero, SpriteEffects.None, 0);
    }

    spriteBatch.End();

What is the best way to solve this problem. I would really appreciate some Pseudo code, Tutorials or just a hint. I googled the last days and found nothing that helps me.

Hi :slight_smile:

For a flamethrower, you could use a texture showing many flames instead of one, and so reduce the number of particles.
Did you also deactive the depth test between them ? (in 2D, there is no depth ?)

I think an “easy” way of speeding things up would be to use DrawIndexedPrimitive or other methods like this one.

Another way would be to use instancing. One draw call for all the particles.
My last idea, maybe “simpler” than instancing: draw the flamethrower’s fire in a sort of animated/joint object (sort of cone, and spheres attached to the largest part, the end of the spay, these spheres fading away with time).
Then use the texture of flames UVs in a pixel shader to move the texture and give the feeling of it getting out of the weapon (according to the direction of the player to the end of the spray)

Last would be GPU particles, but it would be overkill for a 2D game, but why not…

Spritebatch should be able to handle this efficiently. Are you sure the drawing is the bottleneck? Are you developing for desktop or mobile? MG.Extended has a 2D particle engine that currently also uses spritebatch to render and IIRC I could render this many particles on my pretty crappy laptop at 60 fps. Still it would be better to optimize it like @Alkher says so you can do more than just render some particles :slight_smile: but at least for non-ancient desktops I think you should be able to pull it off like this.

As Jjagg says, are you sure this is the bottleneck - might be an idea to put some timing code before and after the for loop and see how long it’s taking.

Maybe show us the real code you are using, we might spot something in there and advice a tweak.

thx for the fast replies :relaxed:.

I have created a test application for this problem, to get the bottleneck with time measure. I am sure the main problem is the drawing. (Update in the main Project still needs improvement, too much garbage collection :sweat_smile:)

The Particlebase Ressource is a white dot with 1width and 1height. i don’t change the content settings. (No compression etc.).

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System;

namespace MonogameTestParticle
{
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        Texture2D particleBase;
        Rectangle particleRec = new Rectangle(0, 0, 1, 1);

        SpriteFont pericles6Font;

        int frameRate = 0;
        int frameCounter = 0;

        int drawingMillieSeconds = 0;

        TimeSpan elapsedTime = TimeSpan.Zero;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        protected override void Initialize()
        {
            base.Initialize();
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(this.GraphicsDevice);
            particleBase = this.Content.Load<Texture2D>("ParticleBase1");
            pericles6Font = this.Content.Load<SpriteFont>(@"pericles6");
        }

        protected override void Update(GameTime gameTime)
        {
            elapsedTime += gameTime.ElapsedGameTime;

            if (elapsedTime > System.TimeSpan.FromSeconds(1))
            {
                elapsedTime -= System.TimeSpan.FromSeconds(1);
                frameRate = frameCounter;
                frameCounter = 0;
            }

            base.Update(gameTime);
        }
        protected override void Draw(GameTime gameTime)
        {
            System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch();
            stopWatch.Start();

            frameCounter++;

            this.GraphicsDevice.Clear(Color.Black);
            spriteBatch.Begin(SpriteSortMode.Texture, BlendState.Additive);

            spriteBatch.DrawString(pericles6Font, string.Format("fps: {0}, drawing: {1}", frameRate, drawingMillieSeconds), new Vector2(2, 2), Color.AliceBlue);

            for (int i = 0; i < 30000; i++)
            {
                spriteBatch.Draw(particleBase, particleRec, null, Color.White, 180, Vector2.Zero, SpriteEffects.None, 0);
            }

            spriteBatch.End();

            drawingMillieSeconds = (int)stopWatch.ElapsedMilliseconds;

            base.Draw(gameTime);
        }
    }
}

With 30000 Particles i am getting 40 fps and lower. 10000 Particles still needs 8 ms to be drawn. (Way too long).
I am using Monogame 3.5 with Visual Studio 2013 Community edition.
My Desktop PC is 6 years old with this specifiations:

Graphic: ATI Radeon HD 5670 1 GB.
Processor: Intel Cor I3 3 GHZ Quad Core.
RAM: 6 GB.

Are the Specifications too bad to handle this much Particle? I Would like to use gpu particles but i can’t find any tutorial for xna 4.0 or monogame.

Since you use the same texture, use SpriteSortMode.Deferred. This will skip sorting through 30000 items and give you some extra performance.

EDIT:
Also, if you are not going to alter the rotation on each particle, pre-rotate your texture and pass 0 instead of 180. SpriteBatch skips a couple of expensive calculations when rotation is 0. (probably it could handle special cases like 90,180,270 but doesn’t at the moment).

2 Likes

As mentioned above, drawing 10k particles in deferred mode shouldn´t be an issue at all as it can be done pretty much in single draw call.