Enabling the Universal Render Pipeline (URP) automatically enables a powerful rendering optimization feature called SRP Batcher. This is a core technology that minimizes the CPU overhead caused by draw calls, but if there is a setting error or the way the shader is written, it will no longer function (determined as incompatible), and performance will deteriorate rapidly. This article provides an easy-to-understand explanation of the internal mechanism by which non-conformity bugs occur in SRP Batcher, using the analogy of a chef's cooking process, and presents the correct solution approach to achieve a 100% conformance rate.
1. Understand with an analogy: The waste of ``a chef's worktop arrangement''
Let's consider the operating principle of the SRP Batcher and the waste when it becomes unsuitable using the analogy of ``the cooking process of making many hamburgers in a row in a kitchen''.
The cook (CPU) needs to serve a large amount of hamburgers to the counter (GPU). However, every time an order is received (an object is drawn), the chef has to clean up all the frying pans and knives (shaders and drawing states) on the cooking table, clean the sink again, and then pull out and set the same tools again to make the next hamburger steak. This wasted time spent on "loading and cleaning tools" is CPU state binding overhead in SRP Batcher Incompatibility. Chefs spend more time cleaning up and preparing food than they do preparing the food itself.
On the other hand, when SRP Batcher is compatible, heavy tools (shader cords) such as frying pans and knives remain firmly fixed to the cooking table. Then, depending on the incoming orders, we quickly change the topping sauce and ingredients (material parameters) and continue baking. Since no cooking utensils are put away, the cook (CPU) can continuously send food (draw calls) to the counter (GPU) at ultra-high speed without having to take a breather.
The SRP Batcher incompatibility bug is a bug in which the shader property is defined incorrectly, causing the system to judge that ``this material cannot use the fast cooking method of replacing only the parameters without cleaning up the utensils (Incompatible)'', and executes an inefficient method of forcing the cooking table to be cleaned up clean every time (heavily resetting the state every time a draw call is made).
2. Solution A: Correct declaration of CBUFFER in your home-built HLSL shader
If you are creating your own shader with hand-written HLSL code instead of using Shader Graph, the most common cause of non-compliance is incorrectly declared property variables. All material properties must be grouped together in a constant buffer block named `UnityPerMaterial`.
Code before modification (nonconforming):
// It is inconsistent because variables are declared separately outside the CBUFFER block.
float4 _BaseColor;
float _Glossiness;
float _Metalness;
Modified code (conforming):
// ★Super important: Wrap it in a CBUFFER block named UnityPerMaterial
CBUFFER_START(UnityPerMaterial)
float4 _BaseColor;
float _Glossiness;
float _Metalness;
CBUFFER_END
*Texture objects (`Texture2D`) and sampler states (`SamplerState`) are resource registers that cannot be stored in constant buffers, so there is no problem writing them outside this CBUFFER block (global scope).
3. Solution B: Completely eliminate the use of MaterialPropertyBlock in C# scripts
A common trap that programmers fall into is calling `Renderer.SetPropertyBlock(MaterialPropertyBlock)` to dynamically change the color or material parameters of individual game objects from C#. In traditional built-in render pipelines, this was the recommended technique to reduce draw calls, but inURP's SRP Batcher environment, objects with MaterialPropertyBlocks are forced to be SRP Batcher incompatible.
The currently recommended resolution approach is:
- Using individual material instances: Duplicate and update materials using `renderer.material.SetColor` etc. directly from the script. SRP Batcher automatically batches draw calls even if the material instances are separate, as long as the shader is the same and `UnityPerMaterial` CBUFFER is maintained.
- Using Vertex Color: By designing the shader to receive vertex colors as input, and updating the vertex data of the mesh itself from the C# side, individual colors can be expressed without rewriting any material parameters.
This allows you to benefit 100% from SRP Batcher's lightning-fast batching, even when applying dynamic changes in C# scripts.