If I'm understanding things correctly (please correct me if I'm wrong):

  • All modern graphics APIs (vulkan, dx12, metal, webgpu) provide ways to bundle and replay draw calls. The bundle includes which vertices + indices for all bundled draw calls, so frame to frame you're not changing which triangles are drawn in the bundle. This saves a ton of CPU work.
  • With frustum/occlusion culling, each frame, you would typically test each object against the view pyramid and skip the draw calls for objects that are outside the view.

How should these work together? With bundling, you lose the fine-grain ability to decide which sets of triangles to draw, so how do you achieve frustum culling?

The only option I can think of is to have many bundles, ideally of co-located objects, and maintain some bounding volume for the whole bundle and frustum cull at the bundle level.

But it seems difficult to maintain co-located sets of objects. Are the bundles anchored to a region of space and objects move in and out of that bundle? That seems like it'd cause a lot of rebundling.

How do people solve this, or what part of the problem am I misunderstanding?

Edit, more context: I'm building my first renderer using WebGPU which calls this "bundling"[0][1] and since this sits on top of {DX12,Metal,Vulkan}, I assume all these APIs can implement this. My understanding is that bundling is one of the big performance wins in webgpu over webgl. (Note: there doesn't seem to be a webgpu tag on this site yet.)

[0] https://gpuweb.github.io/gpuweb/#bundles

[1] https://austin-eng.com/webgpu-samples/samples/animometer (Chrome only; see "bundle" option)


1 Answer 1


If by bundles you mean secondary command buffers, I think the answer is that people generally don't use them, or at least not in the sense you mean. They might be used to parallelize command recording across threads, but not to reduce draw-call-related overhead because, as you mention, the set of things to be drawn in a frame changes constantly, due to both frustum/occlusion and also LOD switching, as well as any moving objects or other changes that may be happening in the rendered world.

There are other ways to address draw-call overhead, including:

  • Pull together vertex and index buffers for different objects into larger buffers, so you're not constantly having to switch between buffers. You can issue draw calls with the starting vertex offset and index offset parameters to draw a subset of vertices/triangles from a larger buffer.
  • Similar things can be done for constant buffers, bindless textures, etc. Usually it's not the individual draw calls that create CPU overhead so much as the state changes between them.
  • Use instancing and/or indirect draws (ExecuteIndirect, vkCmdDrawIndirect, etc) to execute a large number of draw calls with one API-level command.
  • Move culling and LOD selection to the GPU ("GPU-driven rendering"), taking advantage of GPU compute to offload those tasks from the CPU.
  • 1
    $\begingroup$ Thanks! This list is very useful. I misunderstood what people actually use bundling (a dx12 term?) for. And the vulkan term "secondary command buffers" got me a bunch of helpful web results. $\endgroup$
    – Daryl
    Oct 7, 2022 at 19:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.