操作系統:Windows8.1
顯卡:Nivida GTX965M
開發工具:Visual Studio 2017
General structure
在上一節中,我們創建了一個正確配置、可運行的的Vulkan應用程序,並使用測試代碼進行了測試。本節中我們從頭開始,使用如下代碼構建一個基於GLFW的Vulkan應用程序原型框架的雛形。
#include <vulkan/vulkan.h> #include <iostream> #include <stdexcept> #include <functional> 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::runtime_error& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; }
首先從LunarG SDK中添加Vulkan頭文件,它提供了購機愛你Vulkan應用程序需要的函數、結構體、和枚舉。我們包含stdexcept和iostream頭文件用於拋出異常信息,而functional頭文件用於資源管理部分支持lambda表達式。
程序被封裝到一個類中,該類結構將會存儲Vulkan私有成員對象,並添加基本的函數來初始化他們。首先會從initVulkan函數開始調用。當一切准備好,我們進入主循環開始渲染幀。我們將會加入mainLoop函數包含loop循環調用,該循環調用直到GLFW窗體管理才會停止。當窗體關閉並且mainLoop返回時,我們需要釋放我們已經申請過的任何資源,該清理邏輯在cleanup函數中去定義。
程序運行期間,如果發生了任何嚴重的錯誤異常,我們會拋出std::runtime_error 並注明異常描述信息,這個異常信息會被main函數捕獲及打印提示。很快你將會遇到一個拋出error的例子,是關於Vulkan應用程序不支持某個必要的擴展功能。
基本上在之后的每一個小節中都會從initVulkan函數中增加一個新的Vulkan函數調用,增加的函數會產生Vulkan objects 並保存為類的私有成員,請記得在cleanup中進行資源的清理和釋放。
Resource management
我們知道通過malloc分配的每一個內存快在使用完之后都需要free內存資源,每一個我們創建的Vulkan object不在使用時都需要明確的銷毀。在C++中可以利用<memory> 完成 auto 資源管理,但是在本節中,選擇明確編寫所有的內存的分配和釋放操作,其主要原因是Vulkan的設計理念就是明確每一步操作,清楚每一個對象的生命周期,避免可能存在的未知代碼造成的異常。
當然在本節之后,我們可以通過重載std::shared_ptr來實現auto 資源管理。對於更大體量的Vulkan程序,建議遵循RAII的原則維護資源的管理。
Vulkan對象可以直接使用vkCreateXXX系函數創建,也可以通過具有vkAllocateXXX等功能的一個對象進行分配。確保每一個對象在不使用的時候調用vkDestroyXXX和vkFreeXXX銷毀、釋放對應的資源。這些函數的參數通常因不同類型的對象而不同,但是他們共享一個參數:pAllocator。這是一個可選的參數,Vulkan允許我們自定義內存分配器。我們將在本教程忽略此參數,始終以nullptr作為參數。
Integrating GLFW
如果我們開發一些不需要基於屏幕顯示的程序,那么純粹的Vulkan本身可以完美的支持開發。但是如果創建一些讓人興奮的可視化的內容,我們就需要引入窗體系統GLFW,並將#include <vulkan/vulkan.h> 進行相應的替換。
#define GLFW_INCLUDE_VULKAN #include <GLFW/glfw3.h>
在新版本的GLFW中已經提供了Vulkan相關的支持,詳細的使用建議參閱官方資料。
通過替換,將會使用GLFW對Vulkan的支持,並自動加載Vulkan的頭文件。在run函數中添加一個initWindow函數調用,並確保在其他函數調用前優先調用。我們將會通過該函數完成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);
前三個參數定義窗體的寬度、高度和Title。第四個參數允許制定一個監聽器來打開窗體,最后一個參數與OpenGL有關,我們選擇nullptr。
使用常量代替硬編碼寬度和高度,因為我們在后續的內容中會引用該數值多次。在HelloTriangleApplication類定義之上添加以下幾行:
const int WIDTH = 800; const int 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(); } }
這段代碼應該很容易看懂。它循環並檢查GLFW事件,直到按下X按鈕,或者關閉窗體。該循環結構稍后會調用渲染函數。
一旦窗體關閉,我們需要通過cleanup函數清理資源、結束GLFW本身。
void cleanup() { glfwDestroyWindow(window); glfwTerminate(); }
運行程序,我們應該會看到一個名為Vulkan的白色窗體,直到關閉窗體終止應用程序。
ok,到現在我們已經完成了一個Vulkan程序的骨架原型,在下一小節我們會創建第一個Vulkan Object!
獲取工程代碼 GitHub checkout