加载设备级函数
我们已经创建了一个逻辑设备,可以在该逻辑设备上执行任何想要执行的操作,如渲染3D场景、计算游戏中的物体碰撞效果和处理视频帧。这些操作都是通过设备级函数执行的,但在获取设备级函数前我们无法使用这些函数。
具体处理过程
(1)获取逻辑设备的句柄,将其存储在一个VkDevice类型的变量中,将该变量命名为logical_device。
(2)选择想要加载的设备级函数的名称(使用<函数名>的形式表示)。
(3)为了加载所有选中的设备级函数,可创建一个PFN_<函数名>
类型的变量,将该变量命名为<函数名>
。
(4)调用vkGetDeviceProcAddr(logical_device,"<函数名>")
函数,将第一个参数设置为逻辑设备的句柄,将第二个参数设置为想要加载的设备级函数的名称。将该函数调用操作的结果,存储在类型为PFN_<函数名>
、名为<函数名>
的变量中。
(5)通过查明<函数名>变量的值不等于nullptr,确认该函数调用操作成功完成。
具体运行情况
几乎所有3D渲染程序进行的典型工作,都是使用设备级函数完成的。设备级函数用于创建缓冲区、图像、采样器和着色器。使用设备级函数可以创建管线对象、同步基准(synchronization primitive)、帧缓冲区等许多资源。最为重要的是,这些资源用于记录已提交给队列的操作(还是通过调用设备级函数),硬件会处理队列中的操作。所有这些工作都是通过设备级函数完成的。
与其他类型的Vulkan函数一样,可以使用vkGetInstanceProcAddr()函数加载设备级函数,但这种方式不是最优解决方案。灵活性是Vulkan与生俱来的特点。使用Vulkan可以通过一个应用程序,在多个设备上执行多个操作,但在调用vkGetInstanceProcAddr()函数时,无法使用与逻辑设备有关的参数。因此,该函数返回的函数指针,无法与我们想要用来执行具体操作的设备关联起来。在调用vkGetInstanceProcAddr()函数时,该设备甚至尚不存在。这就是vkGetInstanceProcAddr()函数会根据其参数返回一个调度函数,并由这个调度函数为指定的逻辑设备调用、执行具体操作函数的原因。然而,这种多层次的跳转迂回调用操作是要付出性能代价的,尽管该代价非常小,但实际上会占用用于调用正确函数的处理器时间。
如果想要避免跳转调用操作,直接获取与指定设备关联的函数指针,就应该使用vkGetDeviceProcAddr()函数。这样就可以避免执行中间函数调用操作,并提高应用程序的性能。但这种方式也有缺点,我们需要为在应用程序中创建的每个设备获取函数指针。如果想要在多个设备上执行操作,就需要为每个逻辑设备创建独立的函数指针列表。不能使用通过一个设备获取的函数在另一个设备上执行操作。但在使用C++的预处理指令时,可以非常轻松地获取与指定设备对应的函数指针。

怎样才能辨别出一个函数是设备级、全局级或实例级的呢?设备级函数的第一个参数的数据类型,如VkDevice、VkQueue或VkCommandBuffer。从现在开始,下面内容介绍的函数大多数是设备级的。
要加载设备级函数,应更新ListOfVulkanFunctions.inl
文件。

在上面的代码中,我们添加了多个设备级函数的名称。所有这些名称都被封装在DEVICE_LEVEL_VULKAN_FUNCTION(该宏封装的是Vulkan API核心中定义的函数),或DEVICE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION宏(该宏封装的是通过扩展引入的函数)中,这两种宏都是使用预处理指令#ifndef和#undef定义的。当然,此处没有列出所有设备级函数,因为设备级函数的数量非常多。
注意,在创建逻辑设备的过程中,应该先启用指定的扩展,然后才能加载通过该扩展引入的函数。如果物理设备不支持某个扩展,那么该扩展中的函数就不可用,而且加载这些函数的操作会失败。这就是需要将加载函数的代码划分为两段的原因,这与加载实例级函数的方式类似。
首先,在使用前面介绍的宏加载Vulkan API核心函数时,应编写下列代码。

上面的代码用于在ListOfVulkanFunctions.inl文件中定义DEVICE_LEVEL_VULKAN_FUNCTION()宏,通过这个宏调用vkGetDeviceProcAddr()函数,并获取想要加载函数的名称。该操作的结果会被设置为合适的数据类型,并会存储在与已获得的函数的名称同名的变量中。如果该操作执行失败,这段代码则会在屏幕上显示补充说明信息。
其次,我们需要加载通过扩展引入的函数。这些扩展必须在创建逻辑设备的过程中被启用。


上面代码定义的宏循环遍历了所有已经启用的扩展。这些扩展的定义存储在类型为std::vector<char const*>
、名为enabled_extensions的变量中。在每次迭代过程中,该vector容器中已启用扩展的名称,会与指定函数所属扩展的名称进行比较。如果二者相同,就加载指定函数的指针;如果两者不相同,指定函数就会被跳过,因为无法加载未启用扩展中的函数。
酷客网相关文章:
评论前必须登录!
注册