Drawing a triangle -- setup -- Instance

目录

[创建实例(Creating an instance)](#创建实例(Creating an instance))

[遇到 VK_ERROR_INCOMPATIBLE_DRIVER 错误(Encountered VK_ERROR_INCOMPATIBLE_DRIVER)](#遇到 VK_ERROR_INCOMPATIBLE_DRIVER 错误(Encountered VK_ERROR_INCOMPATIBLE_DRIVER))

[检查扩展支持(Checking for extension support)](#检查扩展支持(Checking for extension support))

[清理资源(Cleaning up)](#清理资源(Cleaning up))


创建实例(Creating an instance)

你需要做的第一件事是通过创建实例(Instance)来初始化 Vulkan 库。实例是应用程序与 Vulkan 库之间的连接,创建实例时需要向驱动程序指定应用程序的一些相关细节。

首先添加一个 createInstance 函数,并在 initVulkan 函数中调用它:

cpp 复制代码
void initVulkan() {
    createInstance();
}

另外添加一个数据成员来存储实例的句柄:

cpp 复制代码
private:
VkInstance instance;

现在,要创建实例,我们首先需要填充一个结构体,包含应用程序的相关信息。从技术上讲,这些数据是可选的,但它可能会向驱动程序提供一些有用的信息,以便优化我们的特定应用程序(例如,因为它使用了一个知名的图形引擎,具有某些特殊行为)。这个结构体名为 VkApplicationInfo

cpp 复制代码
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 驱动程序我们想要使用哪些全局扩展(global extensions)和验证层(validation layers)。这里的 "全局" 意味着它们适用于整个程序,而不是特定的设备,这一点在接下来的章节中会变得更加清晰。

cpp 复制代码
VkInstanceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
createInfo.pApplicationInfo = &appInfo;

前两个参数很直观。接下来的两个参数指定所需的全局扩展。如概述章节所述,Vulkan 是一个与平台无关的 API,这意味着你需要一个扩展来与窗口系统交互。GLFW 有一个便捷的内置函数,可以返回它所需的扩展,我们可以将其传递给该结构体:

cpp 复制代码
uint32_t glfwExtensionCount = 0;
const char** glfwExtensions;

glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

createInfo.enabledExtensionCount = glfwExtensionCount;
createInfo.ppEnabledExtensionNames = glfwExtensions;

该结构体的最后两个成员决定了要启用的全局验证层。我们将在下一章中更深入地讨论这些内容,所以现在先将它们留空:

cpp 复制代码
createInfo.enabledLayerCount = 0;

现在我们已经指定了 Vulkan 创建实例所需的所有信息,终于可以调用 vkCreateInstance 了:

cpp 复制代码
VkResult result = vkCreateInstance(&createInfo, nullptr, &instance);

你会发现,Vulkan 中对象创建函数参数的通用模式如下:

  1. 指向创建信息结构体的指针
  2. 指向自定义分配器回调的指针(本教程中始终为 nullptr
  3. 指向存储新对象句柄的变量的指针

如果一切顺利,实例的句柄将存储在 VkInstance 类成员中。几乎所有 Vulkan 函数都会返回一个 VkResult 类型的值,该值要么是 VK_SUCCESS,要么是一个错误代码。要检查实例是否创建成功,我们不需要存储结果,只需直接检查是否为成功值即可:

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

现在运行程序,确保实例创建成功。

遇到 VK_ERROR_INCOMPATIBLE_DRIVER 错误(Encountered VK_ERROR_INCOMPATIBLE_DRIVER)

如果在 macOS 上使用最新的 MoltenVK SDK,调用 vkCreateInstance 可能会返回 VK_ERROR_INCOMPATIBLE_DRIVER。根据入门说明(Getting Start Notes),从 1.3.216 版本的 Vulkan SDK 开始,VK_KHR_PORTABILITY_subset 扩展是必需的。

要解决此错误,首先需要向 VkInstanceCreateInfo 结构体的 flags 中添加 VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR 位,然后将 VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME 添加到实例的启用扩展列表中。

通常代码如下所示:

cpp 复制代码
...

std::vector<const char*> requiredExtensions;

for(uint32_t i = 0; i < glfwExtensionCount; i++) {
    requiredExtensions.emplace_back(glfwExtensions[i]);
}

requiredExtensions.emplace_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);

createInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;

createInfo.enabledExtensionCount = (uint32_t) requiredExtensions.size();
createInfo.ppEnabledExtensionNames = requiredExtensions.data();

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

检查扩展支持(Checking for extension support)

如果你查看 vkCreateInstance 的文档,会发现其中一个可能的错误代码是 VK_ERROR_EXTENSION_NOT_PRESENT。我们可以直接指定所需的扩展,如果返回该错误代码就终止程序。这对于像窗口系统接口这样的核心扩展来说是合理的,但如果我们想要检查可选功能呢?

要在创建实例之前获取支持的扩展列表,可以使用 vkEnumerateInstanceExtensionProperties 函数。它接受一个指向存储扩展数量的变量的指针,以及一个用于存储扩展详细信息的 VkExtensionProperties 数组。它还有一个可选的第一个参数,允许我们按特定的验证层过滤扩展,目前我们先忽略这个参数。

要分配一个数组来存储扩展详细信息,我们首先需要知道扩展的数量。你可以通过留空后一个参数来仅请求扩展的数量:

cpp 复制代码
uint32_t extensionCount = 0;
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);

现在分配一个数组来存储扩展详细信息(需要包含 <vector> 头文件):

cpp 复制代码
std::vector<VkExtensionProperties> extensions(extensionCount);

最后,我们可以查询扩展的详细信息:

cpp 复制代码
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());

每个 VkExtensionProperties 结构体都包含一个扩展的名称和版本。我们可以通过一个简单的 for 循环来列出它们(\t 是用于缩进的制表符):

cpp 复制代码
std::cout << "available extensions:\n";

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

如果你想提供有关 Vulkan 支持的一些详细信息,可以将此代码添加到 createInstance 函数中。作为一个练习,尝试创建一个函数,检查 glfwGetRequiredInstanceExtensions 返回的所有扩展是否都包含在支持的扩展列表中。

清理资源(Cleaning up)

VkInstance 应该只在程序退出前销毁。可以在 cleanup 函数中使用 vkDestroyInstance 函数来销毁它:

cpp 复制代码
void cleanup() {
    vkDestroyInstance(instance, nullptr);

    glfwDestroyWindow(window);

    glfwTerminate();
}

vkDestroyInstance 函数的参数很直观。如前一章所述,Vulkan 中的分配和释放函数都有一个可选的分配器回调,我们通过传递 nullptr 来忽略它。在后续章节中我们将创建的所有其他 Vulkan 资源,都应该在实例销毁之前清理干净。

在继续实例创建之后的更复杂步骤之前,是时候通过了解验证层(validation layers)来评估我们的调试选项了。

相关推荐
千里马-horse4 小时前
Drawing a triangle -- setup -- Validation layers
validation·vulkan·layers
李坤林1 个月前
Android Vulkan 开启VK_GOOGLE_DISPLAY_TIMING 后,一个vsync 会释放两个imageBuffer现象分析
android·vulkan
不知所云,1 个月前
1. 开篇简介
c++·vulkan
CHPCWWHSU3 个月前
osg中相机矩阵到vsg相机矩阵的转换
opengl·osg·投影矩阵·vulkan·vsg
星星也在雾里5 个月前
Vulkan入门教程 | 第二部分:创建实例
vulkan
Hi202402178 个月前
RK3588 ArmNN CPU/GPU ResNet50 FP32/FP16/INT8 推理测试
嵌入式·rk3588·vulkan·ai推理·armnn
TYYJ-洪伟9 个月前
【Vulkan 入门系列】创建帧缓冲、命令池、命令缓存,和获取图片(六)
音视频·gpu·vulkan·图像渲染
CHPCWWHSU10 个月前
vulkanscenegraph显示倾斜模型(5.4)-相机操纵器
c++·osg·vulkan·vsg
CHPCWWHSU10 个月前
vulkanscenegraph显示倾斜模型(5.3)-相机
c++·osg·vulkan·vsg