创建实例
初始化Vulkan库时,你需要做的第一件事就是创建实例VkInstance
。它是应用程序与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实例我们还需要另一个结构体来提供充足的的信息。这个结构体是必须的,它告诉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
:
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
的文档,你可能会发现一个叫VK_ERROR_EXTENSION_NOT_PRESENT
的错误码。我们可以简单地指定我们需要的扩展,如果出现错误,我们可以结束程序。必须的扩展可以这样处理,但是对于可选的扩展,我们要用合适的方法来处理。
在创建实例之前,我们可以通过vkEnumerateInstanceExtensionProperties
函数获取支持的扩展列表。它需要一个指向数量和一个指向VkExtensionProperties
的指针(数组)来存储数据。它还有一个可选的验证层参数,可以通过它来过滤扩展,我们现在不需要它。
要分配一个数组保存扩展信息,我们首先需要知道扩展的数量。我们传入空数组指针即可先获取数量:
uint32_t extensionCount = 0;
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
然后分配一个数组保存扩展信息:
std::vector<VkExtensionProperties> extensions(extensionCount);
最后,获取扩展列表:
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());
每一个VkExtensionProperties
都包含扩展的名称和版本。我们可以列出他们:
std::cout << "available extensions:\n";
for (const auto& extension : extensions) {
std::cout << '\t' << extension.extensionName << '\n';
}
你可以把这段代码加到createInstance
函数中来查看扩展支持情况。你可以尝试写一段代码,来判断你需要的扩展是否都在支持列表当中。
清理
VkInstance
需要在程序退出前正确销毁。你可以在cleanup
函数中使用vkDestroyInstance
函数销毁实例:
void cleanup() {
vkDestroyInstance(instance, nullptr);
glfwDestroyWindow(window);
glfwTerminate();
}
vkDestroyInstance
的参数很简单。前面章节介绍到,我们可以传入自定义分配器管理内存,我们会忽略它并传入nullptr
。之后章节中创建的Vulkan资源,我们都应该在实例销毁前销毁。
在继续更复杂的步骤前,我们首先添加验证层来开启调试选项。