为了使Vulkan应用程序获得最佳性能,应用程序应该为每一个Vulkan API入口点设置自己的分派表。对于分派表中的每一个实例级(instance-level)的Vulkan命令,应该用
vkGetInstanceProcAddr
函数来查询和填充函数指针;对于每一个设备级(device-level)的Vulkan命令,应该用
vkGetDeviceProcAddr
来查询和填充函数指针。
为什么要这样做呢?主要在于Instance函数的调用链与Device函数的调用链是如何实现的。
Vulkan Instance是用于提供Vulkan系统级信息的高级结构。因此,Instance函数需要“广播”到系统上的每个可用驱动程序。下图展示了带有3个使能层(Layers)的Instance调用链流程:
上图同样也是Vulkan中Device函数调用链的流程图(如果查询方式是使用
vkGetInstanceProcAddr
函数的话)。
其实,Device函数不需要考虑“广播”问题,因为它们知道自己应该停在哪一个驱动、哪一个物理设备。这样一来,Vulkan Loader也不需要介入任何使能层和驱动程序之间。使用Vulkan Loader导出的Vulkan Device函数的调用链是这样的:
对应用程序来说,还有一个更优的解决方案,就是使用
vkGetDeviceProcAddr
函数查询所有的Device函数,其调用链可以进一步优化成下面这样(彻底将Vulkan Loader从调用链中移除):
还有,如果调用链中没有层被使能,应用程序的函数指针是直接指向驱动的,每减少一层间接的调用就会带来显著的性能提升。
注意:Device函数中仍有一少部分需要使用Vulkan Loader的
trampoline
和
terminator
去拦截,这些函数的典型特征就是Vulkan Loader用自己的数据将其包装了一遍。这种情况下,Device调用链会与Instance调用链一样。例如
vkCreateSwapchainKHR
函数,这个函数就需要Vulkan Loader的
terminator
,因为对于
vkCreateSwapchainKHR
函数,Vulkan Loader需要在将函数的信息传递给驱动程序之前,把KHR_surface对象转换为特定驱动的KHR_surface对象。
总而言是,请记住:
-
vkGetInstanceProcAddr
:是用来查询Instacne函数和物理设备函数的,但是也可用来查询所有函数;
-
vkGetDeviceProcAddr
:仅用于查询Device函数。
Vulkan Loader库的安装典型地要么是通过基于操作系统平台的方式安装(例如Linux系统的软件包package),要么作为驱动的一部分进行安装(例如Windows系统上使用Vulkan Runtime installer)。应用程序或引擎可能希望将Vulkan Loader本地安装到它们的执行树中,作为它们自己的安装过程的一部分(即打包在一起),这是因为提供了特定的Vulkan Loader:
-
保证某些Vulkan API导出在Vulkan Loader中是可用的;
-
确保某些Vulkan Loader的行为是周知的;
-
在用户安装之间提供一致性。
然而,我们非常不建议这样做,因为:
-
打包后的Vulkan Loader可能与将来驱动程序的修订版不兼容 (尤其是在Windows系统上,当操作系统升级更新后驱动的安装位置会变);
-
它不利于应用程序/引擎使用新的Vulkan API版本/扩展的导出;
-
应用程序/引擎可能会错过重要的bug修复;
-
打包后的Vulkan Loader不会包含有用的功能更新(例如:改进了Vulkan Loader的可调试性等)。
当然,即使一个应用程序或引擎在最初发布的时候确实附带了特定版本的Vulkan Loader,它也可以选择在未来的某个时刻升级或移除这个Vulkan Loader,因为Vulkan Loader中所需要的功能会随着时间的推移而显露,不过这取决于最终用户是否正确执行更新程序。
在Windows上有一个更好的选择是:为产品提供所需版本的Vulkan Loader打包Vulkan Runtime installer。 然后,安装过程可以使用Vulkan Runtime installer来确保最终用户的系统是最新的,Runtime installer将检测已安装的版本,并且只在有必要的情况下安装新的runtime。
另一种替代方法是编写应用程序,让它可以回退到更早的版本,但会显示警告,指示哪些功能已禁用,直到用户将其系统更新到特定的runtime或驱动程序。
应用程序想要的功能超过了当前系统上Vulkan驱动能够提供的功能怎么办呢?可以选择使用各种各样的层(Layers)来增强Vulkan API。无法添加新Vulkan核心API 的入口点的层是不会在Vulkan.h中暴露的,但是这些层可以提供扩展的实现,这个扩展的实现会引入额外的入口点,这些额外的扩展入口点能够通过Vulkan扩展接口来查询。
层的常用功能是验证API,只需在应用程序开发过程中使能就行,最后在应用程序发布前再禁用。这样就能够简单地控制由于应用程序使能API的验证而产生的开销,不过这在早期的图形API中不一定能实现。
对于一个应用程序来说,哪些层是可用的呢?Vulkan Loader会在系统的各个位置寻找层,并且报告它找到的所有层。
要启用特定的层,只需在调用期间在参数中传递要使能的层的名字就行。一旦使能,这些层对于所有Vulkan函数及其子对象都是活跃状态。
注意:当某些层会与其它层交互时,层的顺序会变得很重要,在应用程序中使能层的时候就需要特别注意,详见下文中的“层总体排序”。
下面的代码展示了怎样使能验证层
VK_LAYER_KHRONOS_validation
: