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);
}
}
}
}