目錄
1、前言及本章內容提要
經過了之前3篇教程的跨度有點大的學習,我想大家現在應該對D3D12編程有了初步掌握。本篇教程算一個小小的過渡:一是鞏固一下之前所學的知識;二是將還剩下的兩個知識點講解清楚;從而爲後面進一步學習相對高(fu)級(za)的內容進一步掃清障礙。
本章示例將在上一章基礎上實現一個旋轉的帶紋理的正方體的繪製,並實現了簡單的攝像機控制以及旋轉速度控制。可以說這章起我們纔算是真正的繪製了3D的圖形。
最終本章例子運行效果如下:
2、初識DirectXMath庫
DirectXMath是一個用於遊戲和圖形應用程序的全內聯SIMD c++線性代數庫,它提供了單精度3D向量(2-4維)、矩陣(3X3、4X4)、歐拉角、四元數等的基本計算支持,底層利用了現代x86架構中的sse1-sse4等高級SIMD指令,得益於這些指令的高效及流水線並行優化的特性,所以它是目前在x86 CPU上執行效率最高的3D數學庫,非常合適用來封裝引擎或遊戲等3D應用。
一般它伴隨Windows SDK一起發佈,但微軟現在也將它放在了GitHub上並在不定期的更新和發佈,網址在https://github.com/Microsoft/DirectXMath。因爲它是放在SDK中,所以當下載了最新版之後,最好是全部解壓之後複製進SDK的目錄中,覆蓋原來的文件。否則單獨放目錄編譯的話會比較麻煩。
其實DirectXMath庫是是歷史上D3Dx@@Math庫的自然演化物,只是說D3DXMath庫依賴於其dll,版本久遠,而現在的DirectXMath庫則全部採用內聯代碼方式,在使用、調試、發佈上更有優勢。所以現在我們就應該使用最新的DirectXMath庫函數來實現3D應用中的數學部分。
要使用這個庫,首先就包含它的頭文件#include <DirectXMath.h>,這在我們之前的示例代碼中已經很常見了,同時它的所有的函數都使用了名字空間DirectX,所以我們爲了代碼編寫上的方便就直接使用了using namespace DirectX;語句,這樣我們直接使用它的常量、結構、函數都沒有問題。
因爲DirectXMath庫中默認使用了處理器上的SIMD指令,這就與GPU使用數據類似,都會要求數據邊界對齊,當然對於CPU上的SIMD指令來說只需要簡單的16字節邊界對齊即可。因此在使用動態內存分配的時候,考慮使用邊界對齊的分配和釋放方法:_aligned_malloc 、_aligned_free。
變量邊界對齊的要求也給我們帶來了一定的方便性,當我們需要將變換矩陣等數據常量上傳到GPU的資源中時,使用Map方法返回的指針天然就是邊界對齊的,並且它對齊與64k邊界,是16字節的倍數,所以我們可以直接向這個指針的內存中複製DirectXMath定義的變量。
也因爲邊界對齊的要求,總體上來說,DirectXMath庫定義了兩套結構體系,均以XM開頭。第一套是以XMFLOAT、XMINT、XMUINT等成員類型名開頭,它是普通結構體,用於我們在代碼中任意地方定義需要的向量、矩陣等變量,另一組則是以XMVECTOR、XMMATRIX等開頭用以定義和指向邊界對齊的等價向量和矩陣值,並且所有的XM開頭的庫函數都只操作邊界對齊的這組結構體定義的變量。那麼在實際使用時,就需要首先使用XMLoad*****函數族,將我們定義的XMFLOAT***、XMINT****等值加載成邊界對齊的等價XMVECTOR***、XMMATRIX**等類型的變量,運算完成後就使用XMStore*****函數族將邊界對齊的變量值存回任意的XMFLOAT***、XMINT****變量中。因此我們經常會看到如下框架性的代碼段:
XMMATRIX matWorld = XMMatrixRotationQuaternion( XMLoadFloat4( &obb.Orientation ) );
XMMATRIX matScale = XMMatrixScaling( obb.Extents.x, obb.Extents.y, obb.Extents.z );
matWorld = XMMatrixMultiply( matScale, matWorld );
XMVECTOR position = XMLoadFloat3( &obb.Center );
需要注意的是,現代的64位VC++編譯器已經能夠正確的自動對齊類似XMVECTOR、XMMATRIX等的自動變量,所以現在我們更多的是直接定義自動變量來使用DirectXMath庫。
最終要完全用好DirectXMath庫,就需要我們對3D數學有深刻的理解和掌握,這將在我後續的其他教程中進一步講解,目前我們任然聚焦於D3D12自身的編程學習方面。
在本章例子中,我們主要在立方體的旋轉方面用到了這個庫的能力,代碼如下:
n64tmCurrent = ::GetTickCount();
//計算旋轉的角度:旋轉角度(弧度) = 時間(秒) * 角速度(弧度/秒)
//下面這句代碼相當於經典遊戲消息循環中的OnUpdate函數中需要做的事情
dModelRotationYAngle += (( n64tmCurrent - n64tmFrameStart ) / 1000.0f) * fPalstance;
n64tmFrameStart = n64tmCurrent;
//旋轉角度是2PI週期的倍數,去掉週期數,只留下相對0弧度開始的小於2PI的弧度即可
if ( dModelRotationYAngle > XM_2PI )
{
dModelRotationYAngle = fmod( dModelRotationYAngle, XM_2PI );
}
//模型矩陣 model
XMMATRIX xmRot = XMMatrixRotationY(static_cast<float>(dModelRotationYAngle));
//計算 模型矩陣 model * 視矩陣 view
XMMATRIX xmMVP = XMMatrixMultiply(xmRot, XMMatrixLookAtLH(Eye, At, Up));
//投影矩陣 projection
xmMVP = XMMatrixMultiply(xmMVP, (XMMatrixPerspectiveFovLH(XM_PIDIV4, (FLOAT)iWidth / (FLOAT)iHeight, 0.1f, 1000.0f)));
XMStoreFloat4x4(&pMVPBuffer->m_MVP, xmMVP);
上述代碼中我們用到了最簡單的物理公式:旋轉角度=角速度X時間,這是高中的內容,剩下的就是實現經典的MVP矩陣。M矩陣就是繞Y軸旋轉的矩陣,因爲在D3D中默認座標系Y軸垂直向上,而V矩陣就是根據代碼中簡單的攝像機計算得到的,最後P矩陣在代碼中已經顯示很清楚了。注意我們使用的是左手座標系,所以函數是以LH結尾的,對應的有可以用於OpenGL的RH結尾的函數版本。最後我們雖然調用了一次XMStoreFloat4x4,但其實這裏可以直接使用memcpy的,因爲pMVPBuffer指針來自於我們對常量緩衝的Map操作。我這樣做只是一個習慣問題,沒有特別的含義,大家不要被迷惑。
這裏需要注意的一個問題就是在DirectXMath中矩陣是行主序存儲的,而在我們使用的HLSL中,使用的是SM5.0語法,它裏面的矩陣是列主序存儲的,因此在最終將MVP矩陣傳入Shader的常量緩衝中時可能需要一次轉置操作。在我們的示例中,我們是使用編譯Shader時指定行主序編譯標誌,省去了這個轉置操作,代碼如下:
//編譯爲行矩陣形式
compileFlags |= D3DCOMPILE_PACK_MATRIX_ROW_MAJOR;
TCHAR pszShaderFileName[] = _T("D:\\Projects_2018_08\\D3D12 Tutorials\\3-D3D12TextureCube\\Shader\\TextureCube.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));
這樣我們就可以直接將DirectXMath的矩陣傳入Shader中使用了。
在後面我們在簡單的攝像機控制中,也使用了簡單的DirectXMath庫函數,用於變換攝像機的位置,代碼如下:
//初始的默認攝像機的位置
XMVECTOR Eye = XMVectorSet(0.0f, 0.0f, -10.0f, 0.0f); //眼睛位置
XMVECTOR At = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f); //眼睛所盯的位置
XMVECTOR Up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f); //頭部正上方位置
......
......
if (VK_UP == n16KeyCode || 'w' == n16KeyCode || 'W' == n16KeyCode)
{
Eye = XMVectorAdd(Eye, XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f));
}
if (VK_DOWN == n16KeyCode || 's' == n16KeyCode || 'S' == n16KeyCode)
{
Eye = XMVectorAdd(Eye, XMVectorSet(0.0f, -1.0f, 0.0f, 0.0f));
}
if (VK_RIGHT == n16KeyCode || 'd' == n16KeyCode || 'D' == n16KeyCode)
{
Eye = XMVectorAdd(Eye, XMVectorSet(1.0f, 0.0f, 0.0f, 0.0f));
}
if (VK_LEFT == n16KeyCode || 'a' == n16KeyCode || 'A' == n16KeyCode)
{
Eye = XMVectorAdd(Eye, XMVectorSet(-1.0f, 0.0f, 0.0f, 0.0f));
}
這段代碼很簡單,就是簡單的上下左右,變換眼睛的位置而已,我想函數的名字已經很清楚的標明的了代碼在幹什麼,我就不多解釋了。當然這個變換因爲簡單,但不是很正確,後續的其他教程中我會講解更準確的方法。因爲那是比較燒腦的純數學的問題,所以就不放在這個系列的教程中去囉嗦了。
3、使用獨立堆創建常量緩衝
在之前的例子中,因爲我們渲染的基本是平面的圖形,那麼就沒有用到什麼常量緩衝。這章中我們需要渲染的是一個立方體,爲了體現3D的效果,我們特意讓它旋轉起來,顯示我們其實是在做3D的渲染。
爲了旋轉我們就需要用到經典的MVP矩陣,並且要讓它能夠傳遞到Shader中,做逐個頂點的旋轉計算。當然爲了看3D的效果,我們必須爲哪怕最簡單的場景都指定View矩陣、和Projection矩陣,也就是我們通常說的攝像機矩陣和裁剪矩陣,關於這些矩陣的推導和理解我將放在其他的教程。
這裏需要理解的其他幾個地方就是,首先對於任何3D場景來說,一幀畫面在渲染(3D->2D)的過程中,它的攝像機位置基本是不會變化的,否則最終的畫面將不符合人類的視覺習慣了,也就成了無效的渲染;其次對於GPU渲染來說攝像機相關的矩陣數據其實都是隻讀的,因此爲了進一步提高性能,我們就乾脆把它放在GPU可以訪問的只讀常量緩衝中;最後我們要注意的就是對於不同的幀來說,攝像機的位置、方位等參數就可能是不斷變化的了,因此我們就需要在每幀畫面渲染開始的時候就更新他們的值;另外對於我們所熟知的骨骼動畫的矩陣調色板來說也是如此,而這些矩陣的計算其實最終是CPU完成的,根據現代渲染管線中CPU和GPU的職責分工來說,這些矩陣有一個典型的特徵就是1對多,直白的說,就是比如一個攝像機的VP矩陣就可以用於變換整個場景所有物體的所有頂點,而Model矩陣(有些教程中還要在其後追加一個World矩陣,用於變換整個場景的方位)則是用來變換一個物體,骨骼動畫的矩陣調試版也是按照索引和權重變換一個具體的物體。所以這些矩陣的量本質上來說數量是很少的,少則0~1個,多則幾十個而已,用CPU來計算的話是比較合理和划算的。最終就形成了CPU在每幀渲染開始前不斷的計算這些矩陣,然後上傳到GPU的緩衝中的實際運行場景。
綜合這些情況,以及我們之前幾章學習的顯存管理的知識來看,我們可以這樣考慮數據緩衝存儲,第一就是我們考慮將他們放在CPU和GPU都能訪問的共享內存中,就我們目前可以控制的存儲堆類型來說這對應着上傳堆;第二考慮到這些數據每幀都要變化和重新傳輸,因此我們只做第一次Copy,即從內存中將數據複製到上傳堆緩衝中即可,而不再額外的做第二次GPU複製引擎的Copy動作了(考慮雖然GPU訪問上傳堆中的數據有性能損失,但是比起浪費GPU的複製引擎週期來複制數據,則完全少了複製的開銷,因爲複製引擎訪問這個數據也要付出同樣的性能損失的代價);第三也因爲這些數據需要反覆計算和傳輸,同時我們現在已經完全控制了堆的生命週期,所以我們直接一次性Map之後,就反覆memcpy,直到程序結束前才調用Unmap。所有這些考慮其實只有一個目的就是——性能!
最終綜合上面這些陳述我們就可以編寫如下的代碼:
n64BufferOffset = GRS_UPPER(n64BufferOffset + nszIndexBuffer, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT);
// 創建常量緩衝 注意緩衝尺寸設置爲256邊界對齊大小
GRS_THROW_IF_FAILED(pID3DDevice->CreatePlacedResource(
pIUploadHeap.Get()
, n64BufferOffset
, &CD3DX12_RESOURCE_DESC::Buffer(szMVPBuffer)
, D3D12_RESOURCE_STATE_GENERIC_READ
, nullptr
, IID_PPV_ARGS(&pICBVUpload)));
// Map 之後就不再Unmap了 直接複製數據進去 這樣每幀都不用map-copy-unmap浪費時間了
GRS_THROW_IF_FAILED(pICBVUpload->Map(0, nullptr, reinterpret_cast<void**>(&pMVPBuffer)));
在渲染前我們就像下面這樣將數據複製進常量緩衝中:
XMStoreFloat4x4(&pMVPBuffer->m_MVP, xmMVP);
根據前面我們對DirectXMath庫基本用法的描述,這裏的XMStoreFloat4x4其實就相當於一個memcpy操作了。綜合前面各章的描述大家一定要注意區分我每次說memcpy時,其實是泛指CPU的memory copy操作,而不是簡單只是說memcpy函數而已,希望你明白這個意思。
當然按照之前我們對根簽名的學習,這裏其實我們只是說準備好了一個常量緩衝而已,接下來其實我們還需要準備兩級對應的“指針”:一個就是根簽名中根參數,一個是描述符堆上的常量緩衝視圖(即常量緩衝描述符),綜合起來他們的代碼如下:
......
CD3DX12_DESCRIPTOR_RANGE1 stDSPRanges[3];
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_CBV, 1, 0);
stDSPRanges[2].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 1, 0);
CD3DX12_ROOT_PARAMETER1 stRootParameters[3];
stRootParameters[0].InitAsDescriptorTable(1, &stDSPRanges[0], D3D12_SHADER_VISIBILITY_PIXEL);//SRV僅PS可見
stRootParameters[1].InitAsDescriptorTable(1, &stDSPRanges[1], D3D12_SHADER_VISIBILITY_ALL); //CBV是所有Shader可見
stRootParameters[2].InitAsDescriptorTable(1, &stDSPRanges[2], D3D12_SHADER_VISIBILITY_PIXEL);//SAMPLE僅PS可見
......
//我們將紋理視圖描述符和CBV描述符放在一個描述符堆上
D3D12_DESCRIPTOR_HEAP_DESC stSRVHeapDesc = {};
stSRVHeapDesc.NumDescriptors = 2; //1 SRV + 1 CBV
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)));
......
D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {};
cbvDesc.BufferLocation = pICBVUpload->GetGPUVirtualAddress();
cbvDesc.SizeInBytes = static_cast<UINT>(szMVPBuffer);
CD3DX12_CPU_DESCRIPTOR_HANDLE cbvSrvHandle(pISRVHeap->GetCPUDescriptorHandleForHeapStart()
, 1
, pID3DDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV));
pID3DDevice->CreateConstantBufferView(&cbvDesc, cbvSrvHandle);
上面的代碼與我們之前例子中的同類代碼很類似,就不多囉嗦講解的了。這裏只是再強調一下兩級指針的含義,第一級指針是常量緩衝描述符指向常量緩衝,標誌是語句:cbvDesc.BufferLocation = pICBVUpload->GetGPUVirtualAddress(); 從其函數名我們應該立即明白這就是獲取指針,而第二級指針的設定就在渲染循環中的:
CD3DX12_GPU_DESCRIPTOR_HANDLE stGPUCBVHandle(pISRVHeap->GetGPUDescriptorHandleForHeapStart()
, 1
, pID3DDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV));
//設置CBV
pICommandList->SetGraphicsRootDescriptorTable(1, stGPUCBVHandle);
我們仔細觀察其中的pISRVHeap->GetGPUDescriptorHandleForHeapStart(),這個語句雖然它名字中沒有明顯出現Address字眼,但其實Handle、Heap Start等字眼我想對於一個搞C/C++的老程序員來說,這只不過都是障眼法而已,我們需要的是用火眼金睛,快速的看出他的本質——還是指針!
這樣最終就形成了從描述符堆(描述符數組)中對應元素首地址指向對應描述符、再從描述符指向緩衝開始地址的二級指針的結構。其實對於加入了顯存管理的D3D12編程來說,加入這樣的二級指針框架來具體關聯存儲區域的做法,我想對於C/C++程序來說應該很好理解的。當然這裏再次重複這個概念的目的就是爲了讓你深刻的鞏固過去的知識並進一步加深理解。
那麼對於我們C/C++程序員來說這個“二級指針”框架一旦認識了它的本質就沒什麼好稀奇的了,而我們的職責就是在封裝引擎或構建3D應用時,將它徹底的封裝埋藏起來,並且讓最終的腳本編寫人員或引擎調用人員不再需要去感知和理解它。當然這是題外話了,我只是提供些方向性的建議。
4、理解管線狀態對象
在一開始的基礎教程中,我只是說了在D3D12中渲染管線狀態對象被獨立了出來,形成了渲染管線狀態對象。這極大的方便了對渲染管線狀態的設置和管理工作。
在D3D12中整個渲染管線的狀態以及對應的Shader程序都被放置在一個統一的結構體中,然後通過調用ID3D12Device::CreateGraphicsPipelineState創建成一個渲染管線狀態的對象。這裏需要注意的是這個渲染管線狀態只是一個靜態的狀態,如果如前面的教程中所講將根簽名看做渲染管線的參數描述的話,這個渲染管線狀態對象就是一個函數體的定義了。這樣繼續理解的話,那麼就是說一個渲染管線狀態可以配以不同的根簽名描述,當然需要的就是參數個數種類必須一致,順序可以不一致。而反過來不同的根簽名就可以用以描述不同的渲染管線狀態。
在運行時,或者說渲染時,渲染管線狀態對象就要綁定到具體的命令列表上去,然後我們通過命令列表來設置渲染管線需要的各種參數,最終在命令隊列中排隊等待圖形引擎的執行。
這樣我們就基本上完整的明白了D3D12中整個渲染部分的全部框架了,那就是渲染管線狀態對象決定了渲染的靜態配置和渲染的程序(各種Shader),而命令列表和命令隊列決定了渲染的動態運行狀態。而整個渲染管線的編程就是圍繞它們展開的,它們最終決定了我們編程的基本框架。
更進一步建議各位去看看D3D12_GRAPHICS_PIPELINE_STATE_DESC這個結構體的定義,從而加深理解,它的定義我就不粘貼在這裏了。其成員我也不做多的解釋了,因爲它的名字已經描述的很清楚了。
5、理解圍欄同步
在之前的例子中,我們以及接觸了很多次圍欄(Fence)對象了,我們反覆的說它是用來同步的,我相信很多人在看到這個東西時是有點暈的,因爲我們一直都只是一個單線程的程序,需要同步什麼呢?其實本質上說雖然直到目前我們一直都使用的單線程(其實就是程序的默認主線程)程序,但實質上我們同時操作的是CPU和GPU。根據我們前面的描述,在D3D12中,其實CPU和GPU已經是完全的並行執行了,即CPU不論發送給GPU何種命令,以至於最終的ExecuteCommandLists函數都是立即返回,此時GPU和CPU就是真正的並行執行了,GPU上就是運行一個完整的渲染過程了,而CPU就是按照我們之後的C/C++代碼繼續運行。
最終我們實際需要知道的就是GPU運行到哪裏了?那就需要圍欄來幫忙了。
從原理上來說,圍欄本身關聯與一個值,也叫作圍欄值,我們通過下面的語句來關聯他們:
const UINT64 fence = n64FenceValue;
GRS_THROW_IF_FAILED(pICommandQueue->Signal(pIFence.Get(), fence));
n64FenceValue++;
圍欄值其實是一個計數值。而Signal函數是命令隊列本身爲數不多的幾個命令函數之一,也就是直接在GPU對應引擎上執行的命令。所以它是我們直接排隊在命令隊列中的命令,如我們之前教程中所說,雖然命令列表可能可以隨時記錄命令甚至可以並行亂序記錄,但他們排隊到命令隊列中之後,各引擎在執行時卻是串行的,也就是先進先出的執行命令,所以代表各引擎執行命令的對象就被命名爲命令隊列。因此當我們按順序排隊完需要的命令或命令列表之後,我們放置一個Signal命令,那麼含義就是說前面的命令順序執行完之後,就執行Signal,而當Signal執行後,對應的圍欄對象的圍欄值就被GPU變成了我們調用它排隊進命令隊列中的值。此時如果我們用CPU檢測這個圍欄值變成我們指定的值時,就說明之前的命令已經全部被GPU執行完了,之後CPU就可以重新組織和編排新的命令或命令列表去執行了。
當然我們最好不要通過一個死循環不停的在CPU端取這個圍欄值來看GPU到底執行完之前的命令沒有,因爲這樣完全失去了CPU和GPU並行執行的意義了,通常我們是爲這個圍欄對象的圍欄值綁定一個CPU側的同步對象,在我們例子中我們綁定的都是Event對象(通過調用ID3D12Fence::SetEventOnCompletion),當GPU命令執行到指定的圍欄值時,這個Event對象就被設置爲有信號狀態,此時在其上等待的Wait函數族就會退出等待信號狀態而返回。這樣CPU通過Wait函數的返回就知道了圍欄值已經變成了我們設定的那個值,從而進一步知道它之前的命令都已經被GPU執行完了。
當然我們例子代碼以及大多數D3D12例子代碼中,都是通過同步的Wait函數“死等”這個信號的,但其實建議在真實的引擎或3D應用渲染過程中,最好不要指定INFINITE(無限)等待的調用,而是指定一個0參數的Wait函數進行輪詢,正如我們本章例子中對MsgWait函數進行的修改一樣,其實這就是一個提示,我建議有能力的看官,將例子中的Wait整合進MsgWait函數中去,從而最終明白我一直都堅持使用MsgWait函數的“良苦用心”。
最後關於圍欄的這段代碼我就不再粘貼了,最後請查看完整的代碼清單。
6、完整代碼
#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 ST_GRS_VERTEX
{//這次我們額外加入了每個頂點的法線,但Shader中還暫時沒有用
XMFLOAT3 m_vPos; //Position
XMFLOAT2 m_vTex; //Texcoord
XMFLOAT3 m_vNor; //Normal
};
struct ST_GRS_FRAME_MVP_BUFFER
{
XMFLOAT4X4 m_MVP; //經典的Model-view-projection(MVP)矩陣.
};
UINT nCurrentSamplerNO = 0; //當前使用的採樣器索引
UINT nSampleMaxCnt = 5; //創建五個典型的採樣器
//初始的默認攝像機的位置
XMVECTOR Eye = XMVectorSet(0.0f, 0.0f, -10.0f, 0.0f); //眼睛位置
XMVECTOR At = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f); //眼睛所盯的位置
XMVECTOR Up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f); //頭部正上方位置
double fPalstance = 10.0f * XM_PI / 180.0f; //物體旋轉的角速度,單位:弧度/秒
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 fBoxSize = 3.0f;
float fTCMax = 3.0f;
ST_GRS_FRAME_MVP_BUFFER* pMVPBuffer = nullptr;
SIZE_T szMVPBuffer = GRS_UPPER_DIV(sizeof(ST_GRS_FRAME_MVP_BUFFER), 256) * 256;
D3D12_VERTEX_BUFFER_VIEW stVertexBufferView = {};
D3D12_INDEX_BUFFER_VIEW stIndexBufferView = {};
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> pICBVUpload;
ComPtr<ID3D12Resource> pIVertexBuffer;
ComPtr<ID3D12Resource> pIIndexBuffer;
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\\3-D3D12TextureCube\\Texture\\timg.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、枚舉適配器創建設備
{//選擇NUMA架構的獨顯來創建3D設備對象,暫時先不支持集顯了,當然你可以修改這些行爲
DXGI_ADAPTER_DESC1 desc = {};
D3D12_FEATURE_DATA_ARCHITECTURE stArchitecture = {};
for (UINT adapterIndex = 0; DXGI_ERROR_NOT_FOUND != pIDXGIFactory5->EnumAdapters1(adapterIndex, &pIAdapter); ++adapterIndex)
{
DXGI_ADAPTER_DESC1 desc = {};
pIAdapter->GetDesc1(&desc);
if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
{//跳過軟件虛擬適配器設備
continue;
}
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 )
{
break;
}
pID3DDevice.Reset();
}
//---------------------------------------------------------------------------------------------
if (nullptr == pID3DDevice.Get())
{// 可憐的機器上居然沒有獨顯 還是先退出了事
throw CGRSCOMException(E_FAIL);
}
}
//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圖形引擎、計算引擎、複製引擎等)
//注意初始時並沒有使用PSO對象,此時其實這個命令列表依然可以記錄命令
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
));
//注意此處使用了高版本的SwapChain接口的函數
GRS_THROW_IF_FAILED(pISwapChain1.As(&pISwapChain3));
nFrameIndex = pISwapChain3->GetCurrentBackBufferIndex();
//創建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);
}
}
//9、創建 SRV CBV Sample堆
{
//我們將紋理視圖描述符和CBV描述符放在一個描述符堆上
D3D12_DESCRIPTOR_HEAP_DESC stSRVHeapDesc = {};
stSRVHeapDesc.NumDescriptors = 2; //1 SRV + 1 CBV
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)));
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);
}
//10、創建根簽名
{
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[3];
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_CBV, 1, 0);
stDSPRanges[2].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 1, 0);
CD3DX12_ROOT_PARAMETER1 stRootParameters[3];
stRootParameters[0].InitAsDescriptorTable(1, &stDSPRanges[0], D3D12_SHADER_VISIBILITY_PIXEL);//SRV僅PS可見
stRootParameters[1].InitAsDescriptorTable(1, &stDSPRanges[1], D3D12_SHADER_VISIBILITY_ALL); //CBV是所有Shader可見
stRootParameters[2].InitAsDescriptorTable(1, &stDSPRanges[2], D3D12_SHADER_VISIBILITY_PIXEL);//SAMPLE僅PS可見
CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC stRootSignatureDesc;
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)));
}
//11、編譯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
//編譯爲行矩陣形式
compileFlags |= D3DCOMPILE_PACK_MATRIX_ROW_MAJOR;
TCHAR pszShaderFileName[] = _T("D:\\Projects_2018_08\\D3D12 Tutorials\\3-D3D12TextureCube\\Shader\\TextureCube.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));
// 我們多添加了一個法線的定義,但目前Shader中我們並沒有使用
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 },
{ "NORMAL", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 20, 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)));
}
//12、創建紋理的默認堆
{
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)));
}
//13、創建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;
//-----------------------------------------------------------------------------------------------------------
//使用“定位方式”來創建紋理,注意下面這個調用內部實際已經沒有存儲分配和釋放的實際操作了,所以性能很高
//同時可以在這個堆上反覆調用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);
}
//14、創建上傳堆
{
//-----------------------------------------------------------------------------------------------------------
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)));
//-----------------------------------------------------------------------------------------------------------
}
//15、使用“定位方式”創建用於上傳紋理數據的緩衝資源
{
GRS_THROW_IF_FAILED(pID3DDevice->CreatePlacedResource(pIUploadHeap.Get()
, 0
, &CD3DX12_RESOURCE_DESC::Buffer(n64UploadBufferSize)
, D3D12_RESOURCE_STATE_GENERIC_READ
, nullptr
, IID_PPV_ARGS(&pITextureUpload)));
}
//16、加載圖片數據至上傳堆,即完成第一個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);
}
//17、向直接命令列表發出從上傳堆複製紋理數據到默認堆的命令,執行並同步等待,即完成第二個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()));
//---------------------------------------------------------------------------------------------
}
//18、定義正方形的3D數據結構,注意此處的紋理座標我故意設置爲大於1
ST_GRS_VERTEX stTriangleVertices[] = {
{ {-1.0f * fBoxSize, 1.0f * fBoxSize, -1.0f * fBoxSize}, {0.0f * fTCMax, 0.0f* fTCMax}, {0.0f, 0.0f, -1.0f} },
{ {1.0f * fBoxSize, 1.0f * fBoxSize, -1.0f * fBoxSize}, {1.0f* fTCMax, 0.0f* fTCMax}, {0.0f, 0.0f, -1.0f} },
{ {-1.0f * fBoxSize, -1.0f * fBoxSize, -1.0f * fBoxSize}, {0.0f* fTCMax, 1.0f* fTCMax}, {0.0f, 0.0f, -1.0f} },
{ {-1.0f * fBoxSize, -1.0f * fBoxSize, -1.0f * fBoxSize}, {0.0f * fTCMax, 1.0f * fTCMax}, {0.0f, 0.0f, -1.0f} },
{ {1.0f * fBoxSize, 1.0f * fBoxSize, -1.0f * fBoxSize}, {1.0f * fTCMax, 0.0f * fTCMax}, {0.0f, 0.0f, -1.0f} },
{ {1.0f * fBoxSize, -1.0f * fBoxSize, -1.0f * fBoxSize}, {1.0f * fTCMax, 1.0f * fTCMax}, {0.0f, 0.0f, -1.0f} },
{ {1.0f * fBoxSize, 1.0f * fBoxSize, -1.0f * fBoxSize}, {0.0f * fTCMax, 0.0f * fTCMax}, {1.0f, 0.0f, 0.0f} },
{ {1.0f * fBoxSize, 1.0f * fBoxSize, 1.0f * fBoxSize}, {1.0f * fTCMax, 0.0f * fTCMax}, {1.0f, 0.0f, 0.0f} },
{ {1.0f * fBoxSize, -1.0f * fBoxSize, -1.0f * fBoxSize}, {0.0f * fTCMax, 1.0f * fTCMax}, {1.0f, 0.0f, 0.0f} },
{ {1.0f * fBoxSize, -1.0f * fBoxSize, -1.0f * fBoxSize}, {0.0f * fTCMax, 1.0f * fTCMax}, {1.0f, 0.0f, 0.0f} },
{ {1.0f * fBoxSize, 1.0f * fBoxSize, 1.0f * fBoxSize}, {1.0f * fTCMax, 0.0f * fTCMax}, {1.0f, 0.0f, 0.0f} },
{ {1.0f * fBoxSize, -1.0f * fBoxSize, 1.0f * fBoxSize}, {1.0f * fTCMax, 1.0f * fTCMax}, {1.0f, 0.0f, 0.0f} },
{ {1.0f * fBoxSize, 1.0f * fBoxSize, 1.0f * fBoxSize}, {0.0f * fTCMax, 0.0f * fTCMax}, {0.0f, 0.0f, 1.0f} },
{ {-1.0f * fBoxSize, 1.0f * fBoxSize, 1.0f * fBoxSize}, {1.0f * fTCMax, 0.0f * fTCMax}, {0.0f, 0.0f, 1.0f} },
{ {1.0f * fBoxSize, -1.0f * fBoxSize, 1.0f * fBoxSize}, {0.0f * fTCMax, 1.0f * fTCMax}, {0.0f, 0.0f, 1.0f} },
{ {1.0f * fBoxSize, -1.0f * fBoxSize, 1.0f * fBoxSize}, {0.0f * fTCMax, 1.0f * fTCMax}, {0.0f, 0.0f, 1.0f} },
{ {-1.0f * fBoxSize, 1.0f * fBoxSize, 1.0f * fBoxSize}, {1.0f * fTCMax, 0.0f * fTCMax}, {0.0f, 0.0f, 1.0f} },
{ {-1.0f * fBoxSize, -1.0f * fBoxSize, 1.0f * fBoxSize}, {1.0f * fTCMax, 1.0f * fTCMax}, {0.0f, 0.0f, 1.0f} },
{ {-1.0f * fBoxSize, 1.0f * fBoxSize, 1.0f * fBoxSize}, {0.0f * fTCMax, 0.0f * fTCMax}, {-1.0f, 0.0f, 0.0f} },
{ {-1.0f * fBoxSize, 1.0f * fBoxSize, -1.0f * fBoxSize}, {1.0f * fTCMax, 0.0f * fTCMax}, {-1.0f, 0.0f, 0.0f} },
{ {-1.0f * fBoxSize, -1.0f * fBoxSize, 1.0f * fBoxSize}, {0.0f * fTCMax, 1.0f * fTCMax}, {-1.0f, 0.0f, 0.0f} },
{ {-1.0f * fBoxSize, -1.0f * fBoxSize, 1.0f * fBoxSize}, {0.0f * fTCMax, 1.0f * fTCMax}, {-1.0f, 0.0f, 0.0f} },
{ {-1.0f * fBoxSize, 1.0f * fBoxSize, -1.0f * fBoxSize}, {1.0f * fTCMax, 0.0f * fTCMax}, {-1.0f, 0.0f, 0.0f} },
{ {-1.0f * fBoxSize, -1.0f * fBoxSize, -1.0f * fBoxSize}, {1.0f * fTCMax, 1.0f * fTCMax}, {-1.0f, 0.0f, 0.0f} },
{ {-1.0f * fBoxSize, 1.0f * fBoxSize, 1.0f * fBoxSize}, {0.0f * fTCMax, 0.0f * fTCMax}, {0.0f, 1.0f, 0.0f} },
{ {1.0f * fBoxSize, 1.0f * fBoxSize, 1.0f * fBoxSize}, {1.0f * fTCMax, 0.0f * fTCMax}, {0.0f, 1.0f, 0.0f} },
{ {-1.0f * fBoxSize, 1.0f * fBoxSize, -1.0f * fBoxSize}, {0.0f * fTCMax, 1.0f * fTCMax}, {0.0f, 1.0f, 0.0f} },
{ {-1.0f * fBoxSize, 1.0f * fBoxSize, -1.0f * fBoxSize}, {0.0f * fTCMax, 1.0f * fTCMax}, {0.0f, 1.0f, 0.0f} },
{ {1.0f * fBoxSize, 1.0f * fBoxSize, 1.0f * fBoxSize}, {1.0f * fTCMax, 0.0f * fTCMax}, {0.0f, 1.0f, 0.0f} },
{ {1.0f * fBoxSize, 1.0f * fBoxSize, -1.0f * fBoxSize}, {1.0f * fTCMax, 1.0f * fTCMax}, {0.0f, 1.0f, 0.0f }},
{ {-1.0f * fBoxSize, -1.0f * fBoxSize, -1.0f * fBoxSize}, {0.0f * fTCMax, 0.0f * fTCMax}, {0.0f, -1.0f, 0.0f} },
{ {1.0f * fBoxSize, -1.0f * fBoxSize, -1.0f * fBoxSize}, {1.0f * fTCMax, 0.0f * fTCMax}, {0.0f, -1.0f, 0.0f} },
{ {-1.0f * fBoxSize, -1.0f * fBoxSize, 1.0f * fBoxSize}, {0.0f * fTCMax, 1.0f * fTCMax}, {0.0f, -1.0f, 0.0f} },
{ {-1.0f * fBoxSize, -1.0f * fBoxSize, 1.0f * fBoxSize}, {0.0f * fTCMax, 1.0f * fTCMax}, {0.0f, -1.0f, 0.0f} },
{ {1.0f * fBoxSize, -1.0f * fBoxSize, -1.0f * fBoxSize}, {1.0f * fTCMax, 0.0f * fTCMax}, {0.0f, -1.0f, 0.0f} },
{ {1.0f * fBoxSize, -1.0f * fBoxSize, 1.0f * fBoxSize}, {1.0f * fTCMax, 1.0f * fTCMax}, {0.0f, -1.0f, 0.0f} },
};
const UINT nVertexBufferSize = sizeof(stTriangleVertices);
UINT32 pBoxIndices[] //立方體索引數組
= {
0,1,2,
3,4,5,
6,7,8,
9,10,11,
12,13,14,
15,16,17,
18,19,20,
21,22,23,
24,25,26,
27,28,29,
30,31,32,
33,34,35,
};
const UINT nszIndexBuffer = sizeof(pBoxIndices);
UINT64 n64BufferOffset = GRS_UPPER(n64UploadBufferSize, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT);
//19、使用“定位方式”創建頂點緩衝和索引緩衝,使用與上傳紋理數據緩衝相同的一個上傳堆
{
//---------------------------------------------------------------------------------------------
//使用定位方式在相同的上傳堆上以“定位方式”創建頂點緩衝,注意第二個參數指出了堆中的偏移位置
//按照堆邊界對齊的要求,我們主動將偏移位置對齊到了64k的邊界上
GRS_THROW_IF_FAILED(pID3DDevice->CreatePlacedResource(
pIUploadHeap.Get()
, n64BufferOffset
, &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(ST_GRS_VERTEX);
stVertexBufferView.SizeInBytes = nVertexBufferSize;
//計算邊界對齊的正確的偏移位置
n64BufferOffset = GRS_UPPER(n64BufferOffset + nVertexBufferSize, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT);
GRS_THROW_IF_FAILED(pID3DDevice->CreatePlacedResource(
pIUploadHeap.Get()
, n64BufferOffset
, &CD3DX12_RESOURCE_DESC::Buffer(nszIndexBuffer)
, D3D12_RESOURCE_STATE_GENERIC_READ
, nullptr
, IID_PPV_ARGS(&pIIndexBuffer)));
UINT8* pIndexDataBegin = nullptr;
GRS_THROW_IF_FAILED(pIIndexBuffer->Map(0, &stReadRange, reinterpret_cast<void**>(&pIndexDataBegin)));
memcpy(pIndexDataBegin, pBoxIndices, nszIndexBuffer);
pIIndexBuffer->Unmap(0, nullptr);
stIndexBufferView.BufferLocation = pIIndexBuffer->GetGPUVirtualAddress();
stIndexBufferView.Format = DXGI_FORMAT_R32_UINT;
stIndexBufferView.SizeInBytes = nszIndexBuffer;
}
//20、在上傳堆上以“定位方式”創建常量緩衝
{
n64BufferOffset = GRS_UPPER(n64BufferOffset + nszIndexBuffer, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT);
// 創建常量緩衝 注意緩衝尺寸設置爲256邊界對齊大小
GRS_THROW_IF_FAILED(pID3DDevice->CreatePlacedResource(
pIUploadHeap.Get()
, n64BufferOffset
, &CD3DX12_RESOURCE_DESC::Buffer(szMVPBuffer)
, D3D12_RESOURCE_STATE_GENERIC_READ
, nullptr
, IID_PPV_ARGS(&pICBVUpload)));
// Map 之後就不再Unmap了 直接複製數據進去 這樣每幀都不用map-copy-unmap浪費時間了
GRS_THROW_IF_FAILED(pICBVUpload->Map(0, nullptr, reinterpret_cast<void**>(&pMVPBuffer)));
}
//21、創建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());
}
//22、創建CBV描述符
{
D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {};
cbvDesc.BufferLocation = pICBVUpload->GetGPUVirtualAddress();
cbvDesc.SizeInBytes = static_cast<UINT>(szMVPBuffer);
CD3DX12_CPU_DESCRIPTOR_HANDLE cbvSrvHandle(pISRVHeap->GetCPUDescriptorHandleForHeapStart()
, 1
, pID3DDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV));
pID3DDevice->CreateConstantBufferView(&cbvDesc, cbvSrvHandle);
}
//23、創建各種採樣器
{
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);
}
//---------------------------------------------------------------------------------------------
//24、創建定時器對象,以便於創建高效的消息循環
HANDLE phWait = CreateWaitableTimer(NULL, FALSE, NULL);
LARGE_INTEGER liDueTime = {};
liDueTime.QuadPart = -1i64;//1秒後開始計時
SetWaitableTimer(phWait, &liDueTime, 1, NULL, NULL, 0);//40ms的週期
//25、記錄幀開始時間,和當前時間,以循環結束爲界
ULONGLONG n64tmFrameStart = ::GetTickCount64();
ULONGLONG n64tmCurrent = n64tmFrameStart;
//計算旋轉角度需要的變量
double dModelRotationYAngle = 0.0f;
//---------------------------------------------------------------------------------------------
//26、開始消息循環,並在其中不斷渲染
DWORD dwRet = 0;
BOOL bExit = FALSE;
while (!bExit)
{//注意這裏我們調整了消息循環,將等待時間設置爲0,同時將定時性的渲染,改成了每次循環都渲染
//但這不表示說MsgWait函數就沒啥用了,堅持使用它是因爲後面例子如果想加入多線程控制就非常簡單了
dwRet = ::MsgWaitForMultipleObjects(1, &phWait, FALSE, 0, QS_ALLINPUT);
switch (dwRet - WAIT_OBJECT_0)
{
case 0:
case WAIT_TIMEOUT:
{//計時器時間到
}
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;
}
//GRS_TRACE(_T("開始第%u幀渲染{Frame Index = %u}:\n"),nFrame,nFrameIndex);
//開始記錄命令
//---------------------------------------------------------------------------------------------
// 準備一個簡單的旋轉MVP矩陣 讓方塊轉起來
{
n64tmCurrent = ::GetTickCount();
//計算旋轉的角度:旋轉角度(弧度) = 時間(秒) * 角速度(弧度/秒)
//下面這句代碼相當於經典遊戲消息循環中的OnUpdate函數中需要做的事情
dModelRotationYAngle += (( n64tmCurrent - n64tmFrameStart ) / 1000.0f) * fPalstance;
n64tmFrameStart = n64tmCurrent;
//旋轉角度是2PI週期的倍數,去掉週期數,只留下相對0弧度開始的小於2PI的弧度即可
if ( dModelRotationYAngle > XM_2PI )
{
dModelRotationYAngle = fmod( dModelRotationYAngle, XM_2PI );
}
//模型矩陣 model
XMMATRIX xmRot = XMMatrixRotationY(static_cast<float>(dModelRotationYAngle));
//計算 模型矩陣 model * 視矩陣 view
XMMATRIX xmMVP = XMMatrixMultiply(xmRot, XMMatrixLookAtLH(Eye, At, Up));
//投影矩陣 projection
xmMVP = XMMatrixMultiply(xmMVP, (XMMatrixPerspectiveFovLH(XM_PIDIV4, (FLOAT)iWidth / (FLOAT)iHeight, 0.1f, 1000.0f)));
XMStoreFloat4x4(&pMVPBuffer->m_MVP, xmMVP);
}
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
pICommandList->SetGraphicsRootSignature(pIRootSignature.Get());
ID3D12DescriptorHeap* ppHeaps[] = { pISRVHeap.Get(),pISamplerDescriptorHeap.Get() };
pICommandList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);
//設置SRV
pICommandList->SetGraphicsRootDescriptorTable(0, pISRVHeap->GetGPUDescriptorHandleForHeapStart());
CD3DX12_GPU_DESCRIPTOR_HANDLE stGPUCBVHandle(pISRVHeap->GetGPUDescriptorHandleForHeapStart()
, 1
, pID3DDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV));
//設置CBV
pICommandList->SetGraphicsRootDescriptorTable(1, stGPUCBVHandle);
CD3DX12_GPU_DESCRIPTOR_HANDLE hGPUSampler(pISamplerDescriptorHeap->GetGPUDescriptorHandleForHeapStart()
, nCurrentSamplerNO
, nSamplerDescriptorSize);
//設置Sample
pICommandList->SetGraphicsRootDescriptorTable(2, 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);
//注意我們使用的渲染手法是三角形列表,也就是通常的Mesh網格
pICommandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
pICommandList->IASetVertexBuffers(0, 1, &stVertexBufferView);
pICommandList->IASetIndexBuffer(&stIndexBufferView);
//---------------------------------------------------------------------------------------------
//Draw Call!!!
pICommandList->DrawIndexedInstanced(_countof(pBoxIndices), 1, 0, 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++);
}
//::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_KEYDOWN:
{
USHORT n16KeyCode = (wParam & 0xFF);
if (VK_SPACE == n16KeyCode)
{//按空格鍵切換不同的採樣器看效果,以明白每種採樣器具體的含義
//UINT nCurrentSamplerNO = 0; //當前使用的採樣器索引
//UINT nSampleMaxCnt = 5; //創建五個典型的採樣器
++nCurrentSamplerNO;
nCurrentSamplerNO %= nSampleMaxCnt;
}
//根據用戶輸入變換
//XMVECTOR Eye = XMVectorSet(0.0f, 5.0f, -10.0f, 0.0f); //眼睛位置
//XMVECTOR At = XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f); //眼睛所盯的位置
//XMVECTOR Up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f); //頭部正上方位置
if (VK_UP == n16KeyCode || 'w' == n16KeyCode || 'W' == n16KeyCode)
{
Eye = XMVectorAdd(Eye, XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f));
}
if (VK_DOWN == n16KeyCode || 's' == n16KeyCode || 'S' == n16KeyCode)
{
Eye = XMVectorAdd(Eye, XMVectorSet(0.0f, -1.0f, 0.0f, 0.0f));
}
if (VK_RIGHT == n16KeyCode || 'd' == n16KeyCode || 'D' == n16KeyCode)
{
Eye = XMVectorAdd(Eye, XMVectorSet(1.0f, 0.0f, 0.0f, 0.0f));
}
if (VK_LEFT == n16KeyCode || 'a' == n16KeyCode || 'A' == n16KeyCode)
{
Eye = XMVectorAdd(Eye, XMVectorSet(-1.0f, 0.0f, 0.0f, 0.0f));
}
if ( VK_ADD == n16KeyCode || VK_OEM_PLUS == n16KeyCode )
{
//double fPalstance = 10.0f * XM_PI / 180.0f; //物體旋轉的角速度,單位:弧度/秒
fPalstance += 10 * XM_PI / 180.0f;
if (fPalstance > XM_PI)
{
fPalstance = XM_PI;
}
}
if ( VK_SUBTRACT == n16KeyCode || VK_OEM_MINUS == n16KeyCode )
{
fPalstance -= 10 * XM_PI / 180.0f;
if ( fPalstance < 0.0f )
{
fPalstance = XM_PI / 180.0f;
}
}
}
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
TextureCube.hlsl
struct PSInput
{
float4 position : SV_POSITION;
float2 uv : TEXCOORD;
};
cbuffer MVPBuffer : register(b0)
{
float4x4 m_MVP;
};
Texture2D g_texture : register(t0);
SamplerState g_sampler : register(s0);
PSInput VSMain(float4 position : POSITION, float2 uv : TEXCOORD)
{
PSInput result;
result.position = mul(position, m_MVP);
result.uv = uv;
return result;
}
float4 PSMain(PSInput input) : SV_TARGET
{
return g_texture.Sample(g_sampler, input.uv);
}