計算著色器雖然是一種可編程的著色器,但Direct3D并沒有將它直接歸為渲染流水線中的一部分。雖然如此,但位于流水線之外的計算著色器卻可以讀寫GPU資源。從本質(zhì)上來說,計算著 色器能夠使我們訪問GPU來實現(xiàn)數(shù)據(jù)并行算法,而不必渲染出任何圖形。由于計算著色器是Direct3D的組成部分,也可以讀寫Direct3D資源, 由此我們就可以將其輸出的數(shù)據(jù)直接綁定到渲染流水線上。
線程與線程組
在GPU編程的過程中,根據(jù)程序具體的執(zhí)行需求,可將線程劃分為由線程組(thread group )構(gòu)成 的網(wǎng)格(grid )o 一個線程組運行于一個多處理器之上。因此,對于擁有16個多處理器的GPU來說,我 們至少應(yīng)將任務(wù)分解為16個線程組,以此令每個多處理器都充分地運轉(zhuǎn)起來。但是,要獲得更佳的性能, 我們還應(yīng)當(dāng)令每個多處理器至少擁有兩個線程組,使它能夠切換到不同的線程組進行處理,以連續(xù)不停地 工作[FunglO](線程組在運行的過程中可能會發(fā)生停頓,例如,著色器在繼續(xù)執(zhí)行下一個指令之前會等待 紋理的處理結(jié)果,此時即可切換至另一個線程組)。
每個線程組中都有一塊共享內(nèi)存,供組內(nèi)的線程訪問。但是,線程并不能訪問其他組中的共享內(nèi)存。 同理,同組內(nèi)的線程間能夠進行同步操作,不同組的線程間卻不能實現(xiàn)這一點。事實上,我們也無法控 制不同線程組間的處理W序,因為這些線程組可能正運行在不同的多處理器上。
一個線程組中含有n個線程。硬件實際上會將這些線程分為多個warp (每個warp中有32個線程), 而且多處理器會以SIMD32的方式(即32個線程同時執(zhí)行相同的指令序列)來處理warpo每個CUDA核 心都可處理一個線程,前面也提到了,“Fermi”架構(gòu)中的每個多處理器都具有32個CUDA核心(因此, CUDA核匚、就像一條專設(shè)的SIMD “計算通道” (lane))o在Direct3D中,我們能夠以非32的倍數(shù)值來指 定線程組的大小。但是出于性能的原因,我們應(yīng)當(dāng)總是將線程組的大小設(shè)置為warp尺寸的整數(shù)倍[FunglO]。
對于各種型號的圖形硬件來說,線程數(shù)為256的線程組是一種普遍適于工作的初始設(shè)置。我們可以 以此值為基礎(chǔ),再根據(jù)具體需求嘗試將其調(diào)整為其他大小。值得注意的是,修改每個線程組中的線程數(shù) 量也會對線程組的分派(dispatch,調(diào)度)次數(shù)產(chǎn)生影響。
NVIDIA公司生產(chǎn)的圖形硬件所用的warp單位共有32個線程。而ATI公司(已被AMD公司收購)采用的 "wavefront”單位則具有64個線程,且建議為其分配的線程組大小應(yīng)總為wavefront尺寸 的整數(shù)倍[Bilodeau 10]。另夕卜,值得一提的是,不管是warp還是wavefront,它們的大小在 未來幾代中都有可能發(fā)生改變。
在Direct3D中可通過調(diào)用下列方法來啟動線程組:
void ID3D12GraphicsCommandList::Dispatch(
UINT ThreadGroupCountX,
UINT ThreadGroupCountY,
UINT ThreadGroupCountZ);
此方法可開啟一個由線程組構(gòu)成的3D網(wǎng)格,但是我們在本書中僅關(guān)注線程組2D網(wǎng)格。下面的調(diào)用 示例會分派一個在x方向上為3、y方向上為2,即總數(shù)為3x2 = 6個線程組的網(wǎng)格(見圖13.3)。
cmdList->Dispatch(3, 2, 1);
一個簡單的計算著色器
以下是將兩個紋理進行簡單累加的計算著色器示例,假設(shè)所有的紋理都具有相同的大小。雖然該著 色器有點索然無味,卻五臟俱全,能詳細(xì)地展示出計算著色器的基本套路語法。
cbuffer cbSettings
{
//計算著色器能訪問的常量緩沖區(qū)數(shù)據(jù)
);
//數(shù)據(jù)源及著色器的輸出
Texture2D glnputA;
Texture2D glnputB;
RWTexture2D<float4> gOutput;
//線程組中的線程數(shù)。組中的線程可以被設(shè)置為ID、2D或3D的網(wǎng)格布局
[numthreads(16, 16, 1)]
void CS(int3 dispatchThreadID : SV_DispatchThreadID) // 線程 ID
{
//對兩種源像素中橫縱坐標(biāo)分別為x、y處的紋素進行求和,并將結(jié)果保存到相應(yīng)的gOutput紋素中
gOutput[dispatchThreadID.xy]=glnputA[dispatchThreadID.xy] + glnputB[dispatchThreadlD.xy];
}
可見,一個計算著色器由下列要素構(gòu)成:
1.通過常量緩沖區(qū)訪問的全局變量。
2. 輸入與輸出資源。
3. [numthreads (X, Y, Z)]屬性,指定3D線程網(wǎng)格中的線程數(shù)量。
4. 每個線程都要執(zhí)行的著色器指令。
5. 線程ID系統(tǒng)值參數(shù)。
不難看出,我們能夠根據(jù)需求定義岀不同的線程組布局。例如,可以定義一個具有X個線程的單行 線程組[numthreads (X, 1, 1)]或內(nèi)含Y個線程的單列線程組[numthreads (1 ,Y, 1)]。抑或通 過將維度z設(shè)為1來定義規(guī)模為X x Y的2D線程組,形如[numthreads (X, Y, 1) ] 。我們應(yīng)結(jié)合所遇到的具體問題來選擇適當(dāng)?shù)木€程組布局。如同前一節(jié)中提到的那樣:針對NVIDIA品牌的顯卡來說, 線程組中的總線程數(shù)應(yīng)為warp大小(32 )的整數(shù)倍,而ATI公司生產(chǎn)的顯卡應(yīng)為wavefront尺寸(64 ) 的整數(shù)倍庁又因wavefront大小的倍數(shù)(64x?)必為warp尺寸的倍數(shù)(32xm),因此,以前者的線程數(shù) 為基礎(chǔ)進行設(shè)置對兩種顯卡都適用。
計算流水線狀態(tài)對象
為了開啟計算著色器,我們還需使用其特定的“計算流水線狀態(tài)描述”。此描述中的字段遠(yuǎn)少于 D3D12_GRAPHICS_PIPELINE_STATE_DESC結(jié)構(gòu)體。這是因為計算著色器位列圖形流水線之外,因 此所有的圖形流水線狀態(tài)都不適用于計算著色器,也就無須以此對它進行設(shè)置。下面給岀一個創(chuàng)建計算流水線狀態(tài)對象的示例:文章來源:http://www.zghlxwxcb.cn/news/detail-437611.html
D3D12_COMPUTE_PIPELINE_STATE_DESC wavesUpdatePSO = {};
wavesUpdatePSO.pRootSignature = mWavesRootSignature.Get();
wavesUpdatePSO.CS =
{
reinterpret_cast<BYTE*>(mShaders["wavesUpdateCS"]->GetBufferPointer()),
mShaders["wavesUpdateCS"]->GetBufferSize()
};
wavesUpdatePSO.Flags = D3D12_PIPELINE_STATE_FLAG_NONE;
ThrowIfFailed(md3dDevice->CreateComputePipelineState(&wavesUpdatePSO, IID_PPV_ARGS(&mPSOs["wavesUpdate"])));
根簽名定義了什么參數(shù)才是著色器所期望的輸入(CBV、SRV等)。而cs (即compute shader的縮 寫)字段就是所指定的計算著色器。下列代碼展示了一個將著色器編譯為字節(jié)碼的示例:文章來源地址http://www.zghlxwxcb.cn/news/detail-437611.html
mShaders["wavesUpdateCS"] = d3dUtil::CompileShader(L"Shaders\\WaveSim.hlsl", nullptr, "UpdateWavesCS", "cs_5_0");
mShaders["wavesDisturbCS"] = d3dUtil::CompileShader(L"Shaders\\WaveSim.hlsl", nullptr, "DisturbWavesCS", "cs_5_0");
到了這里,關(guān)于Direct3D 12——計算著色器——計算著色器概念的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!