[Vulkan规范]Vulkan的一些基础的概念

本文介绍一些基础的概念,包括Vulkan架构和执行模型、队列操作、对象模型。

架构模型

Vulkan和其API为符合以下特征的CPU、GPU和其他硬件加速架构所设计和实现:

• 运行时库支持8位、16位、32位和64位有符号和无符号整形,都可以通过该类型数据的粒度的大小来寻址到。
• 运行时库支持满足 Floating Point Computation 节的范围和精度的32位和64位浮点类型。
• 这些类型的表示和大小端必须满足主机端和设备端一致。

注意:因为Vulkan中很多数据类型和结构可能在主机端和设备端内存来回的映射,主机端和设备端架构必须能够高效的访问到数据,以便很方便的写高性能、可移植的应用程序。

在支持Vulkan的特定平台上此规范对影响ABI()的选项开放,这些选项通常是平台提供商用来向前兼容的。一些选项,比如函数调用惯例,可能在 vk_platform.h 头文件中的不同部分。

注意:例如,Android ABI由Google定义,Linux ABI是通过一系列的GCC默认项、发行版提供商和诸如Linux标准库 的外部标准一同定义的。

执行模型

本小节描绘了Vulkan执行系统执行模型的主框架。

Vulkan对外暴露一个或多个设备,每一个对外暴露一个或多个队列,队列之间异步的处理工作。一个设备支持的一个集合的队列被分到族里。每一个族都支持一个或多个类型的功能,并可能包含多个拥有相近特性的队列。在同一个族中的队列被认为是互相兼容的,同一个族中的队列需要完成的任务可以被任何一个队列执行。这份规范定义了队列可能支持的四种功能:图形,计算,转移,稀疏内存管理。

注意:单个设备可能报告有多个相近的队列族,而非报告含有这些队列中一个或者多个成员。这表明多个族的有相近功能的成员之间可能并不兼容。

设备内存是由应用程序显式的管理的。每一个设备可能宣称有一个或多个堆,表示内存的不同区域。内存堆可能是在主机端或者是设备端,但是,都能被设备所见。关于内存堆的更多细节是通过该堆上获取的内存类型对外暴露的。在一个Vulkan实现上可用的内存区域的例子包含如下:

• device local 设物理连接到设备的内存。
• device local, host visible 是设备端内存,对主机端可见。 the host.
• host local, host visible 是主机端内存,对设备和主机都可见。

在其他的一些架构上,也许只有个堆,可以用作任何用途。

一个Vulkan应用程序通过提交记录了Vulkan库调用激发的设备命令的命令缓冲区来控制多个物理设备。命令缓冲区的内容是通过硬件指定的,对应用程序不可见。一个命令缓冲区一旦被构造出来,就可以马上一次或多次提交到一个设备上以备执行。多个命令缓冲区可能在应用程序里多线程中并行的被构建。

提交到不同的队列的命令缓冲区可能并行的执行或者乱序执行。提交到同一个队列的命令缓冲区遵循submission order, 这在synchronization chapter中深入描述。命令缓冲区在设备上的执行和主机端的执行也是异步的。一旦命令缓冲区被提交到一个队列,控制权马上返回到应用程序。在主机端和设备之间的同步,和不同队列之间的同步是应用程序的职责。

队列操作

Vulkan队列提供了设备执行引擎的接口。执行引擎的命令需要在执行之前被记录到命令缓冲区。这些命令缓冲区然后被一个 _queue submission_提交到队列以供一个或多个批次执行。一旦提交到队列,这些命令将开始并完成执行,不受应用程序的干扰,虽然执行的顺序会受到 implicit and explicit ordering constraints的一些限制。

任务通常被一些队列提交命令提交到队列,这些命令一般形如vkQueue* (比如 vkQueueSubmit, vkQueueBindSparse),且可能接受一些等待任务开始的信号量和一些任务完成才激发的信号量。任务本身,以及激发和等待信号量都是队列操作。

在不同队列上的队列操作并没有隐式的顺序限制,可能以任何顺序执行。不同队列间显式的顺序限制可以通过semaphores 和fences表述。

提交到单个队列的命令缓冲区遵循submission order 和其他 implicit ordering guarantees,否则可能重叠或者乱序执行。对于单一队列上的批次和队列提交的其他类型,和其他队列或批次提交之间并没有隐式的顺序限制。在不同队列和各自的批次之间的附加显式的顺序限制可以通过semaphores 和 fences表述。

在栅栏或信号量被激发之前,可以确定的是之前被提交的队列操作已经完成了,且这些队列操作的内存写入对未来的队列操作可见。等待一个被激发的信号量或者栅栏保证之前的可用的写入对后续的命令是可见的。

在相同或不同的批次或者提交,还有主和次命令缓冲区之间的命令缓冲区边界,不会有任何附加的顺序限制。也就是,在任何信号量或栅栏操作之间提交多个命令缓冲区(包含执行次级命令缓冲区)执行被记录的命令,就如同他们被记录进入 单个主命令缓冲区一样,除了每一个边界当前的状态都被重置 。显式顺序限制可以通过 explicit synchronization primitives表示。

在一个命令缓冲区内多个命令之间有一些隐式顺序保证,但是只包含 一部分执行子集。附加的显式顺序限制可以通过多种显式同步原语来表示。

注意:Vulkan实现对提交到一个队列的任务之间的重叠执行有极大的自由度,这是由 Vulkan设备里深度的管线和并行机制导致的。

被记录在命令缓冲区的命令,要么执行操作(绘制、分发、清除、复制、查询/时间戳操作、开始/结束subpass操作),设置状态(绑定管线、描述符集、缓冲区、设置动态状态、推送常量、设置render pass/subpass状态),要们执行同步(设置/等待时间、管线屏障、renderpass/subpass依赖)。一些命令执行不止一个上述任务。状态设置命令 更新命令缓冲区的 当前状态。一些命令执行操作(如绘制/分发)基于从命令缓冲区开始累积到当前状态集。执行操作的命令内的任务是可以重叠或者重新记录的,但是必须禁止改动每一个操作命令使用的状态。通常,操作命令是那些更改帧缓冲区附件、读写缓冲区或者图像内存、想查询池写入的命令,

同步命令在两个操作命令集合之间引入显式的execution and memory dependencies ,这里第二个命令集合依赖于第一个命令集合。这些依赖强制保证在后面的集合中的某些管线阶段的执行发生在源集合中某些阶段的执行之后,且某些管线阶段执行的内存访问的影响结果顺序发生并对彼此可见。当没有显式的依赖或隐式的顺序保证,操作命令也许重叠执行或者乱序执行,而且看不到 彼此的内存访问的影响结果。

设备执行队列操作和主机端是异步的。当命令缓冲区被提交到队列后控制流马上就退回到应用程序了。应用程序必须按需求在主机端和设备端同步任务。

对象模型

设备、队列和Vulkan中其他的的实体都是通过Vulkan对象表示的。在API层,所有的对象都通过handle来引用。有两种类型的handle:可分发的与不可分发的。可分发的 handle是不可见类型数据的指针。这个指针可被layers使用,被当作拦截API命令的一部分,每一个API命令头接受一个可分发类型的handle作为第一个参数。每一个不可分发类型的对象必须在其生命周期内有唯一一个handle值。

不可分发的 handle 类型是64位整型类型,其含义是Vulkan实现决定的,能把对象信息直接包含到handle里,而非通过指向一个数据结构。不可分发类型的对象,不一定只有一个唯一的handle值。如果其他类型的handle值变得无效了,那么销毁这样的一个handle必须不能导致此对象其他类型的handle失效,如果一个handle值被创建的次数多与被销毁的次数,则必须不能导致同种类型的等价的handle变得无效。

所有通过VkDevice (比如 with a VkDevice 作为第一个参数)的命令创建的对象都是该设备私有的,必须不能被其他设备使用。

对象的生存周期

对象都是通过形如vkCreate* and vkAllocate* 这样的命令创建或者分配的。 一旦一个对象被创建或者分配,它的结构就被认为是不变的,即使某个对象类型的内容仍然是可以被自由的改动。 对象都是通过形如vkDestroy* and vkFree* 的命令来销毁或者释放的。

被分配(而不是创建)的对象从一个已存在的池子对象或者内存堆中获取资源,当被释放时把资源归还给该池子或者堆。但对象的创建和销毁在运行时通常是低频操作,分配或者释放对象可能是高频的。对象池帮助调节分配和释放的性能提升。

应用程序有责任跟踪Vulkan对象的生命周期,且在对象正在被使用时不能销毁它们。

应用程序所拥有的内存被内存传递所到的命令迅速使用。在使用这些内存的命令返回后,应用程序可以立刻更改或者释放这些内存。

以下对象类型被传入Vulkan命令是被使用,此后并不被用它们来创建的对象所访问。它们在传入所到的API命令执行期间被能被销毁:
• VkShaderModule
• VkPipelineCache

一个 VkPipelineLayout 对象在被任何使用它的命令缓冲区记录状态时被销毁。

VkDescriptorSetLayout 对象可以被操作使用其布局的描述符集合的命令访问,在描述符集合布局被销毁后这些描述符集合必须不能被vkUpdateDescriptorSets 更新。否则的话,描述符集合布局可在它们不被Vulkan API命令使用的任何时刻被销毁。

在设备(如从过命令缓冲区执行)已经完成使用Vulkan对象之前,应用程序必须不能销毁这些任何类型的Vulkan对象。

如下类型的Vulkan对象在被命令缓冲区使用或者暂停执行时不能被销毁:
• VkEvent
• VkQueryPool
• VkBuffer
• VkBufferView
• VkImage
• VkImageView
• VkPipeline
• VkSampler
• VkDescriptorPool
• VkFramebuffer
• VkRenderPass
• VkCommandPool
• VkDeviceMemory
• VkDescriptorSet

以下Vulkan对象在队列执行使用到这些对象的命令时不能被销毁:
• VkFence
• VkSemaphore
• VkCommandBuffer
• VkCommandPool

通常,对象可以按照任意顺序销毁或者释放,即使被释放的对象可能使用到另外一个对象(如在视图中使用一个资源, 在描述符集合中使用视图,在命令缓冲区中使用对象,绑定分配的内存到资源),只要使用被释放了的对象的对象不被 再次使用,除了被销毁或者重置这样的导致对象不再使用另外一个对象的情况(比如重置了命令缓冲区)。如果对象被重置,那么它可以被使用,如同从来没有用过被释放的对象一样。一个例外是对象之间存在父子关系时。在这种情况下,在子对象被销毁前应用程序必须不能销毁父对象,除非父对象被释放 时被定义显式的释放它的子对象(比如下面定义的对象池)。

VkCommandPool 对象是 VkCommandBuffer 的父对象。 VkDescriptorPool 对象是 VkDescriptorSet 的父对象。VkDevice 对象是很多对象类型(所有接受VkDevice作为参数来创建)的父对象。

下面的Vulkan对象在被销毁时有特定的限制:

• VkQueue 不能被显式的销毁。当它们所在的VkDevice对象被销毁时才被隐式的销毁。

• 销毁一个池对象隐式的释放从它分配出来的所有对象。特别是,销毁VkCommandPool就会释放所有从之分配出来 的VkCommandBuffer对象,销毁VkDescriptorPool会释放从之分配而来的VkDescriptorSet对象。

• 当所有从VkDevice获取到的VkQueue对象处于空闲状态时,VkDevice 对象可以被销毁,所有依VkQueue而创建的对象也被销毁了。这包括如下对象:
   ○ VkFence
   ○ VkSemaphore
   ○ VkEvent
   ○ VkQueryPool
   ○ VkBuffer
   ○ VkBufferView
   ○ VkImage
   ○ VkImageView
   ○ VkShaderModule
   ○ VkPipelineCache
   ○ VkPipeline
   ○ VkPipelineLayout
   ○ VkSampler
   ○ VkDescriptorSetLayout
   ○ VkDescriptorPool
   ○ VkFramebuffer
   ○ VkRenderPass
   ○ VkCommandPool
   ○ VkCommandBuffer
   ○ VkDeviceMemory

VkPhysicalDevice 不能被显式的销毁。相反,在所有从值获取的VkInstance对象被销毁后被隐式的销毁。

当所有从VkPhysicalDevice中创建的 VkDevice被销毁后, VkInstance 对象才被销毁。

本文转载自:CSDN - know yourself 的关于“Vulkan 规范”的翻译。
声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。

--电子创新网--
粤ICP备12070055号