WSI 同步子通道依赖和 link 颜色附件输出

WSI synchronization subpass dependency and link to color attachment output

我想我确实了解 Vulkan 同步的工作原理,但我在理解与 WSI 的同步方面存在问题。

Synchronization Examples,我们可以找到这段代码

/* 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};

在我看来应该是这样的:

VkSubpassDependency dependency = {
    .srcSubpass = VK_SUBPASS_EXTERNAL,
    .dstSubpass = 0,
    // .srcStageMask needs to be a part of pWaitDstStageMask in the WSI semaphore.
    .srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE,
    .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
    .srcAccessMask = 0,
    .dstAccessMask = 0,
    .dependencyFlags = 0};

确实,由于我们要写入附件,因此无需在 dstAccessMask.

中使用 WRITE_BIT(表示使写入可用)

但真正的问题在于 srcStageMask

我明白为什么 dstStageMaskCOLOR_ATTACHMENT_OUTPUT_BIT。这是因为我们不接触附件,所以前面的阶段可以正常工作。

然而,对于 srcStageMask,我没有看到任何关于 WSI 和 COLOR_ATTACHMENT_OUTPUT_BIT 之间的 link。对我来说,布局转换必须出现在演示文稿的末尾,恰好在 COLOR_ATTACHMENT_OUTPUT 阶段开始之前。 对我来说,演示结束应该用 BOTTOM_OF_PIPE 表示,而不是 COLOR_ATTACHMENT_OUTPUT

我哪里弄错了?

Indeed, since we are going to WRITE into the attachment, there is no need to use a WRITE_BIT (meaning make writes available) in the dstAccessMask.

我不太确定你的逻辑。应该是 WRITE 因为我们要写附件。

这可能有助于描述这里发生的事情(来自 krOoze/Hello_Triangle/doc):

信号量的 VkSubpassDependency 链(通过 pWaitDstStageMask)。然后它执行其布局转换。然后它与 Load Op 同步。 (然后 Load Op 发生在 subpass 中。然后 subpass vkDraws 一些东西到交换链图像中。)

现在假设(这是第一次使用交换链图像的典型情况)我们的加载操作是 VK_ATTACHMENT_LOAD_OP_CLEAR。这意味着 VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT 访问。

所以:

  1. Presentation 将读取交换链图像,并提交信号量信号。

  2. 我们将 VkSubpassDependency 链接到信号(通过 pWaitDstStageMask)。信号量信号已经涵盖所有内存访问,因此我们的 srcAccessMask = 0.

  3. Dependency 执行其对 Layout Transition 的隐式依赖(获取你的 src,并发明一些内部 dst),然后 Layout Transition 发生,它读取图像和它回来,然后依赖执行另一个隐式依赖(发明一些src覆盖布局转换,并使用你的dst)。

  4. Load Op 发生在 subpass 中,你必须明确地确保它发生在上面的所有事情之后。所以你的 Dependency 中的 dst 必须是 VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT 来覆盖 Load Op.

现在内存hazard分为三种:读→写、写→写、写→读(读→读是非危险)。在 WWH 中,有些人写资源,而其他人也写资源。不清楚哪个写入最后发生,因此内存内容变成垃圾。在 RWH 和 WRH 中,读和写可能同时发生。不清楚读取看到的是未修改的内存,还是写入的内存(即读取垃圾)。

在没有显式依赖的情况下,Layout Transition 和后续的 Load Op 形成了写→写的风险。因此 dstAccessMask 必须 不是 0 以解决危险并确保一个写入发生在第二个之前。

(也许值得注意的是,我们引入 VkSubpassDependency 仅仅是为了布局转换。否则信号量等待就已经是所有需要的了。默认是 srcStageMask = TOP_OF_PIPE,这意味着没有显式依赖,布局转换可能发生在信号量等待之前,即在演示文稿完成读取之前;这将是读→写的危险)。


To me, the layout transition must appear at the end of the presentation, and just before the beginning of the COLOR_ATTACHMENT_OUTPUT stage. And the end of presentation, for me, should be represented by BOTTOM_OF_PIPE and not COLOR_ATTACHMENT_OUTPUT

我们在这里有一点选择:pWaitDstStageMask = dependency.srcStageMask = ?

现在我们的情况是这样的:

vkBeginCommandBuffer();
[possibly vkCmdDispatch()?]
vkBeginRenderPass();
vkCmdDraw(); // does vertex shading + fragment shading, then color writes
vkEndRenderPass();
vkEndCommandBuffer();

vkQueueSubmit(.pwaitDstStageMask = ?);

如果我们使用VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,那么信号量等待不会阻止启动假设的vkCmdDispatch() (VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT)。并且 Subpass Dependency src 也不会强制它完成。太棒了!

使用的阶段不应早于VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT。例如。对于 VK_PIPELINE_STAGE_ALL_COMMANDS,信号量等待会不必要地阻塞 vkCmdDispatch() 以及 vkCmdDraw 的顶点和片段着色器。同时,只有当我们实际需要在 Load Op 写入它时,我们才需要交换链图像(你可以想象当 vkCmdDraw() 到达它需要执行颜色写入的点时发生)。

现在,我们可以选择VK_PIPELINE_STAGE_BOTTOM_OF_PIPE。信号量不阻塞任何东西(dstStage\pWaitDstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE 与 "nothing" 的意思相同)。伟大的!但是 Subpass Dependency 现在会阻止所有内容(srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPEVK_PIPELINE_STAGE_ALL_COMMANDS 的含义相同)。这意味着我们的 vkCmdDispatch 必须在我们的子通道开始之前完成。不太好...

所以,最好的做法是使用pWaitDstStageMask = dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT