Vulkan 基本原理,下面介绍Vulkan的基础知识,涉及以下内容:
- Vulkan的执行模型
- Vulkan的队列
- 对象模型
- 对象生命周期与指令语法
- 错误检查与验证
Vulkan的执行模型
支持Vulkan的系统可以直接查询系统信息,并返回可用的物理设备的数量。每个物理设备可以支持一个或多个队列。这些队列被划分到不同的族群中,每个族群有自己独特的功能设定。例如,一个族群可能会支持诸如图形、计算、数据传输,或者内存管理相关的功能。队列族群中的每个成员可能包含了一个或者多个相似的队列,因此它们互相之间是兼容的。例如,某个具体的驱动实现中,可能在同一个队列里同时支持数据传输和图形操作。
Vulkan允许用户显式地在应用程序中管理和控制内存。它暴露了设备中支持的所有不同类型的内存堆(heap),每个堆属于一个不同的内存区域。Vulkan的执行模型是非常简单和直接的。在这里,指令缓存会被发送到队列中,后者将被物理设备按顺序依次执行和消耗。
Vulkan的应用程序负责控制一组Vulkan设备,将一系列指令记录到指令缓存中,并发送到队列。驱动会读取队列并按照记录的顺序依次执行各个工作。指令队列的构建是需要较大代价的,而构建一旦完成,它就可以被缓存和发送到队列中,根据自己的需要多次执行。此外,有些指令缓存在应用程序中也可以以多线程的方式并行同步地构建。
如图所示给出了一个简化后的执行模型:
在这里,应用程序记录了两个指令缓存,其中各包含了多个指令。这些指令随即按照作业性质的不同,被传递给一个或者多个队列。队列将这些缓存作业提交给设备加以执行。最后,设备处理得到结果,并将它们显示到输出设备上,或者返回给用户程序做进一步的处理。
Vulkan中,用户应用程序主要负责以下工作:
- 生产指令执行所必需的所有先决内容:
- 其中可能包括资源的准备、着色器的预编译、将资源关联到着色器、设置着色器的状态、构建流水线,以及绘制调用。
- 内存管理。
- 同步。
- 宿主和设备之间。
- 设备上不同的队列之间。
- 风险管理。
Vulkan的队列
队列是Vulkan的一种中间层机制,负责接收指令缓存并传递给设备。指令缓存记录了一个或者多个指令,并发送给相关的队列。设备则提供了多个可选的队列,然后应用程序负责将指令缓存传递给正确的队列。
指令缓存的发送可以按照以下两种方式来完成:
- 单一队列:
- 按照指令缓存发送的顺序进行维护,以及执行或者回放。
- 指令缓存按照串行的方式执行。
- 多重队列:
- 允许指令缓存以并行的方式在两个或者更多队列中执行。
- 除非特别指定,否则无法保证指令缓存发送和执行的顺序不变。应用程序需要负责进行此类同步操作,否则实际的执行顺序可能会完全与发送时的顺序无关。
Vulkan提供了多种同步图元,让用户可以相应地在单一队列中,或者跨队列地完成工作执行顺序的管理。如下所示:
- 信号量(semaphore):这种同步机制可以跨队列或在单一队列中的粗粒度指令缓存中执行。
- 事件(event):事件控制细粒度同步,并应用于单个队列,从而确保单一指令缓存中或者多个指令缓存之间的同步要求。宿主系统也可以参与到事件触发的同步机制当中。
- 栅栏(fence):允许在宿主和设备之间完成同步。
- 流水线屏障(pipeline barrier):流水线屏障是插入到指令缓存中的一种指令,它可以确保在它之前的指令始终优先执行,而在它之后的指令一定随后执行。
对象模型
在应用程序端,包括设备、队列、指令缓存、帧缓存、流水线等在内的所有对象,统称为Vulkan对象。而在内部的API层面,这些Vulkan对象会被识别为不同的句柄。这些句柄可以分为两种类型:可分发的以及不可分发的。
- 可分发的句柄:这类指针指向了一个不透明的内部图形实体。不透明的数据类型不允许直接访问它的内部结构体成员。你只能通过API函数来访问结构体的内部域。所有的可分发句柄都会关联一个可分发的类型,这样它就可以作为一个参数被传递给其他的API指令。一些典型的示例如下所示。
- 不可分发的句柄:这些64位整型类型的句柄通常不会指向一个结构体,而是直接包含了对象自身的信息。一些典型的示例如表所示。
对象生命周期与指令语法
Vulkan当中的对象是根据应用程序的逻辑需求显式地创建和销毁的,应用程序需要自己管理这些对象。
Vulkan的对象需要使用Create指令创建,以及使用Destroy指令销毁:
- Create语法:对象的创建需要通过
vkCreate*
指令完成,它需要一个Vk*Createinfo
结构体作为输入参数。 - Destroy语法:使用Create指令创建的对象总是需要使用
vkDestroy*
指令销毁。
如果对象是作为已有的对象池或者堆的一部分创建的,那么需要使用Allocate指令创建,并使用Free指令从池或者堆中销毁。 - Allocate语法:一个对象如果是作为对象池的一部分创建,那么需要使用
vkAllocate*
指令,并且需要一个Vk*AllocateInfo
作为输入参数。 - Free语法:已创建的对象需要使用
vkFree*
指令从对象池或者内存中释放。
上述所有的实现方法都可以通过vkGet*
指令轻松获取。而使用了vkCmd*
形式的API接口主要用于把指令记录到指令缓存当中。
错误检查与验证
Vulkan的设计在性能最大化的前提下提供了可选的错误检查和验证功能。在程序运行时,错误检查和验证的需求非常少,因此构建指令缓存和发送到设备的效率很高。这类可选的功能可以在Vulkan的层次化结构中使用,从而在运行系统中实现多个层之间的动态注入(调试和验证)。
评论前必须登录!
注册