Vulkan Overview

本節(jié)闡述vulkan一些大面上的東西,從某個(gè)角度上將屬于廢話。。。
vulkan的起源
vulkan的前輩openGL有幾個(gè)問題:
不同廠商兼容性不一樣(可能N卡能跑A卡就跑不了,都能跑同樣的代碼也可能顯示出來不一樣)
驅(qū)動性能差(浪費(fèi)CPU時(shí)間,不支持多線程)
難以查錯(cuò)
開發(fā)工具鏈比較簡陋
vulkan的出現(xiàn),就是為了解決上述的問題。而且vulkan還吸收了openCL,將openGL 4.3引入的計(jì)算著色器進(jìn)一步發(fā)展。就是說,vulkan和dx,metal一樣,既是圖形API,也是計(jì)算API,在靜等AI和AR技術(shù)的高潮期到來。
vulkan畫三角形的步驟
我接下來要簡述vulkan繪制三角形的各個(gè)步驟,這只是個(gè)概覽,每個(gè)步驟在對應(yīng)章節(jié)都會詳細(xì)闡述。
1 - Instance and physical device selection
Vulkan程序需要創(chuàng)建一個(gè)VkInstance來啟用vulkan API.在創(chuàng)建Instance時(shí)你需要準(zhǔn)確的描述你的應(yīng)用程序的一些屬性,以及你需要使用的各種API。在創(chuàng)建Instance之后,你需要查詢硬件對vulkan的支持,并選擇一個(gè)或者多個(gè)VkPhysicalDevices。 你會查詢各種硬件參數(shù),比如顯存和顯卡兼容性,來選取最理想的設(shè)備。舉個(gè)例子,相比集顯,咱們更喜歡獨(dú)顯(除了個(gè)別搞笑的亮機(jī)卡,不過那些支不支持vulkan都不好說呢,就我了解,N家亮機(jī)卡能支持vulkan的好像就開普勒架構(gòu)的GT730還有GT1030)。
2 - Logical device and queue families
在決定使用哪塊顯卡之后,你需要創(chuàng)建一個(gè)VkDevice (logical device),在創(chuàng)建過程中你還要更詳細(xì)的描述你用到的硬件特性(VkPhysicalDeviceFeatures),比如multi viewport rendering(該特性VR渲染非常有用)和64位浮點(diǎn)支持(科學(xué)計(jì)算和HDR都需要)。你還需要制定你要使用的queue families。 大部分Vulkan操作, 比如繪制命令和內(nèi)存/顯存操作, 都需要通過提交到VkQueue之后異步執(zhí)行。 Queue是從queue family分配的, 每個(gè)queue family中的Queue只支持某個(gè)特定集合的操作。舉個(gè)例子,對于顯卡,執(zhí)行圖形渲染、并行計(jì)算和內(nèi)存交換可能是不同的queue family。在選擇physical device時(shí)queue family的支持也是重要的參考之一。只支持科學(xué)計(jì)算不支持圖形渲染的顯卡有可能存在(事實(shí)上繪圖相關(guān)的queue family是vulkan的擴(kuò)張而非核心功能),但是這年頭支持vulkan但不支持繪圖的設(shè)備,其實(shí)也不多。
3 - Window surface and swap chain
除非你不想顯示圖形(比如你只想離屏渲染),不然你還是需要創(chuàng)建一個(gè)窗口來顯示的。你可以用各個(gè)平臺native的API(win32,xlib,xcb,mir,wayland),或者GLFW、SDL。在本教程中,使用GLFW。
如果真的想渲染到一個(gè)窗口,還需要兩樣?xùn)|西: window surface (VkSurfaceKHR
) 和 swap chain (VkSwapChainKHR
)。注意 KHR
后綴,有openGL開發(fā)經(jīng)驗(yàn)的同學(xué)可能清楚,這個(gè)后綴表示一個(gè)KHR擴(kuò)展。Vulkan API 是平臺無關(guān)的, 所以需要一個(gè)標(biāo)準(zhǔn)化的 WSI (Window System Interface) 擴(kuò)展來和窗口系統(tǒng)進(jìn)行交互。Surface是一個(gè)window渲染目標(biāo)的抽象,創(chuàng)建時(shí)需要一個(gè)native窗口的句柄(和openGL完全一樣)。幸運(yùn)的是,GLFW內(nèi)建的跨平臺的方法來幫我們解決平臺間的差異。
Swap chain是渲染一系列渲染目標(biāo)(render target)的集合。他本來是保證我們渲染的image和屏幕上顯示的image不是同一個(gè)(openGL雙緩沖模型,目的是減少渲染時(shí)屏幕閃爍)。但是除了經(jīng)典的雙緩沖模型,Vulkan引入了更高級的交換方式,來解決雙緩沖模型解決起來比較費(fèi)勁的東西(比如垂直同步)。在創(chuàng)建Swap chain的章節(jié)我們會更詳細(xì)的進(jìn)行討論。
4 - Image views and framebuffers
在從swap chain獲得image之后,我們應(yīng)該繪制它。為了繪制image,我們需要把這個(gè)image用 VkImageView 或者 VkFramebuffer進(jìn)行包裝。 Image view 指向被使用的 image , 而 framebuffer 指向被使用的 image view, color,在著色或者進(jìn)行深度/模板測試時(shí)都用得到。Swap chain中可能存在很多iamge,每個(gè)都需要創(chuàng)建image view還有framebuffer。在渲染時(shí),我們需要選擇正確的image進(jìn)行繪制。
5 - Render passes
對于有圖形開發(fā)經(jīng)驗(yàn)的同學(xué),Render pass應(yīng)該用了很多次了,任何高級渲染技術(shù),都不會少于兩個(gè)Render pass。而Bloom甚至需要5個(gè)Render pass。
在畫三角形的程序中,我們只需要一個(gè)image作為color target。
6 - Graphics pipeline
Vulkan中通過創(chuàng)建VkPipeline對象來創(chuàng)建graphics pipeline。它用來描述顯卡各個(gè)渲染階段的參數(shù)(用過dx的應(yīng)該熟悉一些,在vulkan里,它也是可配置的了)。比如viewport的長寬、如何使用深度緩沖,或者用VkShaderModule詳細(xì)的描述可編程階段的各個(gè)屬性。
vulkan和其他圖形API有個(gè)顯著區(qū)別,就是的幾乎所有配置都需要提前創(chuàng)建。即便是更換一個(gè)shader或者改變定點(diǎn)參數(shù)的布局(vertex layout),你都需要完全從新graphics pipeline創(chuàng)建一個(gè)graphics pipeline。這也意味著你需要創(chuàng)建大量的VkPipeline
對象,來覆蓋你渲染過程中的各種graphics pipeline變化。只有改變viewport或者改變clear color不需要重新生成graphics pipeline。
因?yàn)殇秩竟芫€各階段都提前準(zhǔn)備了,而不是運(yùn)行時(shí)靠驅(qū)動判斷。驅(qū)動可以更好的執(zhí)行優(yōu)化。而且在review代碼時(shí),也能更清楚打發(fā)現(xiàn)作者的意圖。
7 - Command pools and command buffers
前文中已經(jīng)提到了,vulkan中的各種操作,都需要靠提交到一個(gè)命令隊(duì)列(queue)的方式進(jìn)行異步執(zhí)行。而在提交到命令隊(duì)列之前,需要在VkCommandBuffer進(jìn)行記錄。命令緩沖和一個(gè)VkCommandPool關(guān)聯(lián),而VkCommandPool
又和queue family關(guān)聯(lián)。即便只畫一個(gè)簡單的三角形,我們也需要生成如下的command buffer:
Begin the render pass
Bind the graphics pipeline
Draw 3 vertices
End the render pass
因?yàn)閒ramebuffer中的image又swap chain決定,每個(gè)可能的iamge都需要一個(gè)command buffer,這就需要創(chuàng)建大量的command buffer。也可以沒幀重新生成command buffer,但是效率會低一些。
8 - Main loop
在command buffer準(zhǔn)備好了之后,主循環(huán)就簡單多了。我們先使用vkAcquireNextImageKHR
方法得到一個(gè)image。然后選擇合適的command buffer再使用vkQueueSubmit執(zhí)行。最好,使用vkQueuePresentKHR
方法想swap chain傳遞我們準(zhǔn)備在屏幕上顯示的image。
提交到命令隊(duì)列的命令是異步的。但是運(yùn)行是有順序的,而保證運(yùn)行順序,就是開發(fā)者的工作了。vulkan提供了用來同步的對象,比如semaphore。依靠此對象,可以判斷iamge是還在顯示過程中,還是已經(jīng)顯示完整。
總結(jié)
總而言之,畫三角需要如下的步驟:
創(chuàng)建 VkInstance
創(chuàng)建 VkPhysicalDevice
創(chuàng)建 VkDevice 以及 VkQueue
創(chuàng)建 window, window surface 以及 swap chain
使用 VkImageView 包裝swap chain中的images
創(chuàng)建 render pass
創(chuàng)建 framebuffers
創(chuàng)建 graphics pipeline
為每個(gè)可能用到的image創(chuàng)建command buffer,并記錄draw commands
通過獲取image,向image繪制,提交到swap chain的方式來繪制一幀
理解vulkan代碼
我們在本教程中使用c風(fēng)格的vulkan代碼。也就是說將引用vulkan.h
。所有的函數(shù)使用vk
前綴,所有對象使用Vk
前綴,多有常量使用VK_
前綴。
vulkan創(chuàng)建對象的代碼都是如下結(jié)構(gòu):
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;
}
創(chuàng)建對象時(shí),很多屬性需要顯示指定,比如sType
表示createInfo的類型,而pNext
可以指向一個(gè)擴(kuò)展類型,在本教程中,pNext
只會是nullptr
。創(chuàng)建或銷毀vulkan對象的方法可以有一個(gè)VkAllocationCallbacks參數(shù),來讓你自己控制驅(qū)動分配內(nèi)存時(shí)的結(jié)構(gòu),在本教程中,該參數(shù)只會是nullptr
。幾乎所有vulkan方法都有返回值,返回VK_SUCCESS
或一個(gè)錯(cuò)誤碼,來表示函數(shù)運(yùn)行錯(cuò)誤
Validation layers
vulkan作為高性能API,為了降低驅(qū)動開銷,只提供了非常有限的錯(cuò)誤檢查。驅(qū)動會經(jīng)常崩潰,而不是返回錯(cuò)誤信息,或者,顯示異常。
vulkan創(chuàng)建了層的概念,在你的代碼和驅(qū)動之間,可以創(chuàng)建很多層,用來處理錯(cuò)誤,或者記錄API調(diào)用進(jìn)行各種分析。每個(gè)開發(fā)者都可以變現(xiàn)他們自己的驗(yàn)證層,但是為了簡單,本教程將之間使用LunarG vulkan SDK提供的Validation layers。
因?yàn)関ulkan API每步操作都需要顯示指定,vulkan相比openGL和direct3D,能獲得更多更有價(jià)值的錯(cuò)誤信息。
其實(shí)不光繪制三角形,任何繪制邏輯準(zhǔn)備工作其實(shí)也都差不多,無非就是多準(zhǔn)備幾個(gè)buffer修改一個(gè)pipeline的問題。對于幾個(gè)高級圖形API(mantle,metal,dx12,vulkan,webGPU,NXT),其實(shí)編程的步驟也都是這樣大同小異(都偷師農(nóng)企?),相信一旦理解vulkan初始化思路,其他API上手也非常簡單(事實(shí)上vulkan為了平臺無關(guān)做了一些過度設(shè)計(jì),就我個(gè)人實(shí)際開發(fā)經(jīng)驗(yàn),也確實(shí)是這幾個(gè)中最麻煩的一個(gè))