2D Rendering Performance

Godot Version

v4.2.1.stable.mono.arch_linux [b09f793f5]

Question

Am I doing something terribly wrong (I hope I am), can I get a massive performance increase by optimizing this or is this just how things are in Godot? My assumption would be that drawing a texture a few hundred times or even a thousand times should not tank my performance this much.

Context

I’m working on a game that requires a lot of enemies to be on the screen at once and it did not take long for me to run into lag. I first noticed performance issues when I had a few hundred enemies at once. That’s when I started profiling and reading the Optimization using Servers page and figured I’d just avoid the scene tree and use the RenderingServer. That improved it slightly, but after a few days of fiddling around and getting nowhere I thought I should make sure that the lag was not caused by something else in my project.
I created a new empty project and wrote a little test (code below). My plan was to create a small grid of the default Godot icon.
I tested this using the Sprite2D node and the RenderingServer, both yielding similar results.
The test results (FPS):

  • Empty scene: ~4200
  • 16x16 grid (Sprite2D): ~2000
  • 16x16 grid (RenderingServer): ~2100
  • 64x64 grid (Sprite2D): ~250
  • 64x64 grid (RenderingServer): ~270

I also tested this with plain old circles instead of a texture and the results were similar.

Code

RenderingServer method

using Godot;

public sealed partial class Renderer : Node2D
{
    private const uint GRID_SIZE = 64;
    private const float SIZE = 16.0f;

    [Export] public Texture2D texture;
    private Rid _rid;
    private Rid _canvasRid;


    public override void _Ready()
    {
        _canvasRid = RenderingServer.CanvasCreate();
        _rid = RenderingServer.CanvasItemCreate();
        RenderingServer.CanvasItemSetParent(_rid, _canvasRid);
        RenderingServer.CanvasItemSetMaterial(_rid, new CanvasItemMaterial().GetRid());

        Rid textureRid = texture.GetRid();

        for (int x = 0; x < GRID_SIZE; x++)
        {
            for (int y = 0; y < GRID_SIZE; y++)
            {
                RenderingServer.CanvasItemAddTextureRect(_rid, new(x * SIZE, y * SIZE, SIZE, SIZE), textureRid);
            }
        }

        RenderingServer.ViewportAttachCanvas(GetTree().Root.GetViewportRid(), _canvasRid);
    }
}

Sprite2D method

using Godot;

public sealed partial class Renderer : Node2D
{
    private const uint GRID_SIZE = 64;
    private const float SIZE = 16.0f;
    private const float GODOT_LOGO_SIZE = 128.0f;

    [Export] public Texture2D texture;


    public override void _Ready()
    {
        const float scale = SIZE / GODOT_LOGO_SIZE;
        const float offset = SIZE / 2.0f;

        for (int x = 0; x < GRID_SIZE; x++)
        {
            for (int y = 0; y < GRID_SIZE; y++)
            {
                Sprite2D sprite = new();
                sprite.Position = new(x * SIZE + offset, y * SIZE + offset);
                sprite.Texture = texture;
                sprite.Scale = new(scale, scale);

                AddChild(sprite);
            }
        }
    }
}

I encountered the same problem. I don’t understand your scene requirements, so I can only recommend you to use MultiMeshInstance2D. It can render at least 30,000 meshes per frame (on my device) and maintain 80FPS.

Another way is to use Tilemap, which can always render a lot of tiles. I hope my reply can help you.

Hey, thanks for the reply. MultiMeshInstance is looking promising. Do you know if I’d be able to change custom shader variables on specific elements using MultiMeshInstance or would they be shared?

Yes, you can set Transform or Color for specific Meshes, but you cannot set a specific Texture for each Mesh.

1 Like

Thanks! You were very helpful.
I’ve been messing around with the MultiMeshInstance2D and it seems like it’ll work for my game.