实例
创建一个实例
一切开始于创建一个实例(instance)来初始化 Vulkan 库。实例是连接 Vulkan 库和你的程序之间的桥梁,创建实例还涉及到向驱动指定你的应用程序的 一些细节。
添加一个 createInstance
函数,然后在 initVulkan
函数中调用它。
void initVulkan() {
createInstance();
}
再在类中添加一个数据成员,用来保存实例的句柄:
private:
VkInstance instance;
现在,为了创建实例,我们首先需要用我们程序的一些信息去填充一个结构体。从技术上来说,这些信息是可有可无的,但是它们或许能够提供一些信息给驱动,以
使驱动针对我们的特定程序进行优化(例如,它使用了一个具有某些特殊行为的知名图形引擎)。这个结构体叫做 VkApplicationInfo
:
void createInstance() {
VkApplicationInfo appInfo{};
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
appInfo.pApplicationName = "Hello Triangle";
appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.pEngineName = "No Engine";
appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.apiVersion = VK_API_VERSION_1_0;
}
就像之前提到过的那样,Vulkan 中的许多结构体需要你在 sType
成员中显式指定类型。这个结构体也是众多拥有 pNext
成员的结构体之一,这个成员在
将来可以指向扩展信息。我们现在执行默认初始化,所以此处置为 nullptr
(空指针)。
Vulkan 中的许多信息都通过结构体来传递,而不是函数参数。我们还需要填充另一个结构体来为创建实例提供足够多的信息。接下来的这个结构体是必需的,它告 知 Vulkan 驱动我们要使用哪些全局的扩展以及验证层。“全局”意味着它们将在整个程序中生效,而不是某个特定的设备,接下来的几章里我们会说明这个问题。
VkInstanceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
createInfo.pApplicationInfo = &appInfo;
前两个参数的意思非常明显。接下来的两个成员会指定我们想用的全局扩展。就像我们在概述那章里提到过的,Vulkan 是一套平台无关的 API,这意味着你需要 一个扩展与窗口系统(window system)来交互。GLFW 已经集成了一个好用的内置函数,它返回 GLFW 需要的 Vulkan 扩展,我们可以直接把它传给 Vulkan API:
uint32_t glfwExtensionCount = 0;
const char** glfwExtensions;
glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
createInfo.enabledExtensionCount = glfwExtensionCount;
createInfo.ppEnabledExtensionNames = glfwExtensions;
最后两个成员指定哪些全局验证层将会被启用。我们会在下一章深入讨论验证层,这里先暂时留空。
createInfo.enabledLayerCount = 0;
我们已经指定了初始化 Vulkan 实例需要的所有信息,现在终于可以调用 vkCreateInstance
函数了:
VkResult result = vkCreateInstance(&createInfo, nullptr, &instance);
如你所见,Vulkan 中创建对象的函数,其参数通常是这样的:
- 指向创建信息(creation info)的指针
- 指向自定义分配器回调函数的指针,此教程中永远被置为
nullptr
- 指向保存了要被创建的对象的句柄变量的指针
如果一切运行良好,那么创建好的实例的句柄就被保存在 VkInstance
类型的成员变量中了。几乎每一个 Vulkan 函数的返回值都是 VkResult
类型的,
它要么是 VK_SUCCESS
,要么是一个错误代码。如果要检查实例是否被成功创建,我们不需要保存这个返回结果,只需要检查一下返回值就行了:
if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
throw std::runtime_error("failed to create instance!");
}
现在运行这个程序以确定实例创建成功。
遭遇 VK_ERROR_INCOMPATIBLE_DRIVER
如果在 MacOS 上使用最新的 MoltenVK SDK,你可能从 vkCreateInstance
得到 VK_ERROR_INCOMPATIBLE_DRIVER
返回值。根据
Vulkan SDK 的入门指南,从 1.3.216 版本开始,
VK_KHR_PORTABILITY_subset
扩展必须被启用。
为了解决这个错误,首先添加 VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR
标志位到 VkInstanceCreateInfo
结构体的 flags
成员,然后添加 VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME
到实例的启用扩展列表。
典型的代码应该像这样:
...
std::vector<const char*> requiredExtensions;
for(uint32_t i = 0; i < glfwExtensionCount; i++) {
requiredExtensions.emplace_back(glfwExtensions[i]);
}
requiredExtensions.emplace_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
createInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
createInfo.enabledExtensionCount = (uint32_t) requiredExtensions.size();
createInfo.ppEnabledExtensionNames = requiredExtensions.data();
if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
throw std::runtime_error("failed to create instance!");
}
检查插件是否受支持
如果你看过 vkCreateInstance
的文档,你就会看到有一个错误代码是 VK_ERROR_EXTENSION_NOT_PRESENT
。我们可以简单地指定我们想用的扩展,
如果返回了这个错误码就直接终止程序。如果要检查那些必要的扩展,例如窗口系统接口(window system interface, WSI),这么做还有点道理,但如果我
们要检查那些可选的功能呢?
为了在创建实例之前得到所有受支持的扩展列表,可以用 vkEnumerateInstanceExtensionProperties
函数。它需要两个指针变量,一个指向受支持的扩
展数量,另一个指向一个 VkExtensionProperties
类型的、存储着扩展的细节的数组。它的第一个参数是可选的,允许我们使用一个特殊的验证层来选择扩
展,我们现在先忽略它。
为了分配那个存储着扩展的细节的数组,我们需要先知道扩展的数量。你可以通过把最后一个参数留空的方式来只请求扩展的数量:
uint32_t extensionCount = 0;
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
现在来分配一个数组,保存扩展的细节(引入头文件 #include <vector>
):
std::vector<VkExtensionProperties> extensions(extensionCount);
最后我们就可以查询扩展的细节了:
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());
每个 VkExtensionProperties
结构体都包含着扩展的名称和版本。我们可以通过一个简单的循环来输出它们(\t
是一个制表符,用来缩进):
std::cout << "available extensions:\n";
for (const auto& extension : extensions) {
std::cout << '\t' << extension.extensionName << '\n';
}
如果你想输出 Vulkan 支持的详细信息,你可以把这段代码加到 createInstance
函数里。留一个课后练习,尝试创建一个函数,检查
glfwGetRequiredInstanceExtensions
函数返回的所有扩展是不是都在受支持的扩展列表里。
清理
VkInstance
只应该在程序退出之前被销毁。可以在 cleanup
函数中用 vkDestroyInstance
函数销毁它:
void cleanup() {
vkDestroyInstance(instance, nullptr);
glfwDestroyWindow(window);
glfwTerminate();
}
vkDestroyInstance
函数的参数非常直接了当,就像在上一章里提过的那样,Vulkan 中的分配器和回收器都有一个可选的回调函数参数,这个参数被我们设
置为 nullptr
以忽略它。在随后的章节中,我们创建的所有其它 Vulkan 资源都会在实例被销毁之前回收。
在创建了实例之后、开始进行更复杂的步骤之前,是时候看看我们的验证层来评估调试选项了。