Vulkan基礎(chǔ)學(xué)習(xí)及總結(jié)
對于WebGPU來講,學(xué)習(xí)Vulkan是非常好的提升,可以幫助你更好的理解現(xiàn)代GPU的設(shè)計思路和架構(gòu),我對基礎(chǔ)概念和流程進行了總結(jié)。
1.介紹
1.1 Vulkan及其演化史
著名的OpenGL API問世已經(jīng)差不多四分之一個世紀(jì),而且它還在 不斷發(fā)展。本質(zhì)上來說,OpenGL是一個純粹的狀態(tài)機,其中包含了若 干個開關(guān)量,可以設(shè)置為開/關(guān)的狀態(tài)(on/off)。這些狀態(tài)數(shù)據(jù)被用來構(gòu)建設(shè)備中的依賴映射關(guān)系,對資源進行管理,并通過最優(yōu)的方法進行控制以達(dá)到性能的最大化。
這種狀態(tài)機可以隱式地自動化資源管理,但是它對應(yīng)用程序邏輯 的解讀不夠智能化,而應(yīng)用程序正是資源管理背后的驅(qū)動力所在。其產(chǎn)生的結(jié)果可能是用戶無法預(yù)測的,比如實現(xiàn)中斷,導(dǎo)致著色器代碼 被重新編譯,但是應(yīng)用程序并不需要系統(tǒng)這么做。此外,OpenGL API 也會受到其他因素的限制,例如不可預(yù)測的程序行為、多線程的擴展 性、渲染的故障等。后續(xù)會將OpenGL與Vulkan API進行比較來理 解兩者之間的各種差異。 Khronos于2016發(fā)布了革命性的新架構(gòu)Vulkan API,它充分利用了 現(xiàn)代圖形處理器單元的優(yōu)勢,來實現(xiàn)高性能圖形和計算應(yīng)用程序的開 發(fā)。
Khronos是一個會員制的社區(qū)和專注于發(fā)布開放標(biāo)準(zhǔn)和免費API的 組織。更多信息請參閱網(wǎng)站:https://www.khronos.org。
Vulkan的原始概念是由AMD基于它們的私有Mantle API設(shè)計和實現(xiàn) 的。這個API已經(jīng)在幾款不同的游戲中體現(xiàn)了自己的先進特性,它有著 革命性的實現(xiàn)方案,完全滿足了工業(yè)界的競爭性需求。AMD開源了自己 的Mantle API并且貢獻給Khronos組織。在多家硬件和軟件供應(yīng)商的協(xié) 同幫助下,Khronos發(fā)布了Vulkan標(biāo)準(zhǔn)。
Vulkan并不是目前唯一的新一代3D圖形API,它還有多家不同的競 爭者,例如Microsoft的Direct-X 12和Apple的Metal。不過,DirectX只能用于不同的Windows系統(tǒng),而Metal只能用在Mac系統(tǒng)(OS X和 iOS)。因此,Vulkan得以脫穎而出。它的跨平臺特性可以支持所有現(xiàn) 存的OS平臺,其中已經(jīng)包括了Windows(XP、Vista、7、8、10)、 Linux、Tizen、SteamOS和Android。
?
1.2 Vulkan與OpenGL的對比
Vulkan相比OpenGL有了不少的新特性和性能提升,如下所示。
降低驅(qū)動負(fù)載和CPU的使用量:Vulkan在設(shè)計上更為接近于底層 的圖形硬件。因此,它可以向應(yīng)用程序的開發(fā)者提供更為直接的控制權(quán)力,來操作宿主機上的計算資源,使用GPU來盡可能高效地完成渲染工作。這一特性使得相關(guān)軟件可以直接訪問圖形處理器,從而達(dá)到 更好的性能要求。
多線程的可擴展性:OpenGL中的多線程擴展能力是非常有限的,所以很難利用多線程的各種優(yōu)勢來管理CPU資源。不過,Vulkan在設(shè)計時特別考慮了終端用戶對于多線程功能的迫切需求,并且通過非常透明的方式予以支持(并不會隱含任何的全局狀態(tài)變化)。不同線 程下的任務(wù)、任務(wù)的創(chuàng)建過程,以及任務(wù)提交執(zhí)行的過程之間都是完 全獨立的,不存在數(shù)據(jù)耦合。
顯式的API定義:OpenGL的API是隱式的,資源管理的工作交給 驅(qū)動層去完成。驅(qū)動層負(fù)責(zé)讀取應(yīng)用程序端的提示參數(shù)并跟蹤資源的 處理,這樣帶來了很多不必要的負(fù)擔(dān)。
Vulkan采用了顯式的API定義,驅(qū)動并不負(fù)責(zé)資源以及資源之間相互關(guān)系的管理。這些工作由應(yīng)用程序處理。這種清晰的實現(xiàn)方式更容易預(yù)測,驅(qū)動層也不需要在用戶場景的后面偷偷做資源管理的小動 作了(這正是OpenGL的弊端)。這樣的結(jié)果是,用戶任務(wù)的處理可以 直截了當(dāng)?shù)匾粤魉€的方式完成,從而獲得最佳性能和可預(yù)測的行為 模式。
預(yù)編譯的中間級著色語言:OpenGL需要使用OpenGL著色語言 (GLSL)源代碼的形式來實現(xiàn)著色器,而Vulkan使用可移植的標(biāo)準(zhǔn)化 中間級語言(SPIR-V)作為中間級語言標(biāo)準(zhǔn),為并行計算和圖形處理 提供了著色器支持,其他源代碼語言的編譯器(例如GLSL、HLSL或者LLVM)必 須將SPIR-V作為輸出的目標(biāo)語言,并且提供工具來實現(xiàn)SPIR-V輸入數(shù) 據(jù)的支持。Vulkan可以讀取這種能夠馬上執(zhí)行的二進制中間數(shù)據(jù),并且 在著色器執(zhí)行階段直接使用。
驅(qū)動層和應(yīng)用程序?qū)樱篛penGL中的應(yīng)用程序?qū)酉啾闰?qū)動層而言 要單薄得多,因為驅(qū)動層會自動完成資源管理和狀態(tài)跟蹤的工作。 Vulkan與之相反。它的驅(qū)動層更為接近硬件底層,負(fù)載較小。而應(yīng)用程 序?qū)有枰?fù)責(zé)邏輯、資源和狀態(tài)的管理。圖1-2給出了這兩個API各自的 驅(qū)動層和應(yīng)用程序?qū)哟a的總量對比。

內(nèi)存的控制:Vulkan暴露了系統(tǒng)當(dāng)中的多種不同類型的內(nèi)存接口,交由應(yīng)用開發(fā)者去選擇適合自己的內(nèi)存類型,實現(xiàn)各種資源的管理和使用。與之相反,OpenGL是通過驅(qū)動層的內(nèi)部處理機制來完成資源存儲的,不同的供應(yīng)商可能因此有完全不同的實現(xiàn),并且當(dāng)驅(qū)動層改變了資源的存儲位置時。很可能產(chǎn)生預(yù)期之外的內(nèi)存碎片,或者降低存儲效率。
可預(yù)測行為:Vulkan與OpenGL相比,其行為具有很高的可預(yù)測性,它不會在渲染時產(chǎn)生任何延遲或者抖動。用戶任務(wù)傳遞到驅(qū)動層之后會被立即提交,而OpenGL的任務(wù)提交過程不是立即完成的,它需 要等待驅(qū)動層再去進行調(diào)度。?
單一的API:OpenGL有多個獨立的版本,包括桌面端的 API(OpenGL)和嵌入式系統(tǒng)的API(OpenGL ES)。Vulkan相比之下 更為清晰,它只提供了單一的API接口來面對所有類型的系統(tǒng)平臺。 Vulkan會優(yōu)先支持移動平臺,這一點與OpenGL也是不一樣的。 OpenGL通常會優(yōu)先實現(xiàn)某個功能的桌面端版本,然后再將它更新到 OpenGL ES API當(dāng)中。
直接訪問GPU:Vulkan暴露了自己的底層功能和硬件特性,從而給應(yīng)用層用戶提供了大量的控制手段。它提供了多種不同類型的物理設(shè)備、內(nèi)存類型、指令緩存隊列,以及功能擴展。這樣的模式確保軟 件層更接近實際硬件的特性。
1.3 重要術(shù)語
? ? ?在開始深入學(xué)習(xí)基礎(chǔ)知識之前,我們首先了解一些Vulkan中的重要術(shù)語。
物理設(shè)備(physical device)與設(shè)備(device):一臺計算機系統(tǒng)中可能包含了不止一個支持Vulkan的物理硬件設(shè)備。我們所說的物理設(shè)備表示一個獨立的設(shè)備,而設(shè)備指的是該物理設(shè)備在應(yīng)用程序中的邏輯表示。
隊列(queue):隊列表示執(zhí)行引擎與應(yīng)用程序之間的接口。一個物理設(shè)備總是包含了一個或者多個隊列(圖形、計算、DMA/傳輸,等等)。隊列的職責(zé)是收集準(zhǔn)備執(zhí)行的工作(指令緩存)并且分發(fā)到物理設(shè)備執(zhí)行。
內(nèi)存類型(memory type):Vulkan暴露了多種內(nèi)存類型。廣義上來說,總共有兩種類型的內(nèi)存:宿主內(nèi)存和設(shè)備內(nèi)存。之后會討論這些內(nèi)容。
指令(command):每個指令中都可以執(zhí)行一些用戶行為。指令從廣義上可以劃分為動作、狀態(tài)設(shè)置,以及同步。
動作指令(action command):包括繪制圖元、清除表面、復(fù)制緩存、查詢/時間戳操作,以及子通道的開始/結(jié)束操作。這些指令可以修改幀緩存附件、讀取或者寫入內(nèi)存(緩存或者圖像),以及寫入查詢池。
狀態(tài)設(shè)置指令(set state command):這些指令可以用來綁定流水線、描述字集合以及緩存,或者設(shè)置一個動態(tài)狀態(tài),以及渲染通道/ 子通道的狀態(tài)。
同步指令(synchronization command):同步指令用于處理兩個或者更多動作指令同時發(fā)生的情況,此時指令之間可能會爭奪資源或者依賴于某些內(nèi)存。該指令用來設(shè)置同步事件或者等待事件、插入流水線屏障對象,以及渲染通道/子通道的依賴。
指令緩存(command buffer):指令緩存是一組指令的集合,它可以記錄多個指令并統(tǒng)一發(fā)送到隊列中。
? ? ?下面我們將從總體上了解Vulkan的工作模型和一些基礎(chǔ)概念。我們還會嘗試?yán)斫庵噶畹恼Z法規(guī)則,并通過直接閱讀API列表的方式簡單了解所有的API指令。
?

?
?1.4?對象生命周期與指令語法
Vulkan當(dāng)中的對象是根據(jù)應(yīng)用程序的邏輯需求顯式地創(chuàng)建和銷毀 的,應(yīng)用程序需要自己管理這些對象。
Vulkan的對象需要使用Create指令創(chuàng)建,以及使用Destroy指令銷 毀:
Create語法:對象的創(chuàng)建需要通過vkCreate*指令完成,它需要一 個Vk*Createinfo結(jié)構(gòu)體作為輸入?yún)?shù)。
? ? ? ? ???(結(jié)構(gòu)體:C語言的概念,允許用戶自己建立不同類型數(shù)據(jù)組成的組合型數(shù)據(jù)結(jié)構(gòu),它稱為結(jié)構(gòu)體)
Destroy語法:使用Create指令創(chuàng)建的對象總是需要使用 vkDestroy*指令銷毀。
如果對象是作為已有的對象池或者堆的一部分創(chuàng)建的,那么需要 使用Allocate指令創(chuàng)建,并使用Free指令從池或者堆中銷毀。
? ? ? ? ? ?(對象池顧名思義,就是存放一堆對象的池)
Allocate語法:一個對象如果是作為對象池的一部分創(chuàng)建,那么需要使用vkAllocate*指令,并且需要一個Vk*AllocateInfo作為輸入?yún)?shù)。
Free語法:已創(chuàng)建的對象需要使用vkFree*指令從對象池或者內(nèi)存中釋放。
上述所有的實現(xiàn)方法都可以通過vkGet*指令輕松獲取。而使用了 vkCmd*形式的API接口主要用于把指令記錄到指令緩存當(dāng)中。
?
1.5 理解Vulkan應(yīng)用程序
