原文链接:https://vulkan.lunarg.com/doc/sdk/1.2.131.2/windows/tutorial/html/01-init_instance.html
创建Vulkan Instance
这一章节的代码文件是 01-init_instance.cpp
示例程序中的第一步就是创建一个Vulkan instance。在 LunarG Vulkan Sample库的API-Samples目录下找到01-init_instance.cpp 程序,在阅读本章节关于Vulkan instance的时候准备好查看代码。
Vulkan Instances
在Vulkan API中使用vkInstance对象来存储所有的应用状态信息。应用在执行任何Vulkan操作之前都必须先创建一个Vulkan instance。
基本的Vulkan架构如下所示:
如上图所示,一个Vulkan应用已经链接到一个通常被称为loader的Vulkan库。创建一个instance会初始化这个loader。loader会再加载并初始化底层的图形驱动,通常是由GPU硬件厂商提供。
请注意,在这幅图里描绘了几个layer,也是由loader来加载的。layer通常用于验证,也就是通常由驱动执行的错误检查。在Vulkan中,驱动比在其他API(比如OpenGL)中都更加轻量化,部分原因就是它们把验证功能委托给了验证layer。layer是可以配置的,并且可以在每次创建instance的时候选择是否加载。
Vulkan layer不在本教程和示例程序的范围内。所以本教程不再过多涉及layer。更多关于layer的信息可以参考Vulkan SDK download site。
vkCreateInstance
查看 01-init_instance.cpp
源码,然后找到有以下原型的 vkCreateInstance 函数调用:
VkResult vkCreateInstance(
const VkInstanceCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkInstance* pInstance);
一点一点地来分析:
VkResult - 这是函数的返回状态。当你遇到它们时,你可能会需要打开vulkan.h头文件并且检查其中一些定义。你可以在Vulkan Sample代码库的include目录下找到该文件。
VkInstanceCreateInfo - 这个结构体包含了创建该instance所需的所有附加信息。由于这是非常重要的一个部分,稍后你将更深入地了解它。
VkAllocationCallbacks - 这是分配主机内存的函数,带有一个参数,该函数允许应用自己执行主机内存管理。否则,Vulkan将使用默认的系统内存管理功能。比如,应用可能想要管理自己的主机内存便于记录内存分配情况。
示例中没有使用这项功能,所以在这个或其他函数中你会看到该参数一直传的是NULL。
VkInstance - 这是instance创建成功后函数返回的一个句柄。这是一个不透明的句柄,所以不要尝试取消引用它。很多创建对象的Vulkan函数都会以这种方式返回它们所创建对象的句柄。
深入了解 VkInstanceCreateInfo 结构体
创建对象的Vulkan函数通常都有一个VkObjectCreateInfo参数。在示例中找到初始化这个结构体的代码:
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;
前两个成员在许多 Vulkan CreateInfo 结构体里都能找到。
sType - sType字段表明了结构体的类型。目前你把它设置为VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,是因为它是一个 VkInstanceCreateInfo 结构体。这也许看起来多余,因为只有这个类型的结构体才能作为 vkCreateInstance() 的第一个参数输入。但是因为下面这些原因它还是有一些价值的:
- 驱动,校验层,或者该结构体的其他消费者可以执行一个简单的有效性检查,并且如果sType不符合预期则可能驳回请求。
- 在接口参数未完全定义类型的接口中,该结构体可以通过void指针传给消费者。比如,如果一个驱动支持创建instance的extension,它就有可能会查看通过无类型的pNext指针(稍后讨论)传入的结构体。这种情况下,sType成员就会被设置成extension可以识别的值。
由于该成员总是结构体的第一个成员变量,消费者可以轻松地确定结构体的类型然后决定怎么处理它。
pNext - 多数情况下你只需把pNext设置为NULL。此void指针有时用来传递类型化的结构体信息给特定的extension,该结构体里sType成员被设置为extension所定义的值。像之前讨论的那样,extension可以分析随着pNext指针链传入的任何结构体以找到可以识别的。
flags - 目前还没有flag定义,所以设成0。
pApplicationInfo - 这是指向另一个你需要填写的结构体指针。稍后再回到这一部分。
enabledLayerCount 和 ppEnabledLayerNames - 本教程的示例没有使用layer,所以这些成员是空的。
enabledExtensionCount 和 ppEnabledExtensionNames - 本教程的示例没有使用extension。稍后,其他示例会展示extension的用法。
VkApplicationInfo结构体
这个结构体为Vulkan实现提供了应用相关的基本信息:
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 和 pNext - 这里和 vkInstanceCreateInfo 结构体里的具有相同含义。
pApplicationName, applicationVersion, pEngineName, engineVersion - 这些是自由填充的字段,如果需要的话应用可以提供。一些tool, loader, layer, 或者driver 也许会在调试或者收集报告信息的时候使用这些字段来提供信息。对驱动而言,甚至可能会根据当前运行的应用来改变它们的操作。
apiVersion - 这个字段表明了用于编译应用的Vulkan API的major minor 和 patch level。如果你正在使用 Vulkan 1.0,major应该是1,minor应该是0。使用 vulkan.h里的VK_API_VERSION_1_0 宏定义就可以,这样的话patch level是0。patch level之间的不同应该不会影响不同版本的兼容性,区别仅仅在于patch level。通常来讲,你应该设置该字段为VK_API_VERSION_1_0,除非有其他特别的原因。
回到代码里
结构体填好以后,示例应用将创建instance:
VkInstance inst;
VkResult res;
res = vkCreateInstance(&inst_info, NULL, &inst);
if (res == VK_ERROR_INCOMPATIBLE_DRIVER) {
std::cout << "cannot find a compatible Vulkan ICD\n";
exit(-1);
} else if (res) {
std::cout << "unknown error\n";
exit(-1);
}
vkDestroyInstance(inst, NULL);
在上述代码中,应用对最常见的错误做了一下快速检查,根据结果进行报告。请注意,成功的情况下(VK_SUCCESS)值为0,很多应用都为了便捷把非零值作为错误,这里也是这样做的。
最后,应用在退出之前销毁了instance。
现在你已经创建了一个 Vulkan instance,是时候来弄清你的 Vulkan instance都可以使用哪些图形设备了。