[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对象了。