Skip to content

Merging and Batching Draw Calls

SolarLune edited this page Jul 7, 2022 · 5 revisions

Draw Calls

A draw call is when Tetra3D sends triangle data to the GPU to render. This happens for each renderable portion (a MeshPart) in a Mesh. Meshes are usually split apart into MeshParts according to material slots. If a single material slot is used across an entire Mesh, then all its triangles will belong to a single MeshPart, and so will render in a single draw call (which is good, because it's much faster to batch many triangles together into fewer draw calls than to send fewer triangles to the GPU in more draw calls).

Depth testing / intersection has been implemented, but only works between draw calls. However, by using multiple materials on an individual model, you can make it render in multiple draw calls, thereby allowing depth testing between portions of the model, if necessary.

Merging

You can lower draw calls by statically merging Models together using Model.Merge(). When statically merging models together, the merged Models' mesh data gets "absorbed" into the calling Model's mesh. This means that you wouldn't be able to move merged models around after merging.

When merging Models together using Model.Merge(), the receiving Model will reuse its existing MeshParts to merge any passed Models' MeshParts as necessary if:

  1. a MeshPart with the needed Material already exists, and
  2. Merging another MeshPart into it won't exceed the triangle limit (21845) / vertex limit (65535).

Otherwise, it will make a new MeshPart as necessary, which means another draw call. In other words, it will be as efficient as possible, and you won't generally have to worry about the results after calling Model.Merge(). However, to get the best effects, you want the objects merged to share Materials (see the stress demo). If they have unique materials, then they will all necessitate their own MeshParts, which means you won't get any real benefit from merging them together.

Dynamic Batching

Dynamic batching is another tool in the belt of rendering optimization. While not as flexible as static merging and in a bit of a pre-alpha state, it's still rather useful.

First, we'll walk through the way rendering Models in Tetra3D works - essentially, after we've fully prepared all data for rendering in Render(), we use Ebitengine to render our object - this is done through the Flush() function.

Object A:
  MeshPart 0:
    Render()
    Flush()
Object B:
  MeshPart 0:
    Render()
    Flush()
Object C:
  MeshPart 0:
    Render()
    Flush()

... And so on. However, if we're rendering a lot of objects with a single MeshPart and similar visual information, we can avoid flushing until all such objects are rendered, saving a lot of time in the process:

Object A:
  MeshPart 0:
    Render()
Object B:
  MeshPart 0:
    Render()
Object C:
  MeshPart 0:
    Render()
Flush() - (Using some common Material setup)

While not as fast as static merging, dynamic batching is very good for saving time if you have a lot of models that you want to render with a single texture and material setup while maintaining their dynamic nature (i.e. they can move around individually, they can be animated individually, and so on). Note, though, that there are restrictions to dynamic batching:

  1. Dynamically batched Models can't have individual material / object properties (like object color, texture, material blend mode, or texture filtering mode). Vertex colors still work for fading individual triangles / a dynamically batched Model, though.

  2. Dynamically batched objects all count up to a single vertex count, so we can't have more than the max number of vertices after batching all objects together into a single Model (which at this stage is 65535 vertices, or 21845 triangles).

  3. Dynamically batched objects render using the batching object's first meshpart's material for rendering. This is to make it predictable as to how all batched objects appear at all times.

  4. Because we're batching together dynamically batched objects, they can't write to the depth texture individually. This means dynamically batched objects cannot visually intersect with one another - they simply draw behind or in front of one another, and so are sorted according to their objects' depths in comparison to the camera.

  5. Dynamically batched Models all share the same singular texture and all MeshParts of batched Models will render when a Model is batched.

Clone this wiki locally