HLSL 中的資源系結
本主題描述搭配 Direct3D 12 使用高階著色器語言 (HLSL) 著色器模型 5.1 的特定功能。 所有 Direct3D 12 硬體都支援著色器模型 5.1,因此此模型的支援不取決於硬體功能層級。
資源類型和陣列
著色器模型 5 (SM5.0) 資源語法會 register
使用 關鍵字,將資源的重要資訊轉寄至 HLSL 編譯器。 例如,下列語句會宣告位於位置 t3、t4、t5 和 t6 之四個紋理的陣列。 t3 是唯一出現在 語句中的暫存器位置,只是四個數組中的第一個位置。
Texture2D<float4> tex1[4] : register(t3)
著色器模型 5.1 (SM5.1) HLSL 中的資源語法是以現有的暫存器資源語法為基礎,以允許更容易移植。 HLSL 中的 Direct3D 12 資源會系結至邏輯暫存器空間內的虛擬暫存器:
- t – 針對著色器資源檢視, (SRV)
- s – 適用于取樣器
- u – 針對未排序的存取檢視, (UAV)
- b – 針對常數緩衝區檢視 (CBV)
參考著色器的根簽章必須與宣告的暫存器位置相容。 例如,根簽章的下列部分會與使用紋理位置 t3 到 t6 相容,因為它描述位置 t0 到 t98 的描述項資料表。
DescriptorTable( CBV(b1), SRV(t0,numDescriptors=99), CBV(b2) )
資源宣告可能是純量、1D 陣列或多維度陣列:
Texture2D<float4> tex1 : register(t3, space0)
Texture2D<float4> tex2[4] : register(t10)
Texture2D<float4> tex3[7][5][3] : register(t20, space1)
SM5.1 使用與 SM5.0 相同的資源類型和專案類型。 SM5.1 宣告限制更有彈性,而且只會受到執行時間/硬體限制的限制。
space
關鍵字會指定宣告變數所系結的邏輯暫存器空間。
space
如果省略 關鍵字,則會隱含地將 0 的預設空間索引指派給範圍 (,讓 tex2
上述範圍位於 space0
) 中。
register(t3, space0)
永遠不會與 衝突 register(t3, space1)
,也不會與可能包含 t3 的另一個空間中的任何陣列衝突。
陣列資源可能會有未系結的大小,其宣告方式是指定要空白的第一個維度,或 0:
Texture2D<float4> tex1[] : register(t0)
相符的描述中繼資料表可以是:
DescriptorTable( CBV(b1), UAV(u0, numDescriptors = 4), SRV(t0, numDescriptors=unbounded) )
HLSL 中的未系結陣列會比對描述中繼資料表中設定 numDescriptors
的固定數位,而 HLSL 中的固定大小與描述中繼資料表中的未系結宣告相符。
允許多維度陣列,包括未系結的大小。 這些多維度陣列會在暫存器空間中扁平化。
Texture2D<float4> tex2[3000][10] : register(t0, space0); // t0-t29999 in space0
Texture2D<float4> tex3[0][5][3] : register(t5, space1)
不允許資源範圍的別名。 換句話說,對於每個資源類型 (t、s、u、b) ,宣告的暫存器範圍不得重迭。 這包括未系結的範圍。 在不同暫存器空間中宣告的範圍永遠不會重迭。 請注意,上述未系結 () 位於 space0
,而未系 tex2
結 tex3
位於 space1
中,因此不會重迭。
存取已宣告為數組的資源,就像編制索引一樣簡單。
Texture2D<float4> tex1[400] : register(t3);
sampler samp[7] : register(s0);
tex1[myMaterialID].Sample(samp[samplerID], texCoords);
在上述) 程式碼中 myMaterialID
samplerID
, (和上述程式碼中,有一個重要的預設限制,因為不允許在 波內改變索引。 甚至根據實例變更索引會計算為不同。
如果需要改變索引,請在索引上指定 NonUniformResourceIndex
限定詞,例如:
tex1[NonUniformResourceIndex(myMaterialID)].Sample(samp[NonUniformResourceIndex(samplerID)], texCoords);
在某些硬體上,使用此限定詞會產生額外的程式碼,以強制執行正確性 (包括跨執行緒) ,但效能成本很小。 如果沒有這個限定詞而變更索引,而且在繪製/分派內,結果就會未定義。
描述元陣列和紋理陣列
自 DirectX 10 起,已有紋理陣列可供使用。 紋理陣列需要一個描述元,不過所有陣列配量都必須共用相同的格式、寬度、高度和 Mip 計數。 此外,陣列必須佔用虛擬位址空間中的連續範圍。 下列程式碼顯示從著色器存取紋理陣列的範例。
Texture2DArray<float4> myTex2DArray : register(t0); // t0
float3 myCoord(1.0f,1.4f,2.2f); // 2.2f is array index (rounded to int)
color = myTex2DArray.Sample(mySampler, myCoord);
在紋理陣列中,索引可以自由變化,而不需要限定詞,例如 NonUniformResourceIndex
。
對等描述元陣列會是:
Texture2D<float4> myArrayOfTex2D[] : register(t0); // t0+
float2 myCoord(1.0f, 1.4f);
color = myArrayOfTex2D[2].Sample(mySampler,myCoord); // 2 is index
請注意,陣列索引的浮點數使用會取代為 myArrayOfTex2D[2]
。 此外,描述元陣列也提供維度的更多彈性。 此類型 Texture2D
是這個範例,無法有所不同,但格式、寬度、高度和 mip 計數可能會隨著每個描述元而有所不同。
具有紋理陣列的描述元陣列是合法的:
Texture2DArray<float4> myArrayOfTex2DArrays[2] : register(t0);
不合法地宣告結構陣列,每個包含描述元的結構,例如,不支援下列程式碼。
struct myStruct {
Texture2D a;
Texture2D b;
ConstantBuffer<myConstants> c;
};
myStruct foo[10000] : register(....);
這允許記憶體配置 abcabcabc....,但是語言限制,不支援。 執行此動作的其中一個支援方法如下,但此案例中的記憶體配置是 aaa...Bbb。。。ccc....
Texture2D a[10000] : register(t0);
Texture2D b[10000] : register(t10000);
ConstantBuffer<myConstants> c[10000] : register(b0);
若要達到 abcabcabc.... 記憶體配置,請使用描述中繼資料表而不使用 myStruct
結構。
資源別名
HLSL 著色器中指定的資源範圍是邏輯範圍。 它們會透過根簽章機制系結至執行時間的具體堆積範圍。 一般而言,邏輯範圍會對應至不會與其他堆積範圍重迭的堆積範圍。 不過,根簽章機制可讓您將別名 (重迭) 相容類型的堆積範圍。 例如, tex2
上述範例中的 和 tex3
範圍可能會對應至相同的 (或重迭) 堆積範圍,這會影響 HLSL 程式中的別名紋理效果。 如果需要這類別名,則必須使用 D3D10_SHADER_RESOURCES_MAY_ALIAS 選項來編譯著色器,這是使用Effect-Compiler Tool (FXC) 的/res_may_alias選項來設定。 此選項可讓編譯器產生正確的程式碼,方法是防止資源可能別名的特定負載/存放區優化。
發散和衍生
SM5.1 不會對資源索引施加限制;亦即, tex2[idx].Sample(…)
索引 idx 可以是常值常數、cbuffer 常數或插補值。 雖然程式設計模型提供這種絕佳的彈性,但有一些問題需要注意:
- 如果索引在四邊形之間發散,則可能未定義硬體計算衍生和衍生數量,例如 LOD。 HLSL 編譯器會盡最大努力在此案例中發出警告,但不會防止著色器編譯。 此行為類似于在除數控制流程中計算衍生專案。
- 如果資源索引不同,相較于統一索引的情況,效能會降低,因為硬體需要對數個資源執行作業。 資源索引可能不同,必須在 HLSL 程式碼中以 函
NonUniformResourceIndex
式標示。 否則,結果為未定義。
圖元著色器中的 UAV
SM5.1 不會對圖元著色器中的 UAV 範圍施加條件約束,就像 SM5.0 的情況一樣。
常數緩衝區
SM5.1 常數緩衝區 (cbuffer) 語法已從 SM5.0 變更為開發人員編制常數緩衝區的索引。 若要啟用可編制索引的常數緩衝區,SM5.1 引進 ConstantBuffer
了「template」 建構:
struct Foo
{
float4 a;
int2 b;
};
ConstantBuffer<Foo> myCB1[2][3] : register(b2, space1);
ConstantBuffer<Foo> myCB2 : register(b0, space1);
上述程式碼會宣告類型的 Foo
常數緩衝區變數 myCB1
和大小 6,以及純量常數緩衝區變數 myCB2
。 常數緩衝區變數現在可以在著色器中編制索引,如下所示:
myCB1[i][j].a.xyzw
myCB2.b.yy
欄位 'a' 和 'b' 不會變成全域變數,而是必須視為欄位。 為了回溯相容性,SM5.1 支援純量 cbuffer 的舊 cbuffer 概念。 下列語句會將 'a' 和 'b' 全域唯讀變數設為 SM5.0。 不過,這類舊樣式 cbuffer 無法編制索引。
cbuffer : register(b1)
{
float4 a;
int2 b;
};
著色器編譯器目前僅支援 ConstantBuffer
使用者定義結構的範本。
基於相容性考慮,HLSL 編譯器可能會針對 中 space0
宣告的範圍自動指派資源暫存器。 如果在 register 子句中省略 'space',則會使用預設值 space0
。 編譯器會使用符合第一個孔的啟發學習法來指派暫存器。 您可以透過反映 API 擷取指派,此 API 已擴充以新增 空間 的空間欄位,而 BindPoint 欄位則表示資源暫存器範圍的下限。
SM5.1 中的位元組程式碼變更
SM5.1 會變更如何在指示中宣告和參考資源暫存器。 語法牽涉到宣告暫存器 「variable」,類似于群組共用記憶體暫存器完成的方式:
Texture2D<float4> tex0 : register(t5, space0);
Texture2D<float4> tex1[][5][3] : register(t10, space0);
Texture2D<float4> tex2[8] : register(t0, space1);
SamplerState samp0 : register(s5, space0);
float4 main(float4 coord : COORD) : SV_TARGET
{
float4 r = coord;
r += tex0.Sample(samp0, r.xy);
r += tex2[r.x].Sample(samp0, r.xy);
r += tex1[r.x][r.y][r.z].Sample(samp0, r.xy);
return r;
}
這會反組譯為:
// Resource Bindings:
//
// Name Type Format Dim ID HLSL Bind Count
// ------------------------------ ---------- ------- ----------- ----- --------- ---------
// samp0 sampler NA NA S0 a5 1
// tex0 texture float4 2d T0 t5 1
// tex1[0][5][3] texture float4 2d T1 t10 unbounded
// tex2[8] texture float4 2d T2 t0.space1 8
//
//
//
// Input signature:
//
// Name Index Mask Register SysValue Format Used
// -------------------- ----- ------ -------- -------- ------- ------
// COORD 0 xyzw 0 NONE float xyzw
//
//
// Output signature:
//
// Name Index Mask Register SysValue Format Used
// -------------------- ----- ------ -------- -------- ------- ------
// SV_TARGET 0 xyzw 0 TARGET float xyzw
//
ps_5_1
dcl_globalFlags refactoringAllowed
dcl_sampler s0[5:5], mode_default, space=0
dcl_resource_texture2d (float,float,float,float) t0[5:5], space=0
dcl_resource_texture2d (float,float,float,float) t1[10:*], space=0
dcl_resource_texture2d (float,float,float,float) t2[0:7], space=1
dcl_input_ps linear v0.xyzw
dcl_output o0.xyzw
dcl_temps 2
sample r0.xyzw, v0.xyxx, t0[0].xyzw, s0[5]
add r0.xyzw, r0.xyzw, v0.xyzw
ftou r1.x, r0.x
sample r1.xyzw, r0.xyxx, t2[r1.x + 0].xyzw, s0[5]
add r0.xyzw, r0.xyzw, r1.xyzw
ftou r1.xyz, r0.zyxz
imul null, r1.yz, r1.zzyz, l(0, 15, 3, 0)
iadd r1.y, r1.z, r1.y
iadd r1.x, r1.x, r1.y
sample r1.xyzw, r0.xyxx, t1[r1.x + 10].xyzw, s0[5]
add o0.xyzw, r0.xyzw, r1.xyzw
ret
// Approximately 12 instruction slots are used.
每個著色器資源範圍現在都有識別碼 (著色器位元組程式碼唯一的名稱) 。 例如,tex1 (t10) 紋理陣列會在著色器位元組程式碼中變成 'T1'。 為每個資源範圍提供唯一識別碼,允許兩件事:
- 明確識別 (在指示中編制索引dcl_resource_texture2d) 的資源範圍, (請參閱範例指令) 。
- 將一組屬性附加至宣告,例如元素類型、步進大小、點陣作業模式等等。
請注意,範圍的識別碼與 HLSL 下限宣告無關。
反映資源系結的順序 (列在頂端) 和著色器宣告指令 (dcl_*) 相同,有助於識別 HLSL 變數與位元組程式碼識別碼之間的對應。
SM5.1 中的每個宣告指令都會使用 3D 運算元來定義:範圍識別碼、下限和上限。 發出額外的權杖以指定暫存器空間。 您也可以發出其他權杖來傳達範圍的其他屬性,例如 cbuffer 或結構化緩衝區宣告指令會發出 cbuffer 或結構的大小。 您可以在 d3d12TokenizedProgramFormat.h 和 D3D10ShaderBinary::CShaderCodeParser中找到編碼的確切詳細資料。
SM5.1 指令不會 (發出其他資源運算元資訊,如 SM5.0) 所示。 這項資訊現在位於宣告指示中。 在 SM5.0 中,指示編制資源索引所需的資源屬性,以在擴充的 opcode 權杖中描述,因為索引編制混淆宣告的關聯。 在 SM5.1 中,每個識別碼 (例如 't1') 明確關聯至描述必要資源資訊的單一宣告。 因此,不再發出用於描述資源資訊的指示上的擴充 Opcode 權杖。
在非宣告指令中,取樣器、SRV 和 UAV 的資源運算元是 2D 運算元。 第一個索引是指定範圍識別碼的常值常數。 第二個索引代表索引的線性值。 此值會相對於對應暫存器空間的開頭計算, (與邏輯範圍的開頭不相關) ,以便與根簽章相互關聯,並減少調整索引的驅動程式編譯器負擔。
CBV 的資源運算元是 3D 運算元,包含:範圍的常值識別碼、常數緩衝區的索引、位移到常數緩衝區的特定實例。
範例 HLSL 宣告
HLSL 程式不需要知道根簽章的任何專案。 他們可以將系結指派給虛擬「暫存器」系結空間、T# for SRV、u# for UAV、b# for CBV、s# 代表取樣器,或依賴編譯器挑選指派 (,並在之後使用著色器反映查詢產生的對應) 。 根簽章會將描述中繼資料表、根描述元和根常數對應至此虛擬暫存器空間。
以下是 HLSL 著色器可能具有的一些範例宣告。 請注意,根簽章或描述中繼資料表沒有參考。
Texture2D foo[5] : register(t2);
Buffer bar : register(t7);
RWBuffer dataLog : register(u1);
Sampler samp : register(s0);
struct Data
{
UINT index;
float4 color;
};
ConstantBuffer<Data> myData : register(b0);
Texture2D terrain[] : register(t8); // Unbounded array
Texture2D misc[] : register(t0,space1); // Another unbounded array
// space1 avoids overlap with above t#
struct MoreData
{
float4x4 xform;
};
ConstantBuffer<MoreData> myMoreData : register(b1);
struct Stuff
{
float2 factor;
UINT drawID;
};
ConstantBuffer<Stuff> myStuff[][3][8] : register(b2, space3)