跳到主要内容

创建实例

创建第一个实例

我们首先创建一个实例(instance)来初始化Vulkan库,这个实例保存了驱动程序需要使用的应用程序信息。

initVulkan函数中添加createInstance函数调用:

void initVulkan() {
createInstance();
}

添加一个私有成员instance存储实例句柄:

private:
VkInstance instance;

然后,填写应用程序信息,这些信息不是必须的,但可能会为驱动程序提供信息,以便驱动程序进行一些特殊优化。比如,应用程序使用了某个图形引擎,驱动程序对这个引擎有一些特殊处理,可能就会很大的性能提升:

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,指明结构体的类型。此外,许多Vulkan的结构体还有一个pNext成员变量,用来指向未来可能的扩展,我们并没有使用,直接将其设置为nullptr

Vulkan通常做法是通过结构体传递信息,为了创建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;

结构体的最后两个成员用来指定全局校验层,我们在之后的章节进行讨论,这里先将其设置为0,不使用:

createInfo.enabledLayerCount = 0;

填完所有的必要信息,我们就可以调用vkCreateInstance函数来创建Vulkan实例了:

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

我们可看到,创建Vulkan对象的函数参数,一般形式是:

  • 一个包含了创建信息的结构体指针
  • 一个自定义的分配器回调函数,在本教程,我们没有使用自定义分配器,总是设置为nullptr
  • 一个指向新对象句柄的指针

如果一切顺利,我们创建的实例句柄就保存在了VkInstance成员变量中。几乎所有Vulkan API函数调用都会返回一个VkResult来表示函数调用的结果,VK_SUCESS表示调用成功,其他表示调用失败。为了检测实例是否创建成功,我们可以直接判断是否为VK_SUCESS

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

现在可以编译运行程序来确保实例创建成功。

检测支持的扩展

如果读者看过vkCreateInstance函数的官方文档,可能会知道它的返回值错误码包含VK_ERROR_EXTENSION_NOT_PRESENT。我们可以利用这个错误代码,在扩展不满足时直接结束程序,对于像窗口系统这种必要的扩展来说非常适合。但有时候请求的扩展可能是非必须的,有了更好,没有的话,程序仍然可以运行,这时我们该怎么做呢?

实际上Vulkan提供了一个函数叫做vkEnumerateInstanceExtensionProperties可以在Vulkan实例创建之前返回支持的扩展列表。通过它,我们可以获取硬件支持的扩展个数,以及扩展的详细信息,此外,它还允许我们指定校验层对扩展进行过滤,但在这里,我们不使用它,将其设置为nullptr

我们首先需要知道扩展的数量,以便分配合适的数组大小来存储扩展信息。可以通过下面的代码来获取扩展的数量:

uint32_t extensionCount = 0;
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);

知道了扩展的数量后,我们就可以分配数组来存储扩展信息了:

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

使用下面的代码获取所有扩展信息:

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

每个VkExtensionProperties结构体包含了扩展的名字和版本信息。我们可以使用下面的代码将这些信息打印在控制台上:

std::cout << "available extensions:" << std::endl;

for (const auto& extension : extensions) {
std::cout << "\t" << extension.extensionName << std::endl;
}

读者可以将上面的代码加入createInstance中,来获取扩展信息。此外,我们可以编写一个函数来检测glfwGetRequiredInstanceExtensions返回的扩展是否全部在设备扩展列表中。

清理

在应用程序结束前需要对VkInstance对象进行销毁,我们可以在cleanup中调用vkDestroyInstance来完成销毁:

void cleanup() {
vkDestroyInstance(instance, nullptr);

glfwDestroyWindow(window);

glfwTerminate();
}

vkDestroyInstance函数的参数非常直白。之前提到,Vulkan对象的创建和销毁函数都有一个可选的分配器回调参数,本教程没有自定义的分配器,所以,将其设置为nullptr。除了Vulkan实例,其余使用Vulkan API创建的对象也需要被销毁,且应该在Vulkan实例销毁前进行销毁。

创建Vulkan实例后,在进行更复杂的操作前,我们先熟悉一下校验层,来帮助我们调试应用程序。

本章节代码:C++代码