7

So I have a render pass with a single subpass that draws directly to a framebuffer. The specification doesn't force me to use dependencies - if I omit them, the implementation inserts them implicitly (though I don't understand why it uses srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT for the first subpass - this stage means the very beginning, i.e. don't wait for anything).

But as usual with the Vulkan - better to be explicit. And here is the confusion - multiple sources use subpasses differently.

  1. Sdk's cube example does not use them at all.

  2. Vulkan-tutorial uses only one:

    dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
    dependency.dstSubpass = 0;
    dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    dependency.srcAccessMask = 0;
    dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
    

    Why srcAccessMask is zero here?

  3. API without secrets uses two:

    dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
    dependency.dstSubpass = 0;
    dependency.srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
    dependency.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
    dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
    

    and

    dependency.srcSubpass = 0;
    dependency.dstSubpass = VK_SUBPASS_EXTERNAL;
    dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    dependency.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
    dependency.dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
    dependency.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
    

    It's not very clear why srcStageMaskis VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT in the first subpass - isn't this stage should be used for execution dependencies, but here we need a memory dependency? The same question about why dstStageMask is VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT in the second subpass?

  4. Khronos synchronization examples uses one:

    dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
    dependency.dstSubpass = 0;
    dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    dependency.srcAccessMask = 0;
    dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
    

    Why srcAccessMask is 0?

  5. And here's my attempt with two dependencies:

        dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
        dependency.dstSubpass = 0;
        dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; // need to wait until
    presentation is finished reading the image
        dependency.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
        dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; // we are writing to
    the image in this stage
        dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
    
    and
    
        dependency.srcSubpass = 0;
        dependency.dstSubpass = VK_SUBPASS_EXTERNAL;
        dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; // we are writing to
    the image in this stage
        dependency.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
        dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; // presentation reads
    image in this stage (is it?)
        dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
    

All this is very confusing. As you can see multiple competent sources have a different vision. Which one to use? How to understand these dependencies?

nikitablack
  • 4,359
  • 2
  • 35
  • 68

1 Answers1

8

Oh dear, this is a topic that is not that well understood by most people. A lot of misinformation was assumed and proliferated. The proper place for canonical examples is the wikipage in the github repo.

The relevant section about swapchain images acquire/present can be found here.

  1. it's an old example from before a lot of the synchronization specifics were properly nailed down.

  2. The purpose there is to synchronize the semaphore use to wait on the result of vkAcquireNextImage with rendering. This is a write-after-read hazard and the semaphore will include memory visibility synchronization. So srcAccessMask is no necessary.

  3. Again the barrier is meant to sync against the semaphore. When submitting a command buffer to a queue you can set which stages wait on which semaphores. In this case they use the bottom of pipe stage instead of the VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT

  4. same answer as 2, the only difference is with the dstAccessMask

  5. The wiki page I linked above uses the following:

    /* Only need a dependency coming in to ensure that the first
       layout transition happens at the right time.
       Second external dependency is implied by having a different
       finalLayout and subpass layout. */
    VkSubpassDependency dependency = {
        .srcSubpass = VK_SUBPASS_EXTERNAL,
        .dstSubpass = 0,
        // .srcStageMask needs to be a part of pWaitDstStageMask in the WSI semaphore.
        .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
        .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
        .srcAccessMask = 0,
        .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
        .dependencyFlags = 0};
    
    /* Normally, we would need an external dependency at the end as well since we are changing layout in finalLayout,
       but since we are signalling a semaphore, we can rely on Vulkan's default behavior,
       which injects an external dependency here with
       dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
       dstAccessMask = 0. */
    

    You mixed up the subpass indices for the second barrier. Though as mentioned in the comments there is already an implicit barrier at the end of the renderpass which in turn syncs against the semaphore or fence you use to sync against the present. So there is no need fo an explicit dependency.

ratchet freak
  • 47,288
  • 5
  • 68
  • 106
  • ratchet freak, thank you for the explanation. The example `4` is indeed from Khronos github repo. With example `3` (Intel) I'm still confused - the second subpass destination stage is `VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT` but wait destination stage of the submit info is `VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT` - isn't it wrong? Yes, I mixed indicies for the second barrier - a copy-paste error, fixed. The thing I don't understand now is how a semaphore comes into the play? How does it interact with subpass dependencies? Where can I read more about it? – nikitablack Oct 26 '18 at 10:15
  • 1
    the second dependency in 3 syncs the end of the renderpass to the stuff after the command buffer is completed. The mechanisms for inter command buffer sync always wait for the command buffer to be completely finished. – ratchet freak Oct 26 '18 at 10:55
  • 1
    @nikitablack preferrably in the spec [Semaphore Signaling](https://www.khronos.org/registry/vulkan/specs/1.1/html/vkspec.html#synchronization-semaphores-signaling) and [Semaphore Waiting & Unsignaling](https://www.khronos.org/registry/vulkan/specs/1.1/html/vkspec.html#synchronization-semaphores-waiting) chapters. Learn what a Synchronization Scope, Access Scope, and Submission Order is in chapter [Execution and Memory Dependencies](https://www.khronos.org/registry/vulkan/specs/1.1/html/vkspec.html#synchronization-dependencies) and then you practically understand all of Vulkan synchronization. – krOoze Oct 26 '18 at 13:06