[Vulkan]绘制一个三角形/设置/实例(Instance)
创建实例
初始化Vulkan库时,你需要做的第一件事就是创建实例[VkInstance](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/VkInstance.html)。它是应用程序与Vulkan库之间的桥梁。创建实例时,你需要告诉驱动程序你应用程序的一些细节信息。
添加一个createInstance函数,然后在initVulkan函数中调用它。
void initVulkan() {createInstance();}
创建一个成员变量保存实例:
private:VkInstance instance;
现在,我们首先创建一个结构体[VkApplicationInfo](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/VkApplicationInfo.html),并填入你应用程序的信息。这些数据是可选的,但它可以告诉驱动程序一些有用的信息,来优化我们的应用程序。
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实例我们还需要另一个结构体来提供充足的的信息。这个结构体是必须的,它告诉Vulkan驱动,我们想要使用哪些全局扩展和验证层。全局扩展相对与特定设备扩展。之后的章节会讨论。
VkInstanceCreateInfo createInfo{};createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;createInfo.pApplicationInfo = &appInfo;
前两个参数很明确。后两个指定了需要的全局扩展。如概述一章所说,Vulkan是平台无关的,这意味着你需要一个针对窗口系统的扩展。GLFW有一个方便的内置函数返回需要的扩展名,我们将返回值传入结构体:
uint32_t glfwExtensionCount = 0;const char** glfwExtensions;glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);createInfo.enabledExtensionCount = glfwExtensionCount;createInfo.ppEnabledExtensionNames = glfwExtensions;
最后两个参数制定要启用的全局验证层。我们会在下一章深入讨论验证层,现在先置空。
createInfo.enabledLayerCount = 0;
现在,我们已经指定了创建实例所需的所有参数,最后就是调用创建函数[vkCreateInstance](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkCreateInstance.html):
VkResult result = vkCreateInstance(&createInfo, nullptr, &instance);
如你所见,在Vulkan中创建对象的函数参数有如下模式:
- 指向创建信息的结构体指针
- 指向自定义分配器回调的指针(在本教程中一直为
nullptr) - 指向存储新对象句柄变量的指针
正常情况下,instance句柄就保存到我们的成员变量中了。几乎所有的Vulkan函数都会返回一个VkResult,VK_SUCCESS表示成功,其它值表示一个错误码。检查返回值,我们就能知道实例是否创建成功:
if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {throw std::runtime_error("failed to create instance!");}
现在可以尝试运行程序确保实例创建成功。
检查扩展支持
如果你看了[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)的指针(数组)来存储数据。它还有一个可选的验证层参数,可以通过它来过滤扩展,我们现在不需要它。
要分配一个数组保存扩展信息,我们首先需要知道扩展的数量。我们传入空数组指针即可先获取数量:
uint32_t extensionCount = 0;vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
然后分配一个数组保存扩展信息:
std::vector<VkExtensionProperties> extensions(extensionCount);
最后,获取扩展列表:
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());
每一个[VkExtensionProperties](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/VkExtensionProperties.html)都包含扩展的名称和版本。我们可以列出他们:
std::cout << "available extensions:\n";for (const auto& extension : extensions) {std::cout << '\t' << extension.extensionName << '\n';}
你可以把这段代码加到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)函数销毁实例:
void cleanup() {vkDestroyInstance(instance, nullptr);glfwDestroyWindow(window);glfwTerminate();}
vkDestroyInstance的参数很简单。前面章节介绍到,我们可以传入自定义分配器管理内存,我们会忽略它并传入nullptr。之后章节中创建的Vulkan资源,我们都应该在实例销毁前销毁。
在继续更复杂的步骤前,我们首先添加验证层来开启调试选项。
