Vulkan教程(五):实例创建,连接应用与驱动的第一步
目录
1. 代码框架扩展
1.1 添加初始化函数调用
1.2 添加成员变量
2. 填充应用信息结构体
版本选择说明
3. 填充实例创建信息结构体
3.1 基础结构体初始化
3.2 添加窗口系统扩展
3.3 暂留验证层配置
4. 执行实例创建
Vulkan 对象创建的通用模式
5. 错误处理方式
5.1 异常捕获模式(默认)
5.2 无异常模式
6. 常见问题:macOS 平台驱动不兼容错误
7. 扩展支持性检查
7.1 获取支持的扩展列表
7.2 打印扩展列表
7.3 实战挑战
要使用 Vulkan 功能,创建实例(Instance)是必经的第一步。实例是应用程序与 Vulkan 库之间的桥梁,创建时需要向驱动程序提供一些应用相关的信息。
1. 代码框架扩展
首先,在HelloTriangleApplication类中添加实例创建的核心函数与成员变量。
1.1 添加初始化函数调用
修改initVulkan函数,调用新增的createInstance函数完成实例初始化:
cpp
运行
void initVulkan() { createInstance(); }1.2 添加成员变量
在类的私有成员中,添加RAII 上下文和实例对象的声明。RAII 上下文是 Vulkan-Hpp 库的核心组件,负责管理 Vulkan 的全局状态与资源释放:
cpp
运行
private: vk::raii::Context context; vk::raii::Instance instance = nullptr;2. 填充应用信息结构体
创建实例前,需要先填充vk::ApplicationInfo结构体,该结构体用于描述应用的基本信息。这些信息理论上是可选的,但提供后驱动程序可以针对应用的特性进行优化(例如识别出应用使用的知名图形引擎,从而启用特定的优化策略)。
实现createInstance函数,并初始化应用信息结构体:
cpp
运行
void createInstance() { constexpr vk::ApplicationInfo appInfo{ .pApplicationName = "Hello Triangle", .applicationVersion = VK_MAKE_VERSION(1, 0, 0), .pEngineName = "No Engine", .engineVersion = VK_MAKE_VERSION(1, 0, 0), .apiVersion = vk::ApiVersion14 }; }版本选择说明
我们选择 Vulkan 1.4 作为基准版本,而非更早的 1.0 版本。原因如下:
- 旧版本对 RAII 机制的支持不够完善;
- 后续会用到的 Slang 着色语言在新版本中兼容性更好。
对比 C 语言 API:在纯 C 的 Vulkan 开发中,需要手动设置结构体的
sType和pNext字段,代码冗余且繁琐。而使用 C++ 模块的 Vulkan-Hpp 库会自动处理这些细节,大幅简化代码。
3. 填充实例创建信息结构体
Vulkan 中大部分配置信息都通过结构体传递,而非直接作为函数参数。除了应用信息,我们还需要填充实例创建信息结构体(vk::InstanceCreateInfo),这个结构体是必填项,用于告知驱动程序我们需要启用的全局扩展和验证层。
这里的 “全局” 意味着这些扩展和验证层作用于整个应用程序,而非特定的物理设备(物理设备相关的配置会在后续章节讲解)。
3.1 基础结构体初始化
首先,基于已填充的应用信息初始化实例创建结构体:
cpp
运行
vk::InstanceCreateInfo createInfo{ .pApplicationInfo = &appInfo };3.2 添加窗口系统扩展
Vulkan 是一个平台无关的 API,本身不包含任何窗口系统交互的逻辑。要将渲染结果显示到 GLFW 创建的窗口中,必须启用对应的窗口系统扩展。
GLFW 提供了一个便捷函数glfwGetRequiredInstanceExtensions,可以直接返回其运行所需的 Vulkan 扩展列表。我们需要将这些扩展添加到实例创建信息中:
cpp
运行
// 获取GLFW所需的实例扩展 uint32_t glfwExtensionCount = 0; auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); // 检查所需扩展是否被当前Vulkan实现支持 auto extensionProperties = context.enumerateInstanceExtensionProperties(); for (uint32_t i = 0; i < glfwExtensionCount; ++i) { if (std::ranges::none_of(extensionProperties, [glfwExtension = glfwExtensions[i]](auto const& extensionProperty) { return strcmp(extensionProperty.extensionName, glfwExtension) == 0; })) { throw std::runtime_error("Required GLFW extension not supported: " + std::string(glfwExtensions[i])); } } // 填充扩展信息到实例创建结构体 vk::InstanceCreateInfo createInfo{ .pApplicationInfo = &appInfo, .enabledExtensionCount = glfwExtensionCount, .ppEnabledExtensionNames = glfwExtensions };3.3 暂留验证层配置
实例创建信息结构体中还有一个重要字段是验证层,这是 Vulkan 开发中最实用、最重要的调试工具之一。关于验证层的详细配置,我们将在下一章深入讲解,因此当前暂时将其留空。
4. 执行实例创建
完成所有配置后,就可以调用 Vulkan-Hpp 的 RAII 接口创建实例了:
cpp
运行
instance = vk::raii::Instance(context, createInfo);Vulkan 对象创建的通用模式
通过上述代码可以发现,Vulkan 对象的创建参数遵循固定的模式:
- 创建信息结构体指针:包含对象的所有配置参数;
- 自定义分配器回调指针:本教程中统一忽略,传入
nullptr使用默认分配器; - 依赖对象指针:如实例依赖于上下文,设备依赖于实例;
- 返回值:RAII 封装的对象,生命周期结束时自动销毁。
5. 错误处理方式
Vulkan-Hpp 库提供了两种错误处理机制,可根据需求选择。
5.1 异常捕获模式(默认)
默认情况下,Vulkan 操作失败会抛出异常,我们可以通过try-catch块捕获并处理错误:
cpp
运行
try { vk::raii::Context context; vk::raii::Instance instance(context, vk::InstanceCreateInfo{}); vk::raii::PhysicalDevice physicalDevice = instance.enumeratePhysicalDevices().front(); vk::raii::Device device(physicalDevice, vk::DeviceCreateInfo{}); // 使用Vulkan对象 vk::raii::Buffer buffer(device, vk::BufferCreateInfo{}); } catch (const vk::SystemError& err) { std::cerr << "Vulkan error: " << err.what() << std::endl; return 1; } catch (const std::exception& err) { std::cerr << "Error: " << err.what() << std::endl; return 1; }5.2 无异常模式
若定义VULKAN_HPP_NO_EXCEPTIONS宏,函数会返回一个包含错误码和对象的元组,需手动检查错误码:
cpp
运行
auto [result, imageIndex] = swapChain->acquireNextImage(UINT64_MAX, presentCompleteSemaphore[currentFrame], nullptr); if (result == vk::Result::eErrorOutOfDateKHR) { recreateSwapChain(); return; } if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { throw std::runtime_error("failed to acquire swap chain image!"); }注:上述代码来自后续章节的交换链操作,仅作为错误处理示例。
6. 常见问题:macOS 平台驱动不兼容错误
在 macOS 系统中使用最新版 MoltenVK SDK 时,调用vkCreateInstance可能会抛出VK_ERROR_INCOMPATIBLE_DRIVER错误。根据官方文档,从 Vulkan SDK 1.3.216 版本开始,VK_KHR_PORTABILITY_subset扩展成为必选项。
要解决此问题,需要在实例创建信息中添加两个关键配置:
- 设置实例创建标志位
eEnumeratePortabilityKHR; - 启用
VK_KHR_PORTABILITY_subset扩展。
修改后的实例创建代码如下:
cpp
运行
constexpr vk::ApplicationInfo appInfo{ .pApplicationName = "Hello Triangle", .applicationVersion = VK_MAKE_VERSION(1, 0, 0), .pEngineName = "No Engine", .engineVersion = VK_MAKE_VERSION(1, 0, 0), .apiVersion = vk::ApiVersion14 }; vk::InstanceCreateInfo createInfo{ .flags = vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR, .pApplicationInfo = &appInfo, .ppEnabledExtensionNames = { vk::KHRPortabilityEnumerationExtensionName } }; instance = vk::raii::Instance(context, createInfo);7. 扩展支持性检查
查阅vkCreateInstance的官方文档可知,该函数可能返回VK_ERROR_EXTENSION_NOT_PRESENT错误。对于窗口系统交互这类核心扩展,我们可以直接指定并在错误发生时终止程序。但如果是可选扩展,则需要先检查其是否被支持。
7.1 获取支持的扩展列表
通过context.enumerateInstanceExtensionProperties函数,可以获取当前 Vulkan 实现支持的所有扩展列表,该函数返回一个vk::ExtensionProperties结构体的向量,每个结构体包含扩展的名称和版本:
cpp
运行
auto extensions = context.enumerateInstanceExtensionProperties();7.2 打印扩展列表
我们可以通过简单的循环,将所有支持的扩展名称打印出来,方便调试和信息查看:
cpp
运行
std::cout << "available extensions:\n"; for (const auto& extension : extensions) { std::cout << '\t' << extension.extensionName << '\n'; }7.3 实战挑战
你可以尝试编写一个工具函数,验证glfwGetRequiredInstanceExtensions返回的所有扩展是否都在支持列表中。这能帮助你在程序初始化阶段就发现潜在的兼容性问题。
