vkQueuePresentKHR Synchronization Ambiguity

A typical rendering loop is as follows:


while(...){
  vkAcquireNextImageKHR(...); //signal Sema1
  vkQueueSubmit(...); //wait Sema1, signal Sema2, signal Fence
  vkQueuePresentKHR(...); //wait Sema2
  vkWaitForFences(...); //wait Fence
  vkResetFences(...);
}

We can provide a semaphore between vkAcquireNextImageKHR and vkQueueSubmit, and also between vkQueueSubmit and vkQueuePresentKHR to ensure that they execute in order. Then, we can wait for vkQueueSubmit to complete with vkWaitForFences.

However, according to the spec, vkWaitForFences only guarantees that the vkQueueSubmit batches’ completion happen-before vkWaitForFences returns.

It is unclear whether or not the “semaphore signal operations” are considered to be inside of a “batch”. But, regardless, when vkWaitForFences returns, (Sema2 may or may not be signaled, but) we have no guarantee that vkQueuePresentKHR has unsignaled Sema2 yet.

So, the next loop iteration might come, and the next vkQueueSubmit might try to signal Sema2 again before it has been unsignaled, and subsequent vkQueuePresentKHR might deadlock because of this.

Or, perhaps am I missing some implicit synchronization behavior? Some insight would be appreciated.

A typical rendering loop is as follows:

A “typical rendering loop” most certainly should not look like that. Waiting on a fence immediately after submitting to the queue is like issuing an async call and immediately suspending operations until it completes. Actually, the latter makes more sense, as in that case, if you block on a mutex, the system can task-switch the current thread to work on the async call or something else. By contrast, waiting on a mutex means the thread can’t do anything towards preparing the next frame for rendering.

So, the next loop iteration might come, and the next vkQueueSubmit might try to signal Sema2 again before it has been unsignaled, and subsequent vkQueuePresentKHR might deadlock because of this.

Which is why the next iteration of the loop should use different semaphores (and fences). Assuming that the previous frame has completed execution before you start the execution of the next frame is basically throwing performance away.

Basically semaphores and fences should be buffered, just like presentable images are buffered. Each frame, you render to a different image because the image you used last frame might still be in use, and you don’t want to have to synchronize.

[QUOTE=Alfonse Reinheart;43063]Which is why the next iteration of the loop should use different semaphores (and fences). Assuming that the previous frame has completed execution before you start the execution of the next frame is basically throwing performance away.

Basically semaphores and fences should be buffered, just like presentable images are buffered. Each frame, you render to a different image because the image you used last frame might still be in use, and you don’t want to have to synchronize.[/QUOTE]

I see. Is there any indication as to when exactly the semaphore used in vkQueuePresentKHR is in fact safe to be reused? Even if we were to simply create new semaphores for each loop iteration, when would it be safe to delete them?

When all submitted batches referring to the semaphore have completed execution.

Depends what you mean.
As soon as you use that semaphore as to be signaled, you can use it in one wait.

[QUOTE=krOoze;43065]Depends what you mean.
As soon as you use that semaphore as to be signaled, you can use it in one wait.[/QUOTE]

Consider this program flow:

vkAcquireNextImageKHR(...); //signal Sema1
vkQueueSubmit(...); //wait Sema1, signal Sema2
vkQueuePresentKHR(...); //wait Sema2
...
vkAcquireNextImageKHR(...); //signal Sema3
vkQueueSubmit(...); //wait Sema3, signal Sema4
vkQueuePresentKHR(...); //wait Sema4

Obviously we will run out of semaphores after a while, so we have to reuse semaphores.

vkAcquireNextImageKHR(...); //signal Sema1
vkQueueSubmit(...); //wait Sema1, signal Sema2
vkQueuePresentKHR(...); //wait Sema2
...
vkAcquireNextImageKHR(...); //signal Sema1
vkQueueSubmit(...); //wait Sema1, signal Sema2
vkQueuePresentKHR(...); //wait Sema2

If I do this, then the second vkQueueSubmit might signal Sema2, before the first vkQueuePresentKHR unsignaled Sema2, since there’s nothing guaranteeing their order, right? And obviously this is a problem as a Semaphore is getting signalled twice with no unsignal in between.

since there’s nothing guaranteeing their order, right?

Semaphores guarantee order. That’s literally what they’re for. When you wait on a semaphore, you have guaranteed the order of execution for the stuff submitted before the semaphore signal and after the semaphore wait (both in the same queue and in different queues).

Indeed, looking back on it, since you’re using the same queue for all of this, you don’t need to buffer semaphores at all.

[QUOTE=Alfonse Reinheart;43067]Semaphores guarantee order. That’s literally what they’re for. When you wait on a semaphore, you have guaranteed the order of execution for the stuff submitted before the semaphore signal and after the semaphore wait (both in the same queue and in different queues).

Indeed, looking back on it, since you’re using the same queue for all of this, you don’t need to buffer semaphores at all.[/QUOTE]

AH okay, I was confused into thinking that semaphore waiting does not include all the cumulative operations before it. I actually knew that but I guess I just forgot about it in this scenario.