<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【建議收藏】30 分鐘入門 Vulkan (中文翻譯版)

          共 8878字,需瀏覽 18分鐘

           ·

          2021-10-11 08:57

          關于 Vulkan 的學習,網(wǎng)上有一篇很火的文章:《Vulkan in 30 minutes》。

          這篇文章是英文的,原文鏈接如下:

          https://renderdoc.org/vulkan-in-30-minutes.html

          恰好在知乎上有位大佬將它翻譯成了中文,知乎作者就是:fangcun ,鏈接如下:

          https://zhuanlan.zhihu.com/p/59695433

          通過如下的鏈接可以下載文章對應的 PDF 文件和代碼演示:

          http://web.engr.oregonstate.edu/~mjb/vulkan/VulkanIn30Minutes.pdf

          這里轉(zhuǎn)載一波大佬的的翻譯,通俗易懂,值得收藏。

          對于 Vulkan 的學習,如果有興趣也可以看看我寫的 Vulkan 版本的 GPUImage。

          用 Vulkan 渲染寫一個 Android GPUImage

          以下就是翻譯原文:

          本文主要面向具有一定圖形API(D3D11或OpenGL)使用經(jīng)驗的讀者,此外,我們還希望讀者對多線程,暫存資源,同步等知識有所了解。我們將要介紹的Vulkan大量使用了這些知識。

          本文僅僅是為了讓讀者能夠?qū)ulkan的工作方式有一個大致的了解,所以忽略了很多細節(jié)。

          讀者在閱讀完本文之后,可以參考Vulkan的官方規(guī)范或其它Vulkan教程了解我們所忽略的細節(jié)部分。


          概述

          在本文的結尾,我們給出了使用Vulkan來繪制一個三角形的偽代碼,讀者可以參考它來理解本文。

          下面是一些有關Vulkan的小知識:

          • Vulkan是一個標準的C API。
          • Vulkan API對類型的使用非常重度。
          • Vulkan API大量使用結構體作為函數(shù)調(diào)用的參數(shù)。
          • Vulkan API中用于創(chuàng)建和清除對象的函數(shù)帶有一個VkAllocationCallbacks結構體指針參數(shù),允許我們使用它來自定義CPU端的內(nèi)存分配器。如果不想自定義這個CPU端的內(nèi)存分配器,可以將其設置為NULL來使用Vulkan自帶的CPU端的內(nèi)存分配器。

          需要讀者注意的是,本文沒有討論任何有關錯誤處理的內(nèi)容,如果真正地使用Vulkan編寫程序,需要根據(jù)Vulkan具體實現(xiàn)的限制,進行相關處理。

          第一步

          我們通過創(chuàng)建一個Vulkan實例(VkInstance)來完成Vulkan的初始化。

          每個Vulkan實例是完全獨立的,一個Vulkan實例對另一個Vulkan實例不存在任何影響。創(chuàng)建Vulkan實例時,我們指定了需要使用的層(layer)和擴展。

          如果不知道有哪些層(layer)或擴展可以使用,可以使用查詢函數(shù)來枚舉可用的層(layer)和擴展。

          有了VkInstance后,我們可以檢測可用的GPU設備(Vulkan不光可以用于GPU,這里為了方便,統(tǒng)稱為GPU設備)。

          每個GPU設備有一個VkPhysicalDevice類型的句柄。通過GPU設備的句柄,我們可以查詢GPU設備的名稱,屬性,功能等等。可以查詢的詳細信息可以參考vkGetPhysicalDeviceProperties和vkGetPhysicalDeviceFeatures函數(shù)的官方規(guī)范。使用GPU設備句柄VkPhysicalDevice,我們可以創(chuàng)建一個VkDevice。一個VkDevice代表了一個邏輯鏈接,表明我們在這一GPU上使用Vulkan。可以認為VkDevice等價于OpenGL中的context或D3D11中的device。

          一個VkInstance可以有多個VkPhysicalDevice。一個VkPhysicalDevice也可以有多個VkDevice。對于Vulkan 1.0來說,還不支持多GPU交互,但未來版本的Vulkan將會允許多個GPU進行交互。

          Vulkan要求我們顯式地設置一切參數(shù),所以從創(chuàng)建VkInstance到選擇使用地VkPhysicalDevice,再到創(chuàng)建VkDevice需要填寫的參數(shù)相當多。拋去參數(shù)填寫,大致過程看起來是這樣的:vkCreateInstance() → vkEnumeratePhysicalDevices() → vkCreateDevice()。對于我們這樣一個繪制三角形的簡單程序,可以先直接選擇第一個物理設備,等到后面需要錯誤信息、啟用可選的設備特性時再回來根據(jù)需要選擇物理設備。

          圖像和緩沖

          現(xiàn)在我們已經(jīng)創(chuàng)建了一個VkDevice,可以開始創(chuàng)建其它所需的資源。比如VkImage和VkBuffer。

          Vulkan要求我們在VkImage創(chuàng)建時指定它的用途。比如它是用作顏色附著,還是用于在著色器中進行采樣、還是用于圖像加載/存儲等等。

          此外,我們還需要指定VkImage在內(nèi)存中的存儲方式:LINEAR還是OPTIMAL。OPTIMAL存儲方式下,圖像數(shù)據(jù)在內(nèi)存中的組織方式對我們完全不透明。

          LINEAR存儲方式下,圖像數(shù)據(jù)會按照我們可以預期的形式存放。圖像的存儲方式對圖像數(shù)據(jù)是否可以被直接讀取和寫入,以及可以使用的圖像類型有一定影響。不同存儲方式可以支持的圖像類型不同。

          緩沖和圖像類似,需要我們在創(chuàng)建時指定緩沖的用途,以及大小。

          我們并不能直接訪問圖像數(shù)據(jù),需要通過VkImageView來訪問圖像數(shù)據(jù)。VkImageView描述了需要訪問的圖像數(shù)據(jù)范圍,以及將圖像數(shù)據(jù)作為何種格式進行訪問。

          緩沖只是一塊內(nèi)存,可以被直接使用。但如果需要在著色器中直接訪問緩沖中的數(shù)據(jù),則需要通過VkBufferView進行。

          分配GPU內(nèi)存

          緩沖和圖像在創(chuàng)建后并沒有實際為它們分配內(nèi)存。

          我們需要自己為它們分配內(nèi)存。調(diào)用vkGetPhysicalDeviceMemoryProperties函數(shù)可以獲取可以用于分配的內(nèi)存信息。這些信息包括可以用于內(nèi)存分配的一個或多個堆的信息、堆的大小以及可以分配的內(nèi)存類型。每種內(nèi)存類型對應一個可以分配這一類型內(nèi)存的堆。通常,對于帶有獨立顯卡的PC設備,會存在兩個可以用于內(nèi)存分配的堆:一個可以分配系統(tǒng)內(nèi)存,一個可以分配GPU內(nèi)存。所有不同類型的內(nèi)存都由這兩個堆之一進行分配。

          不同類型的內(nèi)存具有不同的屬性。一些類型的內(nèi)存可以被CPU訪問,一些不可以。一些類型可以在GPU和CPU間保持數(shù)據(jù)一致性、一些類型可以被CPU緩存使用等等。可以通過查詢物理設備獲取這些信息。我們可以根據(jù)需要使用不同的內(nèi)存類型,比如對于暫存資源,我們需要使用可以被CPU訪問的內(nèi)存類型。對于用于渲染的圖像,我們通常為其分配GPU內(nèi)存。此外,內(nèi)存分配還存在一個限制,我們會在下一節(jié)討論。

          內(nèi)存分配需要調(diào)用vkAllocateMemory函數(shù)。調(diào)用它需要使用VkDevice和一個描述內(nèi)存分配信息的結構體作為參數(shù)。我們使用這一結構體指定需要分配的內(nèi)存類型、內(nèi)存大小以及分配它的堆。vkAllocateMemory函數(shù)調(diào)用后會返回一個VkDeviceMemory句柄。

          對于CPU可以訪問的內(nèi)存類型,可以使用vkMapMemory/vkUnmapMemory函數(shù)對其進行映射。這一映射是持久化的,只要進行了正確的同步,可以在GPU使用這一內(nèi)存區(qū)域時訪問它。

          vkMapMemory函數(shù)返回的指針可以被保存使用,只要進行了正確的同步,甚至可以在GPU使用這一內(nèi)存區(qū)域時對其進行寫入操作,同步規(guī)則可以保證CPU不會寫入數(shù)據(jù)到GPU正在使用的那部分內(nèi)存。

          顯式刷新的非一致性內(nèi)存調(diào)試起來要比一致性內(nèi)存方便得多。顯式刷新為我們提供了非常好用的斷點位置。

          RenderDoc會對一個使用顯式刷新的內(nèi)存區(qū)域關閉代價極高的內(nèi)存一致性追蹤功能。在調(diào)試時,我們可以對一致性內(nèi)存進行顯式刷新,來獲得更好的調(diào)試體驗。

          綁定內(nèi)存

          VkBuffer和VkImage的內(nèi)存需求可以通過調(diào)用vkGetBufferMemoryRequirements函數(shù)和vkGetImageMemoryRequirements函數(shù)獲取。

          獲取的內(nèi)存需求滿足了多個細化級別間的對齊、隱含的元數(shù)據(jù)和其它需要占用內(nèi)存的信息的需求。此外,內(nèi)存需求還包含了一個掩碼,表明滿足此內(nèi)存需求的內(nèi)存類型。對于使用OPTIMAL存儲方式的用于顏色附著色圖像,只有DEVICE_LOCAL類型的內(nèi)存可以使用,不能對它綁定HOST_VISIBLE類型的內(nèi)存。

          對于同一類的圖像或緩沖,它們需要的內(nèi)存類型是一樣的,只需要對需要的內(nèi)存大小和對齊方式進行檢查,然后分配內(nèi)存即可。

          我們可以一次分配一大塊內(nèi)存,然后將這一大塊內(nèi)存通過使用不同的偏移值分配給多個圖像或緩沖使用。分配的偏移值需要滿足圖像或緩沖的對齊需求。通常,實踐中由于內(nèi)存分配的總次數(shù)有一定限制,我們總是這樣做來減少內(nèi)存分配次數(shù)。

          同一個VkDeviceMemory中存放的VkImage和VkBuffer使用的內(nèi)存之間還需要滿足一個最小間隔bufferImageGranularity。讀者可以閱讀Vulkan規(guī)范,獲取有關它的更多信息。這一要求和性能表現(xiàn)有關。

          綁定圖像或緩沖的內(nèi)存可以通過調(diào)用vkBindImageMemory函數(shù)或vkBindBufferMemory函數(shù)進行。我們需要在使用緩沖或圖像前對它們綁定內(nèi)存,并且綁定是不可更改的。

          指令緩沖和提交指令

          指令需要先被記錄到指令緩沖中,然后提交給隊列執(zhí)行。

          VkCommandBuffer需要使用VkCommandPool來分配。我們可以為每個線程使用一個獨立的VkCommandPool來避免進行同步,不同VkCommandPool使用自己的內(nèi)存資源分配VkCommandBuffer。

          開始記錄VkCommandBuffer后,調(diào)用的GPU指令,會被寫入VkCommandBuffer。等待提交給隊列執(zhí)行。

          指令緩沖完成指令記錄后,會被提交給VkQueue。可以認為VkQueue是一個包含了GPU待執(zhí)行工作的隊列。通過VkPhysicalDevice,我們可以獲取物理設備所支持的具有不同功能的隊列族。比如圖形隊列族和計算隊列族。在創(chuàng)建VkDevice時,可以從這些隊列族請求一定數(shù)量的隊列,在VkDevice創(chuàng)建后通過調(diào)用vkGetDeviceQueue獲取請求的隊列句柄。

          使用多個隊列需要進行同步操作,這里,為了簡單起見,我們只使用一個可以滿足所有需要的隊列。需要注意有些Vulkan實現(xiàn)可能會要求為交換鏈呈現(xiàn)使用獨立的隊列,雖然大多數(shù)情況下應該不需要,但還是提醒讀者注意,更多信息可以參考Vulkan的官方規(guī)范。

          可以通過調(diào)用vkQueueSubmit函數(shù)一次提交多個指令緩沖到一個隊列中,提交到隊列的指令緩沖會按順序被執(zhí)行。Vulkan對于指令執(zhí)行順序有非常具體的要求,讀者需要特別注意Vulkan官方規(guī)范中有關這一部分的說明,保證進行了正確的同步操作。

          著色器和管線狀態(tài)對象

          下面介紹Vulkan的著色器數(shù)據(jù)綁定模型:

          • 每個著色器階段有自己獨立的命名空間,片段著色器的0號紋理綁定和頂點著色器的0號紋理綁定沒有任何關系。
          • 不同類型的資源位于不同的命名空間,0號uniform緩沖綁定和0號紋理綁定沒有任何關系。
          • 資源被獨立地進行綁定和解綁定。

          Vulkan的基本綁定單位是描述符。描述符是一個不透明的綁定表示。它可以表示一個圖像、一個采樣器或一個uniform緩沖等等。它甚至可以表示數(shù)組,比如一個圖像數(shù)組。

          描述符的設置并不是獨立進行的,它被帶有特定VkDescriptorSetLayout的VkDescriptorSet進行統(tǒng)一設置。VkDescriptorSetLayout描述了VkDescriptorSet中每個綁定的類型。

          讀者可以這樣理解:把VkDescriptorSetLayout看作是一個結構體類型,它描述了使用的成員變量的變量類型。VkDescriptorSet是VkDescriptorSetLayout結構體類型的一個實例,它被用于具體的數(shù)據(jù)綁定。

          我們傳遞一個包含了類型、數(shù)組大小和綁定的列表給Vulkan來創(chuàng)建VkDescriptorSetLayout。然后使用它從VkDescriptorPool中分配VkDescriptorSet。VkDescriptorPool和VkCommandPool類似,我們可以為每個線程創(chuàng)建獨立的VkDescriptorPool來避免進行同步操作。

          VkDescriptorSetLayoutBinding?bindings[]?=?{
          ?//?binding?0?is?a?UBO,?array?size?1,?visible?to?all?stages
          ?{?0,?VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,?1,?VK_SHADER_STAGE_ALL_GRAPHICS,?NULL?},
          ?//?binding?1?is?a?sampler,?array?size?1,?visible?to?all?stages
          ?{?1,?VK_DESCRIPTOR_TYPE_SAMPLER,?1,?VK_SHADER_STAGE_ALL_GRAPHICS,?NULL?},
          ?//?binding?5?is?an?image,?array?size?10,?visible?only?to?fragment?shader
          ?{?5,?VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,?10,?VK_SHADER_STAGE_FRAGMENT_BIT,?NULL?},
          };

          有了描述符集后,我們就可以通過綁定來更新數(shù)據(jù),以及在不同描述符集間復制數(shù)據(jù)。

          在創(chuàng)建管線時,可以對一個VkPipelineLayout指定多個需要使用的VkDescriptorSetLayouts。進行數(shù)據(jù)綁定時,只能使用匹配的VkDescriptorSet。不同的描述符集可以按照不同的頻率更新數(shù)據(jù),可以按照更新頻率來劃分描述符集。

          繼續(xù)考慮之前的類比,我們可以將管線看作一個函數(shù),它具有多個結構體參數(shù)。創(chuàng)建管線時,它的每個參數(shù)的類型被確定(VkDescriptorSetLayout),進行數(shù)據(jù)綁定時我們將實例(VkDescriptorSet)傳遞給管線。

          著色器中的綁定設置相對來說就很簡單了,只需要指定資源來自哪個描述符集和描述符集中的哪一綁定即可。

          #version?430
          layout(set?=?0,?binding?=?0)?uniform?MyUniformBufferType?{
          ?//?...
          }?MyUniformBufferInstance;
          //?note?in?the?C++?sample?above,?this?is?just?a?sampler?‐?not?a?combined?image+sampler
          //?as?is?typical?in?GL.
          layout(set?=?0,?binding?=?1)?sampler?MySampler;
          layout(set?=?0,?binding?=?5)?uniform?image2D?MyImages[10];

          同步

          同步大概是Vulkan最難處理的部分,甚至有時忘記進行某一同步操作,程序運行后看起來也跟完全沒有問題一樣。

          在兩個不同的線程上使用同一個VkQueue需要進行同步,否則會引起程序崩潰。

          對于在多個線程使用某一對象是否需要同步可以參考Vulkan的官方規(guī)范。一般來說,使用VkDevice作為參數(shù)的創(chuàng)建函數(shù)不需要進行同步,但像記錄指令和提交指令緩沖這類操作需要進行同步。

          Vulkan沒有對使用的資源進行引用計數(shù),我們需要自己保證在不再使用資源時釋放它。

          Vulkan提供了VkEvent、VkSemaphore和VkFence用于CPU-GPU和GPU-GPU同步。Vulkan的官方規(guī)范對于執(zhí)行順序的明確規(guī)定很少,進行同步操作需要格外小心。

          管線屏障是一個新的概念。它被用來保證GPU端操作的執(zhí)行順序。比如可以保證在開始一個操作前某個操作已經(jīng)完成,或在某一資源上的某一類型操作已經(jīng)完成可以開始另一類型操作。

          有三種內(nèi)存屏障類型:VkMemoryBarrier、VkBufferMemoryBarrier和VkImageMemoryBarrier。VkMemoryBarrier可以進行所有內(nèi)存資源的同步操作,其它兩種類型的內(nèi)存屏障用于同步特定的內(nèi)存資源。

          我們通過內(nèi)存屏障指定需要進行的同步操作。比如設置內(nèi)存屏障的srcAccessMask = ACCESS_COLOR_ATTACHMENT_WRITE和dstAccessMask = ACCESS_SHADER_READ后,著色器讀取數(shù)據(jù)前所有顏色寫入操作必須完成。如果不進行這樣的設置,我們可能會讀取到過期的數(shù)據(jù)。

          圖像布局

          圖像資源存在一個叫做圖像布局的狀態(tài)。VkImageMemoryBarrier可以對圖像資源的圖像布局進行變換。對圖像進行的操作需要圖像滿足一定的布局。存在一個通用的可以進行任意操作的圖像布局,但使用它的性能表現(xiàn)不佳。對于需要在圖像上進行的特定操作使用特定的圖像布局性能表現(xiàn)更好。比如用作顏色附著、深度附著和需要在著色器中進行采樣的圖像都有一個特別適合的圖像布局。

          圖像初始時處于UNDEFINED或PREINITIALIZED狀態(tài)。PREINITIALIZED狀態(tài)用于填充有數(shù)據(jù)的圖像。對于處于UNDEFINED狀態(tài) 的圖像,將它變換到GENERAL狀態(tài)時,會丟失之前的圖像數(shù)據(jù),但處于PREINITIALIZED狀態(tài)的圖像變換到GENERAL狀態(tài)時,不會丟失之前的圖像數(shù)據(jù)。處于這兩個初始圖像布局狀態(tài)的圖像都不能直接被GPU使用,需要進行至少一次圖像布局變換才可以被GPU使用。

          通常我們需要準確指定圖像變換之前的布局和變換之后的布局。但使用UNDEFINED作為之前的圖像布局也是常見的,它表明我們不需要之前的圖像數(shù)據(jù),只需要將圖像變換為需要的新布局。

          渲染流程

          Vulkan使用VkRenderpass來顯式地定義渲染操作流程。對于基于tile的渲染,VkRenderpass可以極大的提高內(nèi)存利用,減少頻繁的數(shù)據(jù)傳輸。

          一個VkRenderPass包含了一系列的子流程。對于我們這個簡單的程序,它只包含了一個子流程。子流程指定了幀緩沖的顏色附著、深度模板附著。如果有多個子流程可能會為它們指定不同的附著設置,一個子流程將其用作數(shù)據(jù)輸入,另一個子流程可能將其用作數(shù)據(jù)輸出。

          繪制指令只可以在VkRenderPass中執(zhí)行,復制數(shù)據(jù)和清除數(shù)據(jù)的指令只可以在VkRenderPass外執(zhí)行。狀態(tài)綁定的指令的執(zhí)行可以在VkRenderPass外也可以VkRenderPass內(nèi)。

          子流程不會繼承之前的狀態(tài)。所以每次開始一個VkRenderPass或進入一個新的子流程,我們必須重新綁定所有狀態(tài)。子流程還指定了讀寫附著時執(zhí)行的附加操作。比如使用值1.0來清除深度附著的內(nèi)容,接下來顏色附著會被新數(shù)據(jù)完全覆蓋掉,不進行顏色附著的清除。這些信息為驅(qū)動程序優(yōu)化提供了很大空間。

          最后需要考慮的是多個不同對象之間的匹配問題。創(chuàng)建VkRenderPass(以及它的所有子流程)時我們指定了使用的所有附著以及附著的格式。之后,創(chuàng)建VkFramebuffer時,指定使用我們創(chuàng)建的VkRenderPass。這樣指定后,并不意味著之后必須使用這一個VkRenderPass,只要和指定的這一個VkRenderPass相兼容(具有相同的附著和附著格式)的VkRenderPass都可以在之后被VkFramebuffer使用。創(chuàng)建VkPipeline時也需要指定使用的VkRenderPass和子流程,同樣之后只要與指定的VkRenderPass和子流程相兼容的對象都可以供VkPipeline使用。

          如果渲染流程帶有多個子流程,就需要定義子流程之間的依賴和內(nèi)存屏障,以及它們使用的附著及其用途。更多信息可以參考Vulkan的官方規(guī)范。

          后臺緩沖和呈現(xiàn)

          Vulkan通過擴展來和原生窗口系統(tǒng)進行交互。我們需要在創(chuàng)建VkInstance和VkDevice時顯式地請求這一擴展。

          首先,我們使用原生窗口系統(tǒng)的信息創(chuàng)建一個VkSurfaceKHR。

          然后,為它創(chuàng)建一個VkSwapchainKHR。這需要我們查詢VkSurfaceKHR支持的圖像數(shù)據(jù)格式,以及我們可以在交換鏈中使用的后臺緩沖個數(shù)。

          我們可以調(diào)用vkGetSwapchainImagesKHR函數(shù)從VkSwapchainKHR獲取VkImage圖像句柄。交換鏈中的圖像由Vulkan自動創(chuàng)建。我們只需要創(chuàng)建對應的圖像視圖就可以訪問它們。

          當需要對交換鏈圖像進行渲染操作時,可以調(diào)用vkAcquireNextImageKHR函數(shù),它會返回一個交換鏈圖像的索引,我們使用這一索引使用對應圖像視圖來對圖像進行渲染。最后調(diào)用vkQueuePresentKHR函數(shù)將渲染的圖像呈現(xiàn)到屏幕上。

          有大量設置可以用于優(yōu)化交換鏈的性能表現(xiàn),但對于我們這樣一個簡單的程序,并非必要。

          總結

          本文跳過了大量繁瑣的細節(jié),也沒有對稀疏資源,主要和次要指令緩沖等一些很酷的特性進行介紹。有關這些內(nèi)容讀者可以參考Vulkan的官方規(guī)范。




          技術交流,歡迎加我微信:ezglumes ,拉你入技術交流群。

          推薦閱讀:

          音視頻面試基礎題

          OpenGL ES 學習資源分享

          開通專輯 | 細數(shù)那些年寫過的技術文章專輯

          NDK 學習進階免費視頻來了

          推薦幾個堪稱教科書級別的 Android 音視頻入門項目

          百萬高薪,十萬獎金!網(wǎng)易應用創(chuàng)新開發(fā)者大賽正式開賽!

          覺得不錯,點個在看唄~


          瀏覽 167
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本黄色大全 | 鸡巴操美女欧美91 | 性爱网站大全 | 日本特黄在线 | 日本一级一片免费视频 |