Search Unity

  1. Good news ✨ We have more Unite Now videos available for you to watch on-demand! Come check them out and ask our experts any questions!
    Dismiss Notice

Is there a way to batch meshes that are drawn with CommandBuffer.DrawMesh?

Discussion in 'General Graphics' started by Shutter, May 7, 2016.

  1. Shutter

    Shutter

    Joined:
    Feb 6, 2016
    Posts:
    22
    Hi there, I am trying to draw a great number of meshes on screen with CommandBuffer.DrawMesh, in order to not have to create a GameObject for each mesh (and yes, it has to be directly with the command buffer).

    I think Unity has made a great improvement when provided us with lower level accessibility like it is the case of the command buffer. However, drawing meshes that way has one very big pitfall: meshes are not batched anymore, neither statically, nor dynamically. Hence, and due to the scarcity of articles and tutorials out there on how to achieve more advanced endeavors with the command buffer DrawMesh command, I came to ask you experts how one could implement batching in such a case.

    Be it high or low level, I am fine learning either. Many thanks for your time.
     
    stett likes this.
  2. Ewanuk

    Ewanuk

    Joined:
    Jul 9, 2011
    Posts:
    246
    Would like to know the same thing.

    It seems silly that it wouldn't batch the calls together when you are drawing the same mesh with the same material over and over.
     
  3. stett

    stett

    Joined:
    Sep 21, 2016
    Posts:
    2
    I'm also interested in this - I'd like to have more direct control over batching in some cases. It would be super cool if I could call DrawMesh and give it an enumerable of Transforms or something like that. There are probably reasons that it would have to be more sophisticated than that though :\
     
  4. forestrf

    forestrf

    Joined:
    Aug 28, 2010
    Posts:
    102
    Even if it is not possible to batch meshes, being able to call multiple times DrawMesh with the same material without having SetPass calls increasing would be awesome, because right now calling DrawMesh on a commandbuffer always triggers a new SetPass
     
  5. gcaseres

    gcaseres

    Joined:
    Jun 17, 2015
    Posts:
    8
    I'm interested in this. Anyone has information about batches with command buffers?
     
  6. cmann

    cmann

    Joined:
    Aug 1, 2015
    Posts:
    30
    Also interested. But I guess you have to do some kind of manual batching yourself, if that's even possible.
     
    Last edited: May 28, 2019
  7. Ludiq

    Ludiq

    Joined:
    Mar 6, 2015
    Posts:
    675
    Bumping this, also very interested. Sorry for the ping but, maybe @Aras would know?
     
  8. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    2,360
    Use CommandBuffer.DrawMeshInstanced if your platform supports it. Should work out better in performance terms where it is available.
     
    Ludiq likes this.
  9. capkoh

    capkoh

    Joined:
    Oct 1, 2016
    Posts:
    17
    Any news on this topic? Unity added new SRP Batcher and it works great. But again there is no way to use it with Command Buffers. So, no custom render pipeleline possible.

    Command Buffers + Dynamic Batching = No
    Command Buffers + SRP Batching = No

    It's really sad that Unity continues to ship half-baked technologies.
     
    Ludiq and forestrf like this.
  10. Ludiq

    Ludiq

    Joined:
    Mar 6, 2015
    Posts:
    675
    Also still interested in this!

    EDIT: Did more research and testing.

    My conclusions are that:
    • CommandBuffers understandably do not support dynamic batching by design -- Graphics.DrawMesh does not either --, so reducing the Batch/DrawCall count is not possible unless you explicitly use DrawMeshInstanced.

    • However, there seems to be no good reason why CommandBuffers need to call SetPass when calling DrawMesh multiple times in a row with the same material, and that seems like a bug.

    • [PerRendererData] or adding proper mesh instancing support to your shader/material does not fix the issue. Even using a single pass material with this setup and no MaterialPropertyBlock will cause multiple SetPass calls to occur.

    I'd be tempted to report this as a bug but I'm not 100% confident in my understanding of the graphics pipeline, I'd appreciate if someone chimed in and confirmed my hunch.
     
    Last edited: Sep 9, 2020
  11. StaggartCreations

    StaggartCreations

    Joined:
    Feb 18, 2015
    Posts:
    1,187
    When using CommandBuffer/Graphics.DrawMesh, you're doing rendering outside of the built-in render loop (the one that draws Mesh Renderers, particles, trails, etc). So any of those rendering calls aren't going to be batched, because Unity doesn't know anything about them. The only way to do so is to use Monobehaviours with Renderer components, but that defeats the purpose of drawing through script ;)

    DrawMeshInstanced allows you to draw a batch (max 1023) of meshes through an array of matrices, paired with a single mesh, material and MaterialPropertyBlock (properties have to be arrays though). This does mean you have to write your own batching system, in order to pair mesh/material combinations into batches. This all hinges on GPU Instancing, which the shaders need to support, so isn't dynamic batching per se.

    Note that the matrices array has to have a constant size of 1023, failing to do so will crash Unity instantly. The same counts for any arrays you set on the MaterialPropertyBlock. A little undocumented caveat...
     
  12. Arycama

    Arycama

    Joined:
    May 25, 2014
    Posts:
    129
    Depending on what Unity verison you're using, you should use BatchRendererGroup. From what I can tell, it's designed to replace Graphics.DrawMesh/DrawMeshInstanced etc.

    From a Monobehaviour, you create a new "BatchRendererGroup" OnEnable/Awake. (Works from [ExecuteAlways] editor scripts too, as long as you pass the correct flag to the sceneViewCullingMask in the constructor)

    Then you add "Batches" to the BatchRendererGroup, which will look similar to Graphics.DrawMesh. Eg you pass in a Mesh, Material, Bounds, Lighting flags, etc.

    The last thing is a custom culling callback, where you can do culling to determine the visibility of each mesh. Though it can be a bit tricky to get the hang of, it does allow you do use Jobs for the culling which means it can be very efficient.

    There's a couple of examples online of using it, although in other languages. Though google translate was helpful enough for me to figure it out. I don't have any simple sample code to share though unfortunately.
     
  13. Ludiq

    Ludiq

    Joined:
    Mar 6, 2015
    Posts:
    675
    Thanks for chiming in, but as I mentioned in my prior post I'm aware that Unity can't possibly do automatic draw call batching for us here. I'm wondering about the SetPass calls, which seem to be useless, because Unity is calling SetPass even when drawing with the same material multiple times in a row. Because the CB knows about that, it could (and I guess should) skip SetPass on all subsequent draw calls that haven't changed the material. This is, I believe, the behaviour you would get when using Graphics.DrawMesh.

    In other words: CommandBuffer.DrawMesh doesn't benefit from the same basic optimization as Graphics.DrawMesh (calling SetPass only once when drawing the same material multiple times). This is what I believe is a bug. Does that make more sense?

    Thanks for the suggestion, I see you also chimed in in another thread, but my question is the same: how does BatchRendererGroup even work with CommandBuffers? It doesn't seem suited for that purpose at all, it seems to be made for ECS and SRPs.
     
  14. StaggartCreations

    StaggartCreations

    Joined:
    Feb 18, 2015
    Posts:
    1,187
    Ah, I see, I was addressing the OP's question, and failed to look at the dates. The stats window can be finicky, I believe it only works correctly for the built-in render loop. DrawMeshInstancedIndirect calls don't affect it at all for example. When using the SRP batcher sometimes it reports a negative amount of drawcalls.

    I think if you are calling DrawMesh, you are always including a pass index, so it's possible this internally amounts to a SetPass call. If the previous pass is the same as the current, this probably isn't necessary, logically speaking. But for all Unity knows, you are setting a different pass. If this behaviour is different between Graphics and CommandBuffer it could be a bug, or some logical explanation eludes us @richardkettlewell?

    I don't know if this actually affects performance or not, it could be changing the render state, or it's simply (re)setting a value over and over again.
     
  15. Ludiq

    Ludiq

    Joined:
    Mar 6, 2015
    Posts:
    675
    According to this 2020 guide, minimizing SetPass calls does have a significant positive performance impact:
    https://thegamedev.guru/unity-performance/draw-call-optimization/#tab-con-19

    I just tested the same render (about 8k cubes, same material) with Graphics.DrawMesh instead of CommandBuffer.DrawMesh.

    Unity 2019.4 LTS.

    Graphics.DrawMesh:
    • As expected, only 1 SetPass call
    • Actually dynamically batches?! (only 1 draw call)
    • Actually automatically does frustum culling?
    • Render thread: 0.5ms
    CommandBuffer.DrawMesh:
    • As many SetPass calls as draw calls (8k SetPass calls)
    • No dynamic batching (8k draw calls)
    • No frustum culling
    • Render thread: 5.0ms
    This makes CommandBuffers a lot less appealing; curious to know whether that is intended behaviour.
     
  16. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    2,360
    Graphics API is probably hooked to the same render pipeline entrypoints that handles them as default renderer batches.

    Well, Unity kinda expects you to perform these operations on your own (batching and culling) if you're using CommandBuffers. (That was mentioned in blogs, forum threads and I saw it somewhere in the manual)

    CmdBuffers bypass rendering pipeline, and work as extension of it.
    Dynamic batching doesn't work with them afaik.

    I'm pretty sure that's intentional, although I might be wrong, and someone from actual UT should comment on this.

    Regarding SetPass (and built-in render pipeline):
    It will always affect your performance, so make sure to keep its count low as possible.
    (CPU sending textures from RAM to VRAM and then switching rendering context is costly. DrawMeshInstancedIndirect actually uploads its only once to the GPU VRAM, and the switch cost is way lower, but it isn't supported everywhere. SMv4.5+ required (I think?) for the compute shaders to work properly)

    TL;DR: Best bet for the CmdBuffers is DrawMeshInstanced where supported, and a Graphics DrawMesh + Dynamic Batching fallback on the platforms that do not support it. You can use SystemInfo.supportsInstancing to figure it out in runtime.

    But that requires <300 vertex meshes though. So it might be wise to just ignore these platforms, or pick single solution.

    Frame debugger does show correct information for the built-in pipeline.

    There's a few caveats for the SRP, but I'd say its temporary issues.
     
    Last edited: Sep 11, 2020
  17. Ludiq

    Ludiq

    Joined:
    Mar 6, 2015
    Posts:
    675
    I suspected as much, but then in that case I would expect one of the following:

    1. Provide an API for CommandBuffer.DrawMesh that does not automatically call SetPass, like DrawMeshNow
    2. Provide an API for CommandBuffer that applies a MaterialPropertyBlock without calling SetPass

    This way, we could manually batch our SetPass calls together and still use MPBs (which is what they're made for). I don't mind having full control, but then we need the tools to actually optimize if Unity isn't doing it for us behind the scenes.

    I could use DrawMeshInstanced, but that does not allow me, for example, to use different MaterialPropertyBlocks within one SetPass call; I'd have to use instancing, which is a really different approach that's not as well supported.
     
unityunity