Metal Triple Buffering and the Main Thread

Was told that all metal command buffers should be committed on the main thread.

How would I go about handling parallel command execution and triple buffering of data if it could block the main thread (and we can't have it block the main thread)?

How could I adapt the code below?

Code Block Swift
_ = semaphore.wait(timeout: DispatchTime.distantFuture)
autoreleasepool {
let commandBuffer = commandQueue.makeCommandBuffer()
let dispatchGroup = DispatchGroup()
do { /* repeat this block for every render pass needed */
dispatchGroup.enter()
metalQueue.async {
/* perform render commands */
if commandBuffer == finalScreenBuffer {
commandBuffer.addCompletedHandler { _ in
semaphore.signal()
}
}
commandBuffer.commit
dispatchGroup.leave()
}
}
_ = dispatchGroup.wait(timeout: DispatchTime.distantFuture)
commandBuffer.waitUntilScheduled()
/* view is an MTKView with presentsWithTransaction set to true */
view.currentDrawable.present()
}
bufferIndex = (bufferIndex + 1) % 3


Accepted Answer
If you would like to use Triple Buffering in your application, you can submit the current frame's workload and, while it's executed in the GPU, you can continue to prepare the next frame's workload as shown below. Since the semaphore is initialized with MaxFramesInFlight, the CPU can continue to prepare the next frame's workloads without being locked, as long as the total number of finished frames in the GPU is less than MaxFramesInFlight.

Code Block
func init () {
/* Create Semaphore with the total number of frames that the CPU
allowed to issue without waiting for GPU to finish. */
semaphore = DispatchSemaphore(MaxFramesInFlight)
}
func render() {
/* Wait if the maximum number of frames specified in the MaxFramesInFlight is exhausted. */
_ = semaphore.wait(timeout:DispatchTime.distantFuture)
/* Update current Triple Buffer index. */
currentTripleBufferIndex = (currentTripleBufferIndex + 1) % MaxFramesInFlight
/* Update working set of the current frame. */
update()
/* Create a CommandBuffer. */
commandBuffer = commandQueue.makeCommandbuffer()
/* Encode current frame to CommandBuffer. */
encode(commandBuffer)
/* Signal current frame's rendering is done. */
commandBuffer.addCompletedHandler { _ in
semaphore.signal()
}
/* Update view's content with current drawable. */
commandBuffer.present(view.currentDrawable)
/* Submit the CommandBuffer. */
commandBuffer.commit()
}

If you have multiple CommandBuffers each encoded on different threads, you will need to ensure they enqueued in the order you like them to execute. Also, calling present and signalling semaphore in this case will be only needed on the CommandBuffer that enqueued last.

Please see Our WWDC session that covers both Triple Buffering and optimizing your render at https://developer.apple.com/videos/play/wwdc2015/610/ and the documentation about CPU and GPU synchronization https://developer.apple.com/documentation/metal/synchronization/synchronizing_cpu_and_gpu_work. Also be sure to check out accompanying sample code that shows how Triple Buffer can be implemented with Metal

Metal Triple Buffering and the Main Thread
 
 
Q