Graphics 为什么对于这个vulkan swapchain渲染循环,一个深度缓冲区就足够了?

Graphics 为什么对于这个vulkan swapchain渲染循环,一个深度缓冲区就足够了?,graphics,3d,vulkan,Graphics,3d,Vulkan,我在和的时候都在关注vulkan教程,作者Alexander Overvoorde提到“我们只需要一个深度图像,因为一次只运行一个绘制操作。”这就是我的问题所在 在过去的几天里,我读了很多关于Vulkan同步的问题和文章/博客文章,但我似乎无法得出结论。到目前为止,我收集的信息如下: vkWaitForFences(..., fence[cf], ...); vkAcquireNextImageKHR(..., /* signal when done: */ sem1[cf], ...); vk

我在和的时候都在关注vulkan教程,作者Alexander Overvoorde提到“我们只需要一个深度图像,因为一次只运行一个绘制操作。”这就是我的问题所在

在过去的几天里,我读了很多关于Vulkan同步的问题和文章/博客文章,但我似乎无法得出结论。到目前为止,我收集的信息如下:

vkWaitForFences(..., fence[cf], ...);
vkAcquireNextImageKHR(..., /* signal when done: */ sem1[cf], ...);
vkResetFences(..., fence[cf]);
vkQueueSubmit(graphicsQueue, ...
    /* wait for: */ sem1[cf], /* wait stage: *, COLOR_ATTACHMENT_OUTPUT ...
    vkCmdBeginRenderPass(cb[cf], ...);
      Subpass Dependency between EXTERNAL -> 0:
          srcStages = COLOR_ATTACHMENT_OUTPUT,
          srcAccess = 0, 
          dstStages = COLOR_ATTACHMENT_OUTPUT,
          dstAccess = COLOR_ATTACHMENT_WRITE
      ...
      vkCmdDrawIndexed(cb[cf], ...);
      (Implicit!) Subpass Dependency between 0 -> EXTERNAL:
          srcStages = ALL_COMMANDS,
          srcAccess = COLOR_ATTACHMENT_WRITE|DEPTH_STENCIL_WRITE, 
          dstStages = BOTTOM_OF_PIPE,
          dstAccess = 0
    vkCmdEndRenderPass(cb[cf]);
    /* signal when done: */ sem2[cf], ...
    /* signal when done: */ fence[cf]
);
vkQueuePresent(presentQueue, ... /* wait for: */ sem2[cf], ...);
相同子进程中的Draw调用在gpu上按顺序执行,但只有当它们绘制到帧缓冲区时才会执行(我记不起我在哪里读到过这篇文章,可能是youtube上的一篇技术演讲,所以我不能100%肯定这一点)。据我所知,这更多的是GPU硬件行为,而不是Vulkan行为,因此这基本上意味着上述情况在总体上是正确的(包括子类甚至渲染过程)——这将回答我的问题,但我找不到任何关于这一点的明确信息

我最接近于回答我的问题的是OP似乎接受的这一点,但理由基于两件事:

  • “高级队列刷新可确保以前提交的渲染过程完成”

  • “渲染过程本身将读取和写入的附件描述为外部依赖项”

我既看不到任何高级队列刷新(除非有某种明确的队列刷新,我在规范中找不到),也看不到渲染过程描述其附件依赖关系的情况—它描述附件,但不描述依赖关系(至少不明确)。我已经多次阅读了规范的相关章节,但我觉得语言不够清晰,初学者无法完全掌握

如果可能的话,我也非常感谢Vulkan规范的引用

编辑:为了澄清,最后一个问题是:
什么样的同步机制可以保证在当前绘制调用完成之前不会提交下一个命令缓冲区中的绘制调用?

否,光栅化顺序(根据规范)不会扩展到单个子类之外。如果多个子进程写入同一深度缓冲区,则它们之间应该存在
VkSubpassDependency
。如果渲染过程之外的内容写入深度缓冲区,则还应该进行显式同步(通过屏障、信号量或围栏)

FWIW我认为vulkan教程示例不一致。至少我没有看到任何可以防止深度缓冲区内存危险的东西。似乎应该将深度缓冲区复制到飞行中的最大帧,或者显式同步

关于未定义行为的秘密部分是错误的代码通常工作正常。不幸的是,在验证层中进行同步验证有点棘手,所以现在唯一剩下的就是要小心

未来证明答案:

我看到的是传统的WSI信号量链(与
vkAnquireNextImageKHR
vkQueuePresentKHR
一起使用),带有
imageAvailable
renderFinished
信号量。只有一个子类依赖项具有
VK\u PIPELINE\u STAGE\u COLOR\u ATTACHMENT\u OUTPUT\u BIT
,它链接到
imageAvailable
信号量。然后是具有
MAX\u FRAMES\u IN\u FLIGHT==2
的围栏,以及保护各个交换链图像的围栏。这意味着两个后续帧应该彼此不受阻碍地进行wrt(除非在极少数情况下,它们获得相同的swapchain图像)。因此,两帧之间的深度缓冲区似乎没有受到保护。

恐怕,我不得不说Vulkan教程是错误的。在其当前状态下,无法保证仅使用一个深度缓冲区时没有内存危险。然而,它只需要一个很小的改变,这样只有一个深度缓冲就足够了


让我们分析在
drawFrame
中执行的代码的相关步骤

我们有两个不同的队列:
presentQueue
graphicsQueue
,以及
MAX\u FRAMES\u IN\u FLIGHT
并发帧。我用
cf
(代表
currentFrame=(currentFrame+1)%MAX\u FRAMES\u in\u flight
)引用“飞行中索引”。我使用
sem1
sem2
表示不同的信号量数组,并使用
fence
表示fences数组

伪代码中的相关步骤如下所示:

vkWaitForFences(..., fence[cf], ...);
vkAcquireNextImageKHR(..., /* signal when done: */ sem1[cf], ...);
vkResetFences(..., fence[cf]);
vkQueueSubmit(graphicsQueue, ...
    /* wait for: */ sem1[cf], /* wait stage: *, COLOR_ATTACHMENT_OUTPUT ...
    vkCmdBeginRenderPass(cb[cf], ...);
      Subpass Dependency between EXTERNAL -> 0:
          srcStages = COLOR_ATTACHMENT_OUTPUT,
          srcAccess = 0, 
          dstStages = COLOR_ATTACHMENT_OUTPUT,
          dstAccess = COLOR_ATTACHMENT_WRITE
      ...
      vkCmdDrawIndexed(cb[cf], ...);
      (Implicit!) Subpass Dependency between 0 -> EXTERNAL:
          srcStages = ALL_COMMANDS,
          srcAccess = COLOR_ATTACHMENT_WRITE|DEPTH_STENCIL_WRITE, 
          dstStages = BOTTOM_OF_PIPE,
          dstAccess = 0
    vkCmdEndRenderPass(cb[cf]);
    /* signal when done: */ sem2[cf], ...
    /* signal when done: */ fence[cf]
);
vkQueuePresent(presentQueue, ... /* wait for: */ sem2[cf], ...);
绘图调用在一个队列上执行:
graphicsQueue
。我们必须检查
graphicsQueue
上的命令理论上是否可以重叠

>让我们考虑前两个帧:

>在代码>图形图形> /代码>中发生的事件。
img[0] -> sem1[0] signal -> t|...|ef|fs|lf|co|b -> sem2[0] signal, fence[0] signal
img[1] -> sem1[1] signal -> t|...|ef|fs|lf|co|b -> sem2[1] signal, fence[1] signal
t |…| ef | fs | lf | co | b
代表不同的管道阶段时,draw调用通过:

  • t
    <代码>管道顶部
  • ef
    <代码>早期碎片测试
  • fs
    <代码>片段着色器
  • lf
    <代码>后期碎片测试
  • co
    <代码>颜色\u附件\u输出
  • b
    <代码>管道底部
虽然
sem2[i]信号->呈现
sem1[i+1]
之间可能存在隐式依赖关系,但这仅适用于交换链仅提供一个映像(或始终提供相同映像)的情况。在一般情况下,这是无法假设的。这意味着,在将第一帧移交给当前帧后,没有任何东西会延迟后续帧的立即进行。围栏也没有帮助,因为在
围栏之后
vkWaitForFences(..., fence[cf], ...);
vkAcquireNextImageKHR(..., /* signal when done: */ sem1[cf], ...);
vkResetFences(..., fence[cf]);
vkQueueSubmit(graphicsQueue, ...
    /* wait for: */ sem1[cf], /* wait stage: *, EARLY_FRAGMENT_TEST...
    vkCmdBeginRenderPass(cb[cf], ...);
      Subpass Dependency between EXTERNAL -> 0:
          srcStages = EARLY_FRAGMENT_TEST|LATE_FRAGMENT_TEST,
          srcAccess = DEPTH_STENCIL_ATTACHMENT_WRITE, 
          dstStages = EARLY_FRAGMENT_TEST|LATE_FRAGMENT_TEST,
          dstAccess = DEPTH_STENCIL_ATTACHMENT_WRITE|DEPTH_STENCIL_ATTACHMENT_READ
      ...
      vkCmdDrawIndexed(cb[cf], ...);
      (Implicit!) Subpass Dependency between 0 -> EXTERNAL:
          srcStages = ALL_COMMANDS,
          srcAccess = COLOR_ATTACHMENT_WRITE|DEPTH_STENCIL_WRITE, 
          dstStages = BOTTOM_OF_PIPE,
          dstAccess = 0
    vkCmdEndRenderPass(cb[cf]);
    /* signal when done: */ sem2[cf], ...
    /* signal when done: */ fence[cf]
);
vkQueuePresent(presentQueue, ... /* wait for: */ sem2[cf], ...);