共用方式為


HLSL 中的資源系結

本主題描述使用高階著色器語言 (HLSL) 著色器模型 5.1 搭配 Direct3D 12 的特定功能。 所有 Direct3D 12 硬體都支援著色器模型 5.1,因此此模型的支援並不取決於硬體功能層級。

資源類型和陣列

著色器模型 5 (SM5.0) 資源語法會使用 register 關鍵詞,將資源的重要資訊轉寄至 HLSL 編譯程式。 例如,下列語句宣告了一個陣列,其中四個紋理綁定於位置 t3、t4、t5 和 t6。 t3 是唯一出現在語句中的暫存器槽,只是四個元素陣列中的第一個。

Texture2D<float4> tex1[4] : register(t3)

HLSL 中的著色器模型 5.1 (SM5.1) 資源語法是以現有的緩存器資源語法為基礎,以方便移植。 HLSL 中的 Direct3D 12 資源會綁定至邏輯暫存器空間內的虛擬暫存器:

  • t – 用於著色器資源檢視(SRV)
  • s – 用於取樣器
  • u – 用於未排序的存取檢視(Unordered Access Views, 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),宣告的緩存器範圍不得重疊。 這也包括無界的範圍。 在不同暫存器空間中宣告的範圍永遠不會重疊。 請注意,未系結的 tex2(上圖)位於 space0中,而未繫結 tex3 則位於 space1中,因此它們不會重疊。

存取已宣告為陣列的資源就像編製索引一樣簡單。

Texture2D<float4> tex1[400] : register(t3);
sampler samp[7] : register(s0);
tex1[myMaterialID].Sample(samp[samplerID], texCoords);

在上述程式代碼中使用的索引(myMaterialIDsamplerID)有一個重要的預設限制,即不允許在 波中變化。 即使根據實例計數變更索引也不同。

如果需要不同的索引,請在索引上指定 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 著色器中指定的資源範圍是邏輯範圍。 這些範圍在執行時會利用根簽名機制綁定到具體的堆範圍。 一般而言,邏輯範圍對應至不會與其他堆範圍重疊的堆範圍。 不過,根簽章機制可讓相容類型的堆疊範圍產生別名(重疊)。 例如,來自上述範例的 tex2tex3 範圍可能被映射到相同的(或重疊)堆積範圍,在 HLSL 程式中導致紋理別名化的效果。 如果需要這類別名,則必須使用 D3D10_SHADER_RESOURCES_MAY_ALIAS 選項來編譯著色器,這是使用 Effect-Compiler 工具 (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「範本」建構:

struct Foo
{
    float4 a;
    int2 b;
};
ConstantBuffer<Foo> myCB1[2][3] : register(b2, space1);
ConstantBuffer<Foo> myCB2 : register(b0, space1);

此程式代碼會宣告類型 Foo 和大小 6 的常數緩衝區變數 myCB1,以及純量常數緩衝區變數 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 已擴充以新增 Space 欄位來表示空間,而 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.

現在,每個著色器資源範圍都有一個對於著色器位元組碼來說唯一的ID(名稱)。 例如,tex1 (t10) 紋理陣列在著色器位元組碼中會變成『T1』。 為每個資源範圍賦予唯一的標識碼可以實現兩個目的:

  • 明確識別指令中正在編製索引的資源範圍(請參閱dcl_resource_texture2d)。請參閱範例指示。
  • 將一組屬性附加至宣告,例如元素類型、步幅大小、點陣作業模式等。

請注意,範圍標識碼與 HLSL 下限宣告無關。

資源綁定的順序(列在頂端)與著色器宣告指令(dcl_*)相同,這有助於識別 HLSL 變數與位元組編碼 ID 的對應。

SM5.1 中的每個宣告指令都會使用 3D作數來定義:範圍標識元、下限和上限。 發出額外的令牌以指定緩存器空間。 其他令牌也可以被發出,以傳達範圍的更多屬性,例如,cbuffer 或結構化緩衝區宣告指令會發出以顯示 cbuffer 或結構的大小。 在 d3d12TokenizedProgramFormat.h 和 D3D10ShaderBinary::CShaderCodeParser中找到編碼的確切詳細數據。

SM5.1 指令不會發出其他資源作數資訊作為指令的一部分(如 SM5.0 所示)。 這項信息現在位於宣告指示中。 在SM5.0中,涉及資源索引的指令要求資源屬性在擴充的opcode令牌中描述,因為索引模糊了與宣告的關聯。 在 SM5.1 中,每個標識碼(例如 't1') 都明確與描述必要資源資訊的單一宣告相關聯。 因此,不再發出用於指令中描述資源資訊的擴展操作碼令牌。

在非宣告指示中,取樣器、SRV 和 UAV 的資源作數是 2D 作數。 第一個索引是一個指定範圍 ID 的文字常量。 第二個索引代表索引的線性化值。 此值會相對於對應寄存器空間的開頭計算(而不是邏輯範圍的開頭),以更好地與根簽名相關聯,並減少驅動程式編譯器調整索引的負擔。

CBV 的資源運算元是 3D 運算元,包含:範圍的常值 ID、常數緩衝區的索引、常數緩衝區特定實例的偏移量。

範例 HLSL 宣告

HLSL 程式不需要知道任何關於根簽章的事物。 使用者可以將系結指派給虛擬「暫存器」系結空間,適用於 SRV 的 t#、適用於 UAV 的 u#、適用於 CBV 的 b#、適用於取樣器的 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)