The Metal documentation makes it pretty clear that command buffers are executed serially, in the order they are enqueued (...and that committing without a prior call to enqueue implies the enqueue call).
What is less clear to me, is if and how the Metal API allows one to establish dependencies between one command buffer and the next, for cases where the output of one command buffer is needed by the next one in the pipeline. Suppose that you have a simple sequence of render passes, implemented through separate command buffers issued on the same command queue:
Pass 1: Render "something" to a texture.
Pass 2: Use the texture rendered in pass 1 to do something else.
Which would – at a very high level – translate into two serial calls to each command buffer:
[commandBuffer1 commit];
[commandBuffer2 commit];
So where is the problem? I have ran into situation where the commands issued by commandBuffer2 seemingly start executing before the commands in the previous buffer are completely executed. It is an issue of timing: sometimes the texture produced by commandBuffer1 is ready by the time commandBuffer2 needs it, sometimes it isn't, and you get garbage or black frames.
One solution would be to insert a call to -(void)waitUntilCompleted, like so:
[commandBuffer1 commit];
[commandBuffer1 waitUntilCompleted];
[commandBuffer2 commit];
But that is a terrible use of a CPU: it blocks the current thread until the GPU is done executing the first command buffer. Yes, potentially a solution exists through this other method:
- (void)addCompletedHandler:(MTLCommandBufferHandler)block;
...but it doesn't really fit this type of problem. The code doesn't need to delay committing the 2nd buffer. The code simply needs to ensure that – on the GPU side – the 2nd command buffer doesn't execute until the previous one is done. Is this implied/guaranteed at all by the Metal API? Does serial execution of command buffers also imply that a command buffer’s status is – by API contract – guaranteed to be completed when the second buffer begins execution?
And if not, is there a way to declare that the execution of one command buffer relies on the completion of a previous command buffer? I can make a comparison with a conceptually similar design: NSOperationQueues. If we treat each NSOperationQueue as a "unit of work" (the same way a Metal command buffer is) you get the following:
[operationQueue addOperation:operation1];
[operationQueue addOperation:operation2];
NSOperationQueue – depending on its configuration – will execute operation1 and operation2 serially or concurrently ...but, should you need to establish a dependency, it is super-simple:
[operation2 addDependency:operation1];
[operationQueue addOperation:operation1];
[operationQueue addOperation:operation2];
Would there be anything similar for the Metal execution model? Is it even required, or – as asked earlier – are command buffers belonging to the same queue serially executed with no overlap in their execution?
Thanks!