Vulkan conditional rendering

Introduction

Note: Source code that demonstrates this feature can be found in this new example at my open source C++ Vulkan examples repository.

With the new VK_EXT_conditional_rendering extension, Vulkan gains the possibility to execute certain rendering and dispatch commands conditionally, based on values stored in a dedicated buffer.

So instead of having to rebuild command buffers if the visibility of objects change, it’s now to possible to just change a single buffer value to control if the rendering commands for that object are executed without the need to touch any command buffers. 

This article and the example will present a simple use-case for this feature, but as with other buffer-based rendering conditions like indirect draw it’s also possible to update such a buffer using compute shaders, not needing any roundtrip to the host at all.

Static command buffers

For this sample we will be rendering the hierarchical node structure of a glTF model:

Drawing nodes is done by a function like this that recursively draws the nodes of the glTF model:

Which is then called at command buffer creation time for all root nodes in the scene:

With this traditional setup, changing visibility of a single node would require you to rebuild the command buffer. 

The conditional buffer

As mentioned in the introduction a buffer is used to conditionally execute the rendering and dispatch commands. So the first step is setting up this buffer. As this is similar to setting other buffers like uniform, vertex, index and shader storage I won’t go into detail.

The only really important parts here are:

  • The new buffer type  VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT is introduced 
  • The buffer format is fixed to consecutive 32-bit values
  • Offset is also aligned at 32-bits

The later one makes this a good match for your typical C/C++ host structures, e.g. a simple vector:

Setting up the buffer itself will then look like this:

With this we get a buffer that matches the size and layout of the host application. Note that for better performance and more complex use-cases you’d create a device local buffer instead and either update using staging or via command buffers.

Adding conditional execution

The VK_EXT_conditional_rendering extension introduces two new functions that allow us to mark regions of a command buffer for conditional execution:

and

Wrapping drawing and dispatch commands in such a region means that they will only be executed if our conditional buffer contains a non-zero value at the given offset.

A basic example of this would look like this:

The conditionalRenderinBeginInfo  structure contains the parameters used by the vkCmdBeginConditionalRenderingEXT  function to determine if the commands in that region are to be executed.

So for this basic example if the 32-bit conditional buffer value at offset 0 is zero, the vkCmdDrawIndexed  will not be executed.

Now changing the buffer value at offset 0 to 1 will have the draw command executed:

Once the buffer is synchronized to the device, the draw call would be executed without the need to update our command buffers.

Moving to our actual example we create a conditional buffer with one 32-bit value per glTF scene node:

Using this setup, each visible glTF node maps to an entry in the conditionalVisibility  by it’s unique node index:

With above layout in mind we can now add conditional rendering to our node drawing function:

And that’s it! With above code we can now toggle visibility for every glTF model node, in our case by adding checkboxes to the user interface, without ever having to rebuild our command buffer:

Closing words

While the use-cases for conditional rendering in a real-world application might be limited, as most of the time command buffers are constantly regenerated anyway, it’s a nice addition to Vulkan, esp. when combined with compute shaders. Combining these two you could e.g. do your visibility calculations and also update the conditional buffer on the GPU without having to do a round-trip to the host.