程序框架
基本结构
我们从本章开始学习使用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对象作为类的私有成员。主要步骤:
- 使用initVulkan函数来初始化Vulkan对象;
- 初始化完成后,进入mainLoop主循环进行渲染操作;
- 关闭窗口时,跳出mainLoop循环;
- 使用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++代码