当创建逻辑设备时,结构体VkDeviceCreateInfo里的字段ppEnabledExtensionNames可能包含一个指针,指向vkEnumerateDeviceExtensionProperties()返回的字符串中的一个。
有些扩展以可以调用的额外入口点的形式提供了新的功能。这些以函数指针的形式提供,这些指针必须在扩展激活后从实例或者设备中查询。实例函数对整个实例有效。如果某个扩展扩充了实例层面的功能,你应该使用实例层面的函数指针访问新特性。
为了获取实例层面的函数指针,调用vkGetInstanceProcAddr(),其原型如下。
PFN_vkVoidFunction vkGetInstanceProcAddr (VkInstance instance,constchar* pName);
参数instance是需要获取函数指针的实例的句柄。如果应用程序使用了多个Vulkan实例,那么这个指令返回的函数指针只对引用的实例所拥有的对象有效。函数名通过pName传入,这是个以nul结尾的UTF-8类型的字符串。如果识别了函数名并且激活了这个扩展,vkGetInstance ProcAddr()的返回值是一个函数指针,可以在应用程序里调用。
PFN_vkVoidFunction是个函数指针定义,其声明如下。
VKAPI_ATTRvoidVKAPI_CALL vkVoidFunction(void);
Vulkan里没有这种特定签名的函数,扩展也不太可能引入这样的函数。绝大部分情况下,需要在使用前将生成的函数指针类型强制转换为有正确签名的函数指针。
实例层面的函数指针对这个实例所拥有的所有对象都有效——假如创建这些对象(或者设备本身,如果函数在这个设备上调度)的设备支持这个扩展,并且这个设备激活了这个扩展。由于每个设备可能在不同的Vulkan驱动里实现,因此实例函数指针必须通过一个间接层登录正确的模块进行调度。因为管理这个间接层可能引起额外开销,所以为了避免这个开销,你可以获取一个特定于设备的函数指针,这样可以直接进入正确的驱动。
为了获取设备层面的函数指针,调用vkGetDeviceProcAddr(),其原型如下。
PFN_vkVoidFunction vkGetDeviceProcAddr (VkDevice device,constchar* pName);
使用函数指针的设备通过参数device传入。需要查询的函数的名字需要使用pName传入,这是个以nul 结尾的UTF-8类型的字符串。返回的函数指针只在参数device指定的设备上有效。device必须指向支持这个扩展(提供了这个新函数)的设备,并且这个扩展已经激活。
vkGetDeviceProcAddr()返回的函数指针特定于参数device。即使同样的物理设备使用同样的参数创建出了多个逻辑设备,你也只能在查询这个函数指针的逻辑设备上使用该指针。
1.8 彻底地关闭应用程序
在程序结束之前,你需要自己负责清理干净。在许多情况下,操作系统会在应用程序结束时清理已经创建的资源。然而,应用程序和代码同时结束的情景并不经常出现。比如你正在写一个大型应用程序的组件,应用程序可能结束了使用Vulkan实现的渲染和计算操作,但是并没有完全退出。
在清除时,通常来说,较好的做法如下。
完成或者终结应用程序正在主机和设备上、Vulkan相关的所有线程里所做的所有工作。按照创建对象的时间逆序销毁对象。
逻辑设备很可能是初始化应用程序时创建的最后一个对象(除了运行时使用的对象之外)。在销毁设备之前,需要保证它没有正在执行来自应用程序的任何工作。为了达到这个目的,调用vkDeviceWaitIdle(),其原型如下。
VkResult vkDeviceWaitIdle (VkDevice device);
把设备的句柄传入device。当vkDeviceWaitIdle()返回时,所有提交给设备的工作都保证已经完成了——当然,除非同时你继续向设备提交工作。需要保证其他可能向设备提交工作的线程已经终止了。
一旦确认了设备处于空闲状态,就可以安全地销毁它了。这需要调用vkDestroyDevice(),其原型如下。
voidvkDestroyDevice (VkDevice device,constVkAllocationCallbacks* pAllocator);
把需要销毁的设备的句柄传递给参数device,并且访问该设备需要在外部同步。需要注意的是,其他指令对设备的访问都不需要外部同步。然而,应用程序需要保证当访问该设备的其他指令正在另一个线程里执行时,这个设备不要销毁。
pAllocator应该指向一个分配的结构体,该结构体需要与创建设备的结构体兼容。一旦设备对象被销毁了,就不能继续向它提交指令了。进一步说,设备句柄就不可能再作为任何函数的参数了,包括其他将设备句柄作为第一个参数的对象销毁方法。这是应该按照创建对象的时间逆序来销毁对象的另一个原因。
一旦与Vulkan实例相关联的所有设备都销毁了,销毁实例就安全了。这是通过调用函数vkDestroyInstance()实现的,其原型如下。
voidvkDestroyInstance (VkInstance instance,constVkAllocationCallbacks* pAllocator);
将需要销毁的实例的句柄传给instance,与vkDestroyDevice()一样,与创建实例使用的分配结构体相兼容的结构体的指针应该传递给pAllocator。如果传递给vkCreateInstance()的参数pAllocator是nullptr,那么传递给vkDestroyInstance()的参数pAllocator也应该是这样。
需要注意的是,物理设备不用销毁。物理设备并不像逻辑设备那样由一个专用的创建函数来创建。相反,物理设备通过调用vkEnumeratePhysicalDevices()来获取,并且属于实例。因此,当实例销毁后,和每个物理设备相关的实例资源也都销毁了。
1.9 总结
本章介绍了Vulkan。你已看到了Vulkan状态整体上如何包含在一个实例里。实例提供了访问物理设备的权限,每个物理设备提供了一些用于执行工作的队列。本章还演示了如何根据物理设备创建逻辑设备,如何扩展Vulkan,如何判断实例,设备能用哪些扩展,以及如何启用这些扩展。最后还演示了如何彻底地关闭Vulkan系统,操作顺序依次是等待设备完成应用程序提交,销毁设备句柄,销毁实例句柄。
本文摘自《Vulkan 应用开发指南》
《Vulkan 应用开发指南》
[美] 格拉汉姆·塞勒斯(Graham Sellers) 著,李晓波 等译
下一代OpenGL规范已经重新进行了设计,从而使得应用程序可以直接控制GPU的加速。本书系统地介绍下一代OpenGL规范Vulkan、它的目标以及构建其API的关键概念,揭示了Vulkan的独特性。
本书讨论的主题非常宽泛,从绘图命令到内存,再到计算着色器的线程。本书重点展示了如何处理现在由开发人员负责的同步、调度和内存管理等任务。本书是Vulkan开发人员的指南和参考手册,有助于读者迅速掌握跨平台图形的下一代规范。你将从本书中学习到可用于从视频游戏到医学成像等领域的3D开发技术,以及解决复杂的科学计算问题的先进方法。
本书主要内容
大量经过反复测试的代码示例,用于演示Vulkan的功能并展示它与OpenGL的区别。Vulkan中的新内存系统。队列、命令和移动数据的方法。SPIR-V二进制着色语言和计算/图形管道。绘图命令、几何处理、片段处理、同步原语,以及将Vulkan数据读入应用程序。完整的案例研究应用程序:使用复杂的多通道架构和多个处理队列的延迟渲染。Vulkan函数和SPIR-V操作码,以及完整的Vulkan词汇表。