VulkanAPI架構

Vulkan API架構及詳述

一、API架構

下圖是Vulkan中主要的組件以及它們之間的關係:
在這裏插入圖片描述

1.1 Device

Device很好理解,一個Device就代表着一個你係統中的物理GPU。它的功能除了讓你可以選擇用來渲染(或者計算)的GPU以外,主要功能就是爲你提供其他GPU上的資源,例如所有要用到顯存的資源,以及接下來會提到的Queue和Synchronization等組件。
在這裏插入圖片描述

1.2 Pipeline

第二個主要的組件,比Device複雜很多,就是Pipeline。一個Pipeline包含了傳統API中大部分的狀態和設定。只不過Pipeline是需要事先創建好的,這樣所有的狀態組合的驗證和編譯都可以在初始化的時候完成,運行時不會再因爲這些操作有任何性能上的浪費。但正是因爲這一點,如果你不同的Pass需要不同的狀態,你需要預先創造多個不同的Pipeline。然而我們不能把所有渲染需要的信息全都prebake進pipeline中,一個Pipeline應該是可以通過綁定不同的資源而複用的。接下來介紹的幾個組件就可以被動態的綁定給任何Pipeline。
在這裏插入圖片描述

1.3 Buffer

接下來是Buffer。Buffer是所有我們所熟悉的Vertex Buffer, Index Buffer, Uniform Buffer等等的統稱。而且一個Buffer的用途非常多樣。在Vulkan中需要特別注意Buffer是從什麼類型的內存中分配的,有的類型CPU可以訪問,有的則不行。有的類型會在CPU上被緩存。現在這些內存的類型是重要的功能屬性,不再只是對驅動的一個提示了。
在這裏插入圖片描述

1.4 Image

Image在Vulkan中代表所有具有像素結構的數組,可以用於表示紋理,Render Target等等。和其他組件一樣,Image也需要在創建的時候指定使用它的模式,例如Vulkan裏有參數指定Image的內存Layout,可以是Linear,也可以是Tiled Linear便於紋理Filter。如果把一個Linear layout的Image當做紋理使用,在某些平臺上可能導致嚴重的性能損失。類似傳統的API,紋理本身並不直接綁定給Pipeline。需要讀取和使用Image則要依賴於ImageView。
在這裏插入圖片描述

1.5 Binding Resources

講了幾種不同類型的內存, 但是內存是從什麼地方分配的呢?在Vulkan中,所有內存都分配與一個指定的Heap。一個Device也許支持幾種不同類型的Heap,有些也許可以分配Mappable的內存,有些不行。具體的類型取決於程序運行的平臺。值得注意的是,Vulkan Heap分配的內存和最終的Vulkan組件例如Buffer和Image直接可以不,也不應該是一對一的映射。一段內存可以分配成數段,並且分配給不同的資源使用。某種程度上這樣的資源複用也是Vulkan基本的設計哲學之一。
在這裏插入圖片描述
上面提到,Buffer和Image可以動態的綁定給任意Pipeline。而具體綁定的規則就是由Descriptor指定。和其他組件一樣,Descriptor Set也需要在被創建的時候,就由App指定它的固定的Layout,以減少渲染時候的計算量。Descriptor Set Layout可以指定綁定在指定Descriptor Set上的所有資源的種類和數量,以及在Shader中訪問它們的索引。App可以定義多個不同的Descriptor Set Layout,所以如何爲你的程序或者引擎設計Descriptor Set的Layout將是優化的重要一環。當然,程序也可以擁有多個指定Layout的Descriptor Set。因爲Descriptor Set是預先創建並且無法更改的,所以改變一個綁定的資源需要重新創建整個Descriptor Set,但改變一個資源的Offset可以非常快速的在綁定Descriptor Set的時候完成。一會我會討論如何利用這一點來實現高效的資源更新。
在這裏插入圖片描述

1.6 Command Buffer

介紹了那麼多組件,都是渲染需要的數據。那麼Command Buffer就是渲染本身所需要的行爲。在Vulkan裏,沒有任何API允許你直接的,立即的像GPU發出任何命令。所有的命令,包括渲染的Draw Call,計算的調用,甚至內存的操作例如資源的拷貝,都需要通過App自己創建的Command Buffer。Vulkan對於Command Buffer有特有的Flag,讓程序制定這些Command只會被調用一次(例如某些資源的初始化),亦或者應該被緩存從而重複調用多次(例如渲染循環中的某個Pass)。另一個值得注意的是,爲了讓驅動能更加簡易的優化這些Command的調用,沒有任何渲染狀態會在Command Buffer之間繼承下來。每一個Command Buffer都需要顯式的綁定它所需要的所有渲染狀態,Shader,和Descriptor Set等等。這和傳統API中,只要你不改某個狀態,某個狀態就一直不會變,這一點很不一樣。
在這裏插入圖片描述

1.7 Command Buffer Pool

Command Buffer Pool是一個需要注意的多線程相關的組件。它是Command Buffer的父親組件,負責分配Command Buffer。Command Buffer相關的操作會對其對應的Command Buffer Pool裏造成一定的工作,例如內存分配和釋放等等。因爲多個線程會並行的進行Command Buffer相關的操作,這個時候如果所有的Command Buffer都來自同一個Command Buffer Pool的話,這時Command Buffer Pool內的操作一定要在線程間被同步。所以這裏建議每個線程都有自己的Command Buffer Pool,這樣每個線程纔可以任意的做任何Command Buffer相關的操作。
在這裏插入圖片描述
Command Buffer Pool的另一個性質就是支持非常高效的重置。一旦重置,所有由當前Pool分配的Command Buffer都會被清零,並且不會有任何內存管理上的碎片。所以程序只要爲每一個幀和線程的組合分配一個Command Buffer Pool,就可以利用這一點,在更新Round Robin中的Command Buffer時非常快速的將需要的Buffer清零。
在這裏插入圖片描述

1.8 Descriptor Pool

另一個類似Command Buffer Pool的組件,就是Descriptor Pool。所有Descriptor Set都由Descriptor Pool分配,Descriptor Set操作會導致對應的Descriptor Pool工作而且需要線程間同步,並且Descriptor Pool也支持非常高效的將所有由當前Pool分配的Descriptor Set一次性清零。所以程序應該爲每個線程分配一個Descriptor Pool,可以根據Descriptor Set的更新頻率,創建不同的Descriptor Pool,例如每幀、每場景等等。

1.9 Queue

最後一個關鍵組件, Queue,是Vulkan中唯一給GPU遞交任務的渠道。Vulkan將Queue設計成了完全透明的對象,所以在驅動裏沒有任何其他的隱藏Queue,也不會有任何的Synchronization發生。在Vulkan中,給GPU遞交任務不再依賴於任何所謂的綁定在單一線程上的Context,Queue的API極其簡單,你向它遞交任務(Command Buffer),然後如果有需要的話,你可以等待當前Queue中的任務完成。這些Synchronization操作是由Vulkan提供的各種同步組件完成的。例如Samaphore可以讓你同步Queue內部的任務,程序無法干預。Fence和Event則可以讓程序知道某個Queue中指定的任務已經完成。所有這些組件組合起來,使得基於Command Buffer和Queue遞交任務的Vulkan非常易於編寫多線程程序。後文會簡單討論一些常見的多線程模式。最後,和前面提到的一樣,Queue不光接收圖形渲染的調用,也接受計算調用和內存操作。
在這裏插入圖片描述

二、相關API簡述

2.1 設備初始化

2.1.1 Instance --> GPU --> Device

Instance表示具體的Vulkan應用。在一個應用程序中可以創建多個實例,這些實例之間相互獨立,互不干擾。

當調用API創建Vulkan實例的時候,Vulkan SDK內部會經由驅動裝載器(loader)查找可用的GPU設備。

創建Vulkan實例需要兩個輸入信息:

  • 應用程序的信息
  • 內存分配回調函數

Vulkan通過用戶輸入的內存分配器來分配內存。

創建好Instance,就可以用Instance枚舉所有可用的Vulkan GPU設備。

有了GPU設備,就可以獲取具體GPU的信息。如果系統中安裝了多個GPU設備,就可用GPU信息比較GPU設備之間的兼容性等。

找到了合適的GPU後,就可以通過GPU創建設備示例。

2.2、繪製

2.2.1 Queues

有了設備,就可以創建命令隊列。命令隊列是與設備綁定的,不能跨設備使用。

隊列封裝了圖形、計算、直接內存訪問功能,獨立調度、異步等調度操作。

2.2.2 Command Buffer

有了設備,就可以創建命令緩衝。命令緩衝是繪製命令的批次集合。

用戶可創建任意多的命令緩衝,支持在多線程中創建。

Command佔用的內存是通過Command Buffer內存池動態分配,不需要指定內存池的大小。

2.2.3 Command

在命令緩衝區中可以創建多個命令。多個命令完成批次即命令緩衝後,可以重複利用。

這裏有點像OpenGL裏面的NameList。

Command Buffer的操作使用pipeline barrier區分。barrier可以等待和觸發事件。

注:

  1. 這裏可以看出Command被包裝在Command Buffer中,當把Command
    Buffer提交給Queue中後,Queue中執行的是Command Buffer中的Command。
  2. Command Buffer和Queue的類別需要匹配,否則不能正確執行,但一個Command
    Buffer並不會跟任何Queue有聯繫。也就意味着,一個Command Buffer可以被提交給多個Queue,只要他們的類別匹配。

2.2.4 Shaders

使用設備創建Shader。

同樣支持多線程。

2.2.5 Pipeline

渲染管線同樣需要設備創建。

渲染管線狀態包括:Shaders,混合、深度、剔除、模版狀態等。

另外Vulkan提供了API保存和加載渲染管線的狀態。

2.2.6 Descriptors

Vulkan資源都用descriptor表示, descriptor分成descriptor set,descriptor set從descriptor pool分配。

每個descriptor set都有個layout佈局,佈局是在pipeline創建的時候確定的。layout在descriptor set 和pipeline之間共享,並且必須匹配。

pipeline可以切換使用相同layout的descriptor set。

多個不同layout的set可以組成鏈在一個pipeline中使用。

2.2.7 Render Pass

Render Pass表示一幀的某個階段,包含了繪製過程中的很多信息,包括:

  • Layout和framebuffer attachment的類型

-Render Pass在開始和結束的時候該做什麼

-Render Pass影響framebuffer的區域-分塊渲染和延遲渲染需要的信息

2.2.8 Drawing

Draws位於Render Pass內,在Command Buffer的上下文中執行。支持多種繪製類型:基於索引的和非索引的,直接的和間接的等

3.1、多線程支持

3.1.1 同步

使用事件同步任務,可以設置、重置、查詢和等待事件。

Command Buffer執行完成後可以觸發事件

3.1.2 任務隊列

任務在設備所屬的隊列中執行,準備好的Command buffer送到隊列中執行。

隊列保留內存,驅動不負責管理內存,同樣由程序管理。

隊列可以觸發事件,等待信號量。

4.1、呈現

4.1.1 Presentation

展現就是如何在屏幕上顯示圖片。

可顯示的資源使用與framebuffer綁定的“圖片”展現,由與平臺相關的模塊創建,即所謂的窗口系統接口WSI。

WSI用了列舉系統的顯示設備和視頻模式,全屏,控制顯示垂直同步等。

Presenta跟命令緩衝一起進入隊列。

5.1、資源創建和釋放

資源包括CPU資源和GPU資源。

CPU資源通過vkCreate創建,GPU資源由vkAllocMemory創建,由vkBindObjectMemory與CPU對象綁定。

應用程序負責Vulkan對象的析構,需要保證順序。Vuilkan資源沒有引用計數機制,都需要顯式釋放。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章