原文地址 Vulkan-tutorial 。
Origin of Vulkan
Vulkan 根據現代顯卡的特點,從頭開始設計,從而解決了這些問題。它通過讓編程人員使用更多的API來明確自己的意圖,從而減輕驅動的負擔,並且支持多線程和並行的提交命令。它將Shader代碼轉換成字節碼(byte code format)解決了不同廠商間Shader不兼容的問題。最後一點,它承認了當代顯卡的計算能力,並將圖形( graphics )和計算( compute )功能集成到了一個API中。
What it takes to draw a triangle
讓我們來大致看一下Vulkan是如何一步一步來渲染一個三角形的。以下幾個概念在後續章節裏還會詳細介紹,這裏只是想讓你對各個概念之間的聯繫有個宏觀的瞭解。
Step 1 - Instance and physical device selection
Vulkan 通過創建VkInstance 引入API來開始整個Vulkan 應用。在創建Instance時,你可以配置你的應用和將來要使用的API擴展。Instance 創建之後,就可以獲取平臺上Vulkan所支持的硬件,然後從中選擇一個或多個VkPhysicalDevice來使用,每個的device ,你都可以獲取它的屬性(properties)和能力(capbilities),比如VRAM 大小, 然後選擇一個適合你需求的device。 比如,你想要一個專用顯卡(dedicated graphics cards) 。Step 2 - Logical device and queue families
獲取你想要的硬件設備(hardware device)後,就可以通過 VkPhysicalDeviceFeatures來描述你所需要的顯卡特性,像多視圖渲染( multi viewport rendering )和使用64bit的float等,然後根據這些特性創建VkDevice( logical device )。當然,也可以告訴VkDevice你想要使用何種隊列。Vulkan中的大多數操作,如繪畫命令和內存操作,都要提交到VkQueue中,在VkQueue中異步執行這些命令。隊列從隊列家族(queue families)中分配,每一種隊列支持一組特定的命令或操作。比如,可能存在一些不同種類的隊列,它們分別支持圖形操作、計算操作、內存轉移操作。隊列的這種特點也可以成爲你你選擇VkPhysicalDevice 的依據。Vulkan可能會支持一些不具有圖形操作的顯卡,不過請放心,目前Vulkan支持的顯卡基本上都已支持你感興趣的各種操作。Step 3 - Window surface and swap chain
除非你只想離線渲染( offline rendering ) ,否則就必須創建一個將渲染結果顯示到屏幕上的窗口(window)。 你可以使用本地平臺的API來創建window ,或者使用像GLFW和SDL這樣的庫。在這篇教程中我們選擇GLFW,這一點你將在後續的教程中看到。我們還需要另外兩個組件才能對window進行渲染: 一個window surafce ( VkSurfaceKHR )和一個 swap chain( VkSwapChainKHR ), 注意KHR後綴表示這些對象是Vulkan的擴展。Vulkan是跨平臺的,這也是爲什麼我們要使用WSI(Window system interface )擴展 來和窗口管理器(Window manager)進行交互。Surface 是對window 的一個抽象,通常他需要window 的引用來創建,比如windows上的HWND ,幸運的是GLFW的內置函數能夠自動爲我們解決不同平臺間的差異。
Swap chain是渲染目標的一個集合,它最簡單的功能就是:保證正在渲染的image 和 現在顯示在屏幕上的image 是兩個不同的image。保證image渲染完畢後才能進行顯示十分重要。每次我們想要畫一個幀時,都必須從swap chain裏請求一個image 來渲染,繪畫完畢後,再將它返回到到swpa chain,以便在某個時間後顯示到屏幕上。渲染的目標數量以及渲染完畢後顯示到屏幕上的時機用present mode 來表示。常見的present mode有雙緩衝和三緩衝,我們將在創建swap chain時再詳細討論這個問題。
Step 4 - Image views and framebuffers
先使用VkImageView和VkFrameBuffer將image包裹起來,然後才能將內容畫到image上。imageView 引用一個image要被使用的特定部分,而framebuffer引用imageView ,把它當做color 、depth和stencil的目標使用。因爲swap chain裏可以有多個image ,所以我們先發制人:爲每一個image 創建一個imageView和framebuffer ,然後在繪畫階段選擇一個正確的來使用。Step 5 - Render passes
Render pass描述了在渲染階段要使用的image類型、如何使用以及如何處理image的內容。在我們的示例三角形應用中,我們告訴Vulkan,要使用一個image 作爲color的目標,並且希望它在繪畫操作前被塗成純色。請注意,Render pass只是描述要使用的image類型,而framebuffer( 通過綁定image )纔是要使用的image實體。Step 6 - Graphics pipeline
在Vulkan中Graphics Pipeline 通過創建VkPipeline對象來建立。它描述了一些顯卡不可編程部分的可配置狀態(configurable state ),比如viewport的大小和depth buffer操作等,以及用VkShaderModule表示的可編程部分。VkShaderModule對象用着色器的字節碼來創建。驅動需要知道哪些渲染目標將在pipeline中使用,而這些目標就是我們在Render pass中定義的image。Vulkan和現存的其他圖形API最顯著地區別就是:幾乎所有不可編程部分的配置都要在pipeline創建前提前完成。這就意味着如果你想換一個着色器(shader)或者僅僅改變一些頂點的佈局(vertex layout) ,那麼你必須重新創建pipeline 。這也意味着你必須提前創建很多pipeline,來應對渲染過程中不同組合的配置。只有很少的一些配置你可以動態改變,比如viewport 的大小和celar 的顏色等。Pipeline中所有的配置狀態你必須顯示的進行定義,比如,顏色混合就沒有爲你提供默認的配置。
它給我們帶來的一個好處就如同提前編譯和當場編譯一樣,驅動將有更多優化的機會和對運行時性能做更多的預測。
Step 7 - Command pools and command buffers
之前也提到,Vulkan中的命令(原文是operation )必須提交到對應的隊列才能執行。這些命令首先要記錄到VkCommandBuffer中,然後才能提交的到隊列。這些commandBuffer來自於一個commandPool,而CommandPool關聯一種具有特定命令的隊列。畫一個三角形,我們需要以下幾個步驟來記錄commandBuffer:
- 開始render pass
- 綁定graphics pipeline
- 畫三個頂點
- 結束render pass。
Step 8 - Main loop
現在繪畫命令已經放在CommandBuffer中, Main loop就變得非常簡單了。首先使用 vkAcquireNextImageKHR.從swap chain裏獲取一個image ,然後根據這個image選擇對應的commandBuffer ,使用vkQueueSubmit執行這個commandBuffer ,最後使用vkQueuePresentKHR將這個Image 返回到swap chain準備顯示。
隊列中的命令採用異步的方式來執行,因此我們必須採用像信號量(semaphore)這樣的同步對象,來確保程序執行的正確順序。繪畫操作必須在獲取image完成後才能進行,否則就會出現當前渲染和顯示共用一個image的情況。vkQueuePresentKHR必須等待渲染結束才能進行交替使用, 這就需要我們再使用一個semaphore來等待渲染結束。
Summary
通過上述大致的講解,想必你已經對畫三角形的過程有了基本的認識。然而我們真正的程序卻包含更多的步驟,像分配vertex buffer和uniform buffer 以及上傳紋理圖片等,我們將在後續的章節一一介紹它們。因爲vulkan具有相當陡峭的學習曲線,我們打算先從簡單入手。這裏我們將耍點小計倆,打算先把頂點座標硬編碼到Shader裏,而不是直接使用veretx buffer ,因爲管理vertex buffer 需要你先對command buffer 有一定了解。總之,爲了畫這個三角形,我們需要:
- 創建VkInstance。
- 選擇一個顯卡(VkPhysicalDevice)
- 爲繪畫和顯示創建一個VkDevice和VkQueue。
- 創建一個window、window surface 和 swap chain。
- 用image view 包裹swap chain裏的image。
- 創建一個render pass ,用它來定義渲染目標和目標的用法。
- 爲render pass 創建一個frameBuffer。
- 創建graphics pipeline。
- 爲每一個可能的swap chain image 繪畫命令分配和記錄command buffer。
- 通過獲取的image 來draw frame,提交正確的繪畫command buffer,最後將繪畫結果(image)返回到swap chain。
API concepts
Coding conventions
Vulkan的所有函數、枚舉變量和結構體都定義在vulkan.h裏,而且包含在Vulkan SDK中 。函數以小寫的vk爲前綴,枚舉類型和結構體以Vk爲前綴,而枚舉值以VK爲前綴。Vulkan API嚴重依賴結構體作爲函數參數。例如,對象的創建將遵循如下方式:<span style="font-size:18px;">VkXXXCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_XXX_CREATE_INFO;
createInfo.pNext = nullptr;
createInfo.foo = ...;
createInfo.bar = ...;
VkXXX object;
if (vkCreateXXX(&createInfo, nullptr, &object) != VK_SUCCESS) {
std::cerr << "failed to create object" << std::endl;
return false;
}</span>
在Vulkan中很多結構都要你定義它的類型sType。pNext 指向一個擴展的結構,在本教程中它總是nullptr。創建和銷燬對象的函數要求一個 VkAllocationCallbacks,我們也將它置位nullptr。
所有函數的返回值是一個 VkResult 類型的枚舉,它要麼是VK_SUCCESS表示成功,要麼是其他錯誤值。