How to see why your draw calls are not batched in 5.6
I am sure that you know about Unity’s built-in Dynamic and Static Batching, which help reduce the number of draw calls in your game.
You can see Batching working in the Stats window when the “Saved by batching” count is greater than zero. But unfortunately it is much harder to tell why Batching is NOT working. The Unity Manual provides some information about why this might be happening, though you have to rely on an educated guess based on that information.
Fortunately, Unity 5.6 adds a new feature to the Frame Debugger which says exactly why Unity started a new batch.
Frame Debugger is a window in Unity which shows every batch in your game along with additional details about shaders, textures, and numerous settings used in a batch. The Frame Debugger window can be opened from the Window > Frame Debugger menu and has been available since Unity 5.0.
Before digging deeper into cases when Unity starts a new batch, let’s talk about what batches and batching are.
To draw an object on screen in your game Unity needs to issue a “Draw” command to the graphics API. This action is essentially called a “Draw Call”. But before doing this, Unity also needs to set all the GPU states required to draw this object: mesh, shader, textures, blending settings and other shader properties. State change commands plus one or more draw commands is what we call a Batch.
What makes a batch slow is the GPU state change commands, while draw commands are actually pretty cheap. This is why Unity tries to pack several objects being rendered using the same GPU state into one batch. This process is called Batching.
Unity has three types of batching: static batching, dynamic batching and GPU instancing.
- Static batching combines static meshes in one or more large meshes at build time and at run time renders them as one batch per mesh.
- Dynamic batching takes several small meshes each frame, transforms their vertices on the CPU, groups many similar vertices together, and draws them all in one go.
- GPU instancing (added in Unity 5.4) draws many identical objects with different positions, rotations, and other shader properties in fewer draw calls.
What breaks batching
Sometimes you can clearly see in the editor that objects you expect to be batched, for some reason, are not. First of all, check if batching is enabled in Player Settings. It sounds silly, but you won’t believe how many support requests we’ve received where this was the culprit.
Especially for you, we’ve prepared a sample project which illustrates all the cases when Unity has to start a new batch. Please clone it to your machine to follow along. Note that you need Unity 5.6 with the updated Frame Debugger to see the batch breaking causes illustrated in this project.
Here are batch breaking causes from the sample project (as of Unity 5.6). Each says why the object being rendered is in a separate batch:
- Additional Vertex Streams — the object has additional vertex streams set using MeshRenderer.additionalVertexStreams.
- Deferred Objects on Different Lighting Layers — the object is on a different light layer.
- Deferred Objects Split by Shadow Distance — one of the objects is within shadow distance, the other one is not.
- Different Combined Meshes — the object belongs to another combined static mesh.
- Different Custom Properties — the object has a different MaterialProperyBlock set.
- Different Lights — the object is affected by a different forward light.
- Different Materials — the object has a different material.
- Different Reflection Probes — the object is affected by a different reflection probe.
- Different Shadow Caster Hash — the objects either have different shadow caster shaders, or have different shader properties / keywords that affect the output of the shadow caster pass.
- Different Shadow Receiving Settings — the objects either have different “Receive Shadows” settings, or some objects are within the shadow distance, while some other objects are not.
- Different Static Batching Flags — the object has different static batching settings.
- Dynamic Batching Disabled to Avoid Z-Fighting — dynamic batching is turned off in Player Settings or disabled temporarily in the current context to avoid z-fighting.
- Instancing Different Geometries — rendering different meshes or sub-meshes with GPU instancing.
- Lightmapped Objects — the object uses a different light map or has different light map uv transformations within the same light map.
- Lightprobe Affected Objects — the object is affected by different light probes.
- Mixed Sided Mode Shadow Casters — objects have different “Cast Shadows” settings.
- Multipass — the object is using a multi-pass shader.
- Multiple Forward Lights — the object is affected by multiple forward lights.
- Non-instanceable Property Set — non-instanced properties are set for an instanced shader.
- Odd Negative Scaling — the object has odd negative scaling (e.g. (1, -1, 1)).
- Shader Disables Batching — the shader explicitly disables batching with the “DisableBatching” tag.
- Too Many Indices in Dynamic Batch — there are too many indices (more than 32k) in a dynamic batch.
- Too Many Indices in Static Batch — there are too many indices in the combined mesh of a static batch. The limit is 48k indices on OpenGL ES, 32k on OSX and 64k on other platforms.
- Too Many Vertex Attributes for Dynamic Batching — a submesh we are trying to dynamically batch has more than 900 vertex attributes.
- Too Many Vertices for Dynamic Batching — a submesh we are trying to dynamically batch has more than 300 vertices.
Now you can use this new exciting Frame Debugger feature in your project and see if there’s something you can do with your scene to improve how Unity batches objects.
Please follow the project on Github for updates if new batch breaking causes are added as the engine evolves.