[Vulkan]绘制一个三角形/设置/实例(Instance)

创建实例

初始化Vulkan库时,你需要做的第一件事就是创建实例[VkInstance](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/VkInstance.html)。它是应用程序与Vulkan库之间的桥梁。创建实例时,你需要告诉驱动程序你应用程序的一些细节信息。

添加一个createInstance函数,然后在initVulkan函数中调用它。

  1. void initVulkan() {
  2. createInstance();
  3. }

创建一个成员变量保存实例:

  1. private:
  2. VkInstance instance;

现在,我们首先创建一个结构体[VkApplicationInfo](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/VkApplicationInfo.html),并填入你应用程序的信息。这些数据是可选的,但它可以告诉驱动程序一些有用的信息,来优化我们的应用程序。

  1. void createInstance() {
  2. VkApplicationInfo appInfo{};
  3. appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
  4. appInfo.pApplicationName = "Hello Triangle";
  5. appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
  6. appInfo.pEngineName = "No Engine";
  7. appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
  8. appInfo.apiVersion = VK_API_VERSION_1_0;
  9. }

之前提到,Vulkan中的许多结构体需要你通过sType显式指定其类型。结构体一般也包含一个pNext成员,它会用来指向扩展信息,这里我们先设置为nullptr

Vulkan中一般用结构体传入参数而不是函数参数。创建Vulkan实例我们还需要另一个结构体来提供充足的的信息。这个结构体是必须的,它告诉Vulkan驱动,我们想要使用哪些全局扩展和验证层。全局扩展相对与特定设备扩展。之后的章节会讨论。

  1. VkInstanceCreateInfo createInfo{};
  2. createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
  3. createInfo.pApplicationInfo = &appInfo;

前两个参数很明确。后两个指定了需要的全局扩展。如概述一章所说,Vulkan是平台无关的,这意味着你需要一个针对窗口系统的扩展。GLFW有一个方便的内置函数返回需要的扩展名,我们将返回值传入结构体:

  1. uint32_t glfwExtensionCount = 0;
  2. const char** glfwExtensions;
  3. glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
  4. createInfo.enabledExtensionCount = glfwExtensionCount;
  5. createInfo.ppEnabledExtensionNames = glfwExtensions;

最后两个参数制定要启用的全局验证层。我们会在下一章深入讨论验证层,现在先置空。

  1. createInfo.enabledLayerCount = 0;

现在,我们已经指定了创建实例所需的所有参数,最后就是调用创建函数[vkCreateInstance](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkCreateInstance.html)

  1. VkResult result = vkCreateInstance(&createInfo, nullptr, &instance);

如你所见,在Vulkan中创建对象的函数参数有如下模式:

  • 指向创建信息的结构体指针
  • 指向自定义分配器回调的指针(在本教程中一直为nullptr
  • 指向存储新对象句柄变量的指针

正常情况下,instance句柄就保存到我们的成员变量中了。几乎所有的Vulkan函数都会返回一个VkResultVK_SUCCESS表示成功,其它值表示一个错误码。检查返回值,我们就能知道实例是否创建成功:

  1. if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
  2. throw std::runtime_error("failed to create instance!");
  3. }

现在可以尝试运行程序确保实例创建成功。

检查扩展支持

如果你看了[vkCreateInstance](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkCreateInstance.html)的文档,你可能会发现一个叫VK_ERROR_EXTENSION_NOT_PRESENT的错误码。我们可以简单地指定我们需要的扩展,如果出现错误,我们可以结束程序。必须的扩展可以这样处理,但是对于可选的扩展,我们要用合适的方法来处理。

在创建实例之前,我们可以通过[vkEnumerateInstanceExtensionProperties](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkEnumerateInstanceExtensionProperties.html)函数获取支持的扩展列表。它需要一个指向数量和一个指向[VkExtensionProperties](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/VkExtensionProperties.html)的指针(数组)来存储数据。它还有一个可选的验证层参数,可以通过它来过滤扩展,我们现在不需要它。

要分配一个数组保存扩展信息,我们首先需要知道扩展的数量。我们传入空数组指针即可先获取数量:

  1. uint32_t extensionCount = 0;
  2. vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);

然后分配一个数组保存扩展信息:

  1. std::vector<VkExtensionProperties> extensions(extensionCount);

最后,获取扩展列表:

  1. vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());

每一个[VkExtensionProperties](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/VkExtensionProperties.html)都包含扩展的名称和版本。我们可以列出他们:

  1. std::cout << "available extensions:\n";
  2. for (const auto& extension : extensions) {
  3. std::cout << '\t' << extension.extensionName << '\n';
  4. }

你可以把这段代码加到createInstance函数中来查看扩展支持情况。你可以尝试写一段代码,来判断你需要的扩展是否都在支持列表当中。

清理

[VkInstance](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/VkInstance.html)需要在程序退出前正确销毁。你可以在cleanup函数中使用[vkDestroyInstance](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkDestroyInstance.html)函数销毁实例:

  1. void cleanup() {
  2. vkDestroyInstance(instance, nullptr);
  3. glfwDestroyWindow(window);
  4. glfwTerminate();
  5. }

vkDestroyInstance的参数很简单。前面章节介绍到,我们可以传入自定义分配器管理内存,我们会忽略它并传入nullptr。之后章节中创建的Vulkan资源,我们都应该在实例销毁前销毁。

在继续更复杂的步骤前,我们首先添加验证层来开启调试选项。