[Vulkan 学习之路] 02 - 万物起源:创建 Vulkan 实例 (Instance)

上一篇我们成功搭建了环境并弹出了一个黑窗口。今天,我们要正式初始化 Vulkan 库。

在 Vulkan 中,没有什么是"默认"发生的。不同于 OpenGL 的上下文(Context),Vulkan 使用 Instance(实例) 来存储所有每个应用层面的状态。这是你的应用程序与 Vulkan 库、显卡驱动之间建立连接的桥梁。

核心概念:Vulkan 的"填表"哲学

在开始写代码前,你需要习惯 Vulkan 的编程模式:"填表 -> 提交"

几乎所有的 Vulkan 操作都遵循这个流程:

  1. 定义一个 CreateInfo 结构体(填表)。

  2. 设置结构体的各种参数(明确你的需求)。

  3. 调用 vkCreateXXX 函数,把表交给驱动(提交)。

创建 Instance 也不例外。


第一步:填写应用程序信息 (VkApplicationInfo)

首先,我们可以(这是可选的,但推荐做)告诉驱动程序我们的应用叫什么名字,版本是多少。这有助于显卡厂商针对特定的游戏或引擎进行驱动层面的优化。

cpp 复制代码
void createInstance() {
    // 1. 填写应用信息
    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 1.0 标准
    
    // ... 后续代码
}

注意: 在 Vulkan 中,几乎所有的结构体都需要你手动设置 sType 成员。这是因为 Vulkan 内部为了性能使用 void* 指针传递结构体,它需要 sType 来识别这是什么类型的数据。


第二步:获取必要的扩展 (Extensions)

Vulkan 是一个平台无关的 API。这意味着核心的 Vulkan 库根本不知道什么是 "Windows" 或 "窗口"。它只懂图形计算。

要让 Vulkan 能在 Windows 的窗口上画图,我们需要启用扩展 (Extensions)。幸好,GLFW 内置了一个帮助函数,能告诉我们需要哪些扩展。

cpp 复制代码
    // ... 接上文
    
    // 获取 GLFW 需要的扩展 (例如 VK_KHR_surface 和 VK_KHR_win32_surface)
    uint32_t glfwExtensionCount = 0;
    const char** glfwExtensions;
    
    glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

虽然 glfwGetRequiredInstanceExtensions 返回的是 C 风格数组,但在后续教程中我们会大量使用 std::vector,Vulkan 的很多 count/data 模式很适合转换成 vector。


第三步:填写实例创建信息 (VkInstanceCreateInfo)

这是创建 Instance 过程中最重要的结构体。它告诉 Vulkan 驱动我们需要哪些全局扩展和校验层。

cpp 复制代码
    // ... 接上文

    // 2. 填写实例创建信息 (这是必须的)
    VkInstanceCreateInfo createInfo{};
    createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
    createInfo.pApplicationInfo = &appInfo; // 链接上面的应用信息

    // 填入扩展信息
    createInfo.enabledExtensionCount = glfwExtensionCount;
    createInfo.ppEnabledExtensionNames = glfwExtensions;

    // 暂时不启用校验层 (Validation Layers),下一章会讲,这里先填 0
    createInfo.enabledLayerCount = 0;

第四步:创建与清理

终于可以调用 vkCreateInstance 了。

在 Vulkan 中,创建函数通常返回一个 VkResult。如果返回值不是 VK_SUCCESS,说明出错了。

同时,我们必须遵循 RAII 原则(资源获取即初始化),或者是手动管理的原则:如果你 Create 了它,你就必须 Destroy 它。

修改 HelloVulkanApp

我们需要在类成员中添加 VkInstance 变量,并完善 initVulkancleanup

cpp 复制代码
class HelloVulkanApp {
public:
    void run() {
        initWindow();
        initVulkan();
        mainLoop();
        cleanup();
    }

private:
    GLFWwindow* window;
    VkInstance instance; // <--- 新增成员变量

    void initVulkan() {
        createInstance();
    }

    void createInstance() {
        // 1. 应用信息
        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;

        // 2. 获取 GLFW 扩展
        uint32_t glfwExtensionCount = 0;
        const char** glfwExtensions;
        glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

        // 3. 实例创建信息
        VkInstanceCreateInfo createInfo{};
        createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
        createInfo.pApplicationInfo = &appInfo;
        createInfo.enabledExtensionCount = glfwExtensionCount;
        createInfo.ppEnabledExtensionNames = glfwExtensions;
        createInfo.enabledLayerCount = 0; // 暂时为 0

        // 4. 创建实例 (检查结果)
        VkResult result = vkCreateInstance(&createInfo, nullptr, &instance);
        
        if (result != VK_SUCCESS) {
            throw std::runtime_error("failed to create instance!");
        }
    }

    // ... mainLoop 保持不变 ...

    void cleanup() {
        // 注意顺序:先销毁 Vulkan 实例,再销毁 GLFW 窗口
        vkDestroyInstance(instance, nullptr); // <--- 新增清理代码
        
        glfwDestroyWindow(window);
        glfwTerminate();
    }
    
    // ... initWindow 保持不变 ...
};

运行与调试

将上述代码整合到你的 main.cpp 中并运行。

cpp 复制代码
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>

#include <iostream>
#include <stdexcept>
#include <cstdlib>

const uint32_t WIDTH = 800;
const uint32_t HEIGHT = 600;

class HelloTriangleApplication {
public:
    void run() {
        initWindow();
        initVulkan();
        mainLoop();
        cleanup();
    }

private:
    GLFWwindow* window;

    VkInstance instance;

    void initWindow() {
        glfwInit();

        glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
        glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);

        window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
    }

    void initVulkan() {
        createInstance();
    }

    void mainLoop() {
        while (!glfwWindowShouldClose(window)) {
            glfwPollEvents();
        }
    }

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

        glfwDestroyWindow(window);

        glfwTerminate();
    }

    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;

        VkInstanceCreateInfo createInfo{};
        createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
        createInfo.pApplicationInfo = &appInfo;

        uint32_t glfwExtensionCount = 0;
        const char** glfwExtensions;
        glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

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

        createInfo.enabledLayerCount = 0;

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

int main() {
    HelloTriangleApplication app;

    try {
        app.run();
    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}
  • 成功: 依然是一个黑框口,而且程序关闭时没有报错(退出代码为 0)。这意味着 Instance 创建成功,并且被正确销毁了。

  • 失败: 如果抛出了 "failed to create instance!" 异常,通常是因为显卡驱动不支持 Vulkan,或者在某些旧的集成显卡上需要更新驱动。

总结

我们今天完成了 Vulkan 开发中最基础的一步:

  1. 理解了 VkApplicationInfoVkInstanceCreateInfo 结构体。

  2. 使用 vkCreateInstance 创建了句柄。

  3. 使用 vkDestroyInstance 进行了资源清理。

虽然看起来还在原地踏步(还是那个黑窗口),但在后台,你的程序已经成功加载了 Vulkan 驱动并准备好大干一场了。


下一步预告

如果你现在写错了代码(比如传了空指针),程序会直接崩溃,没有任何提示。这在开发中是无法接受的。

下一篇,我们将引入 Vulkan 的守护神 ------ 校验层 (Validation Layers)。它会像严厉的老师一样,在你犯错时详细地告诉你错在哪里,而不是让你对着崩溃的程序发呆。

详见:Instance - Vulkan Tutorial

相关推荐
博学的轮船Y2 小时前
绕过Windows 11安装限制,Rufus带给你“奇迹”,低配电脑的春天
windows·资讯
seasonsyy2 小时前
3.虚拟机中安装Win7系统遇到问题及解决
windows·操作系统·vmware·虚拟机
SunkingYang3 小时前
QT如何读取csv文件
c++·qt·csv·读取文件
CoderCodingNo3 小时前
【GESP】C++六级考试大纲知识点梳理, (2) 哈夫曼树、完全二叉树与二叉排序树
开发语言·c++
水饺编程3 小时前
第4章,[标签 Win32] :获取设备环境句柄的第一个方法
c语言·c++·windows·visual studio
skywalk81633 小时前
Windows突然弹窗报错:AndrowsStore.exe 系统错误由于找不到libcef.dll,无法继续执行代码。据说重新安装程序可能会解决此问题
windows
老四啊laosi3 小时前
[C++初阶] 9. STL--string使用(二)
c++
SunkingYang3 小时前
QT中如何使用QMessageBox 实现提示、警告、错误报告和用户决策功能
c++·qt·提示·错误·告警·用法·qmessagebox
Once_day3 小时前
CC++八股文之内存
c语言·c++