Issue multiple instanced draws for different meshes in one single draw call using indirect draw commands.
If the multiDrawIndirect
feature is supported, only one draw call is issued for the plants. If this feature is not available multiple indirect draw commands are used. Note: When issuing many draw counts also make sure to stay within the limitations of maxDrawIndirectCount
.
This example demonstrates the use of indirect draw commands. In addition to draw functions like vkCmdDraw
and vkCmdDrawIndexed
, where the parameters that specify what is drawn are passed directly to the function ("direct drawing"), there also exist indirect drawing commands.
vkCmdDrawIndirect
and vkCmdDrawIndexedIndirect
take the draw commands from a buffer object that contains descriptions on the draw commands to be issued, including instance and index counts, vertex offsets, etc. This also allows to draw multiple geometries with a single draw command as long as they're backed up by the same vertex (and index) buffer.
This adds several new possibilities of generating (and updating) actual draw commands, as that buffer can be generated and updated offline with no need to actually update the command buffers that contain the actual drawing functions.
Using indirect drawing you can generate the draw commands offline ahead of time on the CPU and even update them using shaders (as they're stored in a device local buffer). This adds lots of new possibilities to update draw commands without the CPU being involved, including GPU-based culling.
The example generates a single indirect buffer that contains draw commands for 12 different plants at random position, scale and rotation also using instancing to render the objects multiple times. The whole foliage (and trees) seen in the screen are drawn using only one draw call.
The different plant meshes are loaded from a single file and stored inside a single index and vertex buffer, index offsets are stored inside the indirect draw commands.
For details on the use of instancing (and instanced vertex attributes), see the instancing example
The example generates the indirect drawing buffer right at the start. First step is to generate the data for the indirect draws. Vulkan has a dedicated struct for this called VkDrawIndexedIndirectCommand
and the example uses a std::vector<VkDrawIndexedIndirectCommand>
to store these before uploading them to the GPU:
void prepareIndirectData()
{
...
uint32_t m = 0;
for (auto& meshDescriptor : meshes.plants.meshDescriptors)
{
VkDrawIndexedIndirectCommand indirectCmd{};
indirectCmd.instanceCount = OBJECT_INSTANCE_COUNT;
indirectCmd.firstInstance = m * OBJECT_INSTANCE_COUNT;
indirectCmd.firstIndex = meshDescriptor.indexBase;
indirectCmd.indexCount = meshDescriptor.indexCount;
indirectCommands.push_back(indirectCmd);
m++;
}
...
}
The meshDescriptor is generated by the mesh loader and contains the index base and count of that mesh inside the global index buffer containing all plant meshes for the scenery.
The indirect draw command for the second plant mesh looks like this:
indirectCmd.indexCount = 1668;
indirectCmd.instanceCount = 2048;
indirectCmd.firstIndex = 960;
indirectCmd.vertexOffset = 0;
indirectCmd.firstInstance = 2048;
Which will result in 2048 instances of the index data starting at index 960 (using 1668 indices) being drawn, the first instance is also important as the shader is using it for the instanced attributes of that object (position, rotation, scale).
Once we have filled that vector we need to create the buffer that the GPU uses to read the indirect commands from:
VK_CHECK_RESULT(vulkanDevice->createBuffer(
VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
&indirectCommandsBuffer,
stagingBuffer.size));
vulkanDevice->copyBuffer(&stagingBuffer, &indirectCommandsBuffer, queue);
To use a buffer for indirect draw commands you need to specify the VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT
usage flag at creation time. As the buffer is never again changed on the host side we stage it to the GPU to maximize performance.
If the multiDrawIndirect
is supported, we can issue all indirect draws with one single draw call:
void buildCommandBuffers()
{
...
for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
{
vkCmdDrawIndexedIndirect(drawCmdBuffers[i], indirectCommandsBuffer.buffer, 0, indirectDrawCount, sizeof(VkDrawIndexedIndirectCommand));
}
}
We just pass the buffer handle to the buffer containing the indirect draw commands and the number of drawCounts.
The non-indirect, non-instanced equivalent of this would be:
for (auto indirectCmd : indirectCommands)
{
for (uint32_t j = 0; j < indirectCmd.instanceCount; j++)
{
vkCmdDrawIndexed(drawCmdBuffers[i], indirectCmd.indexCount, 1, indirectCmd.firstIndex, 0, indirectCmd.firstInstance + j);
}
}
If the GPU does not support multiDrawIndirect
we have to issue the indirect draw commands one-by-one using a buffer offset instead:
for (auto j = 0; j < indirectCommands.size(); j++)
{
vkCmdDrawIndexedIndirect(drawCmdBuffers[i], indirectCommandsBuffer.buffer, j * sizeof(VkDrawIndexedIndirectCommand), 1, sizeof(VkDrawIndexedIndirectCommand));
}
- Plant and foliage models by Hugues Muller