Reducing Draw Calls

While balancing optimizations and a good development workflow

Note:

What's a Draw Call?

In short, a draw call is the CPU telling the GPU that it is going to send it a bunch of data on how to draw something.

What went wrong

My workflow for creating entities is a simple Blender to Godot workflow. I create the rig in blender, keep the modifiers (they are applied on export), setup IK for the rig, etc. I would keep the meshes seperate for each major part of a model (for example: the crawler has an eye mesh, body mesh, legs mesh). Each mesh also had their own texture slots (~4 each). Having everything seperated made the iterative workflow of model -> export -> import to godot -> repeat until satisfied very fast, and I can come back anytime to adjust and it is fast.

That's where the good stops. It's convenient for iterating fast, but terrible for the game. Recall that a draw call is the CPU telling the GPU how to draw a shape using a specific material. Since the model consisted of an armature with 3 child meshes, exporting it to Godot does not join the meshes automatically. Instead, the 3 meshes remain seperate in Godot. This is bad. Each mesh is one draw call in itself, so this means that a single rig (consisting of three meshes) is being drawn three times. But that's not it. Remember that there are materials. A mesh is the physical geometry, but what about the textures, lighting, etc? The material determines that. Recall each mesh had 4 materials. The three draw calls mentioned earlier only accounts for 1 material. This means that for each mesh, it had to be drawn 4 times, and there are three meshes. This means that the model was being drawn nearly 12x as much as it needed to be! In short:

So, what's the fix?

The solution used in TaikaSteam, has increased performance by roughly 6x (50FPS -> 300FPS), and it is rather simple. The currrent optimizations applied simply aim to reduce the draw call. Previously at 150 entities there were ~1200 draw calls. Every frame. That's a lot. now, with 150 entities there are 150. Big difference, this was solved by two simple things: texture baking, and joining meshes. Previously, each rig had 3 meshes, in blender that is easily brought to one by appyling modifiers and then joining the meshes. Now theres only 1 mesh in a rig, that already reduces the draw calls by 2. Next, texture baking is the process of converting all textures into one single image that maps to the whole mesh. The method is simple (a little bit of setup, but one button that says "bake"). The issue is that while simple, it does take a while. It's a process that has to be done every time a rig is updated, which takes time and time is quite an important resource for an indie dev, but it's not a dealbreaker.

Quick Optimizations

Misc Optimizations