[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头文件vulkan/vulkan.h,引入了函数、结构体和枚举。stdexcept和iostream用来使用异常和打印日志。cstdlib引入EXIT_SUCCESS和EXIT_FAILURE。
我们将程序封装为一个类,将Vulkan对象作为其私有成员,同时创建一些函数初始化它们,在initVulkan中调用它。所有都准备好后,我们就进入主循环来渲染画面。我们在mainLoop中创建一个无限循环,直到窗口退出。窗口退出,mainLoop返回之后,我们使用cleanup函数来释放资源。
运行时发生任何异常,我们就抛出一个附带描述性信息的std::runtime_error异常。它会被main函数收到并打印出来。我们用std::exception接受类型更全面的异常类型。我们很快就会接触到一个异常的例子,它在程序未找到需要的扩展时产生。
资源管理
像malloc和free一样,每个我们创建的Vulkan对象,在不需要时都需要被显式销毁。在C++中,你可以使用RAII机制或memory提供的智能指针来完成资源管理工作。不过,在本教程中,我们选择显式分配或销毁Vulkan对象,毕竟,Vulkan重要的特性就是显式操作以避免出错。所以,明白Vulkan对象的生命周期对于学习API如何工作非常有利。
本教程之后,你可以实现自动的内存管理机制。比如,编写自定义类型,在构造函数时获取Vulkan对象,在析构函数时释放对象。又比如,你可以使用std::unique_ptr或std::shared_ptr。这取决与你的需求。RAII是大型Vulkan程序的推荐模型。不过对于学习而言,了解幕后发生的事情总是有好处的。
Vulkan对象要么是通过vkCreateXXX直接创建的,要么是通过其它对象使用vkAllocateXXX分配的。当一个对象不再使用后,你应该使用对应的vkDestroyXXX或vkFreeXXX销毁它。这些函数的参数一般是不同的,但它们都有一个共同的参数:pAllocator。它是一个可选的参数,它允许你自定义内存分配器。在本教程中,我们会忽略它并始终传入nullptr。
集成GLFW
如果您想将 Vulkan 用于离屏渲染,则无需创建窗口即可完美运行。但实际显示内容更让人兴奋。我们将#include <vulkan/vulkan.h>替换为:
#define GLFW_INCLUDE_VULKAN#include <GLFW/glfw3.h>12
这样,GLFW会引入自己的定义和Vulkan的头文件。添加一个initWindow函数并在run中调用。我们会用这个函数初始化GLFW并创建一个窗口。
void run() {initWindow();initVulkan();mainLoop();cleanup();}private:void initWindow() {}
在initWindow中,我们首先调用glfwInit()来初始化GLFW库。GLFW一开始是用来创建OpenGL上下文的,我们需要告诉它不要这样做:
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);1
改变窗口大小需要我们有一些特别的操作,之后会讨论,我们现在先禁止窗口改变大小:
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);1
接下来就是创建一个实际的窗口。添加一个私有成员变量GLFWwindow* window;保存窗口指针,初始化:
window = glfwCreateWindow(800, 600, "Vulkan", nullptr, nullptr);1
参数依次是窗口的宽、高、名称和显示器,最后一个参数和OpenGL有关系。
我们最好是建些常量来保存窗口宽高,因为它们之后还会被用到。在HelloTriangleApplication类前定义:
const uint32_t WIDTH = 800;const uint32_t HEIGHT = 600;12
创建窗口改为:
window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);1
现在,initWindow函数大概是这样的:
void initWindow() {glfwInit();glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);}12345678
程序应一直保持运行,除非发生错误或窗口被关闭。我们需要添加一个事件循环到mainLoop:
void mainLoop() {while (!glfwWindowShouldClose(window)) {glfwPollEvents();}}12345
这段代码不言自明。它检查诸如键盘事件等直到窗口被用户关闭。这里也是我们调用绘制一帧函数的地方。
窗口关闭之后,我们需要清理资源,结束GLFW,现在的cleanup是这样的:
void cleanup() {glfwDestroyWindow(window);glfwTerminate();}12345
现在运行程序的话,会显示一个叫Vulkan的窗口。程序会一直运行,直到窗口关闭。现在,Vulkan程序的基本结构已经有了,接下来就是创建第一个Vulkan对象了。
