为什么单个深度缓冲区足以满足此vulkan swapchain渲染循环的需要?

克龙特拉

我在https://vulkan-tutorial.com/上的vulkan教程中以及深度缓冲一章中都遵循过,作者Alexander Overvoorde提到:“我们只需要一个深度图像,因为一次只运行一个绘制操作。” 这就是我的问题所在。

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

在同一子通道中的绘图调用就像在顺序上一样在gpu上执行,但是仅当它们绘制到帧缓冲区时才有效(我无法确切回忆起我读到的内容,这可能是youtube上的技术话题,所以我对此不是100%肯定)。据我了解,这是GPU硬件行为而不是Vulkan行为,因此从本质上讲,这通常是正确的(包括跨子通道甚至是渲染通道),这可以回答我的问题,但我可以做到”在这方面找不到任何明确的信息。

我得到的最接近答案的答案是OP似乎接受的有关reddit的评论,但理由基于两点:

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

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

我既看不到任何高级队列刷新(除非规范中存在我无法找到的某种显式队列刷新),也看不到渲染通道描述对其附件的依赖关系的地方-它描述了附件,但没有描述依赖性(至少不是明确地)。我已经多次阅读了规范的相关章节,但是我觉得这种语言不够清晰,以至于初学者无法完全掌握。

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

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

j00hi

恐怕我不得不说“ Vulkan教程”是错误的。在当前状态下,不能保证仅使用一个深度缓存器就不会造成存储危险。然而,这仅需要很小的改变,从而仅一个深度缓冲器就足够了。


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

我们有两个不同的队列:presentQueuegraphicsQueue,以及MAX_FRAMES_IN_FLIGHT并发帧。我用表示“飞行索引” cf(代表currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT)。我正在使用sem1sem2表示信号量的不同阵列以及fence围栏的阵列。

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

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理论上是否可以重叠。

让我们考虑一下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代表不同的管道阶段,绘制调用通过:

  • t ... TOP_OF_PIPE
  • ef ... EARLY_FRAGMENT_TESTS
  • fs ... FRAGMENT_SHADER
  • lf ... LATE_FRAGMENT_TESTS
  • co ... COLOR_ATTACHMENT_OUTPUT
  • b ... BOTTOM_OF_PIPE

虽然有可能是之间的隐含相关性sem2[i] signal -> presentsem1[i+1],当交换链只提供了一个形象,这仅适用(或如果它总是提供相同的图像)。在一般情况下,这不能被假定。这意味着,在第一帧切换到后,没有什么会延迟下一帧立即进行present栅栏也无济于事,因为在fence[i] signal代码之后,代码会等待fence[i+1],即在通常情况下也不会阻止后续帧的进行。

我所说的全部是:第二帧开始同时渲染到第一帧,据我所知,没有什么可以阻止它同时访问深度缓冲区。


解决方法:

但是,如果我们只想使用一个深度缓冲区,则可以修复本教程的代码:我们想要实现的是efandlf阶段在恢复之前等待上一个draw调用完成。即我们要创建以下方案:

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

其中_表示等待操作。

为了实现这一点,我们将必须添加一个屏障,以防止后续帧同时执行EARLY_FRAGMENT_TESTLATE_FRAGMENT_TEST阶段。只有一个队列执行绘制调用,因此只有graphicsQueuerequire中的命令需要一个屏障。可以通过使用子传递依赖项来建立“障碍”:

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], ...);

这应该graphicsQueue在不同帧的绘制调用之间建立适当的障碍因为它是EXTERNAL -> 0-type子传递依赖项,所以我们可以确保renderpass-external命令是同步的(即与上一帧同步)。

更新:等待的阶段sem1[cf]也必须从更改COLOR_ATTACHMENT_OUTPUTEARLY_FRAGMENT_TEST这是因为布局转换是在vkCmdBeginRenderPass时间上发生的:在第一个同步作用域(srcStagessrcAccess)之后,在第二个同步作用域(dstStagesdstAccess)之前。因此,交换链映像必须已经在那里可用,以便布局转换在正确的时间点发生。

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

为什么在渲染过程中我们必须清除OpenGL中的深度缓冲区?

来自分类Dev

为什么在LZW压缩中压缩缓冲区需要大于输入缓冲区?

来自分类Dev

为什么刚创建输出缓冲区时需要刷新它?

来自分类Dev

为什么在调用glDrawArrays之前不需要绑定顶点缓冲区对象?

来自分类Dev

为什么struct缓冲区不需要初始化

来自分类Dev

使用getchar()时,为什么首先需要清除回车按键的缓冲区?

来自分类Dev

为什么需要在NSInvocation-getArgument:atIndex:上保留__unsafe_unretain缓冲区?

来自分类Dev

为什么在调用glDrawArrays之前不需要绑定顶点缓冲区对象?

来自分类Dev

在OpenGL ES中使用深度和模具渲染缓冲区附件作为帧缓冲区

来自分类Dev

WebGL 渲染缓冲区

来自分类Dev

C ++-缓冲区和流都需要什么?

来自分类Dev

什么时候需要清除scanf缓冲区?

来自分类Dev

C ++-缓冲区和流都需要什么?

来自分类Dev

是否可以使用带有立方贴图颜色附件的深度渲染缓冲区?

来自分类Dev

解析循环缓冲区

来自分类Dev

向量的循环缓冲区

来自分类Dev

为什么此缓冲区指向不可寻址的字节?

来自分类Dev

WebGL-从内存渲染缓冲区

来自分类Dev

绘制渲染缓冲区对象的内容

来自分类Dev

渲染BufferedGraphics缓冲区的特定区域

来自分类Dev

使用 Cocoa 渲染像素缓冲区

来自分类Dev

(阴影贴图)将深度缓冲区渲染为纹理并进行绘制以进行检查

来自分类Dev

如何关闭不再需要的缓冲区

来自分类Dev

我需要多大的缓冲区?

来自分类Dev

为什么在输入数据包时需要将skb_buffer跳过20个字节才能读取传输缓冲区?

来自分类Dev

为什么在输入数据包时需要将skb_buffer跳过20个字节才能读取传输缓冲区?

来自分类Dev

重置循环缓冲区中的元素

来自分类Dev

Java中的循环缓冲区?

来自分类Dev

通过JavaScript中的缓冲区循环

Related 相关文章

  1. 1

    为什么在渲染过程中我们必须清除OpenGL中的深度缓冲区?

  2. 2

    为什么在LZW压缩中压缩缓冲区需要大于输入缓冲区?

  3. 3

    为什么刚创建输出缓冲区时需要刷新它?

  4. 4

    为什么在调用glDrawArrays之前不需要绑定顶点缓冲区对象?

  5. 5

    为什么struct缓冲区不需要初始化

  6. 6

    使用getchar()时,为什么首先需要清除回车按键的缓冲区?

  7. 7

    为什么需要在NSInvocation-getArgument:atIndex:上保留__unsafe_unretain缓冲区?

  8. 8

    为什么在调用glDrawArrays之前不需要绑定顶点缓冲区对象?

  9. 9

    在OpenGL ES中使用深度和模具渲染缓冲区附件作为帧缓冲区

  10. 10

    WebGL 渲染缓冲区

  11. 11

    C ++-缓冲区和流都需要什么?

  12. 12

    什么时候需要清除scanf缓冲区?

  13. 13

    C ++-缓冲区和流都需要什么?

  14. 14

    是否可以使用带有立方贴图颜色附件的深度渲染缓冲区?

  15. 15

    解析循环缓冲区

  16. 16

    向量的循环缓冲区

  17. 17

    为什么此缓冲区指向不可寻址的字节?

  18. 18

    WebGL-从内存渲染缓冲区

  19. 19

    绘制渲染缓冲区对象的内容

  20. 20

    渲染BufferedGraphics缓冲区的特定区域

  21. 21

    使用 Cocoa 渲染像素缓冲区

  22. 22

    (阴影贴图)将深度缓冲区渲染为纹理并进行绘制以进行检查

  23. 23

    如何关闭不再需要的缓冲区

  24. 24

    我需要多大的缓冲区?

  25. 25

    为什么在输入数据包时需要将skb_buffer跳过20个字节才能读取传输缓冲区?

  26. 26

    为什么在输入数据包时需要将skb_buffer跳过20个字节才能读取传输缓冲区?

  27. 27

    重置循环缓冲区中的元素

  28. 28

    Java中的循环缓冲区?

  29. 29

    通过JavaScript中的缓冲区循环

热门标签

归档