[譯]Vulkan教程(04)基礎代碼
General structure 通用結構
In the previous chapter you've created a Vulkan project with all of the proper configuration and tested it with the sample code. In this chapter we're starting from scratch with the following code:
在之前的章節你已經創建了一個Vulkan項目,配置好了項目屬性,用示例代碼進行了測試。本章我們將從零開始寫代碼,先從下面的代碼開始:
1 #include <vulkan/vulkan.h> 2 3 #include <iostream> 4 #include <stdexcept> 5 #include <functional> 6 #include <cstdlib> 7 8 class HelloTriangleApplication { 9 public: 10 void run() { 11 initVulkan(); 12 mainLoop(); 13 cleanup(); 14 } 15 16 private: 17 void initVulkan() { 18 19 } 20 21 void mainLoop() { 22 23 } 24 25 void cleanup() { 26 27 } 28 }; 29 30 int main() { 31 HelloTriangleApplication app; 32 33 try { 34 app.run(); 35 } catch (const std::exception& e) { 36 std::cerr << e.what() << std::endl; 37 return EXIT_FAILURE; 38 } 39 40 return EXIT_SUCCESS; 41 }
We first include the Vulkan header from the LunarG SDK, which provides the functions, structures and enumerations. The stdexcept
and iostream
headers are included for reporting and propagating errors. The functional
headers will be used for a lambda functions in the resource management section. The cstdlib
header provides the EXIT_SUCCESS
and EXIT_FAILURE
macros.
首先,我們include了Vulkan頭文件,它來自LunarG SDK,他提供函數、結構體和枚舉。stdexcept
和iostream
頭文件用於報告和傳播錯誤。functional
頭文件用於資源管理小節中要介紹到的lambda函數。cstdlib
頭文件提供了EXIT_SUCCESS
和EXIT_FAILURE
宏。
The program itself is wrapped into a class where we'll store the Vulkan objects as private class members and add functions to initiate each of them, which will be called from the initVulkan
function. Once everything has been prepared, we enter the main loop to start rendering frames. We'll fill in the mainLoop
function to include a loop that iterates until the window is closed in a moment. Once the window is closed and mainLoop
returns, we'll make sure to deallocate the resources we've used in the cleanup
function.
這個程序封裝了一個class,我們將Vulkan對象保存為private成員,添加了初始化它們的函數,在initVulkan
函數中調用這些初始化函數。一旦一切就緒,我們就進入主循環,開始渲染幀。我們將在mainLoop
函數中循環,直到窗口關閉為止。一旦窗口關閉,mainLoop
返回時,我們將確保在cleanup
函數中銷毀使用過的資源。
If any kind of fatal error occurs during execution then we'll throw a std::runtime_error
exception with a descriptive message, which will propagate back to the main
function and be printed to the command prompt. To handle a variety of standard exception types as well, we catch the more general std::exception
. One example of an error that we will deal with soon is finding out that a certain required extension is not supported.
如果執行過程中發生任何error,那么我們將拋出一個std::runtime_error
異常和描述性消息,它會被傳回main
函數,打印到控制台。為了處理各種標准異常類型,我們捕捉更泛型的std::exception
。我們很快就要處理的一個error的例子是,發現某個要求的擴展不被支持。
Roughly every chapter that follows after this one will add one new function that will be called from initVulkan
and one or more new Vulkan objects to the private class members that need to be freed at the end in cleanup
.
粗略地說,本章之后的每篇教程都會添加被initVulkan
函數調用的新函數,以及添加1個或多個Vulkan對象(作為private成員),這些成員最后都要在cleanup
函數中被釋放。
Resource management 資源管理
Just like each chunk of memory allocated with malloc
requires a call to free
, every Vulkan object that we create needs to be explicitly destroyed when we no longer need it. In modern C++ code it is possible to do automatic resource management through the utilities in the <memory>
header, but I've chosen to be explicit about allocation and deallocation of Vulkan objects in this tutorial. After all, Vulkan's niche is to be explicit about every operation to avoid mistakes, so it's good to be explicit about the lifetime of objects to learn how the API works.
正如每塊用malloc
申請的內存都需要用free
釋放,當我們不需要它們的時候,我們創建的每個Vulkan對象都需要顯式地被銷毀。在現代C++代碼中,可以用<memory>
頭文件實現自動化的資源管理,但在本教程中我選擇了顯式地申請和銷毀Vulkan對象。畢竟,Vulkan的利基是,無論什么操作都是顯式的,以避免錯誤。所以顯式地處理對象的生命周期,對於學習這個API的工作方式是有益的。
After following this tutorial, you could implement automatic resource management by overloading std::shared_ptr
for example. Using RAII to your advantage is the recommended approach for larger Vulkan programs, but for learning purposes it's always good to know what's going on behind the scenes.
學習完本教程后,你可以實現自動資源管理(例如通過std::shared_ptr
)。對於大型的Vulkan程序,使用 RAII是一種推薦方式,但是為了學習,知道場面背后在發生什么,總是好的。
Vulkan objects are either created directly with functions like vkCreateXXX
, or allocated through another object with functions like vkAllocateXXX
. After making sure that an object is no longer used anywhere, you need to destroy it with the counterparts vkDestroyXXX
and vkFreeXXX
. The parameters for these functions generally vary for different types of objects, but there is one parameter that they all share: pAllocator
. This is an optional parameter that allows you to specify callbacks for a custom memory allocator. We will ignore this parameter in the tutorial and always pass nullptr
as argument.
Vulkan對象要么是用vkCreateXXX
這樣的函數直接創建,要么是用vkAllocateXXX
這樣的函數通過其他對象申請。在確定一個對象不會再被使用后,你需要用對應的vkDestroyXXX
或vkFreeXXX
函數銷毀它。不同對象的這些函數的參數區別很大,但是它們都有一個共同的參數:pAllocator
。這是一個可選參數,它允許你標識一個回調函數,用於以自定義的方式分配內存。本教程中我們將忽略這個參數,總是傳給它nullptr
。
Integrating GLFW 集成GLFW
Vulkan works perfectly fine without a creating a window if you want to use it off-screen rendering, but it's a lot more exciting to actually show something! First replace the #include <vulkan/vulkan.h>
line with
如果你想用Vulkan搞離屏渲染,那么不用創建窗口,Vulkan也可以工作得很好。但是顯示點東西才剛令人興奮!首先用下述代碼替換#include <vulkan/vulkan.h>
。
1 #define GLFW_INCLUDE_VULKAN 2 #include <GLFW/glfw3.h>
That way GLFW will include its own definitions and automatically load the Vulkan header with it. Add a initWindow
function and add a call to it from the run
function before the other calls. We'll use that function to initialize GLFW and create a window.
這樣,GLFW將包含它自己的定義,且自動加載Vulkan頭文件。添加initWindow
函數,在run
函數調用其他函數前,先調用它。我們將用這個函數初始化GLFW,並創建窗口。
1 void run() { 2 initWindow(); 3 initVulkan(); 4 mainLoop(); 5 cleanup(); 6 } 7 8 private: 9 void initWindow() { 10 11 }
The very first call in initWindow
should be glfwInit()
, which initializes the GLFW library. Because GLFW was originally designed to create an OpenGL context, we need to tell it to not create an OpenGL context with a subsequent call:
initWindow
函數中第一個函數調用應該是glfwInit()
,它初始化GLFW庫。因為GLFW最初是被設計為創建OpenGL上下文而用的,我們需要在接下來的函數調用中告訴它不要創建OpenGL上下文:
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
Because handling resized windows takes special care that we'll look into later, disable it for now with another window hint call:
因為處理大小可變的窗口要消耗更多精力,我們稍后再談它,現在暫且禁用它:
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
All that's left now is creating the actual window. Add a GLFWwindow* window;
private class member to store a reference to it and initialize the window with:
現在剩下的就是創建實際的窗口了。添加private成員GLFWwindow* window;
,以保存對窗口的引用。用下述代碼初始化窗口:
window = glfwCreateWindow(800, 600, "Vulkan", nullptr, nullptr);
The first three parameters specify the width, height and title of the window. The fourth parameter allows you to optionally specify a monitor to open the window on and the last parameter is only relevant to OpenGL.
前3個參數標明窗口的寬度、高度和標題。第4個參數允許你可選地標明在哪個顯示器上打開窗口,最后一個參數只與OpenGL有關。
It's a good idea to use constants instead of hardcoded width and height numbers because we'll be referring to these values a couple of times in the future. I've added the following lines above the HelloTriangleApplication
class definition:
用常量代替硬編碼的寬度和高度數值是個好主意,因為我們將多次引用這些數據。我在類定義之前添加了下述2行:
1 const int WIDTH = 800; 2 const int HEIGHT = 600;
and replaced the window creation call with
用下述代碼代替了創建窗口的函數調用:
window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
You should now have a initWindow
function that looks like this:
現在你應該有個長這樣的initWindow
函數:
1 void initWindow() { 2 glfwInit(); 3 4 glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 5 glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); 6 7 window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); 8 }
To keep the application running until either an error occurs or the window is closed, we need to add an event loop to the mainLoop
function as follows:
為了讓app持續運行,直到發生錯誤或窗口關閉,我們需要在mainLoop
函數中添加一個事件循環,如下代碼所示:
1 void mainLoop() { 2 while (!glfwWindowShouldClose(window)) { 3 glfwPollEvents(); 4 } 5 }
This code should be fairly self-explanatory. It loops and checks for events like pressing the X button until the window has been closed by the user. This is also the loop where we'll later call a function to render a single frame.
這個代碼是不言自明的。它循環檢查事件(例如點擊X按鈕),直到窗口被用戶關閉為止。在這個循環里,我們稍后會調用另一個函數來渲染一幀。
Once the window is closed, we need to clean up resources by destroying it and terminating GLFW itself. This will be our first cleanup
code:
一旦窗口被關閉,我們需要銷毀資源,關閉GLFW。下面是我們最初的cleanup
代碼:
1 void cleanup() { 2 glfwDestroyWindow(window); 3 4 glfwTerminate(); 5 }
When you run the program now you should see a window titled Vulkan
show up until the application is terminated by closing the window. Now that we have the skeleton for the Vulkan application, let's create the first Vulkan object!
運行此程序,現在你可以看到一個標題為Vulkan
的窗口顯示出來,直到你通過關閉按鈕終結它。既然我們有了Vulkan程序的骨架,我們來創建第一個Vulkan對象吧!
C++ code 完整C++代碼
- Previous上一章
- Next下一章