DirectX12(D3D12)基础教程(三)——使用独立堆以“定位方式”创建资源、创建动态采样器、初步理解采取器类型
目錄
?
1、前言
2、顯卡架構和存儲管理
3、創建默認堆并在其上以“定位方式”創建2D紋理
4、動態采樣器
5、完整代碼
1、前言
經過了第二部分教程的“折騰”之后,后面的教程我覺得應該順暢多了。至少我現在可以一天時間就把教程示例代碼調通,并且可以按照自己的想法自由的去發揮了。我很喜歡這種感覺,就像在打游戲中虐那些無腦的機器AI角色一樣。
經過前面兩章的學習,我相信大家對D3D12的編程復雜性應該有所認識了。那么這一章讓我們放慢腳步,不再過多的學習知識點。開始緩慢爬坡,因為后面還有更吸(fu)引(za)人的內容等著我們一起去學習探討。所以喘口氣還是必要的。
通過前兩章的例子,我發現雖然刻意沒有使用什么封裝,甚至連定義函數都沒有的方式線性的灌輸概念,但是由于D3D12本身在編程上的復雜和繁瑣,因此代碼的可讀性實際上是不高的,這非常不利于學習,所以本章起,我使用中括號將每部分相關代碼都放在一起,并且加上步驟序號和說明,方便大家線性的閱讀和理解(使用VS的大綱折疊顯示功能)。這也是為以后的例子代碼做一個準備。因為說實話我在組織準備每篇教程的例子代碼時,有時也是想吐的,雖然只是畫一個簡單的紋理,但代碼量隨便就超過了1000行,再往后我發現居然有點hold不住了。所以就使用了這個方法稍微組織一下。
言歸正傳,本篇教程中,我將重點講解一下顯卡的架構、獨立堆、定位方式的資源,以及動態采樣器的用法,這些是D3D12中新加入的比較新的概念性的東西,我認為這些才是真正的D3D12中比較核心的一些概念,因此掌握它們才是用好用活D3D12必須要學習的。
2、顯卡架構和存儲管理
在一開始的教程中,我已經提到過D3D12是一個很“低級”的接口,在我的系列文章《D3D11和D3D12多線程渲染框架的比較》中我甚至提到D3D12就是一個“顯卡操作系統”。其實我這樣的說的另一重目的是說,要徹底學懂D3D12,那就需要像我們學習匯編語言或C語言一樣,對計算機底層的架構甚至較詳細的硬件架構有所了解。否則學習這些內容就將停留在表面,很難深入的理解和掌握。
因此學習D3D12,就需要我們對現代的顯卡子系統以及它與系統交互連接的方式有所了解。
首先我們在之前的教程中已經說過現代的GPU上是有很多可以并行執行命令的引擎的,如下圖所示:
這個圖來自MSDN,它很形象的說明了一個GPU上至少有三大類引擎,一個是復制引擎(Copy engine)、一個是計算引擎(Compute engine)、另一個是3D引擎(3D engine),實質上如果以最新的Nvidia的20xx系顯卡GPU核來說,其上還有AI引擎、以及獨立的專門做實時光追運算的內核,當然還有跟我們渲染沒太大關系的視頻處理引擎等,未來不排除D3D中會不會加入對這些獨立的引擎以及對應類型的命令隊列的支持。所以現在了解下GPU的架構及編程理念,至少不至于在未來因為不能理解編程框架而被淘汰掉。
同時這幅圖上實際更進一步的示意說明的了CPU線程和GPU引擎之間如何交互命令,以及并行執行的原理。核心還是使用命令列表記錄命令,再將命令列表在命令隊列中排隊,然后各引擎從命令隊列中不斷獲取命令并執行的基本模式(回憶一下我們之前教程中提到的飯館模型),至此我想關于這個CPU、GPU并行執行的概念大家應該是深刻理解并消化了。需要再次強調的就是雖然命令列表是分開錄制的,但是它們被排隊至命令隊列之后,宏觀上各個引擎在執行它們時仍然是串行順序的,而在微觀上針對一個個的原子數據,比如:紋理的像素、網格的頂點之類的則是并行的(SIMD)。
當然上圖還是從線程角度或者說動態執行的角度來看現代CPU+GPU體系結構的視圖。而另一個方面就需要我們進一步去了解CPU+GPU是如何管理和使用內存的,以便于我們深入掌握D3D12中的內存管理方法,從而為真正提高性能做好準備。我想提高性能對于一個引擎或者游戲來說意味著什么,就不需要我過分強調了。
從類型上來說現代的PC、筆記本甚至手機中CPU和GPU本質上都是獨立的處理器,它們之間使用的是被稱為SMP架構模型進行互聯并相互協作的,SMP即共享存儲型多處理機(Shared Memory mulptiProcessors),)也稱為對稱型多處理機(Symmetry MultiProcessors)。
或者直白的說CPU和GPU間的交互就是通過共享內存這種方式來進行的,但他們各自又有各自的內存控制器和管理器,甚至各自還有自己的片上高速緩存,因此最終要共享內存就需要一些額外的通信控制方式來進行,這也是我們使用D3D12進行存儲管理編程的復雜性的根源。這里要注意的是從第一篇教程起我就特別說明是存儲管理,而不是只說內存管理(CPU側)、顯存管理(GPU側)或者共享內存(SMP中CPU和GPU共享的內存),這里大家要特別注意這個概念上的區別。因為在D3D12中我們需要管理的不僅僅是GPU上的顯存,根據SMP的描述,我們還需要額外管理二者之間共享的內存。當然我想CPU的內存如何管理各位C/C++程序員應該已經輕車熟路了,就不需要我多啰嗦了。
更進一步的SMP架構又被細分為:均勻存儲器存取(Uniform-Memory-Access,簡稱UMA)模型、非均勻存儲器存取(Nonuniform-Memory-Access,簡稱NUMA)模型和高速緩存相關的存儲器結構(cache-coherent Memory Architecture,簡稱CC-UMA)模型,這些模型的區別在于存儲器和外圍資源如何共享或分布。
UMA架構模型示意圖如下:
從圖中可以看出UMA架構中物理存儲器被所有處理機均勻共享。所有處理機對所有存儲器具有相同的存取時間,這就是為什么稱它為均勻存儲器存取的原因。每個處理器(CPU或GPU)可以有私有高速緩存,外圍設備也以一定形式共享(GPU因為沒有訪問外圍其他設備的能力,實質就不共享外圍設備了,這里主要指多個CPU的系統共享外圍設備)。實質上UMA方式是目前已經很少見的主板集成顯卡的方式之一。需要注意的是這里只是一個簡化的示意圖,里面只示意了一個CPU和GPU的情況,實質上它是可以擴展到任意多個CPU或GPU互通互聯的情況的。
NUMA架構的示意圖如下:
從NUMA的示意圖中可以看出,其存儲器物理上是分布在所有處理器的本地存儲器上。本地存儲器的一般具有各自獨立的地址空間,因此一般不能直接互訪問各自的本地存儲。而處理器(CPU或GPU)訪問本地存儲器是比較快的,但要訪問屬于另一個處理器的遠程存儲器則比較慢,并且需要額外的方式和手段,因此其性能也是有額外的犧牲的。其實這也就是我們現在常見的“獨顯”的架構。當然一般來說現代GPU訪問顯存的速度是非常高的,甚至遠高于CPU訪問內存的速度。所以在編程中經常要考慮為了性能,而將盡可能多的純GPU計算需要的數據放在顯存中,從而提高GPU運算的效率和速度。
CC-UMA架構的示意圖如下:
如圖所示,CC-UMA是一種只用高速緩存互聯互通的多處理器系統。CC-UMA模型是NUMA機的一種特例,只是將后者中分布主存儲器換成了高速緩存, 在每個處理器上沒有存儲器層次結構,全部高速緩沖存儲器組成了全局地址空間。通常這是現代CPU中集顯最容易采取的架構方式。當然高速緩存共享或直連的方式擁有最高的互訪性能。但其缺點就是高速緩存因為高昂的價格,所以往往空間很小,目前的集顯上還只有幾兆,最多到幾十兆高速緩沖的樣子,所以對于現代的渲染來說這點存儲量實在是少的可憐了。另外因為高速緩存是在不同的處理器(CPU或GPU)之間直接共享或互聯的,因此還有一個額外的問題就是存儲一致性的問題,就是說高速緩沖的內容跟實質內存中的內容是否一致,比如CPU實質是將數據先加載進內存中然后再加載進高速緩沖的,而GPU在CPU還沒有完成從內存到高速緩沖的加載時,就直接訪問高速緩沖中的數據就會引起錯誤了,反之亦然。因此就需要額外的機制來保證存儲一致性,當然這就導致一些額外的性能開銷。具體的關于存儲一致性的內容,我就不多講了,我們主要還是要靠獨顯來干活。進一步的知識大家有興趣的可以百度一下相關資料。
具體來說,比如我的筆記本電腦上就有一個“集顯”也有一個“獨顯”,集顯跟CPU形成了CC-UMA架構,并且它獨占了128M的內存當做顯存,而獨顯則與CPU形成了NUMA架構,獨顯上有2G的獨立顯存,兩個GPU都與CPU共享了8149M的內存,作為統一的共享內存。其實這也可以看出實際的系統中往往是上述架構混用的形式。
通過運行Dxdiag程序就可以看到這些信息:
上圖是集顯情況,下面則顯示了獨顯的情況:
因為我的系統總共有16G的內存,所以DX子系統干脆就共享了8149M的內存作為三個處理器(CPU+2個GPU,你應該已經習慣我用處理器來指代CPU或GPU了)之間的公共內存。這樣做的意義在哪里呢?除了首先想到的可以擁有巨大的可用的顯存之外,其實它更重要的深層意義就是可以讓這兩個GPU因共享相同的公共內存,實現數據的互通互聯,從而可以并行的工作,這也是D3D12重要的高級特性之一——支持多顯卡渲染尤其是異構多顯卡渲染。也為支持DXR中的多顯卡實時光追渲染提供了基礎支撐能力。
綜上,實質上這些架構之間的主要區別是在各處理器訪問存儲的速度上,簡言之就是說使用高速緩存具有最高的訪問速度。其次就是訪問各自獨占的存儲,而最慢的就是訪問共享內存了,當然對于CPU來說訪問共享內存與自己獨占的內存在性能是基本沒有差異的。這里的性能差異主要是從GPU的角度來說的。因此我們肯定愿意將一些CPU或GPU專有的數據首先考慮放在各自的獨占存儲中,其次需要多方來訪問的數據就放在共享內存中。這也就是我們上一講提到的D3D12中不同種類的存儲堆的本質含義。
另外需要提醒的是,現代的CPU+GPU以及系統的架構都是在不斷進化和變化的,目標就是更高的效率和性能,因此這里說的架構僅僅還只是概念上的模型,跟實際的系統架構可能還有出入,如果想進一步了解這類信息就請各位關注硬件類網站最新的一些CPU或GPU顯卡測試類的文章,其中往往會提及一些最新的架構方面的知識。了解的目的就是為了更好的從根本上去了解軟件框架為什么是這個樣子,從而提高學習的效率和效果。這也是我這么多年學習的一個經驗總結之一。
最后我們可以通過下面的簡單程序來檢測我們的GPU與系統是以什么架構互聯的,同時我們可以準確的知道他們各自獨占存儲和共享存儲的情況。代碼如下(可獨立運行):
#include <SDKDDKVer.h> #define WIN32_LEAN_AND_MEAN // 從 Windows 頭中排除極少使用的資料 #include <windows.h> #include <tchar.h> #include <strsafe.h> //for StringCchxxxx function //添加WTL支持 方便使用COM #include <wrl.h> using namespace Microsoft; using namespace Microsoft::WRL;#include <dxgi1_6.h> #include <d3d12.h> //for d3d12 //linker #pragma comment(lib, "dxguid.lib") #pragma comment(lib, "dxgi.lib") #pragma comment(lib, "d3d12.lib")#define GRS_THROW_IF_FAILED(hr) if (FAILED(hr)){ throw CGRSCOMException(hr); }class CGRSCOMException { public:CGRSCOMException(HRESULT hr) : m_hrError(hr){}HRESULT Error() const{return m_hrError;} private:const HRESULT m_hrError; };#define GRS_USEPRINTF() TCHAR pBuf[1024] = {};DWORD dwRead = 0; #define GRS_PRINTF(...) \StringCchPrintf(pBuf,1024,__VA_ARGS__);\WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE),pBuf,lstrlen(pBuf),NULL,NULL);int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow) {UINT nDXGIFactoryFlags = 0U;ComPtr<IDXGIFactory5> pIDXGIFactory5;ComPtr<IDXGIAdapter1> pIAdapter;ComPtr<ID3D12Device4> pID3DDevice;GRS_USEPRINTF();try{AllocConsole(); //打開窗口程序中的命令行窗口支持#if defined(_DEBUG){//打開顯示子系統的調試支持ComPtr<ID3D12Debug> debugController;if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)))){debugController->EnableDebugLayer();// 打開附加的調試支持nDXGIFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG;}} #endif//1、創建DXGI Factory對象GRS_THROW_IF_FAILED(CreateDXGIFactory2(nDXGIFactoryFlags, IID_PPV_ARGS(&pIDXGIFactory5)));//2、枚舉適配器,并檢測其架構及存儲情況DXGI_ADAPTER_DESC1 desc = {};D3D12_FEATURE_DATA_ARCHITECTURE stArchitecture = {};for (UINT adapterIndex = 0; DXGI_ERROR_NOT_FOUND != pIDXGIFactory5->EnumAdapters1(adapterIndex, &pIAdapter); ++adapterIndex){pIAdapter->GetDesc1(&desc);if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE){//跳過軟件虛擬適配器設備continue;}GRS_PRINTF(_T("顯卡[%d]-\"%s\":獨占顯存[%dMB]、獨占內存[%dMB]、共享內存[%dMB] "), adapterIndex, desc.Description, desc.DedicatedVideoMemory / (1024*1024), desc.DedicatedSystemMemory / (1024*1024), desc.SharedSystemMemory / (1024 * 1024));//創建設備,并檢測架構//檢測顯卡架構類型//D3D12_FEATURE_DATA_ARCHITECTURE.UMA = true一般為集顯//此時若D3D12_FEATURE_DATA_ARCHITECTURE.CacheCoherentUMA = true 則表示是CC-UMA架構 GPU和CPU通過高速緩存來交換數據//若UMA = FALSE 一般表示為獨顯,此時CacheCoherentUMA 無意義GRS_THROW_IF_FAILED(D3D12CreateDevice(pIAdapter.Get(), D3D_FEATURE_LEVEL_12_1, IID_PPV_ARGS(&pID3DDevice)));GRS_THROW_IF_FAILED(pID3DDevice->CheckFeatureSupport(D3D12_FEATURE_ARCHITECTURE, &stArchitecture, sizeof(D3D12_FEATURE_DATA_ARCHITECTURE)));if (stArchitecture.UMA){//UMAif ( stArchitecture.CacheCoherentUMA ){GRS_PRINTF(_T("架構為(CC-UMA)"));}else{GRS_PRINTF(_T("架構為(UMA)"));}}else{//NUMAGRS_PRINTF(_T("架構為(NUMA)"));}if ( stArchitecture.TileBasedRenderer ){GRS_PRINTF(_T(" 支持Tile-based方式渲染"));}pID3DDevice.Reset();GRS_PRINTF(_T("\n"));}::system("PAUSE");//釋放命令行環境,做個干凈的程序員FreeConsole();}catch (CGRSCOMException& e){//發生了COM異常e;}return 0; }獨立建立項目并運行上述程序后,在我的筆記本電腦上運行結果如下:
這實際與運行Dxdiag工具顯示的結果是相同的。當然這種在程序中使用代碼檢測的方式就要比使用獨立的工具的方式更靈活方便,所以建議大家一定要掌握這種代碼方式,主要是為了將來在封裝引擎或開發游戲中可以靈活使用。
當然你也可以使用上述代碼中的檢測手段更準確的判定多顯卡系統中究竟哪個適配器是獨顯,哪個適配器是集顯。這也是后續異構多顯卡渲染教程中將要使用的方法。建議你明白了這段代碼之后,可以在所有的例子中加入判定使用獨顯的判斷代碼,并使用獨顯來運行本系列教程中的例子。
3、創建默認堆并在其上以“定位方式”創建2D紋理
紋理(Txture)作為3D渲染中最重要的資源之一,操作紋理也是任何3D渲染程序都不可或缺的核心功能。因此我們很有必要牢牢掌握操作紋理的能力。
當然紋理一般在整個場景渲染過程中一般是不會變化的,所以我們優先考慮將它放置到顯存中,在D3D12中也就是將紋理放在默認堆上。并且之前的教程中我們已經學習了如何使用最簡單的“隱式堆”的方式來創建默認堆上的紋理資源,并且我們也說過了這種方式因為我們無法控制隱式堆的生命期,所以沒法通過重用緩沖的方式來提高系統性能。
說到這里我想你應該有點明白我為什么在本篇教程一開始啰嗦了一大堆顯卡系統架構的原因了。其實在這里我們可以將D3D12中的默認堆理解為在GPU顯存中的一塊緩沖,它對GPU來說就是“默認”的存儲位置,故名默認堆。因此放置其上的紋理以及資源對于GPU來說是擁有最高的訪問速度,當然付出的代價就是CPU是無法直接訪問GPU上的顯存的(注意前述架構中顯存只能由GPU訪問就明白了),所以我們就需要使用一個額外的上傳堆并利用復制引擎來加載紋理或其它數據。也正如你所想的上傳堆就不折不扣的在共享內存中了,這樣就使得CPU也能訪問它,GPU也能訪問它,當然最終為了從共享內存中把數據傳到顯存中,那么就需要GPU上的復制引擎來干活了。只是付出的代價就是我們需要協調CPU和GPU同時通知它們某段共享內存被用來當做上傳堆了,這甚至比CPU單獨管理內存需要額外付出一些性能代價。對于數據量不大,并且不會反復申請-釋放的程序來說這沒什么,有時候甚至感覺不到這種因為分配和釋放存儲而導致性能低下的問題。但是對于一些大型的游戲程序來說,尤其有不同關卡場景,不同物體的復雜應用來說,不可避免的就需要大量反復的申請-加載-釋放資源的調用。在D3D12之前的接口上,我們要想優化它幾乎是無能為力的。當然之前的教程中我們已經講過,聰明的游戲程序員是通過“化零為整”的方法來減少分配-釋放的次數從而提高性能的。當然如果你能夠在自己的引擎或游戲中綜合利用這兩種能力,那么最終帶來的性能提升一定是相當可觀的。
為了能夠控制這些被分配的數據緩沖(包括紋理)的生命周期,同時能夠通過反復重用,減少分配和釋放存儲的次數從而提高性能,D3D12中就提供了獨立的堆管理的接口。比如創建一個默認堆就可以像下面這樣寫代碼:
D3D12_HEAP_DESC stTextureHeapDesc = {}; //為堆指定紋理圖片至少2倍大小的空間,這里沒有詳細去計算了,只是指定了一個足夠大的空間,夠放紋理就行 //實際應用中也是要綜合考慮分配堆的大小,以便可以重用堆 stTextureHeapDesc.SizeInBytes = GRS_UPPER(2 * nPicRowPitch * nTextureH, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT); //指定堆的對齊方式,這里使用了默認的64K邊界對齊,因為我們暫時不需要MSAA支持 stTextureHeapDesc.Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT; stTextureHeapDesc.Properties.Type = D3D12_HEAP_TYPE_DEFAULT; //默認堆類型 stTextureHeapDesc.Properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; stTextureHeapDesc.Properties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; //拒絕渲染目標紋理、拒絕深度蠟板紋理,實際就只是用來擺放普通紋理 stTextureHeapDesc.Flags = D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES | D3D12_HEAP_FLAG_DENY_BUFFERS;GRS_THROW_IF_FAILED(pID3DDevice->CreateHeap(&stTextureHeapDesc, IID_PPV_ARGS(&pITextureHeap)));代碼中pITextureHeap的類型為ID3D12Heap*。這里再次強調一下雖然它的名字中帶有Heap,但它實際和我們使用的C/C++存儲管理中的堆棧是不同的。D3D12中的堆還是靜態大小的,即我們分配多大它就始終是多大,還沒法完全做到像C/C++中那樣利用realloc這樣的方法來動態改變大小。這主要是因為,如剛才所說這個默認堆的存儲雖然是在GPU的顯存中,但管理它其實是靠CPU的能力來與GPU通信完成的,它具有一定的復雜性和額外的開銷,所以還無法做到類似realloc這樣的功能。當然我想聰明的你一定不會被這個問題難道,事實上我們可以利用復制引擎來封裝一個具有realloc功能的動態堆出來,比如,我們需要將一塊默認堆增大的時候,就可以先重新創建一個更大的ID3D12Heap堆,再利用復制引擎將原來堆上的數據復制到新的更大的堆上,最后釋放原來的堆即可。其實這樣也還是要分配和釋放內存,所以功能實現了,但是性能實質上沒什么改進。最終我們現在可以采取的策略就是像上面示例代碼中這樣事先分配一塊盡可能大的存儲,然后想辦法重用它,而不是反復的分配和釋放浪費時間。
除了大小問題,其實上面代碼中還要求我們的存儲區域是邊界對齊的,目前對于一般的緩沖和紋理來說,D3D12中都要求64K邊界對齊,對應的宏就是D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT,而對于需要MSAA(抗鋸齒)采樣支持的紋理數據來說,就需要4M邊界對齊了,對應的宏是D3D12_DEFAULT_MSAA_RESOURCE_PLACEMENT_ALIGNMENT。如果你指定0,其默認含義一樣是64k邊界對齊。因此,我們對于緩沖區的大小這個參數使用了一個上對其計算,我將它定義成了宏GRS_UPPER,其定義如下:
#define GRS_UPPER(A,B) ((UINT)(((A)+((B)-1))&~(B - 1)))
這個算法與之前教程講到的(A+B-1)/B的上取整算法類似,只是它不用去做除法了,而是直接通過與非運算最大的余數,從而使數字上對齊,這個算法也希望各位能夠記住,因為對于任何與存儲管理相關的內容來說,這兩個算法都是最常用的。
當然你可以把它用作你的面試題,從而難住那些沒有看過我這些文章的應聘者,只是不要說是我說的就行。
這個宏的參數中,A表示我們需要的堆大小,B表示需要邊界對齊的大小,最終結果就是A上對齊到B邊界的大小。比如我們需要分配一塊100K大小的存儲,64k上對齊之后實質需要分配的是128K的內存大小。最后之所以需要存儲邊界對齊,是因為現代GPU是一個大的SIMD處理器也是RISC架構的,訪問邊界對齊的存儲具有巨大的性能優勢,甚至它們都不支持任意位置內存訪問,必須以邊界對齊的格式化的方式訪問存儲。而現代的x86架構的CPU則基本沒有這個限制,當然代價就是其指令的復雜性和一定的性能損失,而優勢就是在軟件編寫上尤其是變量存儲分配上基本沒有什么限制。因此也可以這樣認為,如果我們在軟件編寫上有什么奇怪的限制時,往往其實是因為底層硬件設計使然。這也就是需要學習一些底層硬件架構知識的原因。
結構體D3D12_HEAP_DESC中的Properties參數,則是用來說明堆的類型的,當Type字段是D3D12_HEAP_TYPE_DEFAULT(默認堆),? D3D12_HEAP_TYPE_UPLOAD(上傳堆),D3D12_HEAP_TYPE_READBACK(回讀堆),后面兩個參數CPUPageProperty,MemoryPoolPreference就像這里這樣賦值即可。或者理解為這些堆類型已經是系統預制好的,后面的參數在這些情況下實質上是沒有意義的。
只有當Type是D3D12_HEAP_TYPE_CUSTOM時我們才能指定后面兩個參數的值。通常這時我們需要指定CPUPageProperty就是CPU對這個堆內存的訪問特性,還有就是通過MemoryPoolPreference指定在那個存儲中——共享內存還是顯存中。對于自定義堆將放在后續的教程中酌情進行講解,現在我們暫時只關注前三種堆的類型。
最后我們需要指定的就是D3D12_HEAP_DESC 結構體的Flags參數,它的含義就是明確描述清楚這個堆上將用來存儲什么資源。在這里我們指定的是拒絕屬性,即拒絕放置渲染目標紋理和深度蠟板緩沖紋理以及普通緩沖,其含義就是說我們這個默認堆只是用來放置普通紋理的(因為普通紋理沒有被拒絕)。當然這個標志值有點怪異,必須要反過來思考問題,即我們把不讓放置的資源類型都拒絕掉,那么允許放置的就只有沒被拒絕的類型了。當然這里還有個更奇怪的約定就是代碼中使用的兩個拒絕(Deny)類型必須要一起設置,單獨設置一個是不被允許的。我想關于這個Flags字段的這些奇怪約定和值,應該是GPU特殊要求的,才會是這樣一個嚴重反人類的樣子。
其實上述代碼中真正需要我們關注的正是這個Flags標志,后續通過它我們還可以指定獨立堆應該是多個GPU共享的形式,具體的一些用法我們將逐步的講解,就不在這里一下子全部告訴大家,防止因為概念太多而不好理解和記憶。它實在是設計的太反人類了!
最后一步,我們就是使用ID3D12Device::CreateHeap方法來創建獨立的默認堆。
默認堆創建好之后,我們就使用CreatePlacedResource方法來創建具體的紋理資源了,代碼如下:
stTextureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; stTextureDesc.MipLevels = 1; stTextureDesc.Format = stTextureFormat; //DXGI_FORMAT_R8G8B8A8_UNORM; stTextureDesc.Width = nTextureW; stTextureDesc.Height = nTextureH; stTextureDesc.Flags = D3D12_RESOURCE_FLAG_NONE; stTextureDesc.DepthOrArraySize = 1; stTextureDesc.SampleDesc.Count = 1; stTextureDesc.SampleDesc.Quality = 0;//----------------------------------------------------------------------------------------------------------- 創建默認堆上的資源,類型是Texture2D,GPU對默認堆資源的訪問速度是最快的 因為紋理資源一般是不易變的資源,所以我們通常使用上傳堆復制到默認堆中 在傳統的D3D11及以前的D3D接口中,這些過程都被封裝了,我們只能指定創建時的類型為默認堆 //GRS_THROW_IF_FAILED(pID3DDevice->CreateCommittedResource( // &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT) // , D3D12_HEAP_FLAG_NONE // , &stTextureDesc //可以使用CD3DX12_RESOURCE_DESC::Tex2D來簡化結構體的初始化 // , D3D12_RESOURCE_STATE_COPY_DEST // , nullptr // , IID_PPV_ARGS(&pITexture))); //----------------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------------- //使用“定位方式”來創建紋理,注意下面這個調用內部實際已經沒有存儲分配和釋放的實際操作了,所以性能很高 //同時可以在這個堆上反復調用CreatePlacedResource來創建不同的紋理,當然前提是它們不在被使用的時候,才考慮重用堆 GRS_THROW_IF_FAILED(pID3DDevice->CreatePlacedResource(pITextureHeap.Get(), 0, &stTextureDesc //可以使用CD3DX12_RESOURCE_DESC::Tex2D來簡化結構體的初始化, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&pITexture))); //-----------------------------------------------------------------------------------------------------------//獲取上傳堆資源緩沖的大小,這個尺寸通常大于實際圖片的尺寸 n64UploadBufferSize = GetRequiredIntermediateSize(pITexture.Get(), 0, 1);代碼中的注釋已經說明了這段代碼的一些要點。重要的就是要注意第二個參數,不再是Flags了,而是指定從堆開始處算起的偏移量,單位是字節。主要用于在一個堆上不斷的Placed(放置)不同的數據。當然它也必須要邊界對齊,必須是64K或者4M的整數倍。
默認堆上的2D紋理創建完了,我們就需要創建獨立的上傳堆了,與創建默認堆類似,其代碼如下:
D3D12_HEAP_DESC stUploadHeapDesc = { }; //尺寸依然是實際紋理數據大小的2倍并64K邊界對齊大小 stUploadHeapDesc.SizeInBytes = GRS_UPPER(2 * n64UploadBufferSize, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT); //注意上傳堆肯定是Buffer類型,可以不指定對齊方式,其默認是64k邊界對齊 stUploadHeapDesc.Alignment = 0; stUploadHeapDesc.Properties.Type = D3D12_HEAP_TYPE_UPLOAD; //上傳堆類型 stUploadHeapDesc.Properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; stUploadHeapDesc.Properties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; //上傳堆就是緩沖,可以擺放任意數據 stUploadHeapDesc.Flags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS;GRS_THROW_IF_FAILED(pID3DDevice->CreateHeap(&stUploadHeapDesc, IID_PPV_ARGS(&pIUploadHeap)));這段代碼與之前的類似,需要注意的就是Flags標志,這次我們又正面的指定只允許緩沖的類型D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS,堆大小我們特意設置為兩倍紋理圖片數據的大小,并且上邊界對齊。再一次領教它的反人類設計!
接著我們就可以像下面這樣首先來創建上傳堆上的紋理資源緩沖了:
GRS_THROW_IF_FAILED(pID3DDevice->CreatePlacedResource(pIUploadHeap.Get(), 0, &CD3DX12_RESOURCE_DESC::Buffer(n64UploadBufferSize), D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&pITextureUpload)));從上面代碼我們可以看到其實只使用了一般不到的區域用來做我們的紋理資源數據緩沖了,那么剩下的部分如果不用的話就浪費了,所以我們可以接著象下面這樣,把頂點緩沖也放在上面:
GRS_THROW_IF_FAILED(pID3DDevice->CreatePlacedResource(pIUploadHeap.Get(), GRS_UPPER(n64UploadBufferSize, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT), &CD3DX12_RESOURCE_DESC::Buffer(nVertexBufferSize), D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&pIVertexBuffer)));那么請注意第二個參數,我們設置了偏移量為紋理資源大小上邊界對齊的位置,這里的對齊要求是必須的。從內存視圖上看的話,其實就是我們將頂點緩沖區放在了需要上傳的紋理資源圖片數據的后面。這樣我們就省去了一次頂點緩沖存儲的實際的申請和釋放操作,提高了效率和性能。
4、動態采樣器
在第二篇教程中,我們已經演示和學習了如何使用根簽名的靜態參數化的方式來指定一個采樣器,如當時我們所說,靜態的采樣器是不能隨便改變的,如果要改變的話我們就需要重新創建一個根簽名對象,這對于采樣器的管理以及渲染管線狀態的管理來說都有些太啰嗦了。所以我們這次就使用純動態的方式來創建采樣器。同時為了對采樣器的類型建立一些初步的概念,所以我們就一次性創建了五個采樣器。
要使用動態采樣器,那么首先我們需要的就是創建一個采樣器的描述符堆,代碼如下:
D3D12_DESCRIPTOR_HEAP_DESC stSamplerHeapDesc = {}; stSamplerHeapDesc.NumDescriptors = nSampleMaxCnt; stSamplerHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER; stSamplerHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;GRS_THROW_IF_FAILED(pID3DDevice->CreateDescriptorHeap(&stSamplerHeapDesc,IID_PPV_ARGS(&pISamplerDescriptorHeap)));nSamplerDescriptorSize = pID3DDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER);CD3DX12_CPU_DESCRIPTOR_HANDLE hSamplerHeap(pISamplerDescriptorHeap->GetCPUDescriptorHandleForHeapStart());D3D12_SAMPLER_DESC stSamplerDesc = {}; stSamplerDesc.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;stSamplerDesc.MinLOD = 0; stSamplerDesc.MaxLOD = D3D12_FLOAT32_MAX; stSamplerDesc.MipLODBias = 0.0f; stSamplerDesc.MaxAnisotropy = 1; stSamplerDesc.ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS;// Sampler 1 stSamplerDesc.BorderColor[0] = 1.0f; stSamplerDesc.BorderColor[1] = 0.0f; stSamplerDesc.BorderColor[2] = 1.0f; stSamplerDesc.BorderColor[3] = 1.0f; stSamplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_BORDER; stSamplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_BORDER; stSamplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_BORDER; pID3DDevice->CreateSampler(&stSamplerDesc, hSamplerHeap);hSamplerHeap.Offset(nSamplerDescriptorSize);// Sampler 2 stSamplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP; stSamplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP; stSamplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP; pID3DDevice->CreateSampler(&stSamplerDesc, hSamplerHeap);hSamplerHeap.Offset(nSamplerDescriptorSize);// Sampler 3 stSamplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; stSamplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; stSamplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; pID3DDevice->CreateSampler(&stSamplerDesc, hSamplerHeap);hSamplerHeap.Offset(nSamplerDescriptorSize);// Sampler 4 stSamplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_MIRROR; stSamplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_MIRROR; stSamplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_MIRROR; pID3DDevice->CreateSampler(&stSamplerDesc, hSamplerHeap);hSamplerHeap.Offset(nSamplerDescriptorSize);// Sampler 5 stSamplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_MIRROR_ONCE; stSamplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_MIRROR_ONCE; stSamplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_MIRROR_ONCE; pID3DDevice->CreateSampler(&stSamplerDesc, hSamplerHeap);與靜態采樣器類似我們都需要填充采樣器描述信息的結構體,這里主要是區別下每個紋理坐標上具體的溢出后的采樣方式(本例中我們刻意將最大紋理坐標擴大到了3.0f)。
這些類型我就不再具體啰嗦了,如果你不知道可以百度一下其他教程,或者你可以直接運行和調試本章的例子程序加深理解。如果不太懂紋理,那么先建立一個感性認識也是好的。
接著我們的要做的就是在創建根簽名時明確指出我們需要的是動態的采樣器,代碼如下:
D3D12_FEATURE_DATA_ROOT_SIGNATURE stFeatureData = {}; // 檢測是否支持V1.1版本的根簽名 stFeatureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_1; if (FAILED(pID3DDevice->CheckFeatureSupport(D3D12_FEATURE_ROOT_SIGNATURE, &stFeatureData, sizeof(stFeatureData)))) {stFeatureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_0; } // 在GPU上執行SetGraphicsRootDescriptorTable后,我們不修改命令列表中的SRV,因此我們可以使用默認Rang行為: // D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC_WHILE_SET_AT_EXECUTE CD3DX12_DESCRIPTOR_RANGE1 stDSPRanges[2]; stDSPRanges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0, 0, D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC_WHILE_SET_AT_EXECUTE); stDSPRanges[1].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 1, 0);CD3DX12_ROOT_PARAMETER1 stRootParameters[2]; stRootParameters[0].InitAsDescriptorTable(1, &stDSPRanges[0], D3D12_SHADER_VISIBILITY_PIXEL); stRootParameters[1].InitAsDescriptorTable(1, &stDSPRanges[1], D3D12_SHADER_VISIBILITY_PIXEL);//--------------------------------------------------------------------------------------------- //靜態的采樣器不用了,我們使用動態的在堆上創建的采樣器 //D3D12_STATIC_SAMPLER_DESC stSamplerDesc = {}; //stSamplerDesc.Filter = D3D12_FILTER_MIN_MAG_MIP_POINT; //stSamplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_BORDER; //stSamplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_BORDER; //stSamplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_BORDER; //stSamplerDesc.MipLODBias = 0; //stSamplerDesc.MaxAnisotropy = 0; //stSamplerDesc.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER; //stSamplerDesc.BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK; //stSamplerDesc.MinLOD = 0.0f; //stSamplerDesc.MaxLOD = D3D12_FLOAT32_MAX; //stSamplerDesc.ShaderRegister = 0; //stSamplerDesc.RegisterSpace = 0; //stSamplerDesc.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; //---------------------------------------------------------------------------------------------CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC stRootSignatureDesc; //stRootSignatureDesc.Init_1_1(_countof(stRootParameters), stRootParameters // , 1, &stSamplerDesc // , D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);stRootSignatureDesc.Init_1_1(_countof(stRootParameters), stRootParameters, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);ComPtr<ID3DBlob> pISignatureBlob; ComPtr<ID3DBlob> pIErrorBlob; GRS_THROW_IF_FAILED(D3DX12SerializeVersionedRootSignature(&stRootSignatureDesc, stFeatureData.HighestVersion, &pISignatureBlob, &pIErrorBlob));GRS_THROW_IF_FAILED(pID3DDevice->CreateRootSignature(0, pISignatureBlob->GetBufferPointer(), pISignatureBlob->GetBufferSize(), IID_PPV_ARGS(&pIRootSignature)));我們可以看到,根簽名中多了兩個根參數,分別指向一個SRV和一個Sample的描述符堆,這樣我們在具體渲染時就需要指定具體的Sample堆了,代碼如下:
ID3D12DescriptorHeap* ppHeaps[] = { pISRVHeap.Get(),pISamplerDescriptorHeap.Get()}; pICommandList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps); pICommandList->SetGraphicsRootDescriptorTable(0, pISRVHeap->GetGPUDescriptorHandleForHeapStart());CD3DX12_GPU_DESCRIPTOR_HANDLE hGPUSampler(pISamplerDescriptorHeap->GetGPUDescriptorHandleForHeapStart(), nCurrentSamplerNO, nSamplerDescriptorSize); pICommandList->SetGraphicsRootDescriptorTable(1, hGPUSampler);首先與之前的例子一樣,我們先把所有的描述符堆的對象指針編成一個數組整體的設置到命令列表中,實質也就是指定到渲染管線上去,然后我們在按照根參數中描述的對應序號挨個設定每個參數對應的描述符堆是哪個。這里實質上設定的是GPU地址空間中的首地址,從其結構名字即可理解這一點,因為最終渲染是GPU來完成的,所以它當然需要知道的是自己的地址空間中的指針值。
另外為了動態切換每種采樣器,我們實現了一個簡單的功能就是按空格鍵來切換當前采樣器的序號,然后偏移到指定序號的采樣器首地址處,再將它設置到管線上,當然這里就是發出一個命令,最終執行是需要在命令隊列中排隊等待圖形引擎順序執行的。
這里再次強調一下,采樣器描述符堆跟其他的描述符堆一樣,本質上實際是個數組,不要被Heap這個名字給迷惑了。整個過程簡單的理解,就是我們創建了一個有5個元素的數組,然后按數組索引號設置不同的數組元素的首地址作為當前使用的描述符堆的首地址而已。只是因為代碼的復雜性掩蓋了數組操作的本質而已。
最后關于使用空格鍵改變序號的功能和其他的一些細節就請大家查看最后的完整代碼了。還有不清楚的地方請及時留意垂詢。
5、完整代碼
#include <SDKDDKVer.h> #define WIN32_LEAN_AND_MEAN // 從 Windows 頭中排除極少使用的資料 #include <windows.h> #include <tchar.h> //添加WTL支持 方便使用COM #include <wrl.h> using namespace Microsoft; using namespace Microsoft::WRL;#include <dxgi1_6.h> #include <DirectXMath.h> using namespace DirectX; //for d3d12 #include <d3d12.h> #include <d3dcompiler.h>//linker #pragma comment(lib, "dxguid.lib") #pragma comment(lib, "dxgi.lib") #pragma comment(lib, "d3d12.lib") #pragma comment(lib, "d3dcompiler.lib")#if defined(_DEBUG) #include <dxgidebug.h> #endif//for WIC #include <wincodec.h>#include "..\WindowsCommons\d3dx12.h"#define GRS_WND_CLASS_NAME _T("Game Window Class") #define GRS_WND_TITLE _T("DirectX12 Texture Sample")#define GRS_THROW_IF_FAILED(hr) if (FAILED(hr)){ throw CGRSCOMException(hr); }//新定義的宏用于上取整除法 #define GRS_UPPER_DIV(A,B) ((UINT)(((A)+((B)-1))/(B)))//更簡潔的向上邊界對齊算法 內存管理中常用 請記住 #define GRS_UPPER(A,B) ((UINT)(((A)+((B)-1))&~(B - 1)))class CGRSCOMException { public:CGRSCOMException(HRESULT hr) : m_hrError(hr){}HRESULT Error() const{return m_hrError;} private:const HRESULT m_hrError; };struct WICTranslate {GUID wic;DXGI_FORMAT format; };static WICTranslate g_WICFormats[] = {//WIC格式與DXGI像素格式的對應表,該表中的格式為被支持的格式{ GUID_WICPixelFormat128bppRGBAFloat, DXGI_FORMAT_R32G32B32A32_FLOAT },{ GUID_WICPixelFormat64bppRGBAHalf, DXGI_FORMAT_R16G16B16A16_FLOAT },{ GUID_WICPixelFormat64bppRGBA, DXGI_FORMAT_R16G16B16A16_UNORM },{ GUID_WICPixelFormat32bppRGBA, DXGI_FORMAT_R8G8B8A8_UNORM },{ GUID_WICPixelFormat32bppBGRA, DXGI_FORMAT_B8G8R8A8_UNORM }, // DXGI 1.1{ GUID_WICPixelFormat32bppBGR, DXGI_FORMAT_B8G8R8X8_UNORM }, // DXGI 1.1{ GUID_WICPixelFormat32bppRGBA1010102XR, DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM }, // DXGI 1.1{ GUID_WICPixelFormat32bppRGBA1010102, DXGI_FORMAT_R10G10B10A2_UNORM },{ GUID_WICPixelFormat16bppBGRA5551, DXGI_FORMAT_B5G5R5A1_UNORM },{ GUID_WICPixelFormat16bppBGR565, DXGI_FORMAT_B5G6R5_UNORM },{ GUID_WICPixelFormat32bppGrayFloat, DXGI_FORMAT_R32_FLOAT },{ GUID_WICPixelFormat16bppGrayHalf, DXGI_FORMAT_R16_FLOAT },{ GUID_WICPixelFormat16bppGray, DXGI_FORMAT_R16_UNORM },{ GUID_WICPixelFormat8bppGray, DXGI_FORMAT_R8_UNORM },{ GUID_WICPixelFormat8bppAlpha, DXGI_FORMAT_A8_UNORM }, };// WIC 像素格式轉換表. struct WICConvert {GUID source;GUID target; };static WICConvert g_WICConvert[] = {// 目標格式一定是最接近的被支持的格式{ GUID_WICPixelFormatBlackWhite, GUID_WICPixelFormat8bppGray }, // DXGI_FORMAT_R8_UNORM{ GUID_WICPixelFormat1bppIndexed, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM{ GUID_WICPixelFormat2bppIndexed, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM{ GUID_WICPixelFormat4bppIndexed, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM{ GUID_WICPixelFormat8bppIndexed, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM{ GUID_WICPixelFormat2bppGray, GUID_WICPixelFormat8bppGray }, // DXGI_FORMAT_R8_UNORM{ GUID_WICPixelFormat4bppGray, GUID_WICPixelFormat8bppGray }, // DXGI_FORMAT_R8_UNORM{ GUID_WICPixelFormat16bppGrayFixedPoint, GUID_WICPixelFormat16bppGrayHalf }, // DXGI_FORMAT_R16_FLOAT{ GUID_WICPixelFormat32bppGrayFixedPoint, GUID_WICPixelFormat32bppGrayFloat }, // DXGI_FORMAT_R32_FLOAT{ GUID_WICPixelFormat16bppBGR555, GUID_WICPixelFormat16bppBGRA5551 }, // DXGI_FORMAT_B5G5R5A1_UNORM{ GUID_WICPixelFormat32bppBGR101010, GUID_WICPixelFormat32bppRGBA1010102 }, // DXGI_FORMAT_R10G10B10A2_UNORM{ GUID_WICPixelFormat24bppBGR, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM{ GUID_WICPixelFormat24bppRGB, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM{ GUID_WICPixelFormat32bppPBGRA, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM{ GUID_WICPixelFormat32bppPRGBA, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM{ GUID_WICPixelFormat48bppRGB, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM{ GUID_WICPixelFormat48bppBGR, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM{ GUID_WICPixelFormat64bppBGRA, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM{ GUID_WICPixelFormat64bppPRGBA, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM{ GUID_WICPixelFormat64bppPBGRA, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM{ GUID_WICPixelFormat48bppRGBFixedPoint, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT{ GUID_WICPixelFormat48bppBGRFixedPoint, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT{ GUID_WICPixelFormat64bppRGBAFixedPoint, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT{ GUID_WICPixelFormat64bppBGRAFixedPoint, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT{ GUID_WICPixelFormat64bppRGBFixedPoint, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT{ GUID_WICPixelFormat48bppRGBHalf, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT{ GUID_WICPixelFormat64bppRGBHalf, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT{ GUID_WICPixelFormat128bppPRGBAFloat, GUID_WICPixelFormat128bppRGBAFloat }, // DXGI_FORMAT_R32G32B32A32_FLOAT{ GUID_WICPixelFormat128bppRGBFloat, GUID_WICPixelFormat128bppRGBAFloat }, // DXGI_FORMAT_R32G32B32A32_FLOAT{ GUID_WICPixelFormat128bppRGBAFixedPoint, GUID_WICPixelFormat128bppRGBAFloat }, // DXGI_FORMAT_R32G32B32A32_FLOAT{ GUID_WICPixelFormat128bppRGBFixedPoint, GUID_WICPixelFormat128bppRGBAFloat }, // DXGI_FORMAT_R32G32B32A32_FLOAT{ GUID_WICPixelFormat32bppRGBE, GUID_WICPixelFormat128bppRGBAFloat }, // DXGI_FORMAT_R32G32B32A32_FLOAT{ GUID_WICPixelFormat32bppCMYK, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM{ GUID_WICPixelFormat64bppCMYK, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM{ GUID_WICPixelFormat40bppCMYKAlpha, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM{ GUID_WICPixelFormat80bppCMYKAlpha, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM{ GUID_WICPixelFormat32bppRGB, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM{ GUID_WICPixelFormat64bppRGB, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM{ GUID_WICPixelFormat64bppPRGBAHalf, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT };bool GetTargetPixelFormat(const GUID* pSourceFormat, GUID* pTargetFormat) {//查表確定兼容的最接近格式是哪個*pTargetFormat = *pSourceFormat;for (size_t i = 0; i < _countof(g_WICConvert); ++i){if (InlineIsEqualGUID(g_WICConvert[i].source, *pSourceFormat)){*pTargetFormat = g_WICConvert[i].target;return true;}}return false; }DXGI_FORMAT GetDXGIFormatFromPixelFormat(const GUID* pPixelFormat) {//查表確定最終對應的DXGI格式是哪一個for (size_t i = 0; i < _countof(g_WICFormats); ++i){if (InlineIsEqualGUID(g_WICFormats[i].wic, *pPixelFormat)){return g_WICFormats[i].format;}}return DXGI_FORMAT_UNKNOWN; }struct GRS_VERTEX {XMFLOAT3 m_vPos; //PositionXMFLOAT2 m_vTxc; //Texcoord };UINT nCurrentSamplerNO = 0; //當前使用的采樣器索引 UINT nSampleMaxCnt = 5; //創建五個典型的采樣器LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow) {::CoInitialize(nullptr); //for WIC & COMconst UINT nFrameBackBufCount = 3u;int iWidth = 1024;int iHeight = 768;UINT nFrameIndex = 0;UINT nFrame = 0;UINT nDXGIFactoryFlags = 0U;UINT nRTVDescriptorSize = 0U;HWND hWnd = nullptr;MSG msg = {};float fAspectRatio = 3.0f;D3D12_VERTEX_BUFFER_VIEW stVertexBufferView = {};UINT64 n64FenceValue = 0ui64;HANDLE hFenceEvent = nullptr;UINT nTextureW = 0u;UINT nTextureH = 0u;UINT nBPP = 0u;UINT nPicRowPitch = 0;UINT64 n64UploadBufferSize = 0;DXGI_FORMAT stTextureFormat = DXGI_FORMAT_UNKNOWN;D3D12_PLACED_SUBRESOURCE_FOOTPRINT stTxtLayouts = {};D3D12_RESOURCE_DESC stTextureDesc = {};D3D12_RESOURCE_DESC stDestDesc = {};UINT nSamplerDescriptorSize = 0; //采樣器大小CD3DX12_VIEWPORT stViewPort(0.0f, 0.0f, static_cast<float>(iWidth), static_cast<float>(iHeight));CD3DX12_RECT stScissorRect(0, 0, static_cast<LONG>(iWidth), static_cast<LONG>(iHeight));ComPtr<IDXGIFactory5> pIDXGIFactory5;ComPtr<IDXGIAdapter1> pIAdapter;ComPtr<ID3D12Device4> pID3DDevice;ComPtr<ID3D12CommandQueue> pICommandQueue;ComPtr<ID3D12CommandAllocator> pICommandAllocator;ComPtr<ID3D12GraphicsCommandList> pICommandList;ComPtr<IDXGISwapChain1> pISwapChain1;ComPtr<IDXGISwapChain3> pISwapChain3;ComPtr<ID3D12Resource> pIARenderTargets[nFrameBackBufCount];ComPtr<ID3D12DescriptorHeap> pIRTVHeap;ComPtr<ID3D12Heap> pITextureHeap;ComPtr<ID3D12Heap> pIUploadHeap;ComPtr<ID3D12Resource> pITexture;ComPtr<ID3D12Resource> pITextureUpload;ComPtr<ID3D12Resource> pIVertexBuffer;ComPtr<ID3D12DescriptorHeap> pISRVHeap;ComPtr<ID3D12DescriptorHeap> pISamplerDescriptorHeap;ComPtr<ID3D12Fence> pIFence;ComPtr<ID3DBlob> pIBlobVertexShader;ComPtr<ID3DBlob> pIBlobPixelShader;ComPtr<ID3D12RootSignature> pIRootSignature;ComPtr<ID3D12PipelineState> pIPipelineState;ComPtr<IWICImagingFactory> pIWICFactory;ComPtr<IWICBitmapDecoder> pIWICDecoder;ComPtr<IWICBitmapFrameDecode> pIWICFrame;ComPtr<IWICBitmapSource> pIBMP;try{//1、創建窗口{//---------------------------------------------------------------------------------------------WNDCLASSEX wcex = {};wcex.cbSize = sizeof(WNDCLASSEX);wcex.style = CS_GLOBALCLASS;wcex.lpfnWndProc = WndProc;wcex.cbClsExtra = 0;wcex.cbWndExtra = 0;wcex.hInstance = hInstance;wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);wcex.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH); //防止無聊的背景重繪wcex.lpszClassName = GRS_WND_CLASS_NAME;RegisterClassEx(&wcex);DWORD dwWndStyle = WS_OVERLAPPED | WS_SYSMENU;RECT rtWnd = { 0, 0, iWidth, iHeight };AdjustWindowRect(&rtWnd, dwWndStyle, FALSE);hWnd = CreateWindowW(GRS_WND_CLASS_NAME, GRS_WND_TITLE, dwWndStyle, CW_USEDEFAULT, 0, rtWnd.right - rtWnd.left, rtWnd.bottom - rtWnd.top, nullptr, nullptr, hInstance, nullptr);if (!hWnd){return FALSE;}ShowWindow(hWnd, nCmdShow);UpdateWindow(hWnd);}//2、使用WIC創建并加載一個圖片,并轉換為DXGI兼容的格式{//---------------------------------------------------------------------------------------------//使用純COM方式創建WIC類廠對象,也是調用WIC第一步要做的事情GRS_THROW_IF_FAILED(CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pIWICFactory)));//使用WIC類廠對象接口加載紋理圖片,并得到一個WIC解碼器對象接口,圖片信息就在這個接口代表的對象中了WCHAR* pszTexcuteFileName = _T("D:\\Projects_2018_08\\D3D12 Tutorials\\2-D3D12WICTexture\\Texture\\bear.jpg");GRS_THROW_IF_FAILED(pIWICFactory->CreateDecoderFromFilename(pszTexcuteFileName, // 文件名NULL, // 不指定解碼器,使用默認GENERIC_READ, // 訪問權限WICDecodeMetadataCacheOnDemand, // 若需要就緩沖數據 &pIWICDecoder // 解碼器對象));// 獲取第一幀圖片(因為GIF等格式文件可能會有多幀圖片,其他的格式一般只有一幀圖片)// 實際解析出來的往往是位圖格式數據GRS_THROW_IF_FAILED(pIWICDecoder->GetFrame(0, &pIWICFrame));WICPixelFormatGUID wpf = {};//獲取WIC圖片格式GRS_THROW_IF_FAILED(pIWICFrame->GetPixelFormat(&wpf));GUID tgFormat = {};//通過第一道轉換之后獲取DXGI的等價格式if (GetTargetPixelFormat(&wpf, &tgFormat)){stTextureFormat = GetDXGIFormatFromPixelFormat(&tgFormat);}if (DXGI_FORMAT_UNKNOWN == stTextureFormat){// 不支持的圖片格式 目前退出了事 // 一般 在實際的引擎當中都會提供紋理格式轉換工具,// 圖片都需要提前轉換好,所以不會出現不支持的現象throw CGRSCOMException(S_FALSE);}if (!InlineIsEqualGUID(wpf, tgFormat)){// 這個判斷很重要,如果原WIC格式不是直接能轉換為DXGI格式的圖片時// 我們需要做的就是轉換圖片格式為能夠直接對應DXGI格式的形式//創建圖片格式轉換器ComPtr<IWICFormatConverter> pIConverter;GRS_THROW_IF_FAILED(pIWICFactory->CreateFormatConverter(&pIConverter));//初始化一個圖片轉換器,實際也就是將圖片數據進行了格式轉換GRS_THROW_IF_FAILED(pIConverter->Initialize(pIWICFrame.Get(), // 輸入原圖片數據tgFormat, // 指定待轉換的目標格式WICBitmapDitherTypeNone, // 指定位圖是否有調色板,現代都是真彩位圖,不用調色板,所以為NoneNULL, // 指定調色板指針0.f, // 指定Alpha閥值WICBitmapPaletteTypeCustom // 調色板類型,實際沒有使用,所以指定為Custom));// 調用QueryInterface方法獲得對象的位圖數據源接口GRS_THROW_IF_FAILED(pIConverter.As(&pIBMP));}else{//圖片數據格式不需要轉換,直接獲取其位圖數據源接口GRS_THROW_IF_FAILED(pIWICFrame.As(&pIBMP));}//獲得圖片大小(單位:像素)GRS_THROW_IF_FAILED(pIBMP->GetSize(&nTextureW, &nTextureH));//獲取圖片像素的位大小的BPP(Bits Per Pixel)信息,用以計算圖片行數據的真實大小(單位:字節)ComPtr<IWICComponentInfo> pIWICmntinfo;GRS_THROW_IF_FAILED(pIWICFactory->CreateComponentInfo(tgFormat, pIWICmntinfo.GetAddressOf()));WICComponentType type;GRS_THROW_IF_FAILED(pIWICmntinfo->GetComponentType(&type));if (type != WICPixelFormat){throw CGRSCOMException(S_FALSE);}ComPtr<IWICPixelFormatInfo> pIWICPixelinfo;GRS_THROW_IF_FAILED(pIWICmntinfo.As(&pIWICPixelinfo));// 到這里終于可以得到BPP了,這也是我看的比較吐血的地方,為了BPP居然饒了這么多環節GRS_THROW_IF_FAILED(pIWICPixelinfo->GetBitsPerPixel(&nBPP));// 計算圖片實際的行大小(單位:字節),這里使用了一個上取整除法即(A+B-1)/B ,// 這曾經被傳說是微軟的面試題,希望你已經對它了如指掌nPicRowPitch = GRS_UPPER_DIV(uint64_t(nTextureW) * uint64_t(nBPP), 8);}//3、打開顯示子系統的調試支持{ #if defined(_DEBUG)ComPtr<ID3D12Debug> debugController;if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)))){debugController->EnableDebugLayer();// 打開附加的調試支持nDXGIFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG;} #endif}//4、創建DXGI Factory對象{GRS_THROW_IF_FAILED(CreateDXGIFactory2(nDXGIFactoryFlags, IID_PPV_ARGS(&pIDXGIFactory5)));// 關閉ALT+ENTER鍵切換全屏的功能,因為我們沒有實現OnSize處理,所以先關閉GRS_THROW_IF_FAILED(pIDXGIFactory5->MakeWindowAssociation(hWnd, DXGI_MWA_NO_ALT_ENTER));}//5、枚舉適配器,并選擇合適的適配器來創建3D設備對象{for (UINT adapterIndex = 1; DXGI_ERROR_NOT_FOUND != pIDXGIFactory5->EnumAdapters1(adapterIndex, &pIAdapter); ++adapterIndex){DXGI_ADAPTER_DESC1 desc = {};pIAdapter->GetDesc1(&desc);if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE){//跳過軟件虛擬適配器設備continue;}//檢查適配器對D3D支持的兼容級別,這里直接要求支持12.1的能力,注意返回接口的那個參數被置為了nullptr,這樣//就不會實際創建一個設備了,也不用我們啰嗦的再調用release來釋放接口。這也是一個重要的技巧,請記住!if (SUCCEEDED(D3D12CreateDevice(pIAdapter.Get(), D3D_FEATURE_LEVEL_12_1, _uuidof(ID3D12Device), nullptr))){break;}}//創建D3D12.1的設備GRS_THROW_IF_FAILED(D3D12CreateDevice(pIAdapter.Get(), D3D_FEATURE_LEVEL_12_1, IID_PPV_ARGS(&pID3DDevice)));}//6、創建直接命令隊列{D3D12_COMMAND_QUEUE_DESC stQueueDesc = {};stQueueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;GRS_THROW_IF_FAILED(pID3DDevice->CreateCommandQueue(&stQueueDesc, IID_PPV_ARGS(&pICommandQueue)));}//7、創建直接命令列表{GRS_THROW_IF_FAILED(pID3DDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&pICommandAllocator)));//創建直接命令列表,在其上可以執行幾乎所有的引擎命令(3D圖形引擎、計算引擎、復制引擎等)GRS_THROW_IF_FAILED(pID3DDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, pICommandAllocator.Get(),nullptr, IID_PPV_ARGS(&pICommandList)));}//8、創建交換鏈{DXGI_SWAP_CHAIN_DESC1 stSwapChainDesc = {};stSwapChainDesc.BufferCount = nFrameBackBufCount;stSwapChainDesc.Width = iWidth;stSwapChainDesc.Height = iHeight;stSwapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;stSwapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;stSwapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;stSwapChainDesc.SampleDesc.Count = 1;GRS_THROW_IF_FAILED(pIDXGIFactory5->CreateSwapChainForHwnd(pICommandQueue.Get(), // Swap chain needs the queue so that it can force a flush on it.hWnd,&stSwapChainDesc,nullptr,nullptr,&pISwapChain1));}//9、得到當前后緩沖區的序號,也就是下一個將要呈送顯示的緩沖區的序號{//注意此處使用了高版本的SwapChain接口的函數GRS_THROW_IF_FAILED(pISwapChain1.As(&pISwapChain3));nFrameIndex = pISwapChain3->GetCurrentBackBufferIndex();}//10、創建RTV的描述符{//創建RTV(渲染目標視圖)描述符堆(這里堆的含義應當理解為數組或者固定大小元素的固定大小顯存池)D3D12_DESCRIPTOR_HEAP_DESC stRTVHeapDesc = {};stRTVHeapDesc.NumDescriptors = nFrameBackBufCount;stRTVHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;stRTVHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;GRS_THROW_IF_FAILED(pID3DDevice->CreateDescriptorHeap(&stRTVHeapDesc, IID_PPV_ARGS(&pIRTVHeap)));//得到每個描述符元素的大小nRTVDescriptorSize = pID3DDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);//---------------------------------------------------------------------------------------------CD3DX12_CPU_DESCRIPTOR_HANDLE stRTVHandle(pIRTVHeap->GetCPUDescriptorHandleForHeapStart());for (UINT i = 0; i < nFrameBackBufCount; i++){//這個循環暴漏了描述符堆實際上是個數組的本質GRS_THROW_IF_FAILED(pISwapChain3->GetBuffer(i, IID_PPV_ARGS(&pIARenderTargets[i])));pID3DDevice->CreateRenderTargetView(pIARenderTargets[i].Get(), nullptr, stRTVHandle);stRTVHandle.Offset(1, nRTVDescriptorSize);}}//11、創建根簽名{D3D12_FEATURE_DATA_ROOT_SIGNATURE stFeatureData = {};// 檢測是否支持V1.1版本的根簽名stFeatureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_1;if (FAILED(pID3DDevice->CheckFeatureSupport(D3D12_FEATURE_ROOT_SIGNATURE, &stFeatureData, sizeof(stFeatureData)))){stFeatureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_0;}// 在GPU上執行SetGraphicsRootDescriptorTable后,我們不修改命令列表中的SRV,因此我們可以使用默認Rang行為:// D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC_WHILE_SET_AT_EXECUTECD3DX12_DESCRIPTOR_RANGE1 stDSPRanges[2];stDSPRanges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0, 0, D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC_WHILE_SET_AT_EXECUTE);stDSPRanges[1].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 1, 0);CD3DX12_ROOT_PARAMETER1 stRootParameters[2];stRootParameters[0].InitAsDescriptorTable(1, &stDSPRanges[0], D3D12_SHADER_VISIBILITY_PIXEL);stRootParameters[1].InitAsDescriptorTable(1, &stDSPRanges[1], D3D12_SHADER_VISIBILITY_PIXEL);//---------------------------------------------------------------------------------------------//靜態的采樣器不用了,我們使用動態的在堆上創建的采樣器//D3D12_STATIC_SAMPLER_DESC stSamplerDesc = {};//stSamplerDesc.Filter = D3D12_FILTER_MIN_MAG_MIP_POINT;//stSamplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_BORDER;//stSamplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_BORDER;//stSamplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_BORDER;//stSamplerDesc.MipLODBias = 0;//stSamplerDesc.MaxAnisotropy = 0;//stSamplerDesc.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER;//stSamplerDesc.BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK;//stSamplerDesc.MinLOD = 0.0f;//stSamplerDesc.MaxLOD = D3D12_FLOAT32_MAX;//stSamplerDesc.ShaderRegister = 0;//stSamplerDesc.RegisterSpace = 0;//stSamplerDesc.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;//---------------------------------------------------------------------------------------------CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC stRootSignatureDesc;//stRootSignatureDesc.Init_1_1(_countof(stRootParameters), stRootParameters// , 1, &stSamplerDesc// , D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);stRootSignatureDesc.Init_1_1(_countof(stRootParameters), stRootParameters, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);ComPtr<ID3DBlob> pISignatureBlob;ComPtr<ID3DBlob> pIErrorBlob;GRS_THROW_IF_FAILED(D3DX12SerializeVersionedRootSignature(&stRootSignatureDesc, stFeatureData.HighestVersion, &pISignatureBlob, &pIErrorBlob));GRS_THROW_IF_FAILED(pID3DDevice->CreateRootSignature(0, pISignatureBlob->GetBufferPointer(), pISignatureBlob->GetBufferSize(), IID_PPV_ARGS(&pIRootSignature)));}//12、編譯Shader創建渲染管線狀態對象{#if defined(_DEBUG)// Enable better shader debugging with the graphics debugging tools.UINT compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; #elseUINT compileFlags = 0; #endifTCHAR pszShaderFileName[] = _T("D:\\Projects_2018_08\\D3D12 Tutorials\\2-D3D12WICTexture\\Shader\\Texture.hlsl");GRS_THROW_IF_FAILED(D3DCompileFromFile(pszShaderFileName, nullptr, nullptr, "VSMain", "vs_5_0", compileFlags, 0, &pIBlobVertexShader, nullptr));GRS_THROW_IF_FAILED(D3DCompileFromFile(pszShaderFileName, nullptr, nullptr, "PSMain", "ps_5_0", compileFlags, 0, &pIBlobPixelShader, nullptr));// Define the vertex input layout.D3D12_INPUT_ELEMENT_DESC stInputElementDescs[] ={{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }};// 創建 graphics pipeline state object (PSO)對象D3D12_GRAPHICS_PIPELINE_STATE_DESC stPSODesc = {};stPSODesc.InputLayout = { stInputElementDescs, _countof(stInputElementDescs) };stPSODesc.pRootSignature = pIRootSignature.Get();stPSODesc.VS = CD3DX12_SHADER_BYTECODE(pIBlobVertexShader.Get());stPSODesc.PS = CD3DX12_SHADER_BYTECODE(pIBlobPixelShader.Get());stPSODesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);stPSODesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);stPSODesc.DepthStencilState.DepthEnable = FALSE;stPSODesc.DepthStencilState.StencilEnable = FALSE;stPSODesc.SampleMask = UINT_MAX;stPSODesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;stPSODesc.NumRenderTargets = 1;stPSODesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;stPSODesc.SampleDesc.Count = 1;GRS_THROW_IF_FAILED(pID3DDevice->CreateGraphicsPipelineState(&stPSODesc, IID_PPV_ARGS(&pIPipelineState)));}//13、創建紋理的默認堆{D3D12_HEAP_DESC stTextureHeapDesc = {};//為堆指定紋理圖片至少2倍大小的空間,這里沒有詳細去計算了,只是指定了一個足夠大的空間,夠放紋理就行//實際應用中也是要綜合考慮分配堆的大小,以便可以重用堆stTextureHeapDesc.SizeInBytes = GRS_UPPER(2 * nPicRowPitch * nTextureH, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT);//指定堆的對齊方式,這里使用了默認的64K邊界對齊,因為我們暫時不需要MSAA支持stTextureHeapDesc.Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT;stTextureHeapDesc.Properties.Type = D3D12_HEAP_TYPE_DEFAULT; //默認堆類型stTextureHeapDesc.Properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;stTextureHeapDesc.Properties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;//拒絕渲染目標紋理、拒絕深度蠟板紋理,實際就只是用來擺放普通紋理stTextureHeapDesc.Flags = D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES | D3D12_HEAP_FLAG_DENY_BUFFERS;GRS_THROW_IF_FAILED(pID3DDevice->CreateHeap(&stTextureHeapDesc, IID_PPV_ARGS(&pITextureHeap)));}//14、創建2D紋理{stTextureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;stTextureDesc.MipLevels = 1;stTextureDesc.Format = stTextureFormat; //DXGI_FORMAT_R8G8B8A8_UNORM;stTextureDesc.Width = nTextureW;stTextureDesc.Height = nTextureH;stTextureDesc.Flags = D3D12_RESOURCE_FLAG_NONE;stTextureDesc.DepthOrArraySize = 1;stTextureDesc.SampleDesc.Count = 1;stTextureDesc.SampleDesc.Quality = 0;//-----------------------------------------------------------------------------------------------------------創建默認堆上的資源,類型是Texture2D,GPU對默認堆資源的訪問速度是最快的因為紋理資源一般是不易變的資源,所以我們通常使用上傳堆復制到默認堆中在傳統的D3D11及以前的D3D接口中,這些過程都被封裝了,我們只能指定創建時的類型為默認堆 //GRS_THROW_IF_FAILED(pID3DDevice->CreateCommittedResource(// &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT)// , D3D12_HEAP_FLAG_NONE// , &stTextureDesc //可以使用CD3DX12_RESOURCE_DESC::Tex2D來簡化結構體的初始化// , D3D12_RESOURCE_STATE_COPY_DEST// , nullptr// , IID_PPV_ARGS(&pITexture)));//-----------------------------------------------------------------------------------------------------------//-----------------------------------------------------------------------------------------------------------//使用“定位方式”來創建紋理,注意下面這個調用內部實際已經沒有存儲分配和釋放的實際操作了,所以性能很高//同時可以在這個堆上反復調用CreatePlacedResource來創建不同的紋理,當然前提是它們不在被使用的時候,才考慮//重用堆GRS_THROW_IF_FAILED(pID3DDevice->CreatePlacedResource(pITextureHeap.Get(), 0, &stTextureDesc //可以使用CD3DX12_RESOURCE_DESC::Tex2D來簡化結構體的初始化, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&pITexture)));//-----------------------------------------------------------------------------------------------------------//獲取上傳堆資源緩沖的大小,這個尺寸通常大于實際圖片的尺寸n64UploadBufferSize = GetRequiredIntermediateSize(pITexture.Get(), 0, 1);}//15、創建上傳堆{//-----------------------------------------------------------------------------------------------------------D3D12_HEAP_DESC stUploadHeapDesc = { };//尺寸依然是實際紋理數據大小的2倍并64K邊界對齊大小stUploadHeapDesc.SizeInBytes = GRS_UPPER(2 * n64UploadBufferSize, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT);//注意上傳堆肯定是Buffer類型,可以不指定對齊方式,其默認是64k邊界對齊stUploadHeapDesc.Alignment = 0;stUploadHeapDesc.Properties.Type = D3D12_HEAP_TYPE_UPLOAD; //上傳堆類型stUploadHeapDesc.Properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;stUploadHeapDesc.Properties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;//上傳堆就是緩沖,可以擺放任意數據stUploadHeapDesc.Flags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS;GRS_THROW_IF_FAILED(pID3DDevice->CreateHeap(&stUploadHeapDesc, IID_PPV_ARGS(&pIUploadHeap)));//-----------------------------------------------------------------------------------------------------------}//16、使用“定位方式”創建用于上傳紋理數據的緩沖資源{-----------------------------------------------------------------------------------------------------------創建用于上傳紋理的資源,注意其類型是Buffer上傳堆對于GPU訪問來說性能是很差的,所以對于幾乎不變的數據尤其像紋理都是通過它來上傳至GPU訪問更高效的默認堆中//GRS_THROW_IF_FAILED(pID3DDevice->CreateCommittedResource(// &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),// D3D12_HEAP_FLAG_NONE,// &CD3DX12_RESOURCE_DESC::Buffer(n64UploadBufferSize),// D3D12_RESOURCE_STATE_GENERIC_READ,// nullptr,// IID_PPV_ARGS(&pITextureUpload)));-----------------------------------------------------------------------------------------------------------GRS_THROW_IF_FAILED(pID3DDevice->CreatePlacedResource(pIUploadHeap.Get(), 0, &CD3DX12_RESOURCE_DESC::Buffer(n64UploadBufferSize), D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&pITextureUpload)));}//17、加載圖片數據至上傳堆,即完成第一個Copy動作,從memcpy函數可知這是由CPU完成的{//按照資源緩沖大小來分配實際圖片數據存儲的內存大小void* pbPicData = ::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, n64UploadBufferSize);if (nullptr == pbPicData){throw CGRSCOMException(HRESULT_FROM_WIN32(GetLastError()));}//從圖片中讀取出數據GRS_THROW_IF_FAILED(pIBMP->CopyPixels(nullptr, nPicRowPitch, static_cast<UINT>(nPicRowPitch * nTextureH) //注意這里才是圖片數據真實的大小,這個值通常小于緩沖的大小, reinterpret_cast<BYTE*>(pbPicData)));//{//下面這段代碼來自DX12的示例,直接通過填充緩沖繪制了一個黑白方格的紋理// //還原這段代碼,然后注釋上面的CopyPixels調用可以看到黑白方格紋理的效果// const UINT rowPitch = nPicRowPitch; //nTextureW * 4; //static_cast<UINT>(n64UploadBufferSize / nTextureH);// const UINT cellPitch = rowPitch >> 3; // The width of a cell in the checkboard texture.// const UINT cellHeight = nTextureW >> 3; // The height of a cell in the checkerboard texture.// const UINT textureSize = static_cast<UINT>(n64UploadBufferSize);// UINT nTexturePixelSize = static_cast<UINT>(n64UploadBufferSize / nTextureH / nTextureW);// UINT8* pData = reinterpret_cast<UINT8*>(pbPicData);// for (UINT n = 0; n < textureSize; n += nTexturePixelSize)// {// UINT x = n % rowPitch;// UINT y = n / rowPitch;// UINT i = x / cellPitch;// UINT j = y / cellHeight;// if (i % 2 == j % 2)// {// pData[n] = 0x00; // R// pData[n + 1] = 0x00; // G// pData[n + 2] = 0x00; // B// pData[n + 3] = 0xff; // A// }// else// {// pData[n] = 0xff; // R// pData[n + 1] = 0xff; // G// pData[n + 2] = 0xff; // B// pData[n + 3] = 0xff; // A// }// }//}//獲取向上傳堆拷貝紋理數據的一些紋理轉換尺寸信息//對于復雜的DDS紋理這是非常必要的過程UINT nNumSubresources = 1u; //我們只有一副圖片,即子資源個數為1UINT nTextureRowNum = 0u;UINT64 n64TextureRowSizes = 0u;UINT64 n64RequiredSize = 0u;stDestDesc = pITexture->GetDesc();pID3DDevice->GetCopyableFootprints(&stDestDesc, 0, nNumSubresources, 0, &stTxtLayouts, &nTextureRowNum, &n64TextureRowSizes, &n64RequiredSize);//因為上傳堆實際就是CPU傳遞數據到GPU的中介//所以我們可以使用熟悉的Map方法將它先映射到CPU內存地址中//然后我們按行將數據復制到上傳堆中//需要注意的是之所以按行拷貝是因為GPU資源的行大小//與實際圖片的行大小是有差異的,二者的內存邊界對齊要求是不一樣的BYTE* pData = nullptr;GRS_THROW_IF_FAILED(pITextureUpload->Map(0, NULL, reinterpret_cast<void**>(&pData)));BYTE* pDestSlice = reinterpret_cast<BYTE*>(pData) + stTxtLayouts.Offset;const BYTE* pSrcSlice = reinterpret_cast<const BYTE*>(pbPicData);for (UINT y = 0; y < nTextureRowNum; ++y){memcpy(pDestSlice + static_cast<SIZE_T>(stTxtLayouts.Footprint.RowPitch) * y, pSrcSlice + static_cast<SIZE_T>(nPicRowPitch) * y, nPicRowPitch);}//取消映射 對于易變的數據如每幀的變換矩陣等數據,可以撒懶不用Unmap了,//讓它常駐內存,以提高整體性能,因為每次Map和Unmap是很耗時的操作//因為現在起碼都是64位系統和應用了,地址空間是足夠的,被長期占用不會影響什么pITextureUpload->Unmap(0, NULL);//釋放圖片數據,做一個干凈的程序員::HeapFree(::GetProcessHeap(), 0, pbPicData);}//18、向直接命令列表發出從上傳堆復制紋理數據到默認堆的命令,執行并同步等待,即完成第二個Copy動作,由GPU上的復制引擎完成//注意此時直接命令列表還沒有綁定PSO對象,因此它也是不能執行3D圖形命令的,但是可以執行復制命令,因為復制引擎不需要什么//額外的狀態設置之類的參數{CD3DX12_TEXTURE_COPY_LOCATION Dst(pITexture.Get(), 0);CD3DX12_TEXTURE_COPY_LOCATION Src(pITextureUpload.Get(), stTxtLayouts);pICommandList->CopyTextureRegion(&Dst, 0, 0, 0, &Src, nullptr);//設置一個資源屏障,同步并確認復制操作完成//直接使用結構體然后調用的形式D3D12_RESOURCE_BARRIER stResBar = {};stResBar.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;stResBar.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;stResBar.Transition.pResource = pITexture.Get();stResBar.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;stResBar.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;stResBar.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;pICommandList->ResourceBarrier(1, &stResBar);//或者使用D3DX12庫中的工具類調用的等價形式,下面的方式更簡潔一些//pICommandList->ResourceBarrier(1// , &CD3DX12_RESOURCE_BARRIER::Transition(pITexture.Get()// , D3D12_RESOURCE_STATE_COPY_DEST// , D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE)//);//---------------------------------------------------------------------------------------------// 執行命令列表并等待紋理資源上傳完成,這一步是必須的GRS_THROW_IF_FAILED(pICommandList->Close());ID3D12CommandList* ppCommandLists[] = { pICommandList.Get() };pICommandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);//---------------------------------------------------------------------------------------------// 17、創建一個同步對象——圍欄,用于等待渲染完成,因為現在Draw Call是異步的了GRS_THROW_IF_FAILED(pID3DDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&pIFence)));n64FenceValue = 1;//---------------------------------------------------------------------------------------------// 18、創建一個Event同步對象,用于等待圍欄事件通知hFenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);if (hFenceEvent == nullptr){GRS_THROW_IF_FAILED(HRESULT_FROM_WIN32(GetLastError()));}//---------------------------------------------------------------------------------------------// 19、等待紋理資源正式復制完成先const UINT64 fence = n64FenceValue;GRS_THROW_IF_FAILED(pICommandQueue->Signal(pIFence.Get(), fence));n64FenceValue++;//---------------------------------------------------------------------------------------------// 看命令有沒有真正執行到圍欄標記的這里,沒有就利用事件去等待,注意使用的是命令隊列對象的指針if (pIFence->GetCompletedValue() < fence){GRS_THROW_IF_FAILED(pIFence->SetEventOnCompletion(fence, hFenceEvent));WaitForSingleObject(hFenceEvent, INFINITE);}//---------------------------------------------------------------------------------------------//命令分配器先Reset一下,剛才已經執行過了一個復制紋理的命令GRS_THROW_IF_FAILED(pICommandAllocator->Reset());//Reset命令列表,并重新指定命令分配器和PSO對象GRS_THROW_IF_FAILED(pICommandList->Reset(pICommandAllocator.Get(), pIPipelineState.Get()));//---------------------------------------------------------------------------------------------}//19、創建SRV堆 (Shader Resource View Heap) 和SRV描述符{D3D12_DESCRIPTOR_HEAP_DESC stSRVHeapDesc = {};stSRVHeapDesc.NumDescriptors = 1;stSRVHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;stSRVHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;GRS_THROW_IF_FAILED(pID3DDevice->CreateDescriptorHeap(&stSRVHeapDesc, IID_PPV_ARGS(&pISRVHeap)));//---------------------------------------------------------------------------------------------// 最終創建SRV描述符D3D12_SHADER_RESOURCE_VIEW_DESC stSRVDesc = {};stSRVDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;stSRVDesc.Format = stTextureDesc.Format;stSRVDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;stSRVDesc.Texture2D.MipLevels = 1;pID3DDevice->CreateShaderResourceView(pITexture.Get(), &stSRVDesc, pISRVHeap->GetCPUDescriptorHandleForHeapStart());}//20、創建采樣器堆 和 各種采樣器{D3D12_DESCRIPTOR_HEAP_DESC stSamplerHeapDesc = {};stSamplerHeapDesc.NumDescriptors = nSampleMaxCnt;stSamplerHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER;stSamplerHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;GRS_THROW_IF_FAILED(pID3DDevice->CreateDescriptorHeap(&stSamplerHeapDesc,IID_PPV_ARGS(&pISamplerDescriptorHeap)));nSamplerDescriptorSize = pID3DDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER);CD3DX12_CPU_DESCRIPTOR_HANDLE hSamplerHeap(pISamplerDescriptorHeap->GetCPUDescriptorHandleForHeapStart());D3D12_SAMPLER_DESC stSamplerDesc = {};stSamplerDesc.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;stSamplerDesc.MinLOD = 0;stSamplerDesc.MaxLOD = D3D12_FLOAT32_MAX;stSamplerDesc.MipLODBias = 0.0f;stSamplerDesc.MaxAnisotropy = 1;stSamplerDesc.ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS;// Sampler 1stSamplerDesc.BorderColor[0] = 1.0f;stSamplerDesc.BorderColor[1] = 0.0f;stSamplerDesc.BorderColor[2] = 1.0f;stSamplerDesc.BorderColor[3] = 1.0f;stSamplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_BORDER;stSamplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_BORDER;stSamplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_BORDER;pID3DDevice->CreateSampler(&stSamplerDesc, hSamplerHeap);hSamplerHeap.Offset(nSamplerDescriptorSize);// Sampler 2stSamplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP;stSamplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP;stSamplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP;pID3DDevice->CreateSampler(&stSamplerDesc, hSamplerHeap);hSamplerHeap.Offset(nSamplerDescriptorSize);// Sampler 3stSamplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;stSamplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;stSamplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;pID3DDevice->CreateSampler(&stSamplerDesc, hSamplerHeap);hSamplerHeap.Offset(nSamplerDescriptorSize);// Sampler 4stSamplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_MIRROR;stSamplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_MIRROR;stSamplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_MIRROR;pID3DDevice->CreateSampler(&stSamplerDesc, hSamplerHeap);hSamplerHeap.Offset(nSamplerDescriptorSize);// Sampler 5stSamplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_MIRROR_ONCE;stSamplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_MIRROR_ONCE;stSamplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_MIRROR_ONCE;pID3DDevice->CreateSampler(&stSamplerDesc, hSamplerHeap);} //21、定義正方形的3D數據結構,注意此處的紋理坐標我故意設置為大于1GRS_VERTEX stTriangleVertices[] ={{ { -0.25f* fAspectRatio, -0.25f * fAspectRatio, 0.0f}, { 0.0f, 3.0f } }, // Bottom left.{ { -0.25f* fAspectRatio, 0.25f * fAspectRatio, 0.0f}, { 0.0f, 0.0f } }, // Top left.{ { 0.25f* fAspectRatio, -0.25f * fAspectRatio, 0.0f}, { 3.0f, 3.0f } }, // Bottom right.{ { 0.25f* fAspectRatio, 0.25f * fAspectRatio, 0.0f}, { 3.0f, 0.0f } }, // Top right.};const UINT nVertexBufferSize = sizeof(stTriangleVertices);//22、使用“定位方式”創建頂點緩沖,使用與上傳紋理數據緩沖相同的一個上傳堆{//---------------------------------------------------------------------------------------------//GRS_THROW_IF_FAILED(pID3DDevice->CreateCommittedResource(// &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),// D3D12_HEAP_FLAG_NONE,// &CD3DX12_RESOURCE_DESC::Buffer(nVertexBufferSize),// D3D12_RESOURCE_STATE_GENERIC_READ,// nullptr,// IID_PPV_ARGS(&pIVertexBuffer)));//---------------------------------------------------------------------------------------------//---------------------------------------------------------------------------------------------//使用定位方式在相同的上傳堆上以“定位方式”創建頂點緩沖,注意第二個參數指出了堆中的偏移位置//按照堆邊界對齊的要求,我們主動將偏移位置對齊到了64k的邊界上GRS_THROW_IF_FAILED(pID3DDevice->CreatePlacedResource(pIUploadHeap.Get(), GRS_UPPER(n64UploadBufferSize, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT), &CD3DX12_RESOURCE_DESC::Buffer(nVertexBufferSize), D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&pIVertexBuffer)));//---------------------------------------------------------------------------------------------//使用map-memcpy-unmap大法將數據傳至頂點緩沖對象//注意頂點緩沖使用是和上傳紋理數據緩沖相同的一個堆,這很神奇UINT8* pVertexDataBegin = nullptr;CD3DX12_RANGE stReadRange(0, 0); // We do not intend to read from this resource on the CPU.GRS_THROW_IF_FAILED(pIVertexBuffer->Map(0, &stReadRange, reinterpret_cast<void**>(&pVertexDataBegin)));memcpy(pVertexDataBegin, stTriangleVertices, sizeof(stTriangleVertices));pIVertexBuffer->Unmap(0, nullptr);//創建資源視圖,實際可以簡單理解為指向頂點緩沖的顯存指針stVertexBufferView.BufferLocation = pIVertexBuffer->GetGPUVirtualAddress();stVertexBufferView.StrideInBytes = sizeof(GRS_VERTEX);stVertexBufferView.SizeInBytes = nVertexBufferSize;}//---------------------------------------------------------------------------------------------//23、創建定時器對象,以便于創建高效的消息循環HANDLE phWait = CreateWaitableTimer(NULL, FALSE, NULL);LARGE_INTEGER liDueTime = {};liDueTime.QuadPart = -1i64;//1秒后開始計時SetWaitableTimer(phWait, &liDueTime, 1, NULL, NULL, 0);//40ms的周期//---------------------------------------------------------------------------------------------//24、開始消息循環,并在其中不斷渲染DWORD dwRet = 0;BOOL bExit = FALSE;while (!bExit){dwRet = ::MsgWaitForMultipleObjects(1, &phWait, FALSE, INFINITE, QS_ALLINPUT);switch (dwRet - WAIT_OBJECT_0){case 0:case WAIT_TIMEOUT:{//計時器時間到//GRS_TRACE(_T("開始第%u幀渲染{Frame Index = %u}:\n"),nFrame,nFrameIndex);//開始記錄命令//---------------------------------------------------------------------------------------------pICommandList->SetGraphicsRootSignature(pIRootSignature.Get());ID3D12DescriptorHeap* ppHeaps[] = { pISRVHeap.Get(),pISamplerDescriptorHeap.Get()};pICommandList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);pICommandList->SetGraphicsRootDescriptorTable(0, pISRVHeap->GetGPUDescriptorHandleForHeapStart());CD3DX12_GPU_DESCRIPTOR_HANDLE hGPUSampler(pISamplerDescriptorHeap->GetGPUDescriptorHandleForHeapStart(), nCurrentSamplerNO, nSamplerDescriptorSize);pICommandList->SetGraphicsRootDescriptorTable(1, hGPUSampler);pICommandList->RSSetViewports(1, &stViewPort);pICommandList->RSSetScissorRects(1, &stScissorRect);//---------------------------------------------------------------------------------------------// 通過資源屏障判定后緩沖已經切換完畢可以開始渲染了pICommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(pIARenderTargets[nFrameIndex].Get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));//偏移描述符指針到指定幀緩沖視圖位置CD3DX12_CPU_DESCRIPTOR_HANDLE stRTVHandle(pIRTVHeap->GetCPUDescriptorHandleForHeapStart(), nFrameIndex, nRTVDescriptorSize);//設置渲染目標pICommandList->OMSetRenderTargets(1, &stRTVHandle, FALSE, nullptr);// 繼續記錄命令,并真正開始新一幀的渲染const float clearColor[] = { 0.0f, 0.2f, 0.4f, 1.0f };pICommandList->ClearRenderTargetView(stRTVHandle, clearColor, 0, nullptr);//注意我們使用的渲染手法是三角形帶,這是最快的繪制矩形的方式,也是很多UI庫中核心使用的方法pICommandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);pICommandList->IASetVertexBuffers(0, 1, &stVertexBufferView);//---------------------------------------------------------------------------------------------//Draw Call!!!pICommandList->DrawInstanced(_countof(stTriangleVertices), 1, 0, 0);//---------------------------------------------------------------------------------------------//又一個資源屏障,用于確定渲染已經結束可以提交畫面去顯示了pICommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(pIARenderTargets[nFrameIndex].Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));//關閉命令列表,可以去執行了GRS_THROW_IF_FAILED(pICommandList->Close());//---------------------------------------------------------------------------------------------//執行命令列表ID3D12CommandList* ppCommandLists[] = { pICommandList.Get() };pICommandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);//---------------------------------------------------------------------------------------------//提交畫面GRS_THROW_IF_FAILED(pISwapChain3->Present(1, 0));//---------------------------------------------------------------------------------------------//開始同步GPU與CPU的執行,先記錄圍欄標記值const UINT64 fence = n64FenceValue;GRS_THROW_IF_FAILED(pICommandQueue->Signal(pIFence.Get(), fence));n64FenceValue++;//---------------------------------------------------------------------------------------------// 看命令有沒有真正執行到圍欄標記的這里,沒有就利用事件去等待,注意使用的是命令隊列對象的指針if (pIFence->GetCompletedValue() < fence){GRS_THROW_IF_FAILED(pIFence->SetEventOnCompletion(fence, hFenceEvent));WaitForSingleObject(hFenceEvent, INFINITE);}//執行到這里說明一個命令隊列完整的執行完了,在這里就代表我們的一幀已經渲染完了,接著準備執行下一幀渲染//---------------------------------------------------------------------------------------------//獲取新的后緩沖序號,因為Present真正完成時后緩沖的序號就更新了nFrameIndex = pISwapChain3->GetCurrentBackBufferIndex();//---------------------------------------------------------------------------------------------//命令分配器先Reset一下GRS_THROW_IF_FAILED(pICommandAllocator->Reset());//Reset命令列表,并重新指定命令分配器和PSO對象GRS_THROW_IF_FAILED(pICommandList->Reset(pICommandAllocator.Get(), pIPipelineState.Get()));//GRS_TRACE(_T("第%u幀渲染結束.\n"), nFrame++);}break;case 1:{//處理消息while (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){if (WM_QUIT != msg.message){::TranslateMessage(&msg);::DispatchMessage(&msg);}else{bExit = TRUE;}}}break;default:break;}}//::CoUninitialize();}catch (CGRSCOMException& e){//發生了COM異常e;}return 0; }LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {switch (message){case WM_DESTROY:PostQuitMessage(0);break;case WM_KEYUP:{if (VK_SPACE == (wParam & 0xFF)){//按空格鍵切換不同的采樣器看效果,以明白每種采樣器具體的含義//UINT nCurrentSamplerNO = 0; //當前使用的采樣器索引//UINT nSampleMaxCnt = 5; //創建五個典型的采樣器++nCurrentSamplerNO;nCurrentSamplerNO %= nSampleMaxCnt;}}break;default:return DefWindowProc(hWnd, message, wParam, lParam);}return 0; }shader代碼與前一篇教程中相同,略。
總結
以上是生活随笔為你收集整理的DirectX12(D3D12)基础教程(三)——使用独立堆以“定位方式”创建资源、创建动态采样器、初步理解采取器类型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: photoshop抠图后如何使边缘模糊圆
- 下一篇: 数学分析 积分表及常用积分公式