May 23 2023

Static Batching May Not Reduce Your Draw Calls

We are going to talk about “the boring” static batching… again.

Yes, one of the most commonly spoken about topics when it comes to performance optimization. “Just use static batching and forget about the rest, right?“.

→ BS ←

I assume that you know already a bit about static batching. It’s everywhere: in the news, in the newspapers, magazines, ads..

What static batching does is to “combine” your objects together so that you make the life of your CPU and GPU easier. This is important when you have many different objects, because rendering them individually isn’t profitable business.

One example that I give my students is: imagine you’re at home and you have some sort of bookshelf. You have like maybe 40 books. If you were to give all these books individually to the GPU to render, that will be very expensive, right? Both for the CPU and the GPU. So what we’re going to say is, you know, instead of thinking about each book individually, we’re just gonna say here’s a stack of books.

That’s it. Then the CPU and GPU, you know, they can use their energy and other things rather than to think of individual objects. This is static batching in a nutshell.

But since you know already about it, there is nothing else to speak about, correct?

Wrong. Otherwise I wouldn’t have written this post.

First thing. Using static batching is all about:

  1. Setting your game objects as batching static
  2. Enabling static batching in the player settings
  3. Let Unity apply static batching to all objects that share materials

Second: static batching will NOT necessarily reduce the number of draw calls, even if you fullfill all the requirements. This is because of the way that static batching internally works.

I go in detail about static batching internals in the Performance Taskforce membership, but in a nutshell it works with two buffers:

  1. Vertex buffers: with attributes like position, normal, color, UVs, all of that.
  2. Index buffers: this buffers lets us pick which specific vertices to use from the vertex buffers while rendering.

This is the way to draw stuff in modern graphics (and actually not so modern, just not… super old).

Okay, so we have two buffers, and in order for Unity to draw stuff on the screen with static batching, Unity just goes here and says: “okay, so what do I have on the screen? I have this bucket, this floor, this brush, and whatever”. So now Unity applies frustum culling and occlusion culling to determine what we actually could “see” and should therefore render.

Do we see the rogue, or the assassin behind me, who’s about to backstab? Nope.

Now here’s the key…

In order for Unity to draw this list of objects, Unity must specify a range or a subset of elements within these two buffers to draw. Otherwise we would draw everything.

And if different elements are in non-consecutive regions of that buffer, well, that’s a problem because then we have to draw multiple times specifying different regions of this buffer.

This is why sometimes static batching does not reduce the number of draw calls. Because we need to draw geometry that exists within different areas of the index buffer that we cannot draw consecutively.

Is this a problem?

Nah. It may look like because hey, we have more draw calls than we need. But the key is this: we are still drawing from the same buffer. Therefore we are not changing the GPU state, which is what is actually expensive.

Just like if you have your wallet full of coins in your pocket. Let me use euros, because I live in Europe. I have a coin of five cents, another coin of ten cents, another coin of one euro, another for two euros. The effort for me is to take the wallet of my pants and open the coins compartment. That’s the effort, but once it is open, I just need to take the two euro coin of five cents coin, it’s super easy, right? I just need to just introduce two fingers and just look for that and fetch it, right

Same with GPUs. The effort of binding to this buffer (or taking the wallet) has already been put. So this is why static batching, even if it’s not going to always reduce your draw calls, is going to be extremely beneficial for your performance.

Obviously, this is not going to work if your geometry is not here on the level beforehand. So if I just design my level procedurally you’re not going to be able to use static batching out of the box. For that, you have other types of API calls in which I go in detail in a few modules of the Performance Taskforce membership.

I hope that you discovered something new and enjoy your rest of the week.

Take care.

Ruben (TheGameDev.Guru)

The Gamedev Guru Logo

Performance Labs SL
Paseo de la Castellana 194, Ground Floor B
28046 Madrid, Spain

This website is not sponsored by or affiliated with Facebook, Unity Technologies, or Gamasutra.

The content you find here is based on my own opinions. Use this information at your own risk.
Some icons provided by Icons8