DirectX12_基础知识
Direct3D 是底層圖形應用程序編程接口。我們可以通過它對GPU進行控制和編程,從而在渲染虛擬3D場景時獲得硬件加速的效果。12 比 11 更加偏于底層,更能壓榨硬件性能。從今天開始我們就會開始進行DX12龍書的學習,主要還是為了能夠熟悉它的接口,并可以使用它進行編程,這樣為我們未來的渲染器打好基礎。
首先我們來說說DX12中一些特性(相對于Vulkan或OpenGL中有或沒有的一些概念)。
一、組件對象模型(Component Object Model, COM)
COM的全稱是Component Object Model,它讓DX稱為獨立的編程語言并且向下兼容。我們通常使用特定的函數或者其它COM接口來獲取COM接口引用的指針。另外,COM對接口是引用計數的,使用完畢后,需要調用Release方法來釋放它們。為了管理COM對象的生命周期,Windows Runtime Library提供了wrl.h來管理COM對象的智能指針。龍書中主要用到了ComPtr三個方法:
- Get: 返回其包含的COM接口。
- GetAddressOf: 返回其包含的COM接口的指針指向的地址。
- Reset: 將ComPtr接口設置為空指針,并且減少其包含的COM接口的引用計數。
例如在Office文檔中,可以放入一個視頻,然后這個東西就會顯示成一張靜態的圖片。我們雙擊這個圖片,就會啟用支持這個的軟件對其進行播放。然后后臺程序會啟動,但窗口被隱藏。Office當中的一小塊客戶區域傳遞給這個后臺程序,讓其負責處理這塊區域的繪制和用戶輸入。也就是說,Office程序的WM_PAINT事件處理當中,將Office窗口的整個客戶區域分割為由自己繪制和由OLE繪制的部分。由OLE繪制的部分通過COM技術傳遞給后臺應用進行繪制。
二、紋理格式
組成紋理的數據元素必須是 DXGI_FORMAT 枚舉類型中定義的特定格式。例如:
DXGI_FORMAT_R32G32B32_FLOAT: 每個元素由 3 個 32 位浮點數分量構成 DXGI_FORMAT_R8G8B8A8_SNORM: 每個元素由 4 個 8 位無符號分量構成 DXGI_FORMAT_R32G32_UINT 每個元素由 2 個 32 位無符號整數構成 ...紋理是渲染時信息的主要儲存介質,而不僅限于存儲圖像數據。
三、交換鏈
每一幀圖像的繪制都需要一個過程,為了使這個過程對于使用者不可見,我們需要先將內容全部繪制到后臺緩沖區的紋理中。在一幀的內容全部繪制完成時,前后臺緩沖區對調,新的前臺緩沖區為用戶呈現內容,新的后臺緩沖區開始繪制下一幀。
前后臺緩沖區構成交換鏈(IDXGISwapChain)。使用兩個緩沖區的叫做雙緩沖。而且可以使用更多的緩沖區,例如使用 3 個緩沖區的叫三重緩沖。
四、深度緩沖
深度緩沖區是一種儲存特定像素深度信息的緩沖區。其值為 0.0~1.0 之間。0.0 表示平截頭體中最近能看到的物體,1.0 表示平截頭體最遠能看到的物體。
深度測試用于對比寫入后臺緩沖區的同一像素的不同深度值。較小的值(物體靠前)會最終寫入后臺緩沖區。
深度緩沖區也是一種紋理,所以在 DXGI_FORMAT 枚舉類型中也定義了它能使用的格式:
DXGI_FORMAT_D32_FLOAT_S8X24_UINT 32位深度緩沖 + 8 位無符號模板緩沖 + 24 位對齊 DXGI_FORMAT_D32_FLOAT DXGI_FORMAT_D24_UNORM_S8_UINT DXGI_FORMAT_D16_UNORM五、資源與描述符
GPU想要對資源進行讀寫,就需要將該資源先綁定的渲染管線上。GPU在使用資源的時候大致需要知道兩件事:
- 要使用的是哪個資源?
- 該資源充當什么角色?
資源描述符主要解決上述兩件事。通過資源描述符引用要使用的資源很好理解。主要是資源描述符還需要解釋資源充當的角色。因此我們可以把資源描述符分類,其中常用的有:
- CBV/SRV/UAV 常量緩沖區視圖(constant buffer view)/ 著色器資源視圖(shader resource
view)/ 無序訪問視圖(unordered access view) - 采樣器(sampler)用于紋理貼圖
- RTV 渲染目標視圖(render target view)
- DSV 深度/模板視圖(depth/stencil view)
描述符堆表示特定類型描述符的一塊內存
六、多重采樣技術
書中主要介紹了兩種方式:
- 超級采樣(Super Sample Anti-Aliasing,SSAA)
- 多重采樣(MultiSample Anti-Aliasing,MSAA)
超級采樣使用四倍于屏幕分辨率的后臺緩沖區和深度緩沖區,然后在降采樣的時候以四個像素為一組計算平均值。
采用4X多重采樣,也是需要四倍于屏幕分辨率的后臺緩沖區和深度緩沖區,但在降采樣的時候直接計算像素中心的顏色,然后根據可視性和覆蓋性將信息分享給子像素。
相當于超級采樣每個子像素都要算一遍。而多重采樣是每個像素算一遍然后把結果按條件分給子像素。
子像素是指在像素內部再次細分出用于采樣的子像素。在上述條件下,四倍于屏幕分辨率,則屏幕的每個像素下就有4個用戶采用的子像素。
七、命令隊列&命令列表&命令分配器
每個GPU都至少維護一個命令隊列(command queue)。CPU可以通過命令列表(command list)將命令提交到GPU的命令隊列中。
命令分配器保存由CPU通過命令列表添加的命令,在提交到命令隊列中時,隊列也會引用分配器中的命令。
命令提交到命令隊列中時并不會立即執行。而是添加到執行列表排號。
命令列表和分配器不是線程安全的,不過命令隊列是線程安全的。所以可以在多個線程分別創建命令列表和分配器,而在提交命令時使用相同的命令隊列。
八、CPU與GPU的同步問題
由于兩個處理器并行工作,就必然會遇到需要同步的情況。此時我們需要運用圍欄點(fence point)功能。
圍欄點的邏輯簡單來講就是 CPU 向 GPU 執行路徑上添加一個圍欄點,然后 CPU 可以在需要和 GPU 同步的時候使用該圍欄點的值等待 GPU。如果 GPU 執行到該圍欄點值處會觸發事件,從而達到同步的目的。
九、資源駐留
Direct3D 12 可以主動管理資源在顯存中的駐留情況。可以將短時間不用的資源清出以節省緩存。
十、資源轉換屏障(transition resource barrier)
假如我們要對某個資源進行先寫后讀兩種操作。但是如果 GPU 在寫還沒開始時或未完成時就開始讀資源明顯就會出問題(資源冒險 resource hazard)。為了解決這個問題,Direct3D 引入了一種狀態機制。在操作資源的時候更改資源的狀態。GPU 會根據資源狀態來調整行為避免資源冒險的產生。
十一、多線程
11.1 D3D11多線程渲染基本原理
在D3D11中,對于多線程渲染的支持主要可以概括為幾個“小點”:
首先利用ID3D11Device::CreateDeferredContext方法創建一個(或多個)延遲渲染(Deferred Device Context)的設備上下文接口ID3D11DeviceContext;
接著利用這個Deferred Device Context接口調用諸如:IASetPrimitiveTopology、IASetInputLayout、RSSetState、OMSetBlendState 、OMSetDepthStencilState、DrawIndexed、DrawIndexedInstanced(最后兩個為Draw Call)等等方法(可以統稱為命令)像往常一樣進行渲染調用;
所有的渲染調用都結束后,接著調用ID3D11DeviceContext::FinishCommandList方法,得到一個ID3D11CommandList接口;
最終通過即時設備接口(Immediate Device Context)的ID3D11DeviceContext::ExecuteCommandList方法執行這個Command List;
當所有的Command List都Execute完成之后,就可以調用IDXGISwapChain::Present方法呈現最終渲染畫面了。
其中最最吸引人的就是Deferred Device Context的ID3D11DeviceContext接口可以有多個,并且每一個可以在不同的CPU線程(Windows線程)中分別記錄命令(Command),然后提交給Immediate Device Context所對應的CPU線程(Windows線程)進行Execute(MSDN中稱之為Queues commands),然后也在同樣的線程中調用Present即可。這也就是D3D11多線程渲染的全部核心奧秘了。
當然這不是全貌,只是一個示意性的核心原理說明,但至少說明使用D3D11加入多線程渲染在編程原理和具體實現上其實并不復雜。在D3D11中命令列表中的命令(其實主要是CPU發送給GPU執行的命令)是被快速記錄下來,而不是立即執行的(包括那些可怕的被稱之為Draw Call的性能殺手,當然一定要記得升級你的顯卡驅動程序),直到你調用ExecuteCommandList方法(調用即返回,不等待)才被GPU真正的執行,此時那些使用延遲渲染設備接口的CPU線程以及主渲染線程(不一定是進程的主線程,此處是指調用Immediate Device Context::ExecuteCommandList方法的線程)又可以去干別的事情了,比如繼續下一幀的輸入變換、碰撞檢測、物理變換、動畫矩陣調色板準備、光照準備等等,從而為記錄形成新的命令列表做準備。而此時GPU就忙碌的開始執行渲染命令了。這也就是延遲渲染設備名字的真正含義。最終這就形成了CPU和GPU同時都在忙碌的高效渲染效果。
而在擁有D3D11多線程渲染之前,CPU和GPU的工作就好像兩個人打臺球一樣,一個擊球時,另一個只能在旁邊觀望(CPU線程在Draw Call上等待GPU完成渲染)。而D3D11引入的多線程渲染,不但讓CPU和GPU可以同時處于忙碌狀態,更讓現代多核CPU及多GPU并行執行任務的能力得到根本上的解放。由此也可以看出來多線程渲染也是為解放生產力而生!
11.2 D3D12多線程渲染基本原理
而在D3D12中多線程渲染與D3D11中是異曲同工的:
首先在D3D12中利用命令隊列(Command Queue 接口:ID3D12CommandQueue)代替了ID3D11DeviceContext接口,更準確的說是代替了Immediate Device Context的ID3D11DeviceContext接口,在D3D12中不論什么隊列(Queues)都需要自己創建,而不是像D3D11中Immediate Device Context伴隨ID3D11Device一同被創建。
在D3D12中Command Queue被進一步細分為D3D12_COMMAND_LIST_TYPE_DIRECT(直接命令隊列), D3D12_COMMAND_LIST_TYPE_BUNDLE(捆綁包), D3D12_COMMAND_LIST_TYPE_COMPUTE(計算命令隊列),D3D12_COMMAND_LIST_TYPE_COPY(復制命令隊列), D3D12_COMMAND_LIST_TYPE_VIDEO_DECODE(視頻解碼命令隊列), D3D12_COMMAND_LIST_TYPE_VIDEO_PROCESS(視頻處理命令隊列)。
不論什么命令隊列,它們本質上就是用來執行命令列表的,相當于D3D11中的Immediate Device Context,同樣也是調用ExecuteCommandLists方法來執行命令列表。因此從其豐富的種類就可以感受到D3D12中命令隊列本身就已經開始大大擴展了。
在D3D12中也有與D3D11中相類似的命令列表的概念,具體是用ID3D12GraphicsCommandList接口來表達,它就相當于D3D11中的Deferred Device Context。當然其內涵比在D3D11中要豐富的多,在D3D11中記錄命令列表是由Deferred Device Context越庖代俎的,也就是我們按邏輯調用一堆ID3D11DeviceContext的方法,結束的時候使用FinishCommandList方法得到一個命令隊列的接口ID3D11CommandList,而這個接口幾乎沒什么方法,僅僅是個“概念標志物”,或者直白的說就是僅僅代表GPU上的一個命令隊列而已,其自身并沒有什么方法可供調用。而在D3D12中ID3D12GraphicsCommandList卻包含了幾乎所有的可供放入命令隊列中的命令方法(幾乎全部是渲染相關的方法),并且在D3D12中命令隊列本身是被創建的,使用的是ID3D12Device::CreateCommandList方法,有了這個接口以后你就可以在對應的其它CPU線程中按照渲染邏輯調用ID3D12GraphicsCommandList接口的方法生成一個命令列表(Command List)了,通常這個過程被稱作“記錄(或錄制)”一個命令列表。同樣錄制只是說記錄了你調用的順序和使用的資源(CPU從內存傳入顯存),而不是立即執行這些方法,這些方法都會快速返回,因為是CPU調用這些方法,這個過程可以想象為你到餐館去點菜,并不是你點一個菜,就做一個菜上一個菜再點下一個,而是生成一個菜單(Command Lists),統一提交到廚房(Queues Commands & Execute)。對應于不同的命令隊列,命令列表也分為很多種類,基本上就是有多少種命令隊列,就有多少種命令列表。
最后在命令列表(Command Lists)記錄完成后,與D3D11中相同,提交到對應的命令隊列(Command Queues)上去執行(ExecuteCommandLists)即可。
在命令隊列執行命令列表的對應關系上,D3D12中基本的原則就是直接命令隊列幾乎可以執行所有種類的命令列表,而其它的命令隊列只能執行對應種類的命令列表,如復制命令隊列原則上只執行復制命令列表。
最終當所有的命令列表都執行結束后,主渲染線程(這時往往指的就是運行直接命令隊列的CPU線程了)再調用Present方法將最終畫面呈現出來即可。
需要注意的就是在D3D12中最終的Execute Command Lists操作也是立即返回,此時CPU線程可以進行其它的操作,而GPU就同時忙著執行各種渲染命令了,只有到所有都結束后才調用Present。 當然這很好理解,在所有渲染沒有完成之前,后臺緩沖區里還不是我們想要的最終畫面。
由此可以看出,至少從原理上來說D3D12與D3D11多線程渲染框架基本是一致的。都是通過在不同的CPU線程中錄制命令列表(Command Lists),最后再統一執行(Execute)的方式完成多線程渲染。并且都從根本上屏蔽了令人發指的Draw Call同步調用,而改為CPU和GPU完全異步執行的方式(并行!),從而在整體渲染效率和性能上獲得巨大的提升。
總結
以上是生活随笔為你收集整理的DirectX12_基础知识的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 学习 sentry 源码整体架构,打造属
- 下一篇: react学习(60)--ant des