创建实例
创建第一个实例
我们首先创建一个实例(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++代码