Encoding indirect command buffers on the CPU
Reduce CPU overhead and simplify your command execution by reusing commands.
Overview
This sample app provides an introduction to indirect command buffers (ICB), which enable you to store repeated commands for later use. Because Metal discards a normal command buffer and its commands after Metal executes them, use ICBs to save expensive allocation, deallocation, and encoding time for your app’s common instructions. Additionally, you benefit when using ICBs with:
A reduction in rendering tasks because you execute an ICB with a single call.
By creating ICBs at initialization, it moves expensive command management out of your app’s critical path at rendering or compute-time.
An example of where ICBs are effective is with a game’s head-up display (HUD), because:
You render HUDs every frame.
The appearance of the HUD is usually static across frames.
ICBs are also useful to render static objects in typical 3D scenes. Because encoded commands typically result in lightweight data structures, ICBs are suitable for saving complex draws, too.
This sample demonstrates how to set up an ICB to repeatedly render a series of shapes. While it’s possible to gain even more instruction-parallelism by encoding the ICB on the GPU, this sample encodes an ICB on the CPU for simplicity. See Encoding indirect command buffers on the GPU for the more advanced usage.
Getting started
This sample contains macOS and iOS targets. Run the iOS scheme on a physical device because Metal isn’t supported in the simulator.
ICBs are supported by GPUs of family greater than or equal to:
MTLFeatureSet_iOS_GPUFamily3_v4MTLFeatureSet_macOS_GPUFamily2_v1
You check the GPU that you choose at runtime if it supports ICBs using the MTLDevice method supportsFeatureSet(_:):
This sample calls ‘supportsFeatureSet:’ for this purpose within its view controller’s viewDidLoad: callback.
Individual commands versus indirect command buffers
Metal apps, particularly games, typically contain multiple render commands, each associated with a set of render states, buffers, and draw calls. To execute these commands for a render pass, apps first encode them into a render command encoder within a command buffer.
You encode individual commands into a render command encoder by calling MTLRenderCommandEncoder methods such as setVertexBuffer(_:offset:index:) or drawPrimitives(type:vertexStart:vertexCount:instanceCount:baseInstance:).
[Image]
Recreating draws that were equivalent to ones you did in a previous queue can be tedious from a coding perspective and non-performant at runtime. Instead, move your repeated draws and their data buffers into an MTLIndirectCommandBuffer instance using an MTLIndirectRenderCommand, thereby filling the ICB with commands. When you’re ready to use the ICB, encode individual executions of it by calling an MTLRenderCommandEncoder instance’s executeCommandsInBuffer:withRange: method.
[Image]
Define render commands and inherited render state
For the indirect command buffer, _indirectCommandBuffer, the sample defines render commands that:
Set a vertex buffer using unique vertex data for each mesh
Set another vertex buffer using common transformation data for all meshes
Set another vertex buffer containing an array of parameters for each mesh
Draw the mesh’s triangles
The sample encodes these commands differently for the CPU or the GPU. However, these commands are still encoded into both versions of the indirect command buffer.
The sample also allows _indirectCommandBuffer to inherit the render pipeline state from its parent encoder, renderEncoder. Furthermore, _indirectCommandBuffer implicitly inherits any render state that can’t be encoded into it, such as the cull mode and depth or stencil state for the render pass.
Create an indirect command buffer
The sample creates _indirectCommandBuffer from an MTLIndirectCommandBufferDescriptor, which defines the features and limits of an indirect command buffer.
The sample specifies the types of commands, commandTypes, and the maximum number of commands, maxCount, so that Metal reserves enough space in memory for the sample to encode _indirectCommandBuffer successfully (with the CPU or GPU).
Encode an indirect command buffer with the CPU
From the CPU, the sample encodes commands into _indirectCommandBuffer with an MTLIndirectRenderCommand instance. For each shape to be rendered, the sample encodes two setVertexBuffer(_:offset:at:) commands and one drawPrimitives(_:vertexStart:vertexCount:instanceCount:baseInstance:) command.
The sample performs this encoding only once, before encoding any subsequent render commands. _indirectCommandBuffer contains a total of 16 draw calls, one for each shape to be rendered. Each draw call references the same transformation data, _uniformBuffers, but different vertex data, _vertexBuffers[indx]. Although the CPU encodes data only once, the sample issues 16 draw calls per frame.
[Image]
Update the data used by an ICB
To update data that’s fed to the GPU, you typically cycle through a set of buffers such that the CPU updates one while the GPU reads another (see Synchronizing events between a GPU and the CPU). You can’t apply that pattern literally with ICBs, however, because you can’t update an ICB’s buffer set after you encode its commands, but you follow a two-step process to blit data updates from the CPU. First, update a single buffer in your dynamic buffer array on the CPU:
Then, blit the CPU-side buffer set to the location that’s accessible to the ICB (see _indirectFrameStateBuffer):
Execute an indirect command buffer
The sample calls the executeCommandsInBuffer:withRange: method to execute the commands in _indirectCommandBuffer.
Similar to the arguments in an argument buffer, the sample calls the useResource:usage: method to indicate that the GPU can access the resources within an indirect command buffer.
The sample continues to execute _indirectCommandBuffer each frame.