目录
一、Loader
The Loader
二、Layer
调度链Dispatch Chains
JSON
一、Loader
Vulkan是一个层架构,由Vulkan Application+Loader+Layer+ICDs(Installable Client Drivers)组成。

Vulkan 是一个显式 API,可以直接控制 GPU 的实际工作方式。因此,Vulkan 支持具有多个 GPU 的系统,每个 GPU 运行不同的驱动程序或 ICD(可安装客户端驱动程序)。 Vulkan 还支持多个全局上下文(Vulkan 术语中的实例)。 ICD 加载器是一个放置在 Vulkan 应用程序和任意数量的 Vulkan 驱动程序之间的库,以支持多个驱动程序以及跨这些驱动程序工作的实例级功能。此外,加载程序还管理在应用程序和驱动程序之间插入 Vulkan 层库,例如验证层。
The Loader
application在一端,直接和loader打交道。loader另一端是ICDs,在application和ICDs之间,loader可以插入一系列可选的layers。loader负责和各个layer交互并且支持多GPUs和其驱动。任何一个vulkan api函数可以经过loader,layers和ICDs,loader负责将api传递dispatch给合适的layers和ICDs,vulkan对象模型允许loader插入layers层,并且组成调用链上一环,并最终传递给vulkan api给ICD。
loader的职责简单总结如下:
- 支持1个ICD或者多个ICDs,且保证ICD间不相互干扰,ICD是就是不同厂商对图形API所做的驱动文件。
- 支持Vulkan Layer,Layer是可选的
- 已最可能低的方式影响vulkan应用性能

恰巧vulkan的一个思想就是,去除掉验证层,那么验证层能放在何处?也就是放在这个SDK里,类似各种Graphic Debugger的实现,Vulkan有很多Validation Layer,可以根据需求载入,在真正的函数调用之前,截取一些信息,获得一些信息,最后再调用真正的接口。也就是说整个SDK不仅仅是一个函数指针获取器,还是一个Graphic Debugger。
二、Layer
layers是可选组件,可以增强vulkan系统,可以拦截,修改vulkan api,layers是作为lib库实现,可以通过不同方式使能并且在CreateInstance中被加载。每个layer可以选择任何vulkan api进行拦截,一个layer不需要拦截所有vulkan api function,layer可以选择取拦截所有已知vulkan api,也可以拦截一条vulkan api。
layer的一些示例如下:
- 校验api使用
- 增加debug和trace等调试信息
- 覆盖额外内容
因为layer是可选的,我们可以在调试阶段使能,在release时关闭。
vulkan中很多扩展和函数(api)被分成两个主要组,一个是实例instance相关对象,另一个是device相关对象。
vulkan instance是一个high-level系统级信息或者函数,vulkan对象如VkInstance和VkPhysicalDevice,vulkan函数如vkEnumerateInstanceExtensionProperties、vkEnumeratePhysicalDevices、vkCreateInstance、vkDestroyInstance等。可以使用vkGetInstanceProcAddr查询vulkan instance function,vkGetXXXProcAddr可以查询device或者instance入口点,返回的函数指针对实例或者基于实例创建的对象有效,包括vkDevice对象,同理,instance扩展是一系列vulkan实例函数。
vulkan device是一个逻辑标识,别用于在特定physical device关联的api,device object有vkDevice,vkQueue,vkCommandBuffer,任何是前面三个object孩子的dispatchable object。device function一般是将device object作为第一个参数的api,比如vkQueueSubmit、vkBeginComandBuffer、vkCreateEvent等。
可以使用vkGetInstanceProcAddr和vkGetDeviceProcAddr获取device function。
调度链Dispatch Chains
此时,我们需要讨论单个函数调用(如vkCreateInstance)如何传播到加载器、ICD 或 ICD 以及许多不同的层。
当应用程序调用加载器静态导出的任何函数时,它会调用trampoline function。然后,这个trampoline function调用调度链Dispatch Chains。调度链的思想来自函数指针链。它从加载器的trampoline function入口点开始,然后trampoline function调用第一层,然后第一层调用第二层。该链一直持续到最终到达 ICD 中的端点以真正完成工作。
更明确地说,vkCreateInstance就是这些特殊功能之一。在调度链开头的trampoline function中,它首先验证请求的层和扩展是否有效。一旦满足,它就会分配 Vulkan 实例和调度链,然后调用vkCreateInstance第一层的函数。第一层将初始化自身和任何内部结构,然后将执行传递到vkCreateInstance下一层,依此类推。
现在,从应用程序的角度来看,Vulkan 实例主要是一个加载器概念。它们代表了 Vulkan 使用的所有内容,但它们也将所有可用的 ICD 整合到一起。这意味着当我们到达vkCreateInstance 的调度链末端时,我们不会以 ICD 结束。可能有多个同时使用,并且 ICD 不知道彼此是否链接在一起。
加载器通过将自己的终止符函数放在调度链的末尾terminator function on the end of the dispatch chain,供最后一层调用来解决这个问题。然后,该terminator function 依次调用vkCreateInstance每个可用的 ICD 并存储所有这些 ICD 以供以后使用:

当我们进行时,各层正在初始化自己并为它们在调度链中的位置做好准备。特别是,每一层都用vkGetInstanceProcAddr查找它希望能够在下一层中调用的所有入口点。每层调用下一层的vkGetInstanceProcAddr并将它们存储在调度表中。这只是一个充满函数指针的结构。当加载程序调用链中第一层的入口点时,它已经知道要传递到下一层的位置。这还实现了一个有用的功能 - 层不必挂钩每个Vulkan 函数,只需挂钩它们感兴趣的函数即可。
当加载器和每一层调用vkGetInstanceProcAddr以查找调度链中的下一个函数时,就会发生这种情况。如果某个层不想拦截函数调用,则它不必返回自己的little stub function。相反,t can just forward the call to the next layer and return the result.。只要每个点的调度表都知道下一个要调用的函数,它们是否跳过一两层并不重要。

JSON
加载器将使用它来寻找入口点vkGetInstanceProcAddr/vkGetDeviceProcAddr,用于构建调度链。这些是模块需要导出的唯一入口点。由于必须导出与 API 函数名称完全相同的函数可能会很尴尬或不方便,因此您可以改为导出
SampleLayer_GetInstanceProcAddrSampleLayer_GetDeviceProcAddr。
{
"file_format_version" : "1.0.0",
"layer" : {
"name": "VK_LAYER_SAMPLE_SampleLayer",
"type": "GLOBAL",
"library_path": ".\\sample_layer.dll",
"api_version": "1.0.0",
"implementation_version": "1",
"description": "Sample layer - https://renderdoc.org/vulkan-layer-guide.html",
"functions": {
"vkGetInstanceProcAddr": "SampleLayer_GetInstanceProcAddr",
"vkGetDeviceProcAddr": "SampleLayer_GetDeviceProcAddr"
},
}
}
针对导出的每个函数按顺序strcmp参数pName,然后返回入口点的地址。
应用程序调用其中之一时,它们会调用loader而不是直接调用 Vulkan 驱动程序。
vkGet**ProcAddr:该命令可获取所有Vulkan命令的函数指针
Hook这一层的函数:在vkGet**ProcAddr中返回本地函数指针
调用下一层实体:每层调用下一层的vkGet**ProcAddr,并将其存储在调度表中
loader_platform_open_library(const char * libPath)
loader_scanned_icd_add(...)
loader_icd_scan(...)
vkCreateInstance(...)
main()
由于应用程序与任一调度链都无关,因此真正的区别在于,在可能的情况下,vkGetDeviceProcAddr直接返回指向调度链中第一个条目的函数指针 - 如果不存在任何层,则通常将函数指针直接指向 ICD。vkGetInstanceProcAddr可能会返回与加载程序导出的相同的函数,该函数从可分派句柄中获取分派表并跳转到它。


















