DirectX12(D3D12)基礎教程(二)——理解根簽名、初識顯存管理和加載紋理、理解資源屏障

目錄

 

1、前言及本章內容提要

2、初步理解和使用根簽名

3、使用WIC庫加載圖片

4、D3D12內存管理導論——上傳堆和默認堆

4.1、D3D12中創建資源的三種方式

4.1.1、提交方式(CreateCommittedResource)

4.1.2、定位方式(CreatePlacedResource)

4.1.3、保留方式(CreateReservedResource)

4.2、D3D12中堆的類型(默認堆、上傳堆等)

4.3、資源屏障(Resource Barrier)

5、創建默認堆資源和上傳堆資源並上傳紋理數據

5.1、創建默認堆上的2D紋理

5.2、創建上傳堆上的資源

5.3、複製紋理圖片數據到上傳堆

5.4、調用複製命令並放置圍欄

6、描述符和描述符堆

7、本章全部代碼


1前言及本章內容提要

距第一篇教程推出至今,不知不覺又過了將近一個月時間了,其實並不是我沒有努力的繼續準備教程,而是在調用WIC加載紋理的過程中遇到了點挫折,折騰了我好長時間,期間經過了愉快的國慶假期,帶着家人遊歷了一下祖國的壯麗河山,換了換腦筋,安靜的讀了些資料,問題居然迎刃而解。

通過對上一篇教程的反饋來看,很多網友反映最大的問題就是對D3D12中的新概念——根簽名非常的陌生和不理解,因此在本篇教程中我將詳細講解這個概念,並在後續的教程中不斷加深大家對這個比較核心的概念的理解。

另外本章的例子中爲了循序漸進的學習要求,我特意安排了加載一副紋理並顯示的代碼。其中用到了WIC(Windows Imaging Component)庫,之後我也會詳細介紹該庫的用法,因爲後續的很多示例使用紋理,都需要用到這個庫。當然如果你已經升級到了Windows 10系統的話,這個庫是系統默認自帶的,調用不會有什麼問題。

之所以使用這個庫是因爲這個庫支持的圖片格式類型比較多,同時還支持DDS格式,因此使用它來加載紋理是不錯的選擇。當然如果你熟悉其他的圖片庫,如:CxImage等,使用它們也可以。這裏我只是爲了方便而使用它。另外因爲大量的DX12示例中都用到了這個庫,所以我就講解下這個庫的用法,方便大家閱讀理解那些DX12以及DXR中的示例代碼。

另外這一講中我還將重點再介紹一下資源屏障原理及實際中使用時的方法。雖然上一講中針對渲染目標資源我們已經用到了資源屏障,但是上一講我有意淡化了對它的講解,畢竟一開始就灌輸很多概念會讓人吃不消,所以這一章中我們着重再來講解一下它。另外資源屏障是D3D12中引入的GPU線程間同步的重要核心概念之一,掌握它也是掌握D3D12核心基礎概念之一。

本章教程我們的目標是渲染一個矩形並加載一個紋理顯示其上。教程中用到的渲染矩形的方法可能需要大家理解並記憶,因爲之後自己在封裝引擎的UI部分時既可以此例的方法爲基礎藍本。

本章主要目標:繪製一個帶紋理的矩形,效果圖如下

主要內容:

理解和掌握根簽名

WIC庫的基本使用方法

初步理解顯存管理和加載紋理

2、初步理解和使用根簽名

講到根簽名,我們應當想到的類似概念其實就是函數簽名,也就是C/C++中常說的函數聲明。爲什麼這樣說呢?

其實在概念上,我推薦你將整個GPU的渲染管線理解爲一個大的函數執行體,其主要代碼就是那些運行於GPU上的Shader程序,而根簽名則是說明了這個渲染管線可以傳入什麼參數。更直白的說因爲GPU渲染需要數據(比如:紋理、網格、索引、世界變換矩陣、動畫矩陣調色板、採樣器等等),而這些數據必須是GPU能夠理解的專有格式,這些數據始終是CPU負責加載並傳遞給GPU的,此時就需要提前告訴GPU要傳遞那些數據,以及傳遞到什麼地方,傳遞多少個等信息,而這些信息最終就被封裝在了根簽名裏。實際也就是說根簽名也是CPU向GPU傳遞渲染數據的一個契約。整體上看也就是CPU代碼調用GPU渲染管線“函數”進行渲染,因此要傳遞參數給渲染管線,參數的格式就是根簽名定義的。

那麼爲什麼需要根簽名這麼一個結構化的描述呢?這是因爲無論什麼數據,在存儲中都是線性化的按字節依次排列在存儲(內存及顯存)中的,如果不加額外的描述信息來說明,GPU甚至CPU本身根本無法分辨那塊存儲器中存儲的是什麼數據,即沒有類型說明的數據,這類似C語言中的VOID*指針指向的一塊數據,如果不額外說明,根本不知道里面到底存了些什麼。所以根簽名從根本上爲這些存儲的數據描述清楚了基本類型信息和位置信息。當數據按照指定的方式傳遞到指定的位置後,GPU就可以按照根簽名中的約定訪問這些數據了。而進一步的詳細的類型信息則是由各種描述符來詳細描述了。

或者換種說法,根簽名描述清楚了渲染管線或者說Shader編譯後的執行代碼需要的各種資源以什麼樣的方式傳入以及如何在內存、顯存中佈局,當然主要指定的是GPU上對應的寄存器。另一種等價的說法是說如何將這些數據綁定到渲染管線上,實質是說的一回事情,只是角度不同。

因此每一個執行不同渲染功能的渲染管線“函數”之間就需要不同的根簽名來描述它的資源存儲及傳遞情況。或者你可以理解爲每個不同的渲染管線“大函數”都需要不同的對應的根簽名來描述其“參數”。也就是我們定義了不同的函數,就需要配之以不同的函數聲明。

以上是從宏觀概念上具體去理解根簽名的含義。具體的講,根簽名實際是描述了常量(類似默認參數)、常量緩衝區(CBV)、資源(SRV,紋理)、無序訪問緩衝(UAV,隨機讀寫緩衝)、採樣器(Sample)等的寄存器(Register)存儲規劃的一個結構體。同時它還描述了每種資源針對每個階段Shader的可見性。

並且常量(默認參數),根描述符、靜態採樣器等可以在根簽名中直接被賦值,這有點像函數的默認參數一樣。當然這些默認的“參數”在根簽名中被理解爲靜態的,也就是他們被固化在這個根簽名中,要動態修改他們所指資源位置及寄存器等信息是不行的。

當然像代碼函數一樣,根簽名也需要編譯一下才能被GPU正確理解。代碼中調用API編譯根簽名的方法,在上一講中已經有了介紹。另一種編譯根簽名的方法是使用HLSLI腳本的方法,我不打算介紹,因爲它要藉助獨立的編譯器和編譯環境,對於引擎封裝以及靈活性要求來說這都不是很方便的方法。同Shader的編譯一樣,我都推薦大家使用調用API的方法,因爲這些API可以很方便的由你封裝進工具或者再封裝爲腳本的API,甚至內置在引擎中,靈活性是很高的。所以我們也就主要學會API的調用方法即可。

截止目前,D3D12中支持的根簽名已經升級到1.1版本。主要擴展了用於針對驅動級優化的一些標誌。在這篇教程的示例中,我們將演示如何使用高版本並向下兼容的方法來編譯根簽名。

因爲本章示例中需要顯示一個帶紋理的矩形,因此除了準備頂點緩衝之外,我們還要準備紋理資源及對應的資源描述符、紋理採樣器等。因此本例中根簽名就需要稍微複雜一點。代碼如下:

//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[1];
stDSPRanges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0, 0, D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC_WHILE_SET_AT_EXECUTE);

CD3DX12_ROOT_PARAMETER1 stRootParameters[1];
stRootParameters[0].InitAsDescriptorTable(1, &stDSPRanges[0], 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);

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)));

上面的代碼中,首先我們調用了一個極其重要的API:ID3D12Device::CheckFeatureSupport這個函數是我們檢測GPU能力以及驅動支持特性的不二方法,以後我們還會多次講到並用到這個方法,你可以理解爲這是古老的GetCaps函數的高級版,只是它能查詢的特性更高級,特性項更少而已,而從D3D11起D3D規範已經要求所有支持D3D特性的顯卡及驅動一定要支持幾乎所有的基本特性,否則就認爲是不支持D3D11的,所以調用它來查詢的往往是更高級的一些特性,這些特性在一般的應用中很少用到而已。在這裏我們使用該函數來檢測目前DX12的環境是否支持V1.1版本的根簽名。

接下來的CD3DX12_DESCRIPTOR_RANGE1和CD3DX12_ROOT_PARAMETER1兩個結構體聯合定義了我們的渲染管線需要傳入一個SRV的資源。同樣的這兩個工具類來自D3Dx12.h。這裏建議你將所有在D3D12.h中關於根簽名的枚舉定義、結構體定義等複製到一個單獨的文件中,仔細閱讀理解一下。

其實在D3D中不論渲染管線需要的資源多麼複雜,總體上資源就分爲兩大類:一類是紋理和對應的採樣器,也就是圖片;另一類是數據,比如每一幀都需要傳入的世界變換矩陣、骨骼動畫的矩陣調色板等;我們這裏使用的SRV(Shader Resource View)實際就是來描述需要傳入一個紋理數據的,本質上就是圖片數據。

緊接着一個D3D12_STATIC_SAMPLER_DESC結構體對象定義了一個靜態的採樣器對象,相當於一個默認參數。這是一種快捷的靜態的固化的採樣器參數的定義方式,它的特點就是訪問速度快,但缺點就是如果我們要變換採樣器就需要重新定義一個新的採樣器,並且創建一個新的根簽名對象。後面的教程中我們還將講解動態形式的採樣器的定義、創建和使用的方法。

這裏需要注意的是最後總體組裝一個CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC結構體時,靜態採樣器數組(也就是說我們可以定義多個靜態採樣器,只是此例中我們只使用了一個)是需要單獨作爲參數指定的。

此例中我們的渲染管線最終只有一個描述符表和一個靜態採樣器參數,這從Init_1_1函數(1_1的意思是V1.1)的參數就可以看出來,同時我們的渲染管線還接受原始圖元結構數據輸入,這通過最後一個D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT參數值來指定即可。

最終我們使用了一個D3DX12.h庫中的D3DX12SerializeVersionedRootSignature工具函數對根簽名進行了編譯(其實理解爲序列化更合適),將其轉換成了GPU能夠理解的二進制形式,然後我們利用這個二進制數據創建了根簽名對象。建議有能力的網友去閱讀下這個工具函數的源代碼,理解下它封裝的思路。其實它的代碼就是枯燥無味的判斷和結構體賦值,最終都是調用對應版本的序列化函數對根簽名結構體進行編譯。

那麼最終如何來理解這個根簽名如何規劃了GPU需要的數據呢?一圖勝千言:

圖中的箭頭明確的指出了C++代碼中指定的寄存器序號和Shader腳本中的寄存器序號的關係。當然Shader中register語義文法中的字母都有其特定類型含義,這裏的t即表示Texture寄存器類型,s表示採樣器寄存器類型,其他的還有b表示常量緩衝區,u表示無序訪問存儲區等。而在C++代碼中就需要針對不同類型的數據定義不同的結構體加以區別描述,而這就是根簽名的核心職責。當然同類數據有多個時就是約定兩邊使用相同的寄存器序號即可。

如果在根簽名中定義了參數,而沒有實際傳遞對應類型的數據話,就會引起一個GPU訪問違例的異常,這很好理解,就好像我們在C/C++代碼中訪問了一個並沒有分配內存的指針一樣。反過來,如果Shader中只使用了幾個數據,而我們傳遞了多餘的數據時,不會引起什麼問題,實際效果就是我們傳遞了數據,而GPU完全無視那些多餘的數據而已。

當然上面只是說明了作爲“參數”描述時,聲明中的“形參”在“函數體”Shader中被引用的情況,那麼實際傳遞參數時又是什麼樣的對應關係呢?讓我們來看看下圖:

從上面我們就可以看到在具體的渲染循環中,我們就是使用SetGraphicsRootDescriptorTable方法設置對應根簽名參數索引的描述符表的首地址。而描述符則又是描述具體資源的內存位置、類型、大小等信息的。所以從這裏也可以看出根簽名中參數的類型有點像C/C++代碼中的指針的指針“TypeName **”類型,從右邊看第一重指針是描述符指向被描述的資源,第二重指針就是根簽名參數指向具體的描述符堆的首地址,也就是我們上一講中說的描述符堆目前實際就是描述符數組。也就是說根簽名的中的參數實際描述的是一個“指針數組”,而數組中每個具體的指針元素,也就是描述符,又指向具體的資源數據(紋理、緩衝等)。

這樣我們就可以這樣來理解對GPU渲染管線“函數”調用的完整過程了:如果我們需要向GPU傳遞資源數據,那麼首先就需要將數據加載到GPU可訪問的存儲中(內存或顯存),其次需要創建一個“指針數組”也就是描述符堆(描述符數組),再次我們需要爲每個資源數據利用描述符堆中的一個元素創建一個“指針”也就是描述符,最後我們調用SetGraphicsRootDescriptorTable告訴渲染管線,具體每個根簽名中的參數對應哪個“指針數組”也就是描述符堆,當然給它首地址就行了,因爲GPU確切的知道每個“指針數組”元素也就是描述符的內存大小,這可以通過GetDescriptorHandleIncrementSize方法來得到。

至此我想你應該完全明白根簽名的意義,以及實際的用法了。剩下的就是反覆的利用這一模式,定義和實踐不同的渲染管線,查看渲染的效果了。

3、使用WIC庫加載圖片

本章伊始,我提到了爲了準備例子代碼我遇到了點小挫折,折騰了我一段時間,其實並不是D3D12本身讓我受挫,而是這一小節我們將要學習的WIC接口讓我深深的受了一次挫。

WIC接口,全稱就是Windows Image Component(Windows圖像組件)。第一次撞見這個庫是在看DX12的示例代碼的時候,但對這個庫我一直很無知,對它的歷史我也瞭解不多,網上資料也很有限,都是入門級的材料,沒多少深入的探討和應用,更沒有整體性的介紹。我猜這個庫可能是用來補充GDI+的,或者是爲了彌補Windows系統自身對圖片支持能力不足而產生的。最終按照微軟的風格,WIC妥妥的被封裝成了古老的COM形式。當然好處就是現在的開發語言環境基本都支持這個東東。

初識WIC我的第一印象就是:哇!Windows居然可以有這麼簡單的庫可以支持這麼豐富的圖片格式,再也不用使用慢速的CxImage庫了,歐耶!但是當我遇到挫折並折騰了好久才搞定之後,我想說:靠!用得着這麼複雜的接口體系和概念框架封裝嗎?調用簡直就是體力活!

其實從客觀上講WIC庫的功能不僅僅侷限於解析和解碼各種圖片,它還提供了格式轉換以及編碼壓縮圖片的能力。所以它自身是一個功能比較齊全和豐富的圖片處理庫,當然按照COM封裝的要求的話它的接口過於豐富和複雜也在所難免了。幸運的是在我們調用它加載紋理的過程中,只會使用到它的一小部分功能,還好這些不需要太多的其他基礎知識,只是要求你要對COM有基本的瞭解和掌握即可。

在此例中我們主要用到這個庫的兩大功能一個是加載和解碼圖片;另一個就是將圖片轉換爲D3D12可以加載和識別的格式。

要調用WIC庫,就要包含其頭文件#include <wincodec.h>。因爲這個庫是個通用的庫,所以對它的調用就需要比較純正的COM風格的方式了。同時因爲這個庫支持的圖片格式非常豐富,而實際我們的D3D12能支持的圖片格式就比之簡單多了,所以就存在一個格式匹配和轉換的過程,因此我就將DX12示例庫中的兩個用於格式匹配的工具函數搬到了代碼中,如下:

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;
}

這段代碼本身很好理解,其實它就是做了兩個匹配,一個是WIC格式自身之間的匹配,另一個就是WIC和DXGI圖片格式之間的一個匹配。這裏需要注意的就是WIC中圖片格式也被定義爲一個UUID值,實際就是一個128位的隨機整數值,所以比較的時候需要用到UUID的工具函數。這個沒什麼神奇的地方。轉換的規則在表中已經顯示的很清楚了,大家通過讀表中數據和格式名就理解了。

接着就是使用純正COM調用的形式從文件載入圖片數據,然後檢查並轉換格式,同時獲取一些圖片信息,比如:DXGI格式、長、寬、像素位寬等。代碼如下:

 

// 16、使用WIC創建並加載一個2D紋理
//使用純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);
}

// 定義一個位圖格式的圖片數據對象接口
ComPtr<IWICBitmapSource>pIBMP;

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 ,
// 這曾經被傳說是微軟的面試題,希望你已經對它瞭如指掌
UINT nPicRowPitch = (uint64_t(nTextureW) * uint64_t(nBPP) + 7u) / 8u;

上面代碼中註釋已經比較清楚,關鍵點就在我們獲取了圖片的原始格式並獲得等價格式之後,有一個判斷,如果二者不同,那麼我們就需要創建一個轉換器,然後將原始格式的圖片轉換爲兼容DXGI格式的圖片,當然這就是讓我困惑了好久的問題。因爲開初我簡單的以爲我獲得了DXGI格式的信息,就可以使用CopyPixel方法將數據讀取出來,並複製到上傳堆中,然而我卻被那兩個工具函數給簡單的矇蔽了,其實這兩個工具函數只是告訴我們圖片應該可以轉換爲哪種兼容DXGI格式,但並沒有轉換圖片數據本身,因此我直接複製出來的圖片數據依然是原始格式的,直接複製它最終渲染就會顯示失真。因此當發現原始的WIC格式和目標WIC格式不同時,我們需要的就是利用WIC的圖片格式轉換功能轉換圖片格式到目標格式,然後再CopyPixel獲得圖片數據最終複製到上傳堆中。

4、D3D12內存管理導論——上傳堆和默認堆

拿到了已經轉換爲DXGI兼容格式的圖片數據之後,按照在根簽名介紹中的描述,我們接着要做的就是將數據傳輸到顯存中作爲渲染管線的紋理數據。根據我們前面描述的根簽名的理解,那麼這時我們首先需要做的就是將數據傳輸到GPU可以訪問的存儲(內存或顯存)中。而這個過程在之前的D3D接口中就是創建一個Resource對象接口,接着Map之後,再memcpy圖片數據進Resource中即可,或者可以直接在創建對應的資源時指定初始化的數據。而在D3D12中這個工作有了本質上的不同。

在我之前的系列文章中已經提過,D3D12中的一個重要概念就是引入了完全的顯存管理機制。明確的標誌就是引入了“資源堆”的概念。當然這並不是說之前的D3D接口中就沒有顯存管理的概念了,而是說之前的接口中關於顯存管理其實都封裝在創建Resource接口方法的背後了,比如傳統的D3D9中我們就使用創建資源時的標誌來指定使用的是默認緩衝、動態緩衝、或者回寫緩衝等,對應的文檔中也只是簡單的說默認緩衝對GPU有最高的訪問性能,動態緩衝是CPU只寫GPU只讀權限,性能略差,而回寫緩衝是CPU只讀GPU只寫,性能更差,主要用於流輸出等。而在D3D12中,這些緩衝的概念都被“堆”緩衝的概念替換了,同時D3D12中暴露了更底層的控制方法。

首先在D3D中,GPU渲染需要的數據資源被分爲兩大類:一類是數據緩衝,另一類是紋理。對於數據緩衝來說,實際就是一維的某種類型數據的數組,比如:頂點緩衝、索引緩衝、常量緩衝等;而紋理則稍微複雜一些,從數據索引維度上來說可以分爲1D紋理、2D紋理、和3D紋理,但其實本質上他們在存儲中也是按照線性方式按行來存儲的,就像C/C++中的多維數組一樣,本質上也是線性存儲,只是我們可以用多個索引來訪問。從功能上來說紋理又分爲普通紋理、渲染目標、以及深度緩衝和蠟板緩衝等,其中渲染目標的紋理存儲一般是由DXGI的交換鏈來分配的,這只是說我們渲染的圖形是爲了最終在顯示器上顯示才這樣做的,也只是個默認的做法,實際在一些高級的渲染場合渲染目標可能就是我們提前準備好的一個紋理而已,也就是常說的渲染到紋理的方法,這個將在後續的教程中再詳細介紹。這裏這樣講的目的是不想讓大家在學習的過程中思路被禁錮,總以爲渲染目標就只能從交換鏈來創建。

在之前的D3D版本中,這些資源都是統一以不同的對象接口來表達的,比如:ID3D11Texture2D、ID3D11Buffer等,但他們基本都是從ID3D11Resource這個統一的接口派生的,也就是說D3D12之前的版本中區分不同的資源使用了相對複雜的接口派生機制。但這種派生除了有概念表達上的區分意義之外,實際上也並沒有更多實質性的意義。同時因爲不同類型的資源使用了不同的接口來表達,這必然在代碼編寫上帶來很多額外的問題,雖然我們可以使用基類型接口的指針來統一管理它們,但在具體使用時因爲派生接口又會有不同的方法,而使得動態接口類型轉換成爲了一項非常具有挑戰和風險的工作。或者更直白的說其實這種接口派生方式的封裝都是一種很無聊的“過度設計”。或者更直白的說這種設計使得D3D接口中過多的考慮了“引擎”應該考慮和封裝的事情。

所謂過猶不及,所以在D3D12中,就取消了這些通過複雜的接口派生類型來區分不同類型資源的接口設計,所有的資源統一使用一個接口ID3D12Resource來表達,而區分每種不同的Resource就是從創建它們的描述結構以及對應的創建函數,還有就是可以直接通過獲取資源的描述信息來獲知它們具體的類型。這樣對於具體類型資源的封裝設計就完全的變成了引擎設計或其它使用的他們的程序設計需要考慮的事情了,這樣才真正體現了D3D12接口“低級”的真正含義。

另外更重要的就是在D3D12中加入了堆(Heap)的概念來表達Resource具體放在那裏。也就是說要在D3D12中創建資源,一般就需要先創建堆,並明確在創建具體資源時指定放在哪個堆上。

4.1、D3D12中創建資源的三種方式

總的來說,在D3D12中創建資源的方式主要有三種:

4.1.1、提交方式(CreateCommittedResource)

該方式是通過調用ID3D12Device::CreateCommittedResource方法來創建資源。這個方法其實主要還是爲了兼容舊的存儲管理方式而添加的,D3D12的文檔中也不推薦使用這個方法。

使用它時,被創建的資源是被放在系統默認創建的堆上的,這種堆在代碼中是看不到的,因此也被稱爲隱式堆,所以對其存儲所能做的控制就比較侷限了。當然調用時只需要通過堆屬性參數來指定資源被放到那個默認堆上即可。因此調用它就不用額外自己去創建堆了。

也因爲這個方法的這個特性,所以它具有調用上的方便性,很多簡單的例子代碼中都是使用這個方法來創建資源。我們當前的例子中也暫時使用這個方法,主要是爲了示例代碼的簡潔性,以及方便大家閱讀和理解其他的一些示例代碼。

後續的教程中我們會專門討論其他兩種高級的方法。目前我們主要還是停留在概念建立階段,所以還不宜太“天馬行空”的灌輸過多的內容,所以先掌握這個簡潔的方法,並能夠快速動手自己做一些小例子纔是正確的學習方法。

 

4.1.2、定位方式(CreatePlacedResource)

定位方式就是D3D12中新增的比較正統和創建資源方式了。要創建定位方式的資源,就要首先顯示的調用ID3D12Device::CreateHeap來創建一個至少能容納資源大小的堆,實質目前應該理解爲申請了一塊GPU可訪問的存儲(顯存或共享內存)。然後再在此堆上調用ID3D12Device::CreatePlacedResource方法具體來創建資源。

如果你理解之前的提交方法創建資源的話,看到這裏一定會有一個疑問,使用系統默認堆不是挺好?幹嘛要自己費力創建個額外的堆,再來創建資源?其實這裏的繁瑣手續,要理解的話就需要你瞭解內存池的概念了。在一般的C/C++系統中,爲了管理大量尺寸差不多且頻繁分配和釋放的對象時,我們往往採取的策略就是預先分配一大塊內存,然後在其上“分配”和“釋放”對象,這裏的分配和釋放其實就是一個指針的轉換和標記下某塊內存爲空閒狀態而已,其內部根本沒有複雜耗時的內存分配和釋放的真實底層調用。而換來的是性能上的極大提升。其核心理念就是“重用”內存。那麼在D3D12中自己創建堆的目的跟這個內存池的目的非常類似,也就是要能夠重用,即可以在這個堆上重複創建不同的資源,並且自己控制堆的生命期,從而提高性能。而使用默認堆,或者之前的D3D接口是無法做到這一點的,因爲隱式堆在資源釋放時就自動釋放了。

更進一步理解,在D3D中分配或釋放資源需要的往往是在顯存或者共享內存式的顯存上,對它的管理比一般的純CPU的內存管理要複雜的多,因爲這個存儲的管理需要協調CPU和GPU,其額外耗費的存儲管理調用成本是比一般的內存管理還要高的,或者直白的說它的性能耗費是很大的。所以在D3D12中,就乾脆把這塊管理工作的接口都暴露出來,讓程序自己來管理,通過類似內存池的方式,從根本上提高性能。這也是D3D12較之前D3D接口核心改進擴展的主要方面之一。

那麼關於定位式資源創建方法暫時理解到這裏就可以了,本例教程中我們先不介紹。後續的教程中我會專門講解它,那時大家再深入的學習和掌握它,畢竟學習是一個需要不斷重複的過程。

4.1.3、保留方式(CreateReservedResource)

保留方式創建作爲更高級的方法,就需要你對虛擬內存管理有所瞭解。說白了保留方式創建就是顯存的虛擬管理。這個方式就很類似Windows系統中的VirtualAllloc系列函數族所提供的功能了。也就是說在分配時我們並不是直接保留顯存或共享內存,而只是保留虛擬的地址空間,在需要的時候再一段段的真實分配顯存或虛擬內存。

那麼爲什麼需要這樣的能力呢?其實現代的3D場景渲染中,隨着顯示分辨率的不斷提高,以及畫質細膩度的提高,通常紋理資源的尺寸和分辨率都是非常大的。甚至在使用D3D12之前的一些引擎中,爲了顯存管理和利用的高效性,都會要求將很多很小的紋理拼裝成一個巨大的紋理,然後一次加載,供多個不同的渲染對象來使用。這是一種典型的空間換時間的性能優化的方法。

如果再加上爲紋理設置不同的Mip等級,那麼紋理的尺寸都是非常巨大的。同時它就會佔用非常大的顯存空間,所以現代顯卡的一個重要特性就是都配置了動輒十幾個G的顯存,同時還會去共享一些系統中富裕的內存作爲顯存使用。但是雖然資源存儲的大小問題解決了,但是實質上,這些巨大的紋理,並不一定在每幀場景中都被用到。比如不同人物角色的不同皮膚常常被拼裝在一個巨大的紋理中,但實質在一個場景中可能只會顯示一個角色的一套皮膚而已,過多的存儲實質上都是被浪費了。但爲了性能,我們又不能總是按需來加載不同的皮膚,那樣額外的顯存分配釋放管理的成本就會造成性能上的嚴重下降。(之前文章中講過的Draw Call性能問題的解決方案跟這個很類似,也是積攢很多小的網格對象的Draw Call之後拼裝成多個實例的較大網格數據之後再一次調用Draw Call進行繪製。我很佩服想到這些性能優化方法的遊戲程序員,爲了性能真的很拼!)

那麼有沒有折中的方法來輕鬆達到即可以一次保留大的存儲以提高性能,又可以按需提交來節約顯存呢?這看似魚和熊掌的問題,在D3D12中就通過資源的保留創建方式優雅高效的解決了。

或者直白的說,比如我們現在要加載一個1G大小的紋理,普通的方法就是我們要真實的分配和佔用1G的顯存或虛擬內存。如果再加上傳遞數據的中間緩衝,那麼可能需要佔用至少2G的存儲。而使用保留方式我們就只需要先保留1G的地址空間,然後再按照場景渲染時的需要,分段來爲某段地址空間分配真實的顯存或共享內存,比如只分配其中某段256M的數據,這樣真實佔用的存儲就只有256M了。這樣在不斷的渲染過程中,就會不斷的爲還沒有真實分配存儲的地址空間分配存儲,直到所有的資源都按需加載進存儲。當然如果某段地址空間中的資源在整個過程中都沒有用到,那麼就不會分配真實的存儲,也就不會造成浪費。同時因爲堆管理被獨立了出來,那麼這個保留方式的堆,也可以被反覆重用。這樣我們就做到了空間和性能優化上雙贏的結果。

當然現代的硬件其地址空間是非常巨大的(CPU上是64bits的地址空間,即2^64這麼多),保留地址空間本身,不會造成多大的浪費,就好像我們預留手機號一樣,我們可以一次預留比如1000個號碼,而實際上並不需要真實的購買1000臺手機。

保留方式創建資源,我們也將放在後續的教程中來講解,本講中不在詳解了。這裏要求能夠理解其基本原理及思路即可。

4.2、D3D12中堆的類型(默認堆、上傳堆等)

在D3D12中,因爲CPU和GPU訪問同一塊存儲的方式不同,以及堆具體所在存儲位置的不同,比如堆可以在顯存中也可以在二者都可以訪問的共享內存中,所以D3D12的堆還被細分爲四種基本類型:1、默認堆;2、上傳堆;3、回讀堆;4、自定義堆。D3D12中使用一個枚舉值來標識和區分這些類型:

typedef 
enum D3D12_HEAP_TYPE
{
    D3D12_HEAP_TYPE_DEFAULT            = 1,
    D3D12_HEAP_TYPE_UPLOAD             = 2,
    D3D12_HEAP_TYPE_READBACK           = 3,
    D3D12_HEAP_TYPE_CUSTOM              = 4
} D3D12_HEAP_TYPE;

其中默認堆就對應之前D3D中的創建緩衝時指定D3Dxx_USAGE參數爲Default時的情形,直白的說就是這塊緩衝只是供GPU訪問的,CPU不做任何訪問。通常它就駐留在顯存中。因此默認堆中的數據是CPU無法直接訪問的,因此向它直接傳輸數據就成爲不可能的事情。也就是說它是隻面向GPU的數據,因此從GPU的視角來看的話這就是自熱而然的事情,故名默認堆。這樣它就具備了GPU完全獨佔訪問的權限,所以GPU在訪問它時有最高的性能。通常我們將一些不易變的數據比如紋理之類的都放在這類堆中。

當然由於GPU自身不可能加載數據,所以怎樣向默認堆中傳輸數據呢?這就要用到上傳堆來做中介了。因此對於上傳堆,顧名思義主要就是用來向默認堆上傳數據用的。上傳堆對於CPU來說是隻寫的訪問權限,而對於GPU來說是隻讀的訪問權限,因爲CPU和GPU都要訪問它,所以一般它都會被放在二者都能訪問的共享內存中,所以對於CPU和GPU來說都不是獨佔訪問的,因此GPU訪問上傳堆中的數據是有性能損失的。所以通常對於它裏面的一些不易變的數據,我們就使用GPU上的複製引擎(回憶上一篇教程中關於現代顯示適配器描述的內容)將數據複製到默認堆裏去。但是對於一些幾乎每個渲染週期或每幀都會變動的數據,比如:世界變換矩陣、動畫矩陣調色板等,我們通常就直接放在上傳堆裏面了,此時如果每幀都在堆之間複製它們反而會損失性能(具體原因在資源屏障節中講解)。

因爲上傳堆是映射在CPU和GUP都能訪問的共享內存中,因此使用CPU向它裏面複製數據就相對簡單的多,也就是使用我們傳統的Map、memcpy、Unmap大法(memcpy大法好!)。又因爲堆的生命週期現在是由我們的程序完全控制的,所以對於一些經常要我們Map-memcpy-Unmap複製的數據,比如:每幀都變化的世界變換矩陣等,在D3D12程序中就乾脆在一開始就Map一次,之後在每次反覆memcpy新數據即可,程序退出前再調用Unmap並銷燬堆即可。這樣也可以提高不少的性能。在這裏再裏強調一次,D3D12接口相較於之前的D3D接口最核心的改進就是爲了提高性能!

由此可以看出實質上要將數據從內存中徹底傳遞到默認堆中,至少需要兩個Copy操作,一次在從內存到上傳堆的Map-memcpy-Unmap中,一次在從上傳堆到默認堆中。第一次Copy由CPU完成,而第二次Copy動作則由GPU上的複製引擎完成。並且根據上一講的內容,第二個Copy動作就需要用到命令列表和命令隊列了。

第三種回讀堆則是用於GPU寫入然後CPU讀取類型數據的。通常用於多趟渲染中的流輸出數據等,當然有時也用於讀取離屏渲染畫面數據的時候。從其用途就可以知道對於CPU來說這種堆中的數據是隻讀的,而對於GPU來說通常是隻寫的,並且往往需要向GPU標識其爲UAV(Unorder Access View 無序訪問視圖)形式的數據。當然也因爲CPU和GPU都要訪問這塊數據,所以它也是駐留在共享內存中的。所以GPU訪問這種類型的堆數據時性能也是有所損失的。

最後一種自定義堆類型就爲我們提供了自由組合CPU和GPU訪問方式的可能,同時我們還可以指定它在共享內存中,還是在顯存中。因此它可以實現更多更豐富的組合訪問形式。未來的教程中我們看情況會不會用到,如果用到我們在詳細講解。如果用不到講不到大家也不用着急,因爲前三種堆基本上就可以解決80%-90%的問題了。

4.3、資源屏障(Resource Barrier)

如果看懂了上一小節說的從上傳堆複製數據到默認堆的原理以及原因之後,那麼讓我們再來思考一個問題:圖形命令引擎如何知道複製引擎已經將數據從上傳堆複製進了默認堆中呢?具體的比如我們渲染需要用到一副紋理,尺寸可能有些大,複製過程需要耗費一些時間,但此時可能圖形命令引擎已經開始執行Draw Call命令了,之後在使用紋理時,數據還沒有複製完怎麼辦?這也就是我在之前系列文章中說到的“髒讀”問題。

這時我們就要用到D3D12中的資源屏障這個同步對象了。它的基本設計思路就是針對每種資源的不同訪問狀態的轉換來實現的。具體的比如在我們一開始創建一個默認堆上的資源時我們指定其訪問權限爲可作爲複製目標(D3D12_RESOURCE_STATE_COPY_DEST),此時圖形命令引擎中的命令就不能訪問這塊資源數據,或者說它會進入一個“等待”狀態,等待其有權限訪問。而複製引擎看到這個訪問權限標誌時就可以直接寫入數據,這裏再次說明因爲複製引擎本質上也是在GPU中的,所以它訪問上傳堆和默認堆都是沒有問題。最終我們發現雖然理論上覆制引擎和圖形引擎是獨立的,並且可以完全並行運行,但是在真正需要協作的時候,就需要二者有一定的串行管線,即複製引擎工作完成後圖形引擎才能繼續執行。這也就是之前說對於一些經常需要變動的數據我們就不再強制放到默認堆裏去的原因,主要就是爲了避免這個複製動作造成強制的串行執行關係,而導致性能上的損失。(要深度理解這個性能損失的原因,可能需要你對並行計算的一些理論有一些更深入的知識,比如說阿姆達爾定理等的相關知識。)

那麼在複製引擎複製完成時,我們就放置一個資源屏障的權限轉換同步對象,要求將權限變爲圖形引擎可以訪問的權限標誌,在本例中我們指定的是D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,也就是Pixel Shader程序可以訪問的標誌。具體示例代碼如下:

//向命令隊列發出從上傳堆複製紋理數據到默認堆的命令
CD3DX12_TEXTURE_COPY_LOCATION Dst(pITexcute.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	= pITexcute.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);

上面代碼中pICommandList接口是一個代表直接命令列表對象的接口,所以它能執行所有引擎可以執行的命令。我們可以看到,我們發出了一個複製命令,然後設置了一個資源屏障,並且指明瞭權限轉換的先後狀態標誌。

這裏要注意理解的一個概念就是,雖然命令列表中只是記錄了命令,最終只能到命令對象上才能真正執行,並且命令隊列最終執行命令與CPU執行是異步的,但是命令列表最終在GPU各引擎上的執行順序依然是串行順序的,所以ResourceBarrier命令真正執行完畢後,其實就表示前面的CopyTextureRegion命令已經完成了。這樣ResourceBarrier命令就像一道屏障一樣,完成了複製和使用之間的隔離,所以命名爲資源屏障就實至名歸了。

5、創建默認堆資源和上傳堆資源並上傳紋理數據

5.1、創建默認堆上的2D紋理

有了上面一大堆的理論基礎的準備之後,假如你都明白了,那麼接下來就讓我們看看真實的代碼中需要怎樣的調用。

首先,我們需要創建一個提交方式的默認堆紋理資源:

D3D12_RESOURCE_DESC stTextureDesc	= {};
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(&pITexcute)));

//獲取上傳堆資源緩衝的大小,這個尺寸通常大於實際圖片的尺寸
const UINT64 n64UploadBufferSize = GetRequiredIntermediateSize(pITexcute.Get(), 0, 1);

上面代碼中的主體風格還是初始化結構體然後調用函數的形式。結構體D3D12_RESOURCE_DESC是描述資源類型信息的,如前所述,因爲D3D12中已經不再使用派生接口方式來具體區分是紋理還是緩衝了,所以在這個結構體中,我們就要說清楚我們創建的是一個2D紋理,然後只有1個Mip等級,同時制定它的DXGI格式以及圖片的寬和高等信息。Flags字段暫時設置爲D3D12_RESOURCE_FLAG_NONE。因爲我們的紋理也不使用MSAA,所以SampleDesc就要像這裏這樣指定,以表示關閉MSAA特性。DepthOrArraySize字段則在加載複雜的紋理數組時纔用到,所以這裏我們就指定1即可,表示一維紋理數組,實質也就是隻有一副簡單的圖片的意思。

接着我們就調用CreateCommittedResource通過系統隱式堆的方式在默認堆上創建這個紋理。當然第一個參數我們依舊是使用了D3Dx12.h中的工具類做了簡化處理。需要特別注意的就是我們在創建時就指定了D3D12_RESOURCE_STATE_COPY_DEST權限標誌,這就表示之後的命令列表中的命令在訪問時,只能是複製引擎的對應的複製命令才能訪問它,並且把它作爲複製目標。而如果你直接調用其它的3D圖形命令來訪問它,就有可能引起一個訪問違例的異常。

上段代碼的最後又使用一個D3Dx12.h中的工具方法GetRequiredIntermediateSize來獲取了整個這個紋理資源的大小。其內部實質用到了一個重要的D3D12的方法GetCopyableFootprints。

這裏需要補充說明的就是,在D3D12中或者說根據現代GPU訪問存儲的邊界對齊要求,紋理的行大小必須是256字節邊界對齊,而整個紋理的大小又必須是512字節大小邊界對齊。比如在此例中使用了一副700*700像素大小的圖片,每個像素有RGBA格式各8位共32位大小,也就是每像素32/8=4字節大小,如果直接計算行大小的話是700*4=2800字節大小,但如果要256字節邊界對齊的話,就變成了(int)256*((700*4+(256-1))/256)=2816字節。

這裏用到了一個號稱是微軟面試題的上取整算法:(A+B-1)/B的公式,比如:5/2=2.5直接取整就是2,這是下取整的結果,如果使用公式就變爲(5+2-1)/2 = 3即上取整的結果。希望你看明白並牢記了這個公式,因爲很多關於內存管理邊界對齊的計算都需要用到這個公式。當然如果你敏而好學,一定想知道個爲啥的話,那麼想一下餘數不能大於除數的定理,以及最小的非零餘數爲1,自己推導一下就明白了。

那麼整個紋理數據邊界對齊的大小就是:

(int)512 * ((2816 * 700 + (512-1))/512) = 1971200字節

注意:在調試中GetRequiredIntermediateSize返回的整個紋理尺寸大小爲1971184字節與我這裏的計算值差16字節,正在查找原因,後續我會跟評說明。也請知道原因的網友不吝賜教,先謝了!

5.2、創建上傳堆上的資源

默認堆創建好了,但是實際上裏面什麼也沒有,如前所述我們還需要創建一箇中介——上傳堆來向默認堆上的紋理上傳數據,示例代碼如下:

// 創建用於上傳紋理的資源,注意其類型是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)));

上面的代碼中依然使用了D3Dx12.h中的工具類,初始化了一個隱式上傳堆屬性,然後初始化了一個Buffer類型的資源描述結構體。並且我們爲這個資源設置了GPU訪問初始化狀態爲D3D12_RESOURCE_STATE_GENERIC_READ也就是隻讀屬性。這樣GPU上的任何類型的引擎其實都可以直接從這個資源中讀取數據。

這裏需要強調的一個問題是,對於上傳堆來說,其類型都必須是Buffer類型。這是因爲如前所述,上傳堆中放置的實質上是CPU和GPU都能訪問的資源,而對於CPU來說,它其實不像GPU那麼細緻的區分每種資源的類型,也就是說無論紋理還是其他類型數據,它都認爲是一段緩衝數據而已。所以爲了遷就CPU的“粗獷”,那麼上傳堆無論是要放數據還是紋理,我們就都創建爲Buffer(緩衝)類型。同時也因爲這種粗獷,我們只需要指定一個大型的屬性給這個資源即可。因爲這裏我們要利用這個上傳堆中的資源來向默認堆上的紋理資源傳遞數據,所以我們指定的大小必須要大於紋理本身的大小。在此例中我們使用了前面從默認堆上的紋理資源獲取的大小尺寸來指定了緩衝的大小,這是因爲這個大小必定是大於等於我們圖片尺寸的大小的,因爲它被要求是向上邊界對齊的。

5.3、複製紋理圖片數據到上傳堆

有了上傳堆,那麼我們就可以進行前面理論介紹部分的第一個Copy動作了,代碼如下:

//按照資源緩衝大小來分配實際圖片數據存儲的內存大小
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)));

//獲取向上傳堆拷貝紋理數據的一些紋理轉換尺寸信息
//對於複雜的DDS紋理這是非常必要的過程
UINT64 n64RequiredSize = 0u;
UINT   nNumSubresources = 1u;  //我們只有一副圖片,即子資源個數爲1
D3D12_PLACED_SUBRESOURCE_FOOTPRINT stTxtLayouts = {};
UINT64 n64TextureRowSizes = 0u;
UINT   nTextureRowNum = 0u;

D3D12_RESOURCE_DESC stDestDesc = pITexcute->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);

代碼中的註釋已經描述的比較清楚了。這裏需要再次強調的就是因爲我們上傳的是一副紋理圖片,它屬於不易變的數據,所以我們複製完數據之後,就Unmap了事了。因爲使用的是隱式堆,這個上傳堆的重用性還不能顯現出來,之後的教程示例中我們再做詳細的介紹。

這裏重點要大家掌握的就是那個按行memcpy圖片數據的循環,注意上傳堆中的行大小與實際圖片數據中的行大小是有差異的,因此計算兩個指針的行偏移時使用的是不同的行大小尺寸,而實際複製數據的大小就是真實圖片的行大小。因爲我們的圖片使用的是簡單的RGBA格式,所以複製可以按行進行,對於其他複雜格式的紋理數據的複製,就需要按照實際的數據情況區別對待了。

另外一個需要注意的地方就是我們再一次顯式的調用了GetCopyableFootprints方法來得到資源中詳細的尺寸信息。這個方法幾乎是我們複製紋理時必須要調用的方法,主要用它來得到目標紋理數據的真實尺寸信息。因爲目標紋理如我們前面所描述的主要都是存儲在默認堆上的,而CPU是無法直接訪問它的,所以我們就需要這個方法作爲橋樑讓我們獲知最終存儲在默認堆中的紋理的詳細尺寸信息,以方便我們準備好上傳堆中的數據。而上傳堆因爲都統一爲了緩衝格式,被認爲是一維存放的數據的,所以是沒法獲知這些詳細的尺寸信息的。

5.4、調用複製命令並放置圍欄

紋理圖片數據加載到上傳堆之後,我們要做的的就是進行第二個Copy動作了,並且設置資源屏障,保證複製數據動作在GPU的複製引擎上完全執行結束。代碼如下:

//向命令隊列發出從上傳堆複製紋理數據到默認堆的命令
CD3DX12_TEXTURE_COPY_LOCATION Dst(pITexcute.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	= pITexcute.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);

上面的代碼我們在之前的理論介紹中已經介紹過了,需要額外補充說明的就是這裏我們調用了CopyTextureRegion這個命令來複制紋理數據,實質上還有CopyBufferRegion、CopyResource、CopyTiles等複製引擎的複製命令。而最常用的就是CopyTextureRegion和CopyBufferRegion,前者主要用於紋理的複製,而後者如其名字所示主要用於緩衝的複製。這些方法在之後的教程中我們都會有更詳細的介紹。目前瞭解本例中的用法即可。

之後我們就像下面這樣先執行以下這個命令列表中的複製命令和資源屏障,做第一次同步,代碼如下:

           

// 執行命令列表並等待紋理資源上傳完成,這一步是必須的
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);
}

上面的代碼其實就是執行一個命令列表,然後使用圍欄同步CPU和GPU的執行。到WaitForSingleObject返回時,我們就可以確定從上傳堆複製紋理數據到默認堆的操作已經完全執行完了。也就是紋理數據已經可以使用了,並且已經變成了D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE訪問權限,即我們的Pixel Shader程序中就可以訪問這個紋理了。當然在D3D12中,幾乎所有的Shader階段中都可以訪問紋理,這裏只是一個示例,將紋理用於了Pixel Shader中而已。

6、描述符和描述符堆

按照之前根簽名的描述,那麼加載完資源之後,我們需要做的就是準備好資源描述符了。前一講中我們以及簡單介紹過資源描述符了。在這裏我們在補充一些內容,因爲我始終認爲學習的過程就是一個不斷重複加深的螺旋式上升的過程。

實質上按照本講中的概念來說,我們可以將資源描述符理解爲一個指向實際資源的一次指針,而資源描述符堆則可以理解爲描述符指針的數組。這樣我們就從代碼的角度深入的理解了資源描述符的本質。

其實除了起到“指針”的作用,資源描述符還起到詳細描述資源類型信息的作用,比如被描述的資源是一個紋理,還是一塊純緩衝數據,又或者資源是渲染目標還是深度緩衝等等。

在本例中,因爲我們使用了一個紋理,所以就需要爲這個紋理創建描述符和描述符堆,具體代碼如下:

//10、創建SRV堆 (Shader Resource View Heap)
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(pITexcute.Get(), &stSRVDesc, pISRVHeap->GetCPUDescriptorHandleForHeapStart());

 

這裏我們創建的資源描述符堆使用的是D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,表示這個堆上可以放置CBV、SRV或UAV。之所以資源描述符也需要這樣的堆式創建,其目的依然很簡單就是爲了描述符堆的“重用”,我們可以簡單的釋放具體資源描述符,而不用釋放描述符堆,通過重用描述符堆,從而提升性能。這與我們使用資源堆的目的相一致。同時也帶來了與資源管理在編碼框架上的一致性。

 

7、本章全部代碼

最後我們將所有代碼粘貼如下,依然希望大家自己動手創建項目,複製代碼,自己修改編譯調試運行例子,以加深印象。

 

#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); }

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
};

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;
	DXGI_FORMAT stTextureFormat = DXGI_FORMAT_UNKNOWN;

	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<IDXGISwapChain1>				pISwapChain1;
	ComPtr<IDXGISwapChain3>				pISwapChain3;
	ComPtr<ID3D12DescriptorHeap>		pIRTVHeap;
	ComPtr<ID3D12DescriptorHeap>		pISRVHeap;
	ComPtr<ID3D12Resource>				pIARenderTargets[nFrameBackBufCount];
	ComPtr<ID3D12Resource>				pITexcute;
	ComPtr<ID3D12CommandAllocator>		pICommandAllocator;
	ComPtr<ID3D12RootSignature>			pIRootSignature;
	ComPtr<ID3D12PipelineState>			pIPipelineState;
	ComPtr<ID3D12GraphicsCommandList>	pICommandList;
	ComPtr<ID3D12Resource>				pIVertexBuffer;
	ComPtr<ID3D12Fence>					pIFence;

	ComPtr<IWICImagingFactory>			pIWICFactory;
	ComPtr<IWICBitmapDecoder>			pIWICDecoder;
	ComPtr<IWICBitmapFrameDecode>		pIWICFrame;

	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);

		//---------------------------------------------------------------------------------------------
#if defined(_DEBUG)
		{//打開顯示子系統的調試支持
			ComPtr<ID3D12Debug> debugController;
			if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController))))
			{
				debugController->EnableDebugLayer();
				// 打開附加的調試支持
				nDXGIFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG;
			}
		}
#endif
		//---------------------------------------------------------------------------------------------
		//2、創建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));

		//---------------------------------------------------------------------------------------------
		//3、枚舉適配器,並選擇合適的適配器來創建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;
			}
		}

		//---------------------------------------------------------------------------------------------
		//4、創建D3D12.1的設備
		GRS_THROW_IF_FAILED(D3D12CreateDevice(pIAdapter.Get(), D3D_FEATURE_LEVEL_12_1, IID_PPV_ARGS(&pID3DDevice)));

		//---------------------------------------------------------------------------------------------
		//5、創建直接命令隊列
		D3D12_COMMAND_QUEUE_DESC stQueueDesc = {};
		stQueueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
		GRS_THROW_IF_FAILED(pID3DDevice->CreateCommandQueue(&stQueueDesc, IID_PPV_ARGS(&pICommandQueue)));

		//---------------------------------------------------------------------------------------------
		//6、創建交換鏈
		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
		));

		//---------------------------------------------------------------------------------------------
		//7、得到當前後緩衝區的序號,也就是下一個將要呈送顯示的緩衝區的序號
		//注意此處使用了高版本的SwapChain接口的函數
		GRS_THROW_IF_FAILED(pISwapChain1.As(&pISwapChain3));
		nFrameIndex = pISwapChain3->GetCurrentBackBufferIndex();

		//---------------------------------------------------------------------------------------------
		//8、創建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);

		//---------------------------------------------------------------------------------------------
		//9、創建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);
		}

		//---------------------------------------------------------------------------------------------
		//10、創建SRV堆 (Shader Resource View Heap)
		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)));

		//---------------------------------------------------------------------------------------------
		//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[1];
		stDSPRanges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0, 0, D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC_WHILE_SET_AT_EXECUTE);

		CD3DX12_ROOT_PARAMETER1 stRootParameters[1];
		stRootParameters[0].InitAsDescriptorTable(1, &stDSPRanges[0], 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);

		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創建渲染管線狀態對象
		ComPtr<ID3DBlob> pIBlobVertexShader;
		ComPtr<ID3DBlob> pIBlobPixelShader;
#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、創建命令列表分配器
		GRS_THROW_IF_FAILED(pID3DDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT
			, IID_PPV_ARGS(&pICommandAllocator)));

		//---------------------------------------------------------------------------------------------
		// 14、創建圖形命令列表
		GRS_THROW_IF_FAILED(pID3DDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT
			, pICommandAllocator.Get(), pIPipelineState.Get(), IID_PPV_ARGS(&pICommandList)));

		//---------------------------------------------------------------------------------------------
		// 15、創建頂點緩衝
		// 定義正方形的3D數據結構
		GRS_VERTEX stTriangleVertices[] =
		{
			{ { -0.25f* fAspectRatio, -0.25f * fAspectRatio, 0.0f}, { 0.0f, 1.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 }, { 1.0f, 1.0f } },	// Bottom right.
			{ { 0.25f* fAspectRatio, 0.25f * fAspectRatio, 0.0f}, { 1.0f, 0.0f } },		// Top right.
		};

		const UINT nVertexBufferSize = sizeof(stTriangleVertices);
		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)));

		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;

		//---------------------------------------------------------------------------------------------
		// 16、使用WIC創建並加載一個2D紋理
		//使用純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);
		}

		// 定義一個位圖格式的圖片數據對象接口
		ComPtr<IWICBitmapSource>pIBMP;

		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 ,
		// 這曾經被傳說是微軟的面試題,希望你已經對它瞭如指掌
		UINT nPicRowPitch = (uint64_t(nTextureW) * uint64_t(nBPP) + 7u) / 8u;
		//---------------------------------------------------------------------------------------------

		ComPtr<ID3D12Resource> pITextureUpload;

		D3D12_RESOURCE_DESC stTextureDesc	= {};
		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(&pITexcute)));

		//獲取上傳堆資源緩衝的大小,這個尺寸通常大於實際圖片的尺寸
		const UINT64 n64UploadBufferSize = GetRequiredIntermediateSize(pITexcute.Get(), 0, 1);

		// 創建用於上傳紋理的資源,注意其類型是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)));
		
		//按照資源緩衝大小來分配實際圖片數據存儲的內存大小
		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紋理這是非常必要的過程
		UINT64 n64RequiredSize = 0u;
		UINT   nNumSubresources = 1u;  //我們只有一副圖片,即子資源個數爲1
		D3D12_PLACED_SUBRESOURCE_FOOTPRINT stTxtLayouts = {};
		UINT64 n64TextureRowSizes = 0u;
		UINT   nTextureRowNum = 0u;

		D3D12_RESOURCE_DESC stDestDesc = pITexcute->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);

		//向命令隊列發出從上傳堆複製紋理數據到默認堆的命令
		CD3DX12_TEXTURE_COPY_LOCATION Dst(pITexcute.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	= pITexcute.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(pITexcute.Get()
		//	, D3D12_RESOURCE_STATE_COPY_DEST
		//	, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE)
		//);

		//---------------------------------------------------------------------------------------------
		// 最終創建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(pITexcute.Get(), &stSRVDesc, pISRVHeap->GetCPUDescriptorHandleForHeapStart());

		//---------------------------------------------------------------------------------------------
		// 執行命令列表並等待紋理資源上傳完成,這一步是必須的
		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()));
		//---------------------------------------------------------------------------------------------
		

		//---------------------------------------------------------------------------------------------
		//20、創建定時器對象,以便於創建高效的消息循環
		HANDLE phWait = CreateWaitableTimer(NULL, FALSE, NULL);
		LARGE_INTEGER liDueTime = {};
		liDueTime.QuadPart = -1i64;//1秒後開始計時
		SetWaitableTimer(phWait, &liDueTime, 1, NULL, NULL, 0);//40ms的週期

		//---------------------------------------------------------------------------------------------
		//21、開始消息循環,並在其中不斷渲染
		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() };
				pICommandList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);
				pICommandList->SetGraphicsRootDescriptorTable(0, pISRVHeap->GetGPUDescriptorHandleForHeapStart());
				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);
				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;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

 


Texture.hlsl

struct PSInput
{
	float4 position : SV_POSITION;
	float2 uv : TEXCOORD;
};

Texture2D g_texture : register(t0);
SamplerState g_sampler : register(s0);

PSInput VSMain(float4 position : POSITION, float2 uv : TEXCOORD)
{
	PSInput result;

	result.position = position;
	result.uv = uv;

	return result;
}

float4 PSMain(PSInput input) : SV_TARGET
{
	return g_texture.Sample(g_sampler, input.uv);
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章