Vulkan教程 - 18 階段性總結

Vulkan學習幾周了,稍微整理下。由於一開始的博客就是從環境搭建開始的,所以並沒有對Vulkan的特性和教程的目標及步驟進行記錄。這裏主要就是做這個工作,所以這個總結並不是對Vulkan高屋建瓴的總結心得,暫時還沒這麼厲害。

Vulkan簡介

簡介

一句話來說,Vulkan是一個跨平臺2D和3D圖形接口,由Khronos Group(中文名爲柯羅諾斯,是古希臘神話中的原始神)集團在2015年發佈。

這裏要稍微介紹下Khronos Group,它成立於2000年,致力於發展開發開放標準的應用程序接口,免費授權的移動設備API,實現多樣化平臺及設備上的高質量多媒體創作。該組織最爲廣泛接納的標準有OpenGL及OpenGL ES等,爲桌面及移動設備圖形開發提供了有力支持。

特性

回到Vulkan上來,它的主要特性有:

1)跨平臺支持,包括Windows、Linux及安卓等,對比下微軟的DirectX只能運行在Windows平臺,蘋果的Metal也是僅限iOS和OS X;

2)充分發揮多核與多線程CPU性能;

3)驅動更加簡單,從而降低CPU開銷。對比下如OpenGL/OpenGL ES的驅動很複雜,總是開啓錯誤檢查,會帶來額外的CPU開銷,這個可以看下圖:

4)提供分層架構,使得驅動執行時的正確性驗證和調試信息可以按需加載。在良好驗證的應用情況下,我們可以節省掉錯誤檢測和調試代碼執行的開銷;

5)Vulkan運行時不需要處理整個shader代碼,而是處理SPIR-V中間代碼,從而節省shader編譯鏈接時間;

6)Vulkan不負責內存管理,多線程管理和併發訪問,這些工作轉移給應用程序負責,而Vulkan只專注於提供GPU功能給應用。

7)統一的接口。Vulkan支持不同平臺和不同GPU產品,應用鏈接的是一樣的圖形庫,以用的是一樣的頭文件。無論應用運行在哪裏,只需要編寫同一份代碼,不再像OpenGL一樣需要定義Windows/Android/Linux等相關的預處理宏來處理操作系統相關的代碼。另外,Vulkan仍然保留了OpenGL時代就存在的Extension擴展功能。這是因爲GPU廠商的架構確實沒法完全統一,特殊功能的實現仍然需要擴展接口來支持。

8)高效的接口。圖形程序的性能低下有很大比例是因爲CPU的瓶頸,而其原因一般有兩個,一是OpenGL ES驅動本身CPU開銷過大,這包括驅動內部的GPU/CPU的同步等待,另一個是過多的draw call調用。

本章主要參考:

http://imgtec.eetrend.com/d6-imgtec/news/2016-01/7067.html

使用場景

一般來說,可以通過以下三種方式使用Vulkan:

1)應用程序直接調用Vulkan接口來訪問GPU實現圖形渲染和通用計算,在這種場景下面應用可以獲得最大的靈活性和對GPU的控制權,但是其難度也是最高的。

2)第二種應用場景是應用程序通過工具庫來調用Vulkan,而不是直接使用Vulkan接口。工具庫對Vulkan接口做一個包裝,使得應用程序更易於使用。這種工具庫的一種實現方式就是直接把Vulkan包裝成OpenGL或者OpenGL ES接口。例如Imagination就計劃提供這樣的工具庫,使得利用OpenGL ES的老應用程序也能順利得在Vulkan接口上面運行,而不需要更改任何代碼。

3)第三種應用場景就是應用程序直接調用遊戲引擎。因爲應用程序不需要考慮如何用Vulkan實現,所以應用程序的實現比較簡單。而專業的遊戲引擎廠商負責優化對Vulkan的調用,使得應用可以獲得Vulkan帶來的性能提升。

多線程支持 

Vulkan作爲一個直接提供GPU硬件功能的接口而不負責多線程渲染的調度以及線程併發訪問的安全保護等工作,意味着Vulkan接口內部不再像OpenGL ES一樣有過度的線程同步,CPU/GPU同步等操作。這些操作會爲接口的運行效率帶來不可預測的影響。每個獨立的線程都可以並行地生成渲染的command buffer,這些command buffer最後會被提交給一個統一的command queue,GPU會按照command buffer提交的順序來執行。任何需要跨線程訪問的資源,都需要應用自己保證訪問的一致性。

Vulkan的shading language環境

Vulkan不再使用GLSL作爲輸入,而是使用一個統一的中間代碼SPIR(Standard, Portable Intermediate Representation)。GLSL會被解釋器翻譯成SPIR表示,這樣Vulkan驅動就可以專注於shader後端優化,而不是前端語法解析和語義分析等。

Vulkan工具架構 

Vulkan提供靈活的架構可以使應用按照需求來啓用驅動的錯誤檢測和調試信息。如果應用是一個驗證良好的程序,那麼應用就直接使用Vulkan接口來訪問GPU,不做任何的錯誤檢測和調試,這可以節約大量的CPU開銷,提升應用的性能。反之,應用可以通過Vulkan提供的Debug Layer和Validation Layer來實現錯誤檢測和調試信息的輸出。這使得應用在開發調試時可以獲得大量的有用信息。一旦應用發佈又可以去除掉這些CPU開銷,使得性能提升。

Vulkan和傳統圖形API編程的區別

傳統圖形 API 和全新低級別API(Vulkan)之間有何區別?OpenGL等高級別API使用起來非常簡單。開發人員只需聲明操作內容和操作方式,剩下的都由驅動程序來完成。驅動程序檢查開發人員是否正確使用API調用、是否傳遞了正確的參數,以及是否充分準備了狀態。如果出現問題,將提供反饋。爲實現其易用性,許多任務必須由驅動程序在“後臺”執行。

在低級別API中,開發人員需要負責完成大部分任務。他們需要符合嚴格的編程和使用規則,還必須編寫大量代碼。但這種做法是合理的,開發人員知道他們的操作內容和希望實現的目的,但驅動程序不知道。因此使用傳統API時,驅動程序必須完成更多工作,以便程序正常運行。採用Vulkan等API可避免這些額外的工作。 因此DirectX 12、Metal或Vulkan 也被稱爲精簡驅動程序/精簡API。大部分時候它們僅將用戶請求傳輸至硬件,僅提供硬件的精簡抽象層。爲顯著提升性能,驅動程序幾乎不執行任何操作。

低級別API要求應用完成更多工作。但這種工作是不可避免的,必須要有人去完成。因此由開發人員去完成更加合理,因爲他們知道如何將工作分成獨立的線程,圖像何時成爲渲染對象(顏色附件)或用作紋理/採樣器等等。開發人員知道管道處於何種狀態,或哪些頂點屬性變化更頻繁。 這樣有助於提高顯卡硬件的使用效率。最重要的原因是它行之有效,我們能夠觀察到顯著的性能提升。

但“能夠”一詞非常重要。它要求完成其他工作,但同時也是一種合適的方法。在有一些場景中,我們將觀察到,OpenGL和Vulkan之間在性能方面沒有任何差別。如果不需要多線程化,或應用不是CPU密集型(渲染的場景不太複雜),使用OpenGL即可,而且使用 Vulkan不會實現性能提升(但可能會降低功耗,這對移動設備至關重要)。 但如果我們想最大限度地發揮圖形硬件的功能,Vulkan將是最佳選擇。

該部分參考:

https://software.intel.com/zh-cn/articles/api-without-secrets-introduction-to-vulkan-preface

直觀對比OpenGL ES和Vulkan:

 另外是麒麟960芯片對Vulkan的測試:

Vulkan示例分析

環境搭建

主要是Windows上,下載Vulkan SDK,下載GLFW(Graphics Library Framework),下載GLM(OpenGL Mathematics),配置VS,驗證Vulkan擴展。

目標及步驟

繪製三角形需要哪些步驟?

  1. 實例和物理設備選取; 
  2. 邏輯設備和隊列族;
  3. 窗口表面和交換鏈;
  4. 圖像視圖和幀緩衝;
  5. 渲染通道;
  6. 圖形管線;
  7. 命令池和命令緩衝
  8. 主循環

步驟解析 

第一步就是要建立Vulkan實例,這也是Vulkan應用程序的起始。通過描述你的應用程序和你將要使用什麼API擴展來創建實例,之後查詢Vulkan支持的硬件並選擇一個或多個VkPhysicalDevice。

第二步就要傳將邏輯設備VkDevice了,此時你要更詳細描述你需要使用哪些VkPhysicalDevice特性,比如多視口渲染和64位浮點數等。還要指定使用哪些隊列族。大多數的Vulkan操作,如繪製命令和內存操作,都是提交到VkQueue中異步執行的。隊列是從隊列族中分配出來的,每個隊列族支持一組特定操作。例如,圖形、計算和內存轉移操作可能有着單獨的隊列族。隊列族的可用性也可以用作物理設備選取的一個顯著因素。可能一個支持Vulkan的設備卻不提供圖形功能,但是當前階段的顯卡支持Vulkan的都基本支持了我們所需的隊列操作。

第三步就是創建窗口和呈現渲染好的圖像了。這需要兩個組件,一個窗口表面(VkSurfaceKHR)和一個交換鏈(VkSwapchainKHR)。KHR後綴表示這些對象是Vulkan擴展的一部分。Vulkan API本身是跨平臺的,表面則是要渲染到的窗口的跨平臺抽象,通常用一個原生窗口句柄引用來實例化,例如Windows平臺上就是HWND。

交換鏈就是一批渲染目標,它的基本目的就是保證我們當前渲染的圖像和已經在屏幕上的那個是不一樣的。保證只顯示完整圖像時很重要的,每次我們想要繪製一幀的時候都要向交換鏈請求提供一個圖像。當我我們完成一幀的繪製後就將圖像返回給交換鏈以便它在某個時候呈現到屏幕上。渲染目標個數以及將完成的圖像呈現到屏幕上所需的條件依賴於呈現模式。常見的呈現模式有雙緩衝(垂直同步)和三緩衝。

第四步,爲了繪製從交換鏈獲取的圖像,將它包裝成VkImageView和VkFramebuffer。圖像視圖引用了要用的圖像的某個特殊部分,幀緩衝引用了用於顏色、深度和模板目標的圖像視圖。因爲交換鏈中可能有很多不同圖像,我們預先爲每個圖像創建一個圖像視圖和一個幀緩衝,然後在繪製的時候選擇正確的那個。

第五步,Vulkan的渲染通道描述了渲染操作中用的圖像的類型,以及如何使用它們,如何對待其內容。對於這個教程的三角形程序來說,我們就告訴Vulkan我們將使用單個圖像作爲顏色對象,且要在繪製操作之前被清空爲一個純色。然而渲染通道只描述了圖像類型,VkFramebuffer實際上會向這些槽綁定特定的圖像。

第六步,Vulkan的圖形管線通過創建一個VkPipeline對象來建立,它描述了顯卡的可配置狀態,比如視口大小和深度緩衝操作以及使用VkShaderModule對象時的可編程狀態。VkShaderModule對象是從着色器代碼創建的,我們會通過引用渲染通道指定。

Vulkan和現有API相比最顯著的特性是它的所有圖形管線配置都要提前設置。這意味着如果你想要切換到一個不同的着色器,或者稍微改變頂點佈局,你都要完全重建圖形管線。這也意味着你要爲所有你想要的渲染操作的不同組合提前建立許多VkPipeline對象。只有一些基礎配置,如視口大小和顏色清除等可以動態改變。所有的狀態都要明確描述,比如,連默認顏色混合狀態都沒有。

好消息是,因爲你相當於在做提前編譯而不是即時編譯,驅動就會有更多的優化機會,運行時也更容易得到可預測的表現,因爲像切換到不同圖形管線這也的大型狀態改變都是明確設定好的。

第七步,命令池和命令緩衝。就和之前提到的一樣,Vulkan中許多的操作像繪製命令等都要提交到一個隊列中。這些操作首先需要記錄到VkCommandBuffer,然後才能提交。這些命令緩衝是從VkCommandPool分配的,VkCommandPool又和一個特定隊列族相關聯。爲了繪製一個簡單的三角形,我們要用如下操作來記錄一個命令緩衝:

開始渲染通道;

綁定圖形管線;

繪製三個頂點;

結束渲染通道。

因爲幀緩衝中的圖像依賴於交換鏈到底給我們哪一個圖像,我們要爲每個可能的圖像記錄命令緩衝並在繪製的時候選擇正確的那個。

第八步就是主循環了。現在繪製命令已經包裝成一個命令緩衝,主循環就比較直白了。首先用vkAcquireNextImageKHR從交換鏈獲取圖像,然後爲該圖像選擇合適的命令緩衝,用vkQueueSubmit執行。最終,返回圖像到交換鏈以便用vkQueuePresentKHR呈現到屏幕。

提交到隊列的操作是異步執行的,因此我們必須用類似信號量的同步對象來確保正確的執行順序。繪製命令緩衝的執行一定要等待圖像獲取完成,否則可能會在渲染的時候用了一個還在讀取用於展示到屏幕上的圖像。vkQueuePresentKHR調用需要等待渲染完成,要使用第二個信號量來標記渲染完成。

最後再總結一下,繪製一個三角形要做的工作如下:

創建一個VkInstance;

選擇一個支持的顯卡(VkPhysicalDevice);

創建一個VkDevice和一個VkQueue來繪製和呈現;

創建一個窗口,窗口表面和交換鏈;

將交換鏈圖像包裝到VkImageView;

創建一個渲染通道來指定渲染目標和用法;

爲渲染通道創建幀緩衝;

建立圖形管線;

用繪製命令爲每個可能的交換鏈圖像分配和記錄命令緩衝;

通過獲取圖像來繪製幀,提交正確的繪製命令緩衝並返回圖像到交換鏈。

代碼演進

這裏是做分享的時候講述的,就是教程如何通過各個章節添加不同內容,如何將三角形的硬編碼改進,引入頂點緩衝,繪製矩形及引入MVP等,所以這裏就不再敲一遍了。

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