Performance optimisations with > 10,000 sprite entities

I’m having some performance issues with large numbers of entities in a scene.

At the moment I am just creating large numbers of entities of 16x16 pixel sprites and adding them to the scene. The picture below shows the result of this.

I’m using this code to add the entities:

private void DrawCell(int startX, int startY, int length, Dictionary<string, SpriteComponent> spriteComponents)
    {
        var random = new Random(1);

        for (int x = -length/2; x < length/2; x++)
        {
            for (int y = -length/2; y < length/2; y++)
            {
                var entity = new Entity();

                switch (random.Next(2))
                {
                    case 0:
                        entity.Add(spriteComponents["grass_sq_0"]);
                        break;
                    case 1:
                        entity.Add(spriteComponents["stone_sq_0"]);
                        break;
                }

                entity.Transform.Position = new Vector3(x + startX, y + startY, 0);
                entity.Transform.Rotation = Player.Entity.Transform.Rotation;

                SceneSystem.SceneInstance.Scene.AddChild(entity);
            }
        }
    }

This works OK for small numbers of sprites, but the frame rate drops quite considerably as more of the map is drawn (which is to be expected).

In order to optimise this I’m looking to combine the sprites into a larger image, say 100x100 sprites which is 1600x1600 pixels. The map isn’t going to change an awful lot, and pushing one entity with one texture to the GPU should hopefully alleviate all performance issues.

What would be the best way to manage this? Here are the two options I’ve considered:

  1. Create the images in memory, never write them to file, and assign this texture to an entity on the fly. This should be OK, but you’ll have the drawback of possibly running out of RAM if the map is huge and out of view cells are not disposed of properly. This method means that you will potentially be creating the same chunks over and over again, as you move around the map, which seems like a waste of CPU. The disadvantage of this is I’ve no idea how to get the actual png file that contains the terrain sprites, and how to assign that to an entity in game.
  2. Create the images in memory, write them to file and then load them in via the already existing Game.Asset.Load() methods and assign them to an entity on the fly. Basically switch the advantages/disadvantages of #1 around. I think I will try this option and see how things go!

I’ve written quite a lot here without any real questions I guess. I’d just like confirmation that pushing lots of entities to the GPU every frame is the issue here, and that there’s not an already existing solution to my problem :slight_smile: .

Using one texture will help, do you run the DrawCell every frame? Because if so you’d be adding a ton of unneeded entities to the scene.

You might want to consider creating a 2D tile renderer and remove sprites all together. Drawing individual sprites per frame is very costly because directX or opengl needs to transmit the data from CPU memory to GPU memory each draw call. If paradox perhaps used a deferred dx11 render systme this would be acceptable but it does not. I would start by creating a grid of vertices in block shape you can figure out the size each cell would need to be by doing some projection math(Look at Project and Unproject). Create a shader to render the textures on said grid for this you have two options. The first is to render said textures using a texture atlas. There are a ton of reasons why this shouldn’t be your choice, but for older hardware, and possible phones it might be your only choice. The other option is to use texture arrays. Your grid of vertices will need special uv’s in order for them to render correctly.

The system would be similar to what I’ve done with block voxels only in 2D. This is probably the most optimized case. You can then chunk your tiles into say 32x32 tiles, and cull chunks that aren’t on the screen increasing performance. Smooth scroll as well would be super easy with this system as you can just move the camera around.

Check out my article here on texture arrays in a voxel system: https://startoaster.wordpress.com/2015/05/23/texture-arrays-in-a-voxel-engine-part-1/

The idea is essentially the same for a 2D system as well your vertices would simply be different.

1 Like

DrawCell is only used once during a startup script, so the performance issue is just the pushing from CPU to GPU every frame. Thanks for the link, I’ll read up on that :slight_smile: . I had thought that using something like a mesh with a texture on would be the most efficient way, but I have absolutely no experience with shaders so didn’t know where to start.

I’ll probably be asking you a bunch of questions about this. I get the general idea but I’ve no idea in terms of implementation.

Cheers!

As Toaster is suggesting, it is generally a bad idea with Direct3D11 API to push thousands of draw calls, as this API don’t support this charge well (hence the work that has been done in Direct3D12 to better support this huge loads).
Depending on your use cases, you might need a dedicated renderer (not SpriteBatch) to make it more efficient.

If you want something simple, SpriteBatch with proper parameters should result in a single draw call.

If it uses same texture, SpriteBatch will concat all Draw() between Begin and End in a single dynamic vertex buffer and draw it at once.

Then of course, custom shader or texture update, etc… might be good options as well.

Thanks. It does use the same texture, and I’ve also tried what you suggest before. This kind of alleviates the problem. But as you zoom out you get to a large number of sprites (say 500,000 - 1,000,000) and the problem becomes the CPU time it takes to just iterate over all of those sprites becomes not insignificant. And from my understanding that iteration must happen every frame, as you can’t cache the result from between SpriteBatch.Begin and End?

So I think I will need to go the route of a custom shader. Especially when zooming out you don’t need to draw so many sprites, and some LOD style system will need to be implemented anyway.

Hopefully this thread isn’t too old to revive. I’ve just started trying to implement Toaster’s suggestion of creating a 2D tile renderer using a grid of vertices and then creating a shader to render the textures onto the grid. But I really don’t know where to start. Any pointers?

Personally I would proceed as follow:

  1. Draw your background using the SpriteBatch into TextureA (heavy part)
  2. Use TextureA as input for the rest of the game and draw them using entities and components (light part)

Benefits:

  • if your background does not change every frame you can skip (1) most of the time.
  • if you precisely know the regions that changed in your background you can update those regions only in (1) instead of re-drawing everything (lighter).

Great, thanks, that seems ideal. I’ve got the drawing to the GraphicsDevice using the SpriteBatch. Is there a way to draw it directly to a texture? Or once it’s drawn do I then need do something special to draw it into a Texture? Is there a sample that shows some of these concepts that I can take a look at and learn from?

Once I’ve got the texture I think there’s enough info in the samples to draw it using entities and components. At the moment I don’t have any plans for the background to change, but if I do want to expand that in the future it’s good to know it won’t be too hard.

The game world will probably be quite large though, so I imagine there will be quite a few of these textures held in memory, and I’ll have to set up some pooling of them to manage the player moving around the world etc. But I’ll cross that bridge once I get there :smiley: .

Thanks again for the advice.

To draw into a texture, just create a texture with “RenderTarget” flag, set it has the render target(GraphicsDevice.SetRenderTargets) and then perform your draw calls.
You can have a look at the RenderSceneToTexture sample to see how to do from the editor.

Thanks for all the help here!

I’ve implemented your suggestions about drawing into a texture @xenux, and I’m able to draw the equivalent of 1 million sprites using this method with no performance impact at all :slight_smile: .

This engine is really nice to work with! Thanks for all the hard work :smiley: .

EDIT:
If anyone is interested, I wrote a blog post about this - http://ways-of-old.blogspot.co.uk/2015/09/tiles.html