最初之物 VkInstance¶
更新记录
2024/1/2 增加该文档。
2024/1/25 更新该文档。
2024/1/25 增加
创建 VkInstance
章节。2024/1/25 增加
vkCreateInstance
章节。2024/1/25 增加
VkInstanceCreateInfo
章节。2024/1/25 增加
VkApplicationInfo
章节。2024/1/25 增加
vkEnumerateInstanceVersion
章节。2024/1/28 增加
Layer
章节。2024/1/28 增加
vkEnumerateInstanceLayerProperties
章节。2024/1/28 增加
VkLayerProperties
章节。2024/1/30 增加
Extension
章节。2024/1/30 增加
vkEnumerateInstanceExtensionProperties
章节。2024/1/30 修正
vkEnumerateInstanceLayerProperties
章节中的打印错误。完善说明。2024/1/30 增加
VkExtensionProperties
章节。2024/2/1 增加
示例
章节。2024/2/1 更新
vkEnumerateInstanceExtensionProperties
章节。修正代码错误。2024/2/1 增加
销毁 VkInstance
章节。2024/2/2 更新
示例
章节。增加开源鸿蒙
平台实例代码。2024/2/2 更新
vkCreateInstance
章节。增加分配器说明。2024/2/7 更新
vkEnumerateInstanceExtensionProperties
章节。完善说明。2024/2/7 更新
Extension
章节。完善说明。2024/2/7 更新
VkExtensionProperties
章节。完善说明。
开发 Vulkan
第一步就是创建 VkInstance
,也就是 Vulkan
的 实例
。一个实例代表一整 Vulkan
环境(或上下文)。不同的 Vulkan
环境能够获取到不同的 Vulkan
功能特性。其中最重要的就是配置 Vulkan
要使用的 版本
。
创建 VkInstance¶
通过 vkCreateInstance(...)
函数创建 VkInstance
:
vkCreateInstance¶
// 由 VK_VERSION_1_0 提供
VkResult vkCreateInstance(
const VkInstanceCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkInstance* pInstance);
pCreateInfo 指向
VkInstanceCreateInfo
数据结构对象,用于配置VkInstance
的创建信息。pAllocator 内存分配器。为
nullptr
表示使用内部默认分配器,否则为自定义分配器。pInstance 返回创建的目标
VkInstance
结果。
备注
pAllocator
将会在之后专门章节讲解,这里使用 nullptr
默认分配器即可。
其中 VkInstanceCreateInfo
结构体定义如下:
VkInstanceCreateInfo¶
// 由 VK_VERSION_1_0 提供
typedef struct VkInstanceCreateInfo {
VkStructureType sType;
const void* pNext;
VkInstanceCreateFlags flags;
const VkApplicationInfo* pApplicationInfo;
uint32_t enabledLayerCount;
const char* const* ppEnabledLayerNames;
uint32_t enabledExtensionCount;
const char* const* ppEnabledExtensionNames;
} VkInstanceCreateInfo;
sType 是该结构体的类型枚举值, 必须 是
VkStructureType::VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO
。pNext 要么是
NULL
要么指向其他结构体来扩展该结构体。flags 是
VkInstanceCreateFlagBits
所表示的位域值,用于设置VkInstance
的行为。pApplicationInfo 要么是
NULL
要么指向应用信息结构体,用于应用细节设置。enabledLayerCount 激活的
layer
数量。ppEnabledLayerNames 指向数量为
enabledLayerCount
的layer
字符串数组,用于设置要激活的layer
。enabledExtensionCount 激活
instance
扩展的数量。enabledExtensionCount 指向数量为
enabledExtensionCount
的扩展字符串数组,用于设置要激活的instance
扩展。
其中 VkApplicationInfo
结构体定义如下:
VkApplicationInfo¶
// 由 VK_VERSION_1_0 提供
typedef struct VkApplicationInfo {
VkStructureType sType;
const void* pNext;
const char* pApplicationName;
uint32_t applicationVersion;
const char* pEngineName;
uint32_t engineVersion;
uint32_t apiVersion;
} VkApplicationInfo;
sType 是该结构体的类型枚举值, 必须 是
VkStructureType::VK_STRUCTURE_TYPE_APPLICATION_INFO
。pNext 要么是
NULL
要么指向其他结构体来扩展该结构体。pApplicationName 要么是
NULL
要么指向一个以空字符为结尾的UTF-8
字符串,用于表示用户自定义应用名称。applicationVersion 一个无符号整型,用于用户自定义应用版本。
pEngineName 要么是
NULL
要么指向一个以空字符为结尾的UTF-8
字符串,用于表示用户自定义引擎名称。engineVersion 一个无符号整型,用于用户自定义引擎版本。
apiVersion 应用打算使用的
Vulkan
的最高 核心 版本,并且忽略apiVersion
的patch
版本。
其中 pApplicationName
、 applicationVersion
、 pEngineName
和 engineVersion
这几个值随便设置,甚至可以不设置,赋为 空
都可以,这些参数不影响实例的创建。
而 apiVersion
参数是 最为重要的核心参数 ,当创建实例时,该参数用于指定此实例环境中 Vulkan
的 核心 版本
。目前 Vulkan
有 4
个版本:
Vulkan 1.0
主要提供光栅化图形和并行计算的功能。对应VK_API_VERSION_1_0
。Vulkan 1.1
主要为Vulkan 1.0
不完善的地方进行补全。对应VK_API_VERSION_1_1
。Vulkan 1.2
主要提供硬件光追的功能。对应VK_API_VERSION_1_2
。Vulkan 1.3
主要提供动态光栅化图形的功能。对应VK_API_VERSION_1_3
。
每个 Vulkan
新版本的发布不单单提供基本功能,还会提供一系列扩展功能,并且会将之前版本中的一些扩展功能提升囊括至核心版本中。 VkApplicationInfo::apiVersion
将会在调用 vkCreateInstance
时告诉驱动将使用的 Vulkan
版本,驱动会为您做好必要的初始化。
如果想要使用的功能为高版本中的功能,而创建实例时 VkApplicationInfo::apiVersion
指定的是低版本,此时如果获取高版本的功能函数大概率会返回 空
。所以 VkApplicationInfo::apiVersion
尽可能的设置为自己需要的高版本。比如:
如果
VkApplicationInfo::apiVersion
设置为VK_API_VERSION_1_0
则表示可以使用该实例在Vulkan Loader
中加载Vulkan 1.0
版本发布的函数,而不能加载Vulkan 1.1
及高版本的接口函数。如下:// 由 VK_VERSION_1_0 提供 vkCmdCopyImageToBuffer(...) // 该函数为 Vulkan 1.0 版本中的函数,可以加载(可有效加载)。 // 由 VK_VERSION_1_3 提供 vkCmdCopyImageToBuffer2(...) // 该函数为 Vulkan 1.3 版本中的函数,不可以加载(加载将返回空)。
在 纵览 中我们知道由于历史原因, Vulkan
在 Vulkan 1.1
版本时推出了 vkEnumerateInstanceVersion(...)
函数,用于获取驱动支持加载 Instance 域函数
的 Vulkan
版本。该函数定义如下:
vkEnumerateInstanceVersion¶
// 由 VK_VERSION_1_0 提供
VkResult vkEnumerateInstanceVersion(
uint32_t* pApiVersion);
pApiVersion 返回支持的
Instance 域函数
对应的Vulkan
版本。
vkEnumerateInstanceVersion
该函数为全局函数。
该函数返回的版本为可获取的
Instance 域函数
所对应的版本。与物理设备(
GPU
)支持的Vulkan
版本可能会不同,也就是Device 域函数
对应的Vulkan
版本(VkPhysicalDeviceProperties::apiVersion
)。
Layer¶
在创建 VkInstance
时需要通过 VkInstanceCreateInfo::enabledLayerCount
和 VkInstanceCreateInfo::ppEnabledLayerNames
来配置实例要开启的 层
( Layer
)。
Vulkan
中的 层
一般都是用来作正确性验证检查的。如果在开发后执行阶段发生了使用错误, 层
会输出错误信息,帮助开发者修正错误。
其中最常使用的 层
就是 VK_LAYER_KHRONOS_validation
,用于 Vulkan API
验证和错误检查。
目前 Vulkan
支持的 层
如下:
VK_LAYER_KHRONOS_validation
Vulkan API
验证和错误检查。VK_LAYER_LUNARG_gfxreconstruct 使用 GFXReconstruct 捕获应用的
Vulkan
指令。VK_LAYER_LUNARG_api_dump 输出调用的
API
和传入的参数。VK_LAYER_KHRONOS_profiles 帮助测试硬件的性能,而不需要物理接触每个设备。该
层
将会覆盖从GPU
查询到的数据。VK_LAYER_LUNARG_monitor 在应用界面的标题处显示帧率。
VK_LAYER_LUNARG_screenshot 将显示的画面帧输出到一个图片文件中。
VK_LAYER_KHRONOS_synchronization2 使用系统实现的
VK_KHR_synchronization2
扩展,而不是驱动实现的。VK_LAYER_KHRONOS_shader_object 使用系统实现的
VK_EXT_shader_object
扩展,而不是驱动实现的。
官方 Layer 文档
Vulkan
支持的所有 Layer
可以在 Vulkan Layers 中找到详细文档。
可以通过 vkEnumerateInstanceLayerProperties(...)
获取系统中 Vulkan
支持的 Layer
:
vkEnumerateInstanceLayerProperties¶
// 由 VK_VERSION_1_0 提供
VkResult vkEnumerateInstanceLayerProperties(
uint32_t* pPropertyCount,
VkLayerProperties* pProperties);
pPropertyCount 用于指定
pProperties
成员的数组长度。pProperties 如果为
nullptr
则将会将系统中支持的层
数写入pPropertyCount
中。否则会将查询到的元素写入pProperties
。
如果 pPropertyCount
数量小于系统中支持的 层
数,该函数将 pPropertyCount
个 层
信息写入 pProperties
中,并返回 VkResult::VK_INCOMPLETE
(表示只写入了一部分,并不是所有信息)。
如果 pPropertyCount
数量大于等于系统中支持的 层
数,则会将所有的 层
数据写入 pProperties
中,并返回 VkResult::VK_SUCCESS
。
所以获取 层
信息一般调用两遍 vkEnumerateInstanceLayerProperties(...)
函数:
uint32_t layer_property_count = 0;
vkEnumerateInstanceLayerProperties(&layer_property_count, nullptr);
std::vector<VkLayerProperties> layer_properties(layer_property_count);
vkEnumerateInstanceLayerProperties(&layer_property_count, layer_properties.data());
其中 VkLayerProperties
定义如下:
VkLayerProperties¶
// 由 VK_VERSION_1_0 提供
typedef struct VkLayerProperties {
char layerName[VK_MAX_EXTENSION_NAME_SIZE];
uint32_t specVersion;
uint32_t implementationVersion;
char description[VK_MAX_DESCRIPTION_SIZE];
} VkLayerProperties;
layerName
层
名称。specVersion
层
实现时的Vulkan
版本。implementationVersion
层
自身维护的版本。description
层
的描述信息。
其中 VK_MAX_EXTENSION_NAME_SIZE
和 VK_MAX_DESCRIPTION_SIZE
定义如下:
#define VK_MAX_EXTENSION_NAME_SIZE 256U
#define VK_MAX_DESCRIPTION_SIZE 256U
Extension¶
在创建 VkInstance
时需要通过 VkInstanceCreateInfo::enabledExtensionCount
和 VkInstanceCreateInfo::ppEnabledExtensionNames
来配置实例要开启的 实例扩展
( Instance Extension
)。
在 Vulkan
中有两类扩展:
Instance 扩展 与使用哪一个
GPU
设备无关,与Vulkan
环境有关。VkInstanceCreateInfo
中的enabledExtensionCount
和ppEnabledExtensionNames
就是用于配置此类Instance 扩展
。Device 扩展 与使用哪一个
GPU
设备有关。不同厂家的GPU
设备会支持不同的设备扩展。这将会在之后的章节展开。
VkInstance
支持的实例扩展可以通过 vkEnumerateInstanceExtensionProperties(...)
函数获取:
vkEnumerateInstanceExtensionProperties¶
// 由 VK_VERSION_1_0 提供
VkResult vkEnumerateInstanceExtensionProperties(
const char* pLayerName,
uint32_t* pPropertyCount,
VkExtensionProperties* pProperties);
pLayerName 要么为
空
要么为层
的名称。pPropertyCount 用于指定
pProperties
成员的数组长度。pProperties 如果为
nullptr
则将会将实例支持的扩展
数写入pPropertyCount
中。否则会将查询到的元素写入pProperties
。
如果 pLayerName
为有效的 层
名,则该函数将会返回该层内部使用的 实例扩展
。如果开启了该 层
,则其内部使用的 扩展
将自动开启。
如果 pLayerName
为 nullptr
,则该函数将会返回 Vulkan
实现和默认启用的 层
支持的实例扩展信息。
要想获取全部的扩展,该函数的调用与 vkEnumerateInstanceLayerProperties(...)
类似,调用两遍,第一遍 pProperties
为 nullptr
,第二遍为有效值即可:
uint32_t extension_property_count = 0;
vkEnumerateInstanceExtensionProperties(nullptr, &extension_property_count, nullptr);
std::vector<VkExtensionProperties> extension_properties(extension_property_count);
vkEnumerateInstanceExtensionProperties(nullptr, &extension_property_count, extension_properties.data());
其中 VkExtensionProperties
定义如下:
VkExtensionProperties¶
// 由 VK_VERSION_1_0 提供
typedef struct VkExtensionProperties {
char extensionName[VK_MAX_EXTENSION_NAME_SIZE];
uint32_t specVersion;
} VkExtensionProperties;
extensionName 为扩展名称。
specVersion 为扩展该扩展的版本。
有一些实例扩展我们需要重点关注一下
VK_KHR_surface 代表窗口通用平面扩展。
- VK_{vender}_{platform}_surface 代表各个平台各自的窗口平面(各自平台适配到通用平面)。其中:
vender 表示该扩展的供应商(或维护方),有的没有提供该供应商字段(取决于扩展开发商)。比如
KHR
表示Khronos
组织提供维护的该扩展。platform 表示扩展对应的平台。
比如
VK_KHR_win32_surface 为
Windows
平台,供应商为Khronos
。VK_OHOS_surface 为
OpenHarmony
平台,供应商为华为
。VK_KHR_android_surface 为
Android
平台,供应商为Khronos
。VK_KHR_[wayland/xcb/xlib]_surface 为
Linux
平台(其中[wayland/xcb/xlib]
表示三者其一),供应商为Khronos
。
这些扩展在窗口中显示渲染结果非常重要,对于具体如何使用,将会在之后的章节展开。
销毁 VkInstance¶
当创建完 VkInstance
之后可通过 vkDestroyInstance(...)
函数销毁。
vkDestroyInstance¶
// 由 VK_VERSION_1_0 提供
void vkDestroyInstance(
VkInstance instance,
const VkAllocationCallbacks* pAllocator);
instance 要么为
空
要么 必须 为有效的VkInstance
。pAllocator 分配器。需要与创建
VkInstance
时指定的分配器匹配。
当 instance
销毁时,需要确保所有该实例环境下创建的对象(句柄)都已经回收或销毁。
示例¶
uint32_t vulkan_version = VK_MAKE_API_VERSION(0, 1, 0, 0);
if(vkEnumerateInstanceVersion != nullptr && vkEnumerateInstanceVersion(&vulkan_version) != VkResult::VK_SUCCESS)
{
vulkan_version = VK_MAKE_API_VERSION(0, 1, 0, 0);
}
VkInstance instance = VK_NULL_HANDLE;
VkApplicationInfo application_info = {};
application_info.sType = VkStructureType::VK_STRUCTURE_TYPE_APPLICATION_INFO;
application_info.pNext = nullptr;
application_info.pApplicationName = nullptr;
application_info.applicationVersion = 0;
application_info.pEngineName = nullptr;
application_info.engineVersion = 0;
application_info.apiVersion = vulkan_version;
std::vector<const char *> enable_layer_names;
#if defined(_DEBUG) || defined(NDEBUG)
enable_layer_names.push_back("VK_LAYER_KHRONOS_validation");
#endif
std::vector<const char *> enable_extension_names;
enable_extension_names.push_back("VK_KHR_surface");
#if defined(_WIN16) || defined(_WIN32) || defined(_WIN64) // Windows
enable_extension_names.push_back("VK_KHR_win32_surface");
#elif defined(VK_USE_PLATFORM_OHOS) // 开源鸿蒙
enable_extension_names.push_back("VK_OHOS_surface");
#elif 其他平台...
#endif
VkInstanceCreateInfo instance_create_info = {};
instance_create_info.sType = VkStructureType::VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instance_create_info.pNext = nullptr;
instance_create_info.flags = 0;
instance_create_info.pApplicationInfo = &application_info;
instance_create_info.enabledLayerCount = enable_layer_names.size();
instance_create_info.ppEnabledLayerNames = enable_layer_names.data();
instance_create_info.enabledExtensionCount = enable_extension_names.size();
instance_create_info.ppEnabledExtensionNames = enable_extension_names.data();
VkResult result = vkCreateInstance(&instance_create_info, nullptr, &instance);
if (result != VK_SUCCESS)
{
throw std::runtime_error("VkInstance 创建失败");
}
// 缤纷绚丽的 Vulkan 程序 ...
vkDestroyInstance(instance, nullptr);