up to README.md
You will probably have two semaphors: e.g. imageAcquired and renderingDone.
(You would need more imageAcquired semaphores to prevent vkAcquireNextImage
to signal the same semaphore twice, but let's ignore this aspect for now.)
Let's cover it in an order in which things should actually get executed (i.e. not necesserily in the order we called or recorded the commands):
-
You provide
imageAcquiredsemaphore to thevkAcquireNextImageKHRto be signalled by it. -
You provide
imageAcquiredsemaphore to the command buffer submit to be waited on. As apWaitDstStageMaskyou provide the stage where your first write access to the swapchainVkImagehappens. Most often it will beVK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT(but could be something different based on your command buffer contents). -
In the command buffer you change the layout of the swapchain image (Subpasses, Pipeline Barriers, or Events can do that) from the present one ( use
VK_IMAGE_LAYOUT_UNDEFINEDas an old layout, which means "sacrifice data" — there's rarely ever need to read it after present ). For source stage choose the exact same one as in step 2 and source access mask should be0( because memory dependency is already part of the previous semaphore wait). For efficient loop, destination stage should also be the same as in step 2 and the new layout and destination access mask should be appropriate for whatever you plan to do (likelyVK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMALandVK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT).In the case this is done with Render Pass (which should be the preferred method when we need to draw something), the first use of the
VkImagewould be the automatic layout transition followed byloadOpyou provided during creation. For color attachment load operation does occur in theVK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BITstage (so providing that value asdstStageMaskmakes sure the layout transition does not clash with the load operation). Access mask depends on theloadOpvalue chosen.The layout transition in render pass (and in Pipeline Barriers too) happens between
srcStageMaskanddstStageMaskstages. So we do not need to worry about it too much, assuming our barrier\subpass dependency is otherwisely correct. -
You would write to your
VkImage. Let's assume that is done with a draw command likevkCmdDraw(which implies use of a Render Pass). Now theloadOpguarantees that it happens before our first use of an attachment in a render pass. So, no additional synchronization is needed here. -
In the command buffer you change the layout of the swapchain image (again Subpasses, Pipeline Barriers, or Events can do that) to the present one (
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR). As your source stage, old layout and source access mask choose whatever your last use of the swapchain image was. Destination stage should beVK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT(i.e. non-blocking, becose that is instead handled by folowing semaphore signal) and the destination access mask should be0.In the case of this being a render pass, first a
storeOphappens after the last use of your image in a render pass. Again that is guaranteed and needs no additional synchronization to order these two. The Store Operation for color attachment happens inVK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BITstage and usesVK_ACCESS_COLOR_ATTACHMENT_WRITE_BITaccess. The Store Operation is followed by the automatic layout transition tofinalLayout. So, to preventstoreOpand this transition to clash,srcStageMaskshould be stage the Store Op happens (VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BITas said above). -
You provide
renderingDonesemaphore to the command buffer submit to be signalled. -
You provide
renderingDonesemaphore to the present command to be waited on. -
The circle is now complete! 😷
From this it should be evident we are just declaring dependencies between memory accesses (otherwisely Vulkan is allowed to mercilessly overlap execution of commands). When there is any kind of memory read or write, we must make sure any previous write to the same location has finished. Usually, value used in
dstStageMasksubsequently appears in followingsrcStageMask— so for a simple application it forms a nice dependency chain or path or lifetime of image that is not so hard to reason about:
Image Acquire
=> Semaphore Signal
=> Semaphore Unsignal in VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
=> srcStage=VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT stage of Subpass Dependency\Barrier
=> Layout Transition From Present Layout
=> dstStage=VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT stage of Subpass Dependency\Barrier
=> Load Op (VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT stage) => (Implicit Dependency)
=> Draw (VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT stage again\still)
=> (Implicit Dependency) => Store Op (VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT stage again\still)
=> srcStage stage of Subpass Dependency\Barrier
=> Layout Transition to Present Layout
=> dstStage=VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT stage of Subpass Dependency\Barrier
=> Semaphore Signal
=> Semaphore Unsignal
=> Presenting
From this diagram I can be sure the synchronization is correct:
- The pipeline stage of previous and next step matches
- or it is how the given primitive works (e.g. Semaphore wait does cover any previously submitted semaphore signal)
- or I am given (relatively rare) specification guarantee that things implicitly happen in specific order (e.g. the Load Op vs first subpass attachment use).