为什么某些命令只能在渲染过程之外记录?

Why some commands can be recorded only outside of a render pass?

我不知道它是 API 特性(我几乎可以肯定不是)还是 GPU 特性,但是为什么,例如,vkCmdWaitEvents 可以在内部和外部记录渲染过程,但 vkCmdResetEvent 只能在外部记录?这同样适用于其他命令。

因为 renderpass 是一种特殊的构造,意味着只在帧缓冲区上进行聚焦工作。

此外,每个子通道都允许 运行 并行,除非它们之间有明确的依赖关系。

这会影响它们需要如何与其他子通道中的其他指令同步。

进行复制会控制内存总线的使用,并且会拖延依赖它的渲染工作。在 renderpass 内部这样做会产生一个很大的 gpu 气泡,可以通过将其放在外面并确保在您启动 renderpass 时完成它来轻松解决。

一些硬件还具有独立于图形硬件的专用复制单元,因此您需要在它们之间进行的同步越少越好。

特别是在事件设置方面,它们对渲染过程模型与 tile-based 渲染器的交互方式造成了严重破坏。

回想一下 当 TBR 遇到一系列复杂的子通道时,它 想要 执行它们的方式如下。

它一次性完成所有子通道的所有渲染命令的所有顶点处理阶段,将生成的顶点数据存储在缓冲区中供以后使用。然后对于每个图块,它针对构建该图块所涉及的图元执行每个子通道的光栅化阶段。

请注意,这是理想情况;具体的事情可能会导致不同程度的失败,但即便如此,它往往会成批失败,你可以像这样执行渲染过程的几个子过程。

假设您想在子通道中间设置一个事件。好吧……什么时候真正发生?请记住 set-event 命令实际上是在所有前面的命令完成后设置事件。在 TBR 中,如果一切都按上述方式进行,什么时候设置?理想情况下,entire renderpass 的所有顶点处理都应该在任何光栅化之前发生,因此设置事件必须在顶点处理完成后发生。所有光栅化处理都在 tile-by-tile 基础上进行,处理与该图块重叠的任何图元。由于渲染过程支离破碎,因此很难知道单个渲染命令何时完成。

所以 set-event 调用唯一可能发生的地方是......在 整个渲染通道 完成之后。那显然用处不大。

另一种方法是发出 ckCmdSetEvent 调用的行为从根本上重塑实现构建整个渲染过程的方式。将子通道分解为事件之前发生的事情和事件之后发生的事情。

但是 VkRenderPass 如此庞大和复杂的原因,VkPipeline 必须引用渲染通道的特定子通道的原因,以及 vkCmdPipelineBarrier 在内部的原因渲染通道需要您指定一个子通道 self-dependency,以便 TBR 实现可以预先 知道它何时何地必须打破理想的 TBR 渲染方案。有一个函数在没有预先警告的情况下引入分手违背了这个想法。

此外,Vulkan 的设计是这样的,如果必须非常低效地实施某件事,那么要么无法直接执行,要么 API 确实使它看起来非常低效。 vkCmd(Re)SetEvent 无法在 TBR 硬件上的渲染过程中有效地实现,因此您不能这样做。

请注意 vkCmdWaitEvents 没有这个问题,因为系统知道等待正在等待渲染过程 之外的东西。所以这只是一些特定的阶段,必须等待事件完成。如果它是一个顶点阶段进行等待,那么在该命令处理开始时设置等待就足够了。如果是片段阶段,可以在所有光栅化处理开始时插入wait;这不是处理它的最有效方法,但由于所有顶点处理都已执行,因此到那时事件已经设置的可能性很大。


对于其他类型的命令,回想一下渲染过程中发生的所有事情的依赖图是在 VkRenderPass 本身中定义的。子通道依赖图在那里。您甚至不能在渲染过程中发出正常的 vkCmdPipelineBarrier,除非该子过程在子过程依赖关系图中具有 explicit self-dependency。

那么,如果您不能等待操作在该子通道或稍后的子通道中完成,那么在子通道中间发出计算着色器分派或内存传输操作有什么好处?如果您不能等待操作结束,那么您就不能使用它的结果。如果你不能使用它的结果......你也可以在渲染过程之前发布它。

而您不能拥有其他依赖项的原因可以追溯到 TBR。依赖图是渲染通道不可分割的一部分,它允许 TBR 知道 up-front 子通道之间的关系是什么。这让他们知道他们是否可以构建他们理想的渲染器,并且 when/where 可能会崩溃。

由于渲染过程的 TBR 模型使此类等待变得不切实际,因此允许您发出此类命令毫无意义。