当我们在用Vulkan写一些应用时,无论是用其图形流水线还是计算流水线,或多或少需要用到一些当前设备的Vulkan核心版本所不具备的扩展特征,那么我们如何能既安全又方便地开启一些我们需要的特征呢?本文将给各位进行介绍。
我们要开启当前设备的某一扩展特征基本有两种手段。而且这两种手段并非是互斥的,而是需要一同使用。
一、指定特定的扩展名
这种是常见且常用的方式。我们先调用 vkEnumerateDeviceExtensionProperties
API枚举出当前指定使用的 物理设备 一共支持多少种扩展,最后在创建 逻辑设备 时将将所需要使用的扩展名指定在 VkDeviceCreateInfo
结构体对象的 ppEnabledExtensionNames
成员之中。
下面为枚举扩展名的大致示例代码:
enum MY_CONSTANT
{
MAX_VULKAN_GLOBAL_EXT_PROPS = 256
};
// Query Vulkan extensions the current selected physical device supports
uint32_t extPropCount = 0U;
res = vkEnumerateDeviceExtensionProperties(physicalDevices[deviceIndex], NULL, &extPropCount, NULL);
if (res != VK_SUCCESS)
{
printf("vkEnumerateDeviceExtensionProperties for count failed: %d\n", res);
return res;
}
printf("The current selected physical device supports %u Vulkan extensions!\n", extPropCount);
if (extPropCount > MAX_VULKAN_GLOBAL_EXT_PROPS) {
extPropCount = MAX_VULKAN_GLOBAL_EXT_PROPS;
}
VkExtensionProperties extProps[MAX_VULKAN_GLOBAL_EXT_PROPS];
res = vkEnumerateDeviceExtensionProperties(physicalDevices[deviceIndex], NULL, &extPropCount, extProps);
if (res != VK_SUCCESS)
{
printf("vkEnumerateDeviceExtensionProperties for content failed: %d\n", res);
return res;
}
这里各位需要注意的是,某些扩展可能在当前Vulkan版本中已经被融入到核心里去了,而有些GPU设计商可能就不会再把该扩展名列出来了,因此我们用 vkEnumerateDeviceExtensionProperties
这一API时压根就枚举不到,但这并不意味着我们就无法使用该扩展。
比如说我们当前设备可能枚举不到 VK_KHR_8BIT_STORAGE_EXTENSION_NAME
这一特征,但该特征早在Vulkan 1.2版本中就已经被加入到核心中去了。此时,我们可以通过下面将会介绍的,利用其相应的特征结构体去做进一步查询。而该特征名正好有个特征结构体,名字为:VkPhysicalDevice8BitStorageFeatures
。
另外,对于一些没有相应特征结构体的扩展,也可利用其对应的属性结构体进行查询。比如,如果我们当前设备没有枚举到 VK_EXT_SHADER_SUBGROUP_VOTE_EXTENSION_NAME
这一特征,那么我们可以跟subgroup相关的 VkPhysicalDeviceSubgroupProperties
这个属性中的 supportedOperations
做进一步查询。
下面我们将谈论一下Vulkan中如何通过特征结构体进行特征查询并进行开启。
二、通过指定的特征结构体进行查询
如果当前设备支持 VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME
这一特征(该特征已在Vulkan 1.1版本中作为核心特征,且当前绝大部分设备均能支持),那么我们可以使用 vkGetPhysicalDeviceFeatures2
进行 链式 查询我们所需的扩展特征。该API的第二个参数为 VkPhysicalDeviceFeatures2*
类型,意味着我们需要传递 VkPhysicalDeviceFeatures2
结构体对象的地址进去。该结构体的 pNext
成员即可指向其他扩展特征结构体对象的地址。而其他扩展特征结构体类型中也存在 pNext
成员,可指向其他特征扩展结构体,以此类推。这么一来就形成了扩展特征的查询链。只要在此链表中的特征结构体均能查询到。
下面举一个简单例子:
// ==== The following is query the specific extension features in the feature chaining form ====
// VK_EXT_custom_border_color feature
VkPhysicalDeviceCustomBorderColorFeaturesEXT customBorderColorFeature = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT,
// This is the last node
.pNext = NULL
};
// VK_EXT_subgroup_size_control feature
VkPhysicalDeviceSubgroupSizeControlFeaturesEXT subgroupSizeControlFeature = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_FEATURES_EXT,
// link to customBorderColorFeature node
.pNext = &customBorderColorFeature
};
// physical device feature 2
VkPhysicalDeviceFeatures2 features2 = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
// link to subgroupSizeControlFeature node
.pNext = &subgroupSizeControlFeature
};
// Query all above features
vkGetPhysicalDeviceFeatures2(physicalDevices[deviceIndex], &features2);
上述代码中我们可以看到,我们依次查询了 VkPhysicalDeviceFeatures2
所包含的基本特征,VkPhysicalDeviceSubgroupSizeControlFeaturesEXT
中所包含的特征以及 VkPhysicalDeviceCustomBorderColorFeaturesEXT
中所包含的特征,并且该结构体对象 customBorderColorFeature
中的 pNext
最后指向空,意味着它是该查询链表的末尾节点。
通过这种方式查询得到的特征集,我们可以随意对这些特征结构体对象的成员进行设置。比如,如果我们想要创建的逻辑设备不让它支持 subgroupSizeControl
这一特征,那么我们在创建逻辑设备前直接将 subgroupSizeControlFeature
对象的 subgroupSizeControl
成员置为 VK_FALSE
即可。
综上所述,我们在创建逻辑设备时若是想开启某些扩展特征,可结合上两种设置方式进行实现。下面我们将举一个综合性的例子来阐明如何对创建的逻辑设备开启特定的扩展特征并且确定该特征已被启用。
对当前逻辑设备开启指定特征的正确打开姿势
下面通过相对较完整的纯C语言(C11语法扩展)代码示例来给各位描述一下创建逻辑设备时打开特征的方法:
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <vulkan/vulkan.h>
enum MY_CONSTANTS
{
MAX_VULKAN_GLOBAL_EXT_PROPS = 256,
MAX_QUEUE_FAMILY_PROPERTY_COUNT = 8
};
static VkResult CreateLogicalDevice(VkPhysicalDevice physicalDevices[], uint32_t deviceIndex, VkQueueFlagBits queueFlag, VkDevice *pOutDevice)
{
// Query Vulkan extensions the current selected physical device supports
uint32_t extPropCount = 0U;
VkResult res = vkEnumerateDeviceExtensionProperties(physicalDevices[deviceIndex], NULL, &extPropCount, NULL);
if (res != VK_SUCCESS)
{
printf("vkEnumerateDeviceExtensionProperties for count failed: %d\n", res);
return res;
}
printf("The current selected physical device supports %u Vulkan extensions!\n", extPropCount);
if (extPropCount > MAX_VULKAN_GLOBAL_EXT_PROPS) {
extPropCount = MAX_VULKAN_GLOBAL_EXT_PROPS;
}
VkExtensionProperties extProps[MAX_VULKAN_GLOBAL_EXT_PROPS];
res = vkEnumerateDeviceExtensionProperties(physicalDevices[deviceIndex], NULL, &extPropCount, extProps);
if (res != VK_SUCCESS)
{
printf("vkEnumerateDeviceExtensionProperties for content failed: %d\n", res);
return res;
}
bool supportDeviceProperties2 = false;
bool supportSubgroupVote = false;
bool supportSubgroupSizeControl = false;
bool supportCustomBorderColor = false;
const char* availExtensionNames[4];
uint32_t availExtensionCount = 0;
// Query the required extension names
for (uint32_t i = 0; i < extPropCount; ++i)
{
if (strcmp(extProps[i].extensionName, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME) == 0)
{
supportDeviceProperties2 = true;
availExtensionNames[availExtensionCount++] = extProps[i].extensionName;
continue;
}
if (strcmp(extProps[i].extensionName, VK_EXT_SHADER_SUBGROUP_VOTE_EXTENSION_NAME) == 0)
{
supportSubgroupVote = true;
availExtensionNames[availExtensionCount++] = extProps[i].extensionName;
continue;
}
if (strcmp(extProps[i].extensionName, VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME) == 0)
{
supportSubgroupSizeControl = true;
availExtensionNames[availExtensionCount++] = extProps[i].extensionName;
continue;
}
if (strcmp(extProps[i].extensionName, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME) == 0)
{
supportCustomBorderColor = true;
availExtensionNames[availExtensionCount++] = extProps[i].extensionName;
continue;
}
}
// ATTENTION: the feature `VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME`
// can only be queried by the extension names.
// If the current device support Vulkan 1.1 or later,
// this feature will be the core feature function.
VkPhysicalDeviceProperties props = { 0 };
vkGetPhysicalDeviceProperties(physicalDevices[deviceIndex], &props);
if (props.apiVersion >= VK_MAKE_VERSION(1, 1, 0)) {
supportDeviceProperties2 = true;
}
// VK_EXT_custom_border_color feature
VkPhysicalDeviceCustomBorderColorFeaturesEXT customBorderColorFeature = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT,
// This is the last node
.pNext = NULL
};
// VK_EXT_subgroup_size_control feature
VkPhysicalDeviceSubgroupSizeControlFeaturesEXT subgroupSizeControlFeature = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_FEATURES_EXT,
// link to customBorderColorFeature node
.pNext = &customBorderColorFeature
};
// physical device feature 2
VkPhysicalDeviceFeatures2 features2 = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
// link to subgroupSizeControlFeature node
.pNext = &subgroupSizeControlFeature
};
// If VK_KHR_get_physical_device_properties2 is not supported, fallback to general feature settings
VkPhysicalDeviceFeatures features = { 0 };
// Query all above features
if (supportDeviceProperties2)
{
vkGetPhysicalDeviceFeatures2(physicalDevices[deviceIndex], &features2);
supportSubgroupSizeControl = subgroupSizeControlFeature.subgroupSizeControl == VK_TRUE;
supportCustomBorderColor = customBorderColorFeature.customBorderColors == VK_TRUE;
// If VK_KHR_get_physical_device_properties2 is supported,
// we can query the subgroup vote extension support more precisely.
// SubgroupSize properties
VkPhysicalDeviceSubgroupProperties subgroupProps = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES,
.pNext = NULL
};
VkPhysicalDeviceProperties2 properties2 = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
// link to subgroupProps node
.pNext = &subgroupProps
};
// Query all above properties
vkGetPhysicalDeviceProperties2(physicalDevices[deviceIndex], &properties2);
// Determine subgroup vote support
if ((subgroupProps.supportedOperations & VK_SUBGROUP_FEATURE_VOTE_BIT) != 0) {
supportSubgroupVote = true;
}
}
else {
vkGetPhysicalDeviceFeatures(physicalDevices[deviceIndex], &features);
}
// Now, we can output the supports
printf("Support %s? %s\n", VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, supportDeviceProperties2 ? "YES" : "NO");
printf("Support %s? %s\n", VK_EXT_SHADER_SUBGROUP_VOTE_EXTENSION_NAME, supportSubgroupVote ? "YES" : "NO");
printf("Support %s? %s\n", VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME, supportSubgroupSizeControl ? "YES" : "NO");
printf("Support %s? %s\n", VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME, supportCustomBorderColor ? "YES" : "NO");
// The following code is to specify the queue info.
const float queue_priorities[1] = { 0.0f };
VkDeviceQueueCreateInfo queue_info = {
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.pNext = NULL,
.queueCount = 1,
.pQueuePriorities = queue_priorities
};
uint32_t queueFamilyPropertyCount = 0;
VkQueueFamilyProperties queueFamilyProperties[MAX_QUEUE_FAMILY_PROPERTY_COUNT];
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevices[deviceIndex], &queueFamilyPropertyCount, NULL);
if (queueFamilyPropertyCount > MAX_QUEUE_FAMILY_PROPERTY_COUNT) {
queueFamilyPropertyCount = MAX_QUEUE_FAMILY_PROPERTY_COUNT;
}
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevices[deviceIndex], &queueFamilyPropertyCount, queueFamilyProperties);
bool found = false;
for (uint32_t i = 0; i < queueFamilyPropertyCount; i++)
{
if ((queueFamilyProperties[i].queueFlags & queueFlag) != 0)
{
queue_info.queueFamilyIndex = i;
found = true;
break;
}
}
// There are two ways to enable features:
// (1) Set pNext to a VkPhysicalDeviceFeatures2 structure and set pEnabledFeatures to NULL;
// (2) or set pNext to NULL and set pEnabledFeatures to a VkPhysicalDeviceFeatures structure.
// Here uses the first way
const VkDeviceCreateInfo device_info = {
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.pNext = supportDeviceProperties2 ? &features2 : NULL,
.queueCreateInfoCount = 1,
.pQueueCreateInfos = &queue_info,
.enabledLayerCount = 0,
.ppEnabledLayerNames = NULL,
.enabledExtensionCount = availExtensionCount,
.ppEnabledExtensionNames = availExtensionNames,
.pEnabledFeatures = supportDeviceProperties2 ? NULL : &features
};
// Create the logical device with the features specified
res = vkCreateDevice(physicalDevices[deviceIndex], &device_info, NULL, &s_specDevice);
if (res != VK_SUCCESS) {
printf("vkCreateDevice failed: %d\n", res);
}
return res;
}
这里各位还需要注意的是,创建逻辑设备时我们只能传各类扩展特征以及一小部分的属性结构体。大部分从物理设备处获得的属性值在创建逻辑设备时是不能修改的。
最后,大家在开启特征时务必要注意以下几点:
- 如果当前从
vkEnumerateDeviceExtensionProperties
没有枚举到想要的扩展名,那么我们 不能 将该扩展名强塞到VkDeviceCreateInfo
中的ppEnabledExtensionNames
成员所指向的扩展名数组中,否则对于有些GPU实现驱动在创建逻辑设备时会直接崩溃。 - 如果我们在
vkEnumerateDeviceExtensionProperties
中能查询到想要的扩展名,那么我们倘若要打开此特征,则 必须要 将该扩展名加入到VkDeviceCreateInfo
中的ppEnabledExtensionNames
成员所指向的扩展名数组中。如果不加,即便我们在特征结构体对象中开启该特征,那么实现可能也不会开启,并且在验证层可能会报告出“扩展名中尚未开启该扩展,但实现了该扩展”类似的诊断信息。 - 对于逻辑设备的创建,我们只能用
VkPhysicalDeviceFeatures2
的特征链模式或普通的VkPhysicalDeviceFeatures
这两者的其中之一。倘若我们选择特征链,那么我们必须将VkDeviceCreateInfo
中的pNext
成员指向VkPhysicalDeviceFeatures2
对象的地址或其他特征链中的结点,如果VkPhysicalDeviceFeatures2
对象并非作为该特征链的首节点的话;与此同时,我们必须要将VkDeviceCreateInfo
中的pEnabledFeatures
置空。如果我们选择普通特征指定模式,那就得反过来——将VkDeviceCreateInfo
中的pNext
成员置空;并且将其pEnabledFeatures
成员指向VkPhysicalDeviceFeatures
对象的地址。
动态加载Vulkan扩展API
对于某些GPU厂商的Vulkan扩展API,我们最好使用 vkGetDeviceProcAddr 去动态加载这些API符号,而不是使用当前系统环境下Vulkan SDK自带的API符号。否则可能会导致这些API调用时,GPU驱动直接崩溃的情况。
比如Android设备端,高通对 vkResetQueryPoolEXT
这个符号有自己的实现。此时,如果我们用Android自带的 vulkan_wrapper 做 dlsym
加载,可能会得到一个不合规的实现,此时执行 vkResetQueryPoolEXT
将可能直接引发程序崩溃。这时,我们需要使用 vkResetQueryPoolEXT = (PFN_vkResetQueryPoolEXT)vkGetDeviceProcAddr(device, "vkResetQueryPoolEXT ");
这种形式进行加载。