目錄
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)
{//UMA
if ( stArchitecture.CacheCoherentUMA )
{
GRS_PRINTF(_T("架構爲(CC-UMA)"));
}
else
{
GRS_PRINTF(_T("架構爲(UMA)"));
}
}
else
{//NUMA
GRS_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; //Position
XMFLOAT2 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 & COM
const 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, // 指定位圖是否有調色板,現代都是真彩位圖,不用調色板,所以爲None
NULL, // 指定調色板指針
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_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)));
}
//12、編譯Shader創建渲染管線狀態對象
{
#if defined(_DEBUG)
// Enable better shader debugging with the graphics debugging tools.
UINT compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#else
UINT compileFlags = 0;
#endif
TCHAR 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; //我們只有一副圖片,即子資源個數爲1
UINT 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 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);
}
//21、定義正方形的3D數據結構,注意此處的紋理座標我故意設置爲大於1
GRS_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代碼與前一篇教程中相同,略。