跳到主要内容

程序框架

基本结构

我们从本章开始学习使用Vulkan API编写代码,基本的代码框架如下:

#include <vulkan/vulkan.h>

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

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

private:
void initVulkan() {

}

void mainLoop() {

}

void cleanup() {

}
};

int main() {
HelloTriangleApplication app;

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

return EXIT_SUCCESS;
}

我们首先包含Vulkan API的头文件,它为提供了Vulkan API函数、结构体、枚举的声明,stdexcept和iostream头文件用来打印信息和捕获异常,cstdlib头文件用来使用EXITSUCCESS和EXIT_FAILURE宏。

程序被包装为一个类,将Vulkan对象作为类的私有成员。主要步骤:

  1. 使用initVulkan函数来初始化Vulkan对象;
  2. 初始化完成后,进入mainLoop主循环进行渲染操作;
  3. 关闭窗口时,跳出mainLoop循环;
  4. 使用cleanup函数清理资源。

如果在程序执行过程中发生错误,会抛出std::runtime_error异常,我们在main函数捕获异常,并将异常信息打印到控制台。为了处理各种异常,我们使用std::exception来捕获异常,一个比较常见的异常就是请求的扩展不支持。

在后续的章节中,我们会逐步添加新对象到这个类中,在initVulkan函数中进行初始化,并在cleanup函数中进行清理。

资源管理

使用malloc分配的内存需调用free释放,同理,创建的Vulkan对象在不需要时也要显式地删除。现代C++可以使用RAll或<memory>提供的智能指针自动进行资源管理,本教程为了让大家更清晰地了解Vulkan对象的创建、删除过程和它们的生命周期,选择手动管理资源。Vulkan的一个核心思想就是明确地定义每个操作以避免错误。

学完本教程后,读者可以将RAII应用到自己的程序中,使用std::shared_ptr实现资源的自动管理,但对于学习而言,最好是能清晰地理解每个细节。

Vulkan对象可以通过类似vkCreateXXX或vkAllocateXXX函数创建。当对象不再使用时,使用vkDestroyXXX或vkFreeXXX函数进行删除。这些函数的参数因不同类型的对象而异,但都有一个pAllocator参数,我们可以通过它来自定义内存分配器的回调函数。但本教程中没有使用它,直接设置为nullptr。

集成GLFW

Vulkan可以在没有窗口的情况下使用,即离屏渲染。一般情况还是需要一个窗口来显示渲染结果。接下来,我们学习窗口的相关操作。

首先替换代码中的#include <vulkan/vulkan.h>为:

#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>

上面的代码将GLFW库包含进来,而GLFW库会自动包含Vulkan库头文件。接着添加一个initWindow函数来初始化GLFW,并在run函数中调用它:

void run() {
initWindow();
initVulkan();
mainLoop();
cleanup();
}

private:
void initWindow() {

}

initWIndow调用了glfwInit来初始化GLFW库,由于GLFW库最初是为OpenGL设计的,所以我们需要显式地设置GLFW,告诉它不要创建OpenGL上下文:

glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);

处理窗口大小的变化需要特别小心,我们会在以后介绍它,暂时先禁止改变窗口大小:

glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);

接着,添加了一个GLFWwindow* window变量存储创建的窗口句柄:

window =glfwCreateWindow(800, 600, "Vulkan", nullptr, nullptr);

glfwCreateWindow函数的前三个参数指定了窗口的宽度、高度和标题,第四个参数指定在哪个显示器上打开窗口,最后一个参数与OpenGL相关,对vulkan没有意义。

写死窗口大小不是一个好习惯,我们定义了两个常量,方便以后修改:

const int WIDTH = 800;
const int HEIGHT = 600;

现在,initWindow函数变成这样:

void initWindow() {
glfwInit();

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

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

为了确保程序在没有错误或窗口没有关闭的情况下可以一直运行,我们在mainLoop函数中添加下面的事件循环:

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

上面的代码循环检查事件,检测是否点击了窗口关闭按钮,如果点击了就结束循环。之后的章节,我们会在这个循环中调用渲染函数来渲染一帧画面。

一旦窗口关闭,就可以结束GLFW,然后在cleanup函数中销毁创建的资源:

void cleanup() {
glfwDestroyWindow(window);

glfwTerminate();
}

现在我们就有了一个可以使用Vulkan API的窗口程序框架,下面让我们创建第一个Vulkan对象吧!

本章节代码:C++代码