There are a bunch of objects with mesh renderers, with small vertex counts, all using the same material: Mobile/VertexLit. I noticed that when those objects appear, the draw calls drastically increased, so I enabled and frame debugger and see what’s going on. And those objects are not batched properly, and the reason for the batch failure was “Objects are rendered using different rendering functions”.
This thing eventually increases the draw calls to 15, which is quite a lot. My game is for Android so I have to reduce it as many possible. Those objects only have MeshRenderer and use all the same material. What exactly caused this batching failure and what’s the meaning of the failure reason?
15 is not “quite a lot”. 15 total “draw calls” is so few as to be almost completely negligible. Even an increase of 15 is negligible. On mobile I might call 100 “a lot”, and over 500 “quite a lot”.
Even for extremely well optimized VR Mobile games from 6 years ago, a game with 100 “draw calls” was considered well optimized. If you’re fretting over a total of 15, you’re wasting any effort trying to get rid of them.
Does it really matter the 15 draw calls are ignorable? My question is why the batch was a failure that can be done with a single draw call divided into 15 draw calls. Also, I never said the overall draw calls are 15, the draw calls from the single material are 15. And I don’t think trying my best to optimize the game, even it’s unnoticeable, isn’t wasting my time.
Saying something “increases to” implies that’s the final total.
If that isn’t the total, what is your total? If you’re under 100 total “draw calls” you’re still probably wasting your time looking into draw calls unless you’re profiling the game on a target device and you’re seeing that the render thread is specifically your game’s performance bottleneck. Before spending time on trying to optimize “draw calls”, it’s best to profile the project and see where the bottlenecks are and attack those directly. For mobile it can often be c# side things, or just the total number of objects in a scene, the later of which can end up optimizing your rendering & “draw calls” by simply removing extra objects or making use of LOD Groups to cull objects in the distance.
Note, I keep putting “draw calls” in scare quotes, because draw calls themselves are not necessarily expensive. Most likely you’re actually referring to set pass calls, as that’s what’s being counted in the game’s Stats window. The Frame Debugger is showing what it calls “events”, which can be either set pass or draw calls, or some other things, and are grouped manually by what Unity thinks are important rather than actually being every single draw call. The number of events won’t 1:1 match the set pass number you see in the Stats window because they’re not necessarily the same thing. This is pedantic, but important, because something like a static batch may actually be hundreds or thousands of actual draw calls if you are to profile the game using RenderDoc or your mobile device’s GPU profiler, but each draw call is itself nearly free. What is expensive is changing the render state between draw calls, which is any time the mesh, shader, or material / shader properties need to change. To be fair, I use “draw call” as a shorthand when I’m referring to set pass calls too, as they do often happen at the same time, but it is not strictly accurate most of the time when discussing optimizations.
Now to the actual issue. It is totally possible that a single simple mesh with a single material can increase the set pass count by 15. You can easily get to that number depending on your scene’s lighting setup, what kind of post processing you have enabled, and if you’re using a custom shader with multiple passes. However you’re using the Mobile/Vertex Lit shader which should remove most of those cases. You do not say how many meshes, or how complex the meshes are, or if these are dynamic or static meshes. Post processing is also still potentially an issue here.
I also don’t think I’ve ever seen the reason why meshes aren’t batching be “Objects are rendered using different rendering functions” with mesh renderers that are placed in a scene. To me that says there’s something else in your project that’s rendering the meshes manually from c# rather than these meshes getting rendered through Unity’s default rendering systems.
So, some questions I’d ask are:
Are these meshes set to static? If they’re not and you have dynamic batching enabled, how many vertices do they have? Dynamic batching is limited to meshes with fewer than 300 vertices, or 900 vertex attributes (which for the Mobile/Vertex Lit shader will equate to that same 300 vertex count as the shader uses 3 mesh attributes, position, normal, and uv).
Are you using lightmapping? Dynamic lights? Are there objects in the scene not using the Mobile/Vertex Lit shader?
In the Frame Debugger, what event group are you seeing these extra draw calls listed under? Because if it’s not “RenderForward.RenderLoopJob” then there’s something else happening.
Also in the Frame Debugger, what kind of event is it, and what shader is being used? The event type and shader should be right at the top of the Frame Debugger’s list of information. Something like “Event #20: Draw Mesh” and “Shader Mobile/VertexLit, SubShader #0”. I would expect it to show as a Draw Mesh or Draw Dynamic event. If it’s a different event and/or shader, what are they?
Our mesh renderers should never produce that message, because they all use the same internal rendering function. There must be a non-mesh being drawn before them? Or it’s a bug!
That message is intended for other renderers such as particles, which use a different rendering function when sprite masking is being used.
If you think it may be a bug, I’d be happy to look at a bug report for it with a repro project.
Just figured out the problem. Even those objects are using the same material, some of them used SkinnedMeshRenderer. After I separated a material for those SMR, now batching works and no more “using different rendering functions”.
If the message was something like “SkinnedMeshRenderer and MeshRenderer can’t be batched” kind of stuff, would be easily noticed the problem because I never knew they can’t be. Anyway, thanks.