Vulkan 使用指令缓存

指令缓存就是集合了一系列指令的一个缓存单元。指令缓存可以录制多种不同的Vulkan API指令,它们都是应用程序期望执行的。指令缓存创建之后,就可以一遍又一遍地重用。它们按照应用程序指定的顺序记录指令。这些指令用于执行不同类型的工作,包括绑定顶点缓存、绑定流水线、录制渲染通道指令、设置视口和裁切矩形、设置绘制指令、执行图像和缓存内容的复制操作,等等。

指令缓存的类型主要有两种:主指令缓存次指令缓存

  • 主指令缓存primary command buffer):包含次指令缓存,负责执行它们,并且直接发送它们到队列中。
  • 次指令缓存secondary command buffer):通过主指令缓存执行的,自己不能直接提交到队列。

一个应用程序中指令缓存的数量可能成百上千。Vulkan API的设计是为了最大化地提升性能。因此,指令缓存的分配有必要通过指令池来完成,从而降低多个指令缓存之间的资源创建带来的性能消耗(尤其对于多线程的开发环境来说,因此,指令缓存不能直接被创建;我们必须通过指令池的方式去分配它们,如图所示。
Vulkan 使用指令缓存

指令缓存可以是一直存在的,它们只需要创建一次,就可以一直反复地使用。此外,如果我们不想继续使用某一个指令缓存了,可以通过一个简单的休眠指令让它恢复到可复用的状态,这样另一个录制流程就可以使用它了。相比传统的“先销毁缓存,再创建一个新的缓存”这样的流程,上述方案是非常高效的。

显式同步

在多线程环境中创建多个指令缓存时,建议通过为每个线程中引入单独的指令池来确保它们的同步运行。这样指令缓存的分配过程就会变得更为高效,并且应用程序不需要显式地在不同的线程之间执行同步操作。如图所示。
Vulkan 使用指令缓存

但是,如果对于多个线程之间共享的指令缓存的同步操作来说,就必须交由应用程序负责管理了。

作为对比,OpenGL采用了隐式的同步模型。OpenGL中有大量可以自动完成的工作,但是也需要大量的消耗来进行资源跟踪、缓存刷新、依赖链条的构建等操作。所有这些工作都是在幕后完成的,并且需要相当的CPU资源来支持。Vulkan相比之下所需环境是非常简单的,这种显式的同步机制可以确保系统中不会存在什么隐藏的风险,让用户猝不及防。

应用程序需要关注它自己的资源,包括它们的用途和依赖情况。驱动程序并不会精准地定位所有的依赖情况。因此,OpenGL的实现过程可能会存在预料之外的着色器重编译、缓存刷新等消耗。但是Vulkan的显式同步机制绝对不会出现这种情形,硬件端的工作会因此变得更有效率。

另一个不同之处是,OpenGL中的指令缓存提交方法——指令缓存是在场景幕后进行管理的,应用程序没有权利控制它的执行。应用程序发送指令的时候,并不能保证这些指令中的内容都会被系统执行。这是因为OpenGL是按批次来执行指令缓存的。它会等待指令构建成一个新的批次,然后统一进行分发。而相比之下,Vulkan通过显式的指令缓存控制方式,以便通过将其提交到所需的队列来预先进行处理。

指令缓存中的指令类型

指令缓存中记录了一条或者更多条指令。这些指令主要可以划分为以下三种类型:

  • 行为action):这一类指令会执行诸如绘制、分发、清屏、复制、查询/时间戳,以及子通道的开始/结束等操作。
  • 状态管理state management):这一类指令包括描述符集合,绑定流水线、缓存,用于设置动态状态、推送常量,以及设置渲染通道/子通道的状态。
  • 同步synchronization):这一类指令用来执行各种同步:流水线屏障、设置事件、等待事件,以及渲染通道/子通道的依赖。

指令缓存与队列

指令缓存提交到硬件队列的过程和它们被执行的过程是异步进行的。我们通过将指令缓存按照批次打包,然后一次性执行的做法,可以提升队列提交的效率。Vulkan采用一种延迟指令执行的模式,指令缓存中记录的绘制调用,和指令缓存的提交过程会被视为两个不同的操作,可以各自单独完成。从应用程序的角度来说,这样会很有帮助。因为我们可能已经知道场景中大部分内容的情况,并且可以采用上述策略在提交过程中进行适当的优化操作,而在OpenGL中这一点是非常难做到的。

Vulkan提供了硬件队列的一个逻辑视图,每个逻辑视图都会紧密地关联到一个硬件队列。一个Vulkan硬件队列可以被多个不同的逻辑队列分别对应,而每个逻辑队列是通过不同的队列参数创建而成的。例如,要显示一幅渲染后的交换链图像,我们可能需要使用指令缓存将它提交给一个图形队列,而后者也是可以用作渲染展示的。

执行顺序

指令缓存可以提交到一个队列或者多个队列中:

  • 单队列提交single queue submission):多个指令缓存提交到一个队列的过程是可以叠加执行的。在单队列提交过程中,指令缓存必须按照具体操作的顺序排列,按照指令顺序和API顺序执行。酷客教程(vulkan)中只涉及函数vkQueueSubmit的指令提交,并不涉及稀疏内存绑定的指令缓存的问题(通过vkQueueBindSparse)。
  • 多队列提交multiple queue submission):提交给多个队列的指令缓存可能会按照任何一种顺序执行,除非我们已经通过信号量、屏障等同步机制,显式地定义了排序的约束条件。

酷客网相关文章:

赞(0)

评论 抢沙发

评论前必须登录!