实例
创建实例
您需要做的第一件事是通过创建一个实例来初始化 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,这意味着您需要一个扩展来与窗口系统接口。GLFW 有一个方便的内置函数,可以返回它需要的扩展,我们可以将其传递给结构体
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 中对象创建函数参数遵循的一般模式是
- 指向包含创建信息的结构体的指针
- 指向自定义分配器回调的指针,在本教程中始终为
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
如果在使用最新 MoltenVK sdk 的 MacOS 上,您可能会从 vkCreateInstance
返回 VK_ERROR_INCOMPATIBLE_DRIVER
。根据 入门指南。从 1.3.216 Vulkan SDK 开始,VK_KHR_PORTABILITY_subset
扩展是强制性的。
要解决此错误,首先将 VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR
位添加到 VkInstanceCreateInfo
结构体的标志中,然后将 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
。我们可以简单地指定我们需要的扩展,并在返回该错误代码时终止。这对于像窗口系统接口这样的基本扩展是有意义的,但是如果我们想检查可选功能呢?
要在创建实例之前检索支持的扩展列表,可以使用 vkEnumerateInstanceExtensionProperties
函数。它接受一个指向变量的指针,该变量存储扩展的数量,以及一个 VkExtensionProperties
数组,用于存储扩展的详细信息。它还接受一个可选的第一个参数,允许我们按特定的验证层过滤扩展,我们现在忽略它。
要分配一个数组来保存扩展详细信息,我们首先需要知道有多少扩展。您可以通过将后一个参数留空来仅请求扩展的数量
uint32_t extensionCount = 0;
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
现在分配一个数组来保存扩展详细信息(include <vector>
)
std::vector<VkExtensionProperties> extensions(extensionCount);
最后,我们可以查询扩展详细信息
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());
每个 VkExtensionProperties
结构体包含扩展的名称和版本。我们可以用一个简单的 for 循环列出它们(\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 资源都应在实例销毁之前清理干净。
在继续实例创建之后更复杂的步骤之前,是时候通过查看验证层来评估我们的调试选项了。