目录
General structure
在上一章中,您创建了一个具有所有正确配置的Vulkan项目,并使用示例代码对其进行了测试。在本章中,我们将从以下代码开始:
#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;
}
我们首先包含了LunarG SDK中的Vulkan标头,它提供了函数、结构和枚举。stdexcept和iostream标头用于报告和传播错误。cstdlib标头提供EXIT_SUCCESS和EXIT_FAILURE宏。
程序本身被封装到一个类中,我们将在其中存储Vulkan对象作为私有类成员,并添加函数来初始化每个对象,这将从initVulkan函数调用。一切准备就绪后,我们进入主循环开始渲染帧。我们将填充mainLoop函数,以包含一个循环,该循环将迭代直到窗口立即关闭。一旦窗口关闭并且mainLoop返回,我们将确保释放清理函数中使用的资源。
如果在执行过程中发生任何类型的致命错误,那么我们将抛出一个std::runtime_error异常和一条描述性消息,该消息将传播回主函数并打印到命令提示符。为了处理各种标准异常类型,我们捕捉了更一般的std::exception。我们将很快处理的一个错误示例是发现某个必需的扩展不受支持。
大致上,本章之后的每一章都将添加一个从initVulkan调用的新函数,以及一个或多个新Vulkan对象到需要在清理结束时释放的私有类成员。
Resource management
就像分配给malloc的每个内存块都需要调用释放一样,我们创建的每个Vulkan对象都需要在不再需要时显式销毁。在C++中,可以使用RAII或<memory>头中提供的智能指针执行自动资源管理。然而,在本教程中,我选择了明确Vulkan对象的分配和释放。毕竟,Vulkan的优势是明确每一个操作以避免错误,所以明确对象的生命周期以了解API的工作原理是很好的。
遵循本教程后,您可以通过编写C++类来实现自动资源管理,这些C++类在构造函数中获取Vulkan对象,并在析构函数中释放它们,或者根据所有权要求,为std::unique_ptr或std::shared_ptr提供自定义删除器。RAII是大型Vulkan项目的推荐模式,但出于学习目的,了解幕后情况总是很好的。
Vulkan对象要么直接使用vkCreateXXX等函数创建,要么通过另一个具有vkAllocateXXX等函数的对象分配。确保某个对象不再在任何地方使用后,您需要使用对应的vkDestroyXXX和vkFreeXXX销毁它。对于不同类型的对象,这些函数的参数通常有所不同,但它们都共享一个参数:pAllocator。这是一个可选参数,允许您为自定义内存分配器指定回调。我们将在教程中忽略此参数,并始终传递nullptr作为参数。
Integrating GLFW
如果你想将Vulkan用于屏幕外渲染,那么它在不创建窗口的情况下工作得非常好,但实际显示一些东西会让你兴奋得多!首先将#include<vulkan/vulkan.h>行替换为
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
这样,GLFW将包含自己的定义并自动加载Vulkan标头。添加initWindow函数,并在其他调用之前从run函数添加对它的调用。我们将使用该函数初始化GLFW并创建一个窗口。
void run() {
initWindow();
initVulkan();
mainLoop();
cleanup();
}
private:
void initWindow() {
}
initWindow中的第一个调用应该是glfwInit(),它初始化GLFW库。因为GLFW最初设计用于创建OpenGL上下文,所以我们需要告诉它不要在后续调用中创建OpenGL上下文:
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
因为处理调整大小的窗口需要特别小心,我们稍后将对此进行研究,所以现在使用另一个窗口提示调用禁用它:
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
现在剩下的就是创建实际的窗口。添加GLFWwindow*窗口;私有类成员,以存储对它的引用,并使用以下命令初始化窗口:
window = glfwCreateWindow(800, 600, "Vulkan", nullptr, nullptr);
前三个参数指定窗口的宽度、高度和标题。第四个参数允许您选择指定要打开窗口的监视器,最后一个参数仅与OpenGL相关。
使用常量而不是硬编码的宽度和高度数字是一个好主意,因为我们将在将来多次引用这些值。我在HelloTriangleApplication类定义上方添加了以下行:
const uint32_t WIDTH = 800;
const uint32_t HEIGHT = 600;
并将窗口创建调用替换为
window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
您现在应该有一个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();
}
}
这段代码应该是不言自明的。它循环并检查事件,如按下X按钮,直到用户关闭窗口。这也是我们稍后将调用函数来渲染单个帧的循环。
一旦窗口关闭,我们需要通过销毁它并终止GLFW本身来清理资源。这将是我们的第一个清理代码:
void cleanup() {
glfwDestroyWindow(window);
glfwTerminate();
}
当您现在运行程序时,应该会看到一个名为Vulkan的窗口出现,直到关闭窗口终止应用程序。现在我们有了Vulkan应用程序的框架,让我们创建第一个Vulkan对象!