Vulkan的执行模型和同步化

时间:2019-07-01 15:36:00

标签: vulkan

我正在努力消除对Vulkan执行模型的困惑,我希望验证我的理解并获得对我仍然不清楚的问题的答案。

所以我的理解是:

  1. 主机和设备相对于彼此完全异步执行。我必须使用VkFence在它们之间进行同步,即,当我想知道特定的提交已在设备上完成执行时,我必须在主机上等待相应的VkFence发出信号。

  2. 不同的命令队列彼此异步执行。 Vulkan规范不提供有关对这些队列的提交开始或完成执行的顺序的任何保证。因此,队列A上的vkQueueSubmit与队列B上的vkQueueSubmit完全独立执行,我必须使用VkSemaphore以确保例如对队列B的提交在向队列提交后开始执行A完成。

  3. 但是,提交给同一命令队列的不同命令遵循它们的提交顺序,这意味着除非早先提交的命令已经开始执行,否则以后提交的命令将不会开始执行,但是另一方面,这并不意味着这些后面的命令无法在前面的命令之前完成执行。

  4. 状态设置命令(例如vkCmdBindPipelinevkCmdBindVertexBuffers ...)不是异步的,并且延迟了以后(例如vkCmdDraw)。实际上,它们立即在主机(而不是在设备)上执行并修改VkCommandBuffer的状态,并且此累积修改的状态用于记录随后的操作命令。

  5. 从同步的角度来看,VkRenderPass可以看作是管道障碍的更简单接口。可以认为在渲染通道实例的开头(代替vkCmdBeginRenderPass)有一个管道屏障,在渲染通道实例的末尾(代替vkCmdEndRenderPass)有一个管道屏障,并且有一个管道屏障在每个子通道之后(代替vkCmdNextSubpass)。

  6. 在我看来,关于如何在单个命令队列上执行命令的思维模型就像一大堆命令流(按将它们记录到命令缓冲区的顺序和将这些命令缓冲区提交到的顺序排列)队列)被管道壁垒分割。每个管道屏障将流分为两部分,屏障之前的命令(A部分)和屏障之后的命令(B部分)。仅在A节中的所有命令完成了流水线阶段X的执行之后,才允许B节中的命令开始(或继续在流水线阶段Y继续执行)。

问题:

  1. Vulkan规范( 2.2.1。队列操作)规定:

      

    命令缓冲区提交到单个队列遵守提交顺序   和其他隐式排序保证,但否则可能会重叠或   乱序执行。其他类型的批次和队列提交   针对单个队列(例如稀疏内存绑定)没有隐式   其他队列提交或批处理的排序约束。

    可以说,在我的程序中,我只有一个通用队列,可以发出各种命令(图形,计算,传输,表示等),所以上面的语句是什么意思? vkQueueSubmit #3仅在vkQueueSubmit #2已经开始执行之后才开始执行,该执行仅在vkQueueSubmit #1已经开始执行之后才开始,...但是vkQueueBindSparsevkQueuePresentKHR可以在下面开始无论何时,它们都是由主机发布的...换句话说,我总是必须使用VkSemaphore以确保演示文稿(vkQueuePresentKHR)在正确的时间开始(仅在我所有的图形工作之后)已提交并执行,因此可以展示。)

  2. 我对命令缓冲区本身中的提交顺序的定义有些困惑。规范状态(第6.2节。隐式同步保证):

    1)

      

    对于在渲染过程之外记录的命令,这包括所有其他   在渲染过程之外记录的命令,包括   vkCmdBeginRenderPass和vkCmdEndRenderPass命令;它不是   直接在渲染过程中包含命令。

    2)

      

    对于在渲染过程中记录的命令,这包括所有其他   记录在同一子通道内的命令,包括   vkCmdBeginRenderPass和vkCmdEndRenderPass命令来分隔   相同的渲染通道实例;它不包括记录到   其他子通道。

    第一个要点似乎很清楚。提交顺序是将命令记录到命令缓冲区的顺序,而就此项目符号而言,vkCmdBeginRenderPassvkCmdEndRenderPass块内部的任何内容都被视为一个命令。第二个要点对我来说还不清楚。在这里如何定义提交顺序?显然,除非先前的命令已开始执行或除非执行了vkCmdBeginRenderPass,否则特定子通道中的任何命令都不会开始执行。但是不同的子通道呢?这是否意味着子通道1在子通道0开始执行之前就可以开始执行?这对我来说没有意义。有意义的是,如果仅允许先前的子通道完成后才允许以后的子通道开始。

  3. Vulkan规范(第6.1.2节“管道阶段” )规定:

      

    跨流水线阶段执行操作必须遵循隐式   订单保证,特别是包括管道阶段订单。

    这是否意味着,例如,除非调用调用1的顶点着色器阶段已经开始执行,否则不允许调用调用2的顶点着色器阶段开始执行?

  4. 我对Vulkan命令队列执行的思维模型(我的理解是第6个问题)引发了一个问题,即提交到命令缓冲区(B)开头的管道屏障是否会影响较早的命令缓冲区(A)。我的意思是说,这会使命令缓冲区B中的命令等待开始执行直到命令缓冲区A中的命令完成吗?我在某处读到,不同命令缓冲区之间的同步是事件的工作,但据我了解,使用障碍也应该可行。

  5. 如果我将VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT用作流水线屏障的源阶段,并将VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT用作流水线屏障的目标阶段,则该管道屏障应基本上禁用屏障前后的命令之间的任何重叠,对吗?

  6. 如我所见,Vulkan中有几种不同的并行性:

    1. 在CPU和GPU之间,它们与VkFence同步
    2. GPU上不同的命令队列之间,它们与VkSemaphore同步
    3. 在同一队列的不同提交之间,例外似乎是带有vkQueueSubmit的提交。它们也与VkSemaphore同步。
    4. 在不同的绘制调用之间。这些与管道障碍同步。

      这对我来说最令人困惑。因此,如果我有一个通过某种方式使用任何先前调用的结果或写入同一渲染目标(帧缓冲区)的调用,那么据我所知,我需要确保后面的调用能够看到所有调用的记忆效应。以前的通话。但是,当我用一堆游戏角色,树木和建筑物渲染场景时,该怎么办?可以说,每个这样的对象都是一个drawcall,所有这些drawcall都写入同一帧缓冲区。每次抽奖之后我都需要发出存储障碍吗?直观上,这是多余的,我签出的演示在这种情况下没有发出任何障碍,但是是否有任何保证在逻辑上遵循drawcall之后才能在逻辑上看到drawcall的记忆效果?问题是,何时需要在不同的调用之间进行同步?

    5. 在一个绘制调用中。着色器原子指令可以在此级别进行同步。

      但是,只要我没有做任何异常的事情,例如从多个着色器实例写入同一内​​存地址或从我刚刚写入的同一内存中读取(例如,在片段着色器中实现自定义混合),我应该没事的。换句话说,如果每个片段着色器仅读写相应的像素或顶点数据,那么我就不必担心在同一drawcall中的同步。

1 个答案:

答案 0 :(得分:1)

  

主机和设备相对于彼此完全异步执行。

是的

除非使用显式同步(即VkFencevk*WaitIdleVkEvent)。或一种罕见的隐式同步(主机写入对于任何后续vkQueueSubmit的设备访问都是可见的)。

请注意,还必须有一个“内存域操作”。即在读取CPU上GPU的输出时,必须使用VK_PIPELINE_STAGE_HOST_BIT。 ({VkFence单独执行执行和内存依赖是不够的。)

  

不同的命令队列彼此异步执行。

正确。换句话说,来自任何两个队列的命令可以依次运行,彼此相邻(并行)运行,甚至可以被抢占和分时运行,或者是上述某种组合。什么都可以。除非使用显式同步(VkSemaphoreVkFence)。

  

但是,提交到同一命令队列的不同命令会遵循其提交顺序

是的。但是只有规范形式主义没有现实效果。它只是被指定,因此我们有一个正式的语言框架,可以在其中描述规范中的其他内容(例如,它指定了描述管道屏障行为所必需的术语)。

  

状态设置命令(例如vkCmdBindPipelinevkCmdBindVertexBuffers ...)不是异步的,并且稍后会延迟(例如vkCmdDraw)。

不,那不完全是我的描述方式。 它们不是“延迟”的。只需在命令缓冲区中记录的确切位置执行它们即可。

这也许是我们需要“提交命令”形式主义的事情之一。状态命令之后,所有命令按提交顺序稍后会看到新状态。 (即,只有在state命令之后记录的命令才能看到新状态)。

  

从同步的角度来看,VkRenderPass可以看作是管道障碍的更简单接口。

我不这么认为。实际上,它可能稍微复杂一些。

它所做的是更有效的同步,尽管它在功能上可能定义了与流水线屏障相同的同步。它的不同之处在于(除其他外)它将同步定义为一个整体(即,您先告诉驾驶员您将使用哪些资源,并概述以后将要对它们进行的所有操作)。

Render Pass是移动切片架构GPU所必需的工具。在台式机上,如果它们从移动GPU中获得一些架构灵感,或者只是作为驱动程序优化的预言,也很有用。

  

那么,上面的陈述意味着什么? vkQueueSubmit#3仅在vkQueueSubmit#2已经开始执行后才开始执行,只有在vkQueueSubmit#1已经开始执行之后才开始

是的,不是。阅读以上有关提交顺序的形式主义的信息。 从技术上讲,是的,可以保证命令按顺序执行其VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT但是,该阶段不执行任何操作。。

AIS,它只是用于其他事物的规范形式。它本身并没有说什么。

  

我对命令缓冲区本身中的提交顺序的定义有些困惑。

是的,语言有点棘手。使您绊倒的部分是子通道。请注意,根据定义,子通道也是异步的。因此,我们不能在引号“ 1)”中使用简单规则。

如果我将其解码,则规范引用的意思是:

a)在渲染通道实例之前(即vkCmdBeginRenderPass之前)记录的任何命令的提交顺序都比vkCmdBeginRenderPass早,并且早于任何命令和所有命令在子通道中。 (反之亦然,子过程中的所有内容都将按提交顺序稍后进行。)

b)同样,在渲染过程实例之后(即vkCmdEndRenderPass之后记录的任何命令,其提交顺序比vkCmdEndRenderPass ,并且晚于所有子通道中的命令。

c)单个子通道中的命令的提交顺序与它们在(vkCmd*中记录的顺序相同。

d)任何两个子阶段中的命令彼此之间的提交顺序相同。

记住提交顺序只是形式主义。实际上,“ d)”的意思是您不能在子通道vkCmdPipelineBarrier中执行1,并且期望该障碍能够覆盖子通道0中的所有内容。 (您必须做的是使用VkSubpassDependency而不是vkCmdPipelineBarrier来实现子遍历01之间的依赖性。)

  
    

跨流水线阶段执行操作必须遵守隐式顺序保证,尤其是包括流水线阶段顺序的情况。

  

这只是介绍性说明,它链接到规范中的其他内容。它本身并没有说什么。

“隐式订购保证”链接到我们涵盖的提交订单。

“管道阶段顺序”仅链接到管道阶段顺序。这只是在流水线阶段之间指定“逻辑顺序”(例如,“顶点着色器”位于“片段着色器”之前)。这意味着无论何时在任何srcStage参数中使用阶段标志位,Vulkan都会隐式假定您还指任何逻辑上较早的阶段标志位。 (对于dstStage也是如此)。

  

我对Vulkan命令队列执行的思维模型(我的理解是第6个问题)引发了一个问题,即提交给命令缓冲区(B)开头的管道屏障是否会影响早期的命令缓冲区(A)

是的,这是一般想法。

这样想:vkQueueSubmit将来自队列末尾命令缓冲区中的命令连接起来。由于某种原因,它被称为“队列”。因此,流水线屏障会影响先前提交的命令缓冲区。 (顺便说一句,这就是为什么它被称为提交订单)

  

如果我将VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT用作流水线屏障的源阶段,将VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT用作流水线屏障的目标阶段,则该管道屏障应基本上禁用屏障前后的命令之间的任何重叠,对吗?

是的,但这是一个代码腐烂。

在这种情况下,请使用VK_PIPELINE_STAGE_ALL_COMMANDS_BIT。对于阅读此类代码的人来说,它更容易理解。

  

据我所知,Vulkan中有几种不同的并行性:

异步。

不能保证平行。即允许驱动程序序列化工作负载或分时工作。

但是例如根据一些常识,您可以猜测,如果是专用GPU,则CPU和GPU之间将存在(显着)并行性。

  

问题是,何时需要在不同的调用之间进行同步?

是的,我认为绘制命令之间没有帧缓冲区同步是Vulkan的例外/简化之一。

我相信人们会通过Primitive OrderRasterization Order的规范来支持它。

即在单个子传递中,您不需要两个vkCmdDraw*之间的流水线屏障来同步颜色和深度缓冲区。 (我认为)您仍然需要在子通道中与其他子通道以及渲染通道实例外部显式地同步绘制。

  

但是,只要我没有做任何异常的事情,例如从多个着色器实例写入同一内​​存地址或从我刚刚写入的同一内存中读取(例如,在片段着色器中实现自定义混合),我应该很好。

是的。流水线以及固定和可编程阶段的工作方式应与OpenGL类似。大多数情况下,您应该可以使用OpenGL着色器,而几乎不需要修改就可以实现相同的行为。