I am using exactly 3 images for the swapchain and one VkCommandBuffer
(CB) per swapchain image. GPU synchronization is done with two semaphores, one for presentation_finished
and one for rendering_finished
. The present mode is VK_PRESENT_MODE_MAILBOX_KHR
(quick overview).
Now when I am running my example without waiting for any CB fences, the validation layers report this error as soon as any swapchain image is used for the second time:
Calling
vkBeginCommandBuffer()
on active CB before it has completed. You must check CB fence before this call.
At first sight it seems resonable to me, as processing the commands from the CB might just not be finished yet. But the more I think about it the more I come conclude that this should not happen at all.
My current understanding is when vkAcquireImageKHR
returns a specific image index it implies that the image returned has to be finished with rendering.
That is because I'm passing the rendering_finished
semaphore to vkQueueSubmit
to be signaled when rendering finises and to vkQueuePresentKHR
to wait until it becomes signaled before presenting the image.
The specification of VkQueuePresentInfoKHR
says:
pWaitSemaphores
, if not VK_NULL_HANDLE, is an array ofVkSemaphore
objects withwaitSemaphoreCount
entries, and specifies the semaphores to wait for before issuing the present request
Meaning: I will never present any image which hasn't finshed rendering and thus the associated CB cannot be in use any more as soon as the image is presented.
The second semaphore presentation_finished
is signalled by vkAqcuireImageKHR
and passed to the same vkQueueSubmit
(to start the rendering). This means the rendering of any image will start no earlier than allowed by the presentation engine.
To conclude: A present request from vkQueuePresentKHR
is not issued before the rendering of the image is finished and vkAcquireImageKHR
blocks until an image is available and also will never return an images currently acquired.
What am I missing that makes a fence necessary?
I have included a minimal code example containing only the conceptually important parts to illustrate the problem.
VkImage[] swapchain_images;
VkCommandBuffer[] command_buffers;
VkSemaphore rendering_finished;
VkSemaphore presentation_finished;
void RenderLoop()
{
/* Acquire an image from the swapchain. Block until one is available.
Signal presentation_finished when we are allowed to render into the image */
int index;
vkAcquireImageKHR(device, swapchain, UINT64_MAX, presentation_finished, nullptr, &index);
/* (...) Frambuffer creation, etc. */
/* Begin CB: The command pool is flagged to reset the command buffer on reuse */
VkCommandBuffer cb = command_buffers[index];
vkBeginCommandBuffer(cb, ...);
/* (...) Trivial rendering of a single color image */
/* End CB */
vkEndCommandBuffer(cb);
/* Queue the rendering and wait for presentation_finished.
When rendering is finished, signal rendering_finished.
The VkSubmitInfo will have these important members set among others:
.pWaitSemaphores = &presentation_finished;
.pSignalSemaphores = &rendering_finished;
*/
vkQueueSubmit(render_queue, &submit_info);
/* Submit the presentation request as soon as the rendering_finished
semaphore gets signalled
The VkPresentInfoKHR will have these important members set among others:
.pWaitSemaphores = &rendering_finished;
*/
vkQueuePresentKHR(present_queue, &present_info);
}
Inserting a fence when submitting the CB to the rendering queue and waiting on it before using that CB again obviously fixes the issue, but - as explained - seems redundant.