|
| 1 | +--- |
| 2 | +title: 实例 |
| 3 | +--- |
| 4 | + |
| 5 | +## 创建一个实例 |
| 6 | + |
| 7 | +一切开始于创建一个*实例*(instance)来初始化 Vulkan 库。实例是连接 Vulkan 库和你的程序之间的桥梁,创建实例还涉及到向驱动指定你的应用程序的 |
| 8 | +一些细节。 |
| 9 | + |
| 10 | +添加一个 `createInstance` 函数,然后在 `initVulkan` 函数中调用它。 |
| 11 | + |
| 12 | +```c++ |
| 13 | +void initVulkan() { |
| 14 | + createInstance(); |
| 15 | +} |
| 16 | +``` |
| 17 | + |
| 18 | +再在类中添加一个数据成员,用来保存实例的句柄: |
| 19 | + |
| 20 | +```c++ |
| 21 | +private: |
| 22 | +VkInstance instance; |
| 23 | +``` |
| 24 | + |
| 25 | +现在,为了创建实例,我们首先需要用我们程序的一些信息去填充一个结构体。从技术上来说,这些信息是可有可无的,但是它们或许能够提供一些信息给驱动,以 |
| 26 | +使驱动针对我们的特定程序进行优化(例如,它使用了一个具有某些特殊行为的知名图形引擎)。这个结构体叫做 `VkApplicationInfo`: |
| 27 | + |
| 28 | +```c++ |
| 29 | +void createInstance() { |
| 30 | + VkApplicationInfo appInfo{}; |
| 31 | + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; |
| 32 | + appInfo.pApplicationName = "Hello Triangle"; |
| 33 | + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); |
| 34 | + appInfo.pEngineName = "No Engine"; |
| 35 | + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); |
| 36 | + appInfo.apiVersion = VK_API_VERSION_1_0; |
| 37 | +} |
| 38 | +``` |
| 39 | + |
| 40 | +就像之前提到过的那样,Vulkan 中的许多结构体需要你在 `sType` 成员中显式指定类型。这个结构体也是众多拥有 `pNext` 成员的结构体之一,这个成员在 |
| 41 | +将来可以指向扩展信息。我们现在执行默认初始化,所以此处置为 `nullptr`(空指针)。 |
| 42 | + |
| 43 | +Vulkan 中的许多信息都通过结构体来传递,而不是函数参数。我们还需要填充另一个结构体来为创建实例提供足够多的信息。接下来的这个结构体是必需的,它告 |
| 44 | +知 Vulkan 驱动我们要使用哪些全局的扩展以及验证层。“全局”意味着它们将在整个程序中生效,而不是某个特定的设备,接下来的几章里我们会说明这个问题。 |
| 45 | + |
| 46 | +```c++ |
| 47 | +VkInstanceCreateInfo createInfo{}; |
| 48 | +createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; |
| 49 | +createInfo.pApplicationInfo = &appInfo; |
| 50 | +``` |
| 51 | + |
| 52 | +前两个参数的意思非常明显。接下来的两个成员会指定我们想用的全局扩展。就像我们在概述那章里提到过的,Vulkan 是一套平台无关的 API,这意味着你需要 |
| 53 | +一个扩展与窗口系统(window system)来交互。GLFW 已经集成了一个好用的内置函数,它返回 GLFW 需要的 Vulkan 扩展,我们可以直接把它传给 |
| 54 | +Vulkan API: |
| 55 | + |
| 56 | +```c++ |
| 57 | +uint32_t glfwExtensionCount = 0; |
| 58 | +const char** glfwExtensions; |
| 59 | + |
| 60 | +glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); |
| 61 | + |
| 62 | +createInfo.enabledExtensionCount = glfwExtensionCount; |
| 63 | +createInfo.ppEnabledExtensionNames = glfwExtensions; |
| 64 | +``` |
| 65 | + |
| 66 | +最后两个成员指定哪些全局验证层将会被启用。我们会在下一章深入讨论验证层,这里先暂时留空。 |
| 67 | + |
| 68 | +```c++ |
| 69 | +createInfo.enabledLayerCount = 0; |
| 70 | +``` |
| 71 | + |
| 72 | +我们已经指定了初始化 Vulkan 实例需要的所有信息,现在终于可以调用 `vkCreateInstance` 函数了: |
| 73 | + |
| 74 | +```c++ |
| 75 | +VkResult result = vkCreateInstance(&createInfo, nullptr, &instance); |
| 76 | +``` |
| 77 | + |
| 78 | +如你所见,Vulkan 中创建对象的函数,其参数通常是这样的: |
| 79 | + |
| 80 | +* 指向创建信息(creation info)的指针 |
| 81 | +* 指向自定义分配器回调函数的指针,此教程中永远被置为 `nullptr` |
| 82 | +* 指向保存了要被创建的对象的句柄变量的指针 |
| 83 | + |
| 84 | +如果一切运行良好,那么创建好的实例的句柄就被保存在 `VkInstance` 类型的成员变量中了。几乎每一个 Vulkan 函数的返回值都是 `VkResult` 类型的, |
| 85 | +它要么是 `VK_SUCCESS`,要么是一个错误代码。如果要检查实例是否被成功创建,我们不需要保存这个返回结果,只需要检查一下返回值就行了: |
| 86 | + |
| 87 | +```c++ |
| 88 | +if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { |
| 89 | + throw std::runtime_error("failed to create instance!"); |
| 90 | +} |
| 91 | +``` |
| 92 | + |
| 93 | +现在运行这个程序以确定实例创建成功。 |
| 94 | + |
| 95 | +## 遭遇 VK_ERROR_INCOMPATIBLE_DRIVER |
| 96 | + |
| 97 | +如果在 MacOS 上使用最新的 MoltenVK SDK,你可能从 `vkCreateInstance` 得到 `VK_ERROR_INCOMPATIBLE_DRIVER` 返回值。根据 |
| 98 | +[Vulkan SDK 的入门指南](https://vulkan.lunarg.com/doc/sdk/1.3.216.0/mac/getting_started.html),从 1.3.216 版本开始, |
| 99 | +`VK_KHR_PORTABILITY_subset` 扩展必须被启用。 |
| 100 | + |
| 101 | +为了解决这个错误,首先添加 `VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR` 标志位到 `VkInstanceCreateInfo` 结构体的 `flags` |
| 102 | +成员,然后添加 `VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME` 到实例的启用扩展列表。 |
| 103 | + |
| 104 | +典型的代码应该像这样: |
| 105 | + |
| 106 | +```c++ |
| 107 | +... |
| 108 | + |
| 109 | +std::vector<const char*> requiredExtensions; |
| 110 | + |
| 111 | +for(uint32_t i = 0; i < glfwExtensionCount; i++) { |
| 112 | + requiredExtensions.emplace_back(glfwExtensions[i]); |
| 113 | +} |
| 114 | + |
| 115 | +requiredExtensions.emplace_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); |
| 116 | + |
| 117 | +createInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; |
| 118 | + |
| 119 | +createInfo.enabledExtensionCount = (uint32_t) requiredExtensions.size(); |
| 120 | +createInfo.ppEnabledExtensionNames = requiredExtensions.data(); |
| 121 | + |
| 122 | +if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { |
| 123 | + throw std::runtime_error("failed to create instance!"); |
| 124 | +} |
| 125 | +``` |
| 126 | + |
| 127 | +## 检查插件是否受支持 |
| 128 | + |
| 129 | +如果你看过 `vkCreateInstance` 的文档,你就会看到有一个错误代码是 `VK_ERROR_EXTENSION_NOT_PRESENT`。我们可以简单地指定我们想用的扩展, |
| 130 | +如果返回了这个错误码就直接终止程序。如果要检查那些必要的扩展,例如窗口系统接口(window system interface, WSI),这么做还有点道理,但如果我 |
| 131 | +们要检查那些可选的功能呢? |
| 132 | + |
| 133 | +为了在创建实例之前得到所有受支持的扩展列表,可以用 `vkEnumerateInstanceExtensionProperties` 函数。它需要两个指针变量,一个指向受支持的扩 |
| 134 | +展数量,另一个指向一个 `VkExtensionProperties` 类型的、存储着扩展的细节的数组。它的第一个参数是可选的,允许我们使用一个特殊的验证层来选择扩 |
| 135 | +展,我们现在先忽略它。 |
| 136 | + |
| 137 | +为了分配那个存储着扩展的细节的数组,我们需要先知道扩展的数量。你可以通过把最后一个参数留空的方式来只请求扩展的数量: |
| 138 | + |
| 139 | +```c++ |
| 140 | +uint32_t extensionCount = 0; |
| 141 | +vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); |
| 142 | +``` |
| 143 | +
|
| 144 | +现在来分配一个数组,保存扩展的细节(引入头文件 `#include <vector>`): |
| 145 | +
|
| 146 | +```c++ |
| 147 | +std::vector<VkExtensionProperties> extensions(extensionCount); |
| 148 | +``` |
| 149 | + |
| 150 | +最后我们就可以查询扩展的细节了: |
| 151 | + |
| 152 | +```c++ |
| 153 | +vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data()); |
| 154 | +``` |
| 155 | +
|
| 156 | +每个 `VkExtensionProperties` 结构体都包含着扩展的名称和版本。我们可以通过一个简单的循环来输出它们(`\t` 是一个制表符,用来缩进): |
| 157 | +
|
| 158 | +```c++ |
| 159 | +std::cout << "available extensions:\n"; |
| 160 | +
|
| 161 | +for (const auto& extension : extensions) { |
| 162 | + std::cout << '\t' << extension.extensionName << '\n'; |
| 163 | +} |
| 164 | +``` |
| 165 | + |
| 166 | +如果你想输出 Vulkan 支持的详细信息,你可以把这段代码加到 `createInstance` 函数里。留一个课后练习,尝试创建一个函数,检查 |
| 167 | +`glfwGetRequiredInstanceExtensions` 函数返回的所有扩展是不是都在受支持的扩展列表里。 |
| 168 | + |
| 169 | +## 清理 |
| 170 | + |
| 171 | +`VkInstance` 只应该在程序退出之前被销毁。可以在 `cleanup` 函数中用 `vkDestroyInstance` 函数销毁它: |
| 172 | + |
| 173 | +```c++ |
| 174 | +void cleanup() { |
| 175 | + vkDestroyInstance(instance, nullptr); |
| 176 | + |
| 177 | + glfwDestroyWindow(window); |
| 178 | + |
| 179 | + glfwTerminate(); |
| 180 | +} |
| 181 | +``` |
| 182 | + |
| 183 | +`vkDestroyInstance` 函数的参数非常直接了当,就像在上一章里提过的那样,Vulkan 中的分配器和回收器都有一个可选的回调函数参数,这个参数被我们设 |
| 184 | +置为 `nullptr` 以忽略它。在随后的章节中,我们创建的所有其它 Vulkan 资源都会在实例被销毁之前回收。 |
| 185 | + |
| 186 | +在创建了实例之后、开始进行更复杂的步骤之前,是时候看看我们的[验证层](!zh/Drawing_a_triangle/Setup/Validation_layers)来评估调试选项了。 |
| 187 | + |
| 188 | +[C++ 代码](https://vulkan-tutorial.com/code/01_instance_creation.cpp) |
0 commit comments