Ressourcenbindung in HLSL

In diesem Thema werden einige spezifische Features der Verwendung von High Level Shader Language (HLSL) Shader Model 5.1 mit Direct3D 12 beschrieben. Alle Direct3D 12-Hardware unterstützt Shadermodell 5.1. Daher hängt die Unterstützung für dieses Modell nicht von der Hardwarefeatureebene ab.

Ressourcentypen und Arrays

Die Ressourcensyntax shader Model 5 (SM5.0) verwendet die register Schlüsselwort (keyword), um wichtige Informationen über die Ressource an den HLSL-Compiler weiterzu leiten. Die folgende Anweisung deklariert beispielsweise ein Array von vier Texturen, die an die Slots t3, t4, t5 und t6 gebunden sind. t3 ist der einzige Registerslot, der in der -Anweisung angezeigt wird und einfach der erste im Array von vier ist.

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

Die Ressourcensyntax des Shadermodells 5.1 (SM5.1) in HLSL basiert auf der vorhandenen Registerressourcensyntax, um eine einfachere Portierung zu ermöglichen. Direct3D 12-Ressourcen in HLSL sind an virtuelle Register innerhalb logischer Registerräume gebunden:

  • t – für Shaderressourcenansichten (SRV)
  • s – für Sampler
  • u – für ungeordnete Zugriffsansichten (UAV)
  • b – für konstanten Pufferansichten (CBV)

Die Stammsignatur, die auf den Shader verweist, muss mit den deklarierten Registerslots kompatibel sein. Beispielsweise wäre der folgende Teil einer Stammsignatur mit der Verwendung der Texturslots t3 bis t6 kompatibel, da er eine Deskriptortabelle mit den Slots t0 bis t98 beschreibt.

DescriptorTable( CBV(b1), SRV(t0,numDescriptors=99), CBV(b2) )

Eine Ressourcendeklaration kann ein Skalar, ein 1D-Array oder ein mehrdimensionales Array sein:

Texture2D<float4> tex1 : register(t3,  space0)
Texture2D<float4> tex2[4] : register(t10)
Texture2D<float4> tex3[7][5][3] : register(t20, space1)

SM5.1 verwendet dieselben Ressourcen- und Elementtypen wie SM5.0. SM5.1-Deklarationsgrenzwerte sind flexibler und nur durch die Laufzeit-/Hardwaregrenzwerte eingeschränkt. Die space Schlüsselwort (keyword) gibt an, an welchen logischen Registerraum die deklarierte Variable gebunden ist. Wenn die space Schlüsselwort (keyword) ausgelassen wird, wird der Standardspeicherindex 0 implizit dem Bereich zugewiesen (der obige Bereich befindet sich also tex2 in space0). register(t3, space0) wird niemals mit register(t3, space1)oder mit einem Array in einem anderen Raum in Konflikt treten, das möglicherweise t3 enthalten kann.

Eine Arrayressource kann eine ungebundene Größe aufweisen, die durch Angabe der ersten Dimension als leer deklariert wird, oder 0:

Texture2D<float4> tex1[] : register(t0)

Die entsprechende Deskriptortabelle kann wie folgt sein:

DescriptorTable( CBV(b1), UAV(u0, numDescriptors = 4), SRV(t0, numDescriptors=unbounded) )

Ein ungebundenes Array in HLSL entspricht einer festen Zahl, die mit numDescriptors in der Deskriptortabelle festgelegt ist, und eine feste Größe in der HLSL entspricht einer ungebundenen Deklaration in der Deskriptortabelle.

Mehrdimensionale Arrays sind zulässig, einschließlich einer unbegrenzten Größe. Diese mehrdimensionalen Arrays werden im Registerbereich abgeflacht.

Texture2D<float4> tex2[3000][10] : register(t0, space0); // t0-t29999 in space0
Texture2D<float4> tex3[0][5][3] : register(t5, space1)

Das Aliasing von Ressourcenbereichen ist nicht zulässig. Anders ausgedrückt: Für jeden Ressourcentyp (t, s, u, b) dürfen sich deklarierte Registerbereiche nicht überlappen. Dies schließt auch ungebundene Bereiche ein. Bereiche, die in verschiedenen Registerräumen deklariert wurden, überlappen sich nie. Beachten Sie, dass sich ungebunden tex2 (oben) in space0befindet, während sich ungebunden tex3 in space1befindet, sodass sie sich nicht überlappen.

Der Zugriff auf Ressourcen, die als Arrays deklariert wurden, ist so einfach wie das Indizieren dieser Ressourcen.

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

Es gibt eine wichtige Standardeinschränkung für die Verwendung der Indizes (myMaterialID und samplerID im obigen Code), da sie innerhalb einer Welle nicht variieren dürfen. Selbst das Ändern des Indexes basierend auf der Anzahl der Instancings ist unterschiedlich.

Wenn das Variieren des Indexes erforderlich ist, geben Sie den NonUniformResourceIndex Qualifizierer für den Index an, z. B.:

tex1[NonUniformResourceIndex(myMaterialID)].Sample(samp[NonUniformResourceIndex(samplerID)], texCoords);

Auf mancher Hardware generiert die Verwendung dieses Qualifizierers zusätzlichen Code, um die Korrektheit (auch über Threads hinweg) zu erzwingen, jedoch zu geringfügigen Leistungskosten. Wenn ein Index ohne diesen Qualifizierer und innerhalb einer Ziehung/Versendung geändert wird, sind die Ergebnisse nicht definiert.

Deskriptorarrays und Texturarrays

Texturarrays sind seit DirectX 10 verfügbar. Texturarrays erfordern einen Deskriptor, aber alle Array-Slices müssen das gleiche Format, die gleiche Breite, Höhe und MIP-Anzahl aufweisen. Außerdem muss das Array einen zusammenhängenden Bereich im virtuellen Adressraum belegen. Der folgende Code zeigt ein Beispiel für den Zugriff auf ein Texturarray über einen Shader.

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

In einem Texturarray kann der Index frei variiert werden, ohne dass Qualifizierer wie NonUniformResourceIndexerforderlich sind.

Das entsprechende Deskriptorarray wäre:

Texture2D<float4> myArrayOfTex2D[] : register(t0); // t0+
float2 myCoord(1.0f, 1.4f);
color = myArrayOfTex2D[2].Sample(mySampler,myCoord); // 2 is index

Beachten Sie, dass die unbeholfene Verwendung eines Floats für den Arrayindex durch myArrayOfTex2D[2]ersetzt wird. Auch Deskriptorarrays bieten mehr Flexibilität bei den Dimensionen. Der Typ kann Texture2D in diesem Beispiel nicht variieren, aber Das Format, die Breite, die Höhe und die MIP-Anzahl können mit den einzelnen Deskriptoren variieren.

Es ist legitim, über ein Deskriptorarray von Texturarrays zu verfügen:

Texture2DArray<float4> myArrayOfTex2DArrays[2] : register(t0);

Es ist nicht legitim, ein Array von Strukturen zu deklarieren. Jede Struktur, die Deskriptoren enthält, wird z. B. der folgende Code nicht unterstützt.

struct myStruct {
    Texture2D                    a; 
    Texture2D                    b;
    ConstantBuffer<myConstants>  c;
};
myStruct foo[10000] : register(....);

Dies hätte das Speicherlayout abcabcabc....zugelassen, ist aber eine Spracheinschränkung und wird nicht unterstützt. Eine unterstützte Methode hierfür wäre wie folgt, obwohl das Speicherlayout in diesem Fall aaa ist... Bbb... ccc....

Texture2D                     a[10000] : register(t0);
Texture2D                     b[10000] : register(t10000);
ConstantBuffer<myConstants>   c[10000] : register(b0);

Um das Speicherlayout abcabcabc.... zu erreichen, verwenden Sie eine Deskriptortabelle ohne Verwendung der myStruct Struktur.

Ressourcenaliasing

Die in den HLSL-Shadern angegebenen Ressourcenbereiche sind logische Bereiche. Sie werden zur Laufzeit über den Stammsignaturmechanismus an konkrete Heapbereiche gebunden. Normalerweise wird ein logischer Bereich einem Heapbereich zugeordnet, der sich nicht mit anderen Heapbereichen überschneidet. Der Stammsignaturmechanismus ermöglicht es jedoch, Heapbereiche kompatibler Typen zu aliasen (überlappen). Beispielsweise tex2 können Und-Bereiche tex3 aus dem obigen Beispiel dem gleichen (oder überlappenden) Heapbereich zugeordnet werden, was sich auf das Aliasen von Texturen im HLSL-Programm auswirkt. Wenn ein solches Aliasing gewünscht ist, muss der Shader mit D3D10_SHADER_RESOURCES_MAY_ALIAS Option kompiliert werden, die mithilfe der Option /res_may_alias für das Effektcompilertool (FXC) festgelegt wird. Die Option bewirkt, dass der Compiler korrekten Code erzeugt, indem bestimmte Lade-/Speicheroptimierungen unter der Annahme verhindert werden, dass Ressourcen Aliase haben.

Divergenz und Ableitungen

SM5.1 erzwingt keine Einschränkungen für den Ressourcenindex; d. h. tex2[idx].Sample(…) – die Index-IDX kann eine Literalkonstante, eine cbuffer-Konstante oder ein interpolierter Wert sein. Während das Programmiermodell eine so große Flexibilität bietet, gibt es Probleme, die beachtet werden müssen:

  • Wenn der Index über ein Quad abweicht, können die hardwarebasierten Ableitungs- und abgeleiteten Mengen wie LOD undefiniert sein. Der HLSL-Compiler unternimmt in diesem Fall die besten Anstrengungen, um eine Warnung auszustellen, verhindert aber nicht, dass ein Shader kompiliert wird. Dieses Verhalten ähnelt dem Berechnen von Ableitungen in divergenten Ablaufsteuerungen.
  • Wenn der Ressourcenindex unterschiedlich ist, verringert sich die Leistung im Vergleich zu einem einheitlichen Index, da die Hardware Vorgänge für mehrere Ressourcen ausführen muss. Ressourcenindizes, die möglicherweise divergent sind, müssen mit der NonUniformResourceIndex Funktion im HLSL-Code gekennzeichnet werden. Andernfalls sind die Ergebnisse nicht definiert.

UAVs in Pixel-Shadern

SM5.1 erzwingt keine Einschränkungen für UAV-Bereiche in Pixel-Shadern, wie dies bei SM5.0 der Fall war.

Konstantenpuffer

Die Syntax der SM5.1-Konstantenpuffer (cbuffer) wurde von SM5.0 geändert, sodass Entwickler Konstantenpuffer indizieren können. Um indizierbare Konstantenpuffer zu aktivieren, führt SM5.1 das ConstantBuffer "Template"-Konstrukt ein:

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

Der vorangehende Code deklariert eine konstante Puffervariable myCB1 vom Typ Foo und der Größe 6 sowie eine skalare, konstante Puffervariable myCB2. Eine konstante Puffervariable kann jetzt im Shader wie folgt indiziert werden:

myCB1[i][j].a.xyzw
myCB2.b.yy

Felder "a" und "b" werden nicht zu globalen Variablen, sondern müssen als Felder behandelt werden. Aus Gründen der Abwärtskompatibilität unterstützt SM5.1 das alte cbuffer-Konzept für skalare Cbuffer. Die folgende Anweisung macht "a" und "b" globale, schreibgeschützte Variablen wie in SM5.0. Ein solch altes Cbuffer kann jedoch nicht indiziert werden.

cbuffer : register(b1)
{
    float4 a;
    int2 b;
};

Derzeit unterstützt der Shadercompiler die ConstantBuffer Vorlage nur für benutzerdefinierte Strukturen.

Aus Kompatibilitätsgründen kann der HLSL-Compiler automatisch Ressourcenregister für in space0deklarierte Bereiche zuweisen. Wenn "space" in der register-Klausel weggelassen wird, wird der Standardwert space0 verwendet. Der Compiler verwendet die First-hole-fits-Heuristik, um die Register zuzuweisen. Die Zuweisung kann über die Reflektions-API abgerufen werden, die erweitert wurde, um das Feld Space für den Raum hinzuzufügen, während das Feld BindPoint die Untergrenze des Ressourcenregisterbereichs angibt.

Bytecodeänderungen in SM5.1

SM5.1 ändert, wie Ressourcenregister deklariert und in Anweisungen referenziert werden. Die Syntax umfasst das Deklarieren eines Registers "Variable", ähnlich wie für Gruppenfreigabespeicherregister:

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

Dadurch wird in Folgendes disassembliert:

// 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.

Jeder Shaderressourcenbereich verfügt jetzt über eine ID (einen Namen), die für den Shader-Bytecode eindeutig ist. Beispielsweise wird das Texturarray tex1 (t10) im Shader-Bytecode zu "T1". Die Angabe eindeutiger IDs für jeden Ressourcenbereich ermöglicht zwei Dinge:

  • Identifizieren Sie eindeutig, welcher Ressourcenbereich (siehe dcl_resource_texture2d) in einer Anweisung indiziert wird (siehe Beispielanweisung).
  • Anfügen einer Reihe von Attributen an die Deklaration, z. B. Elementtyp, Schrittgröße, Rasterbetriebsmodus usw.

Beachten Sie, dass die ID des Bereichs nicht mit der HLSL-Untergrenzendeklaration verknüpft ist.

Die Reihenfolge der Reflektionsressourcenbindungen (Liste oben) und Shaderdeklarationsanweisungen (dcl_*) ist identisch, um die Übereinstimmung zwischen HLSL-Variablen und Bytecode-IDs zu identifizieren.

Jede Deklarationsanweisung in SM5.1 verwendet einen 3D-Operanden, um zu definieren: Bereichs-ID, Unter- und Obergrenze. Ein zusätzliches Token wird ausgegeben, um den Registrierungsraum anzugeben. Es können auch andere Token ausgegeben werden, um zusätzliche Eigenschaften des Bereichs zu übermitteln, z. B. die Anweisung cbuffer oder strukturierte Pufferdeklaration gibt die Größe des cbuffer oder der Struktur aus. Die genauen Details der Codierung finden Sie in d3d12TokenizedProgramFormat.h und D3D10ShaderBinary::CShaderCodeParser.

SM5.1-Anweisungen geben keine zusätzlichen Ressourcenoperndeninformationen als Teil der Anweisung aus (wie in SM5.0). Diese Informationen finden Sie jetzt in den Deklarationsanweisungen. In SM5.0 mussten ressourcenindizierte Ressourcenattribute in erweiterten Opcodetoken beschrieben werden, da die Indizierung die Zuordnung zur Deklaration verschleierte. In SM5.1 ist jede ID (z. B. "t1") eindeutig einer einzelnen Deklaration zugeordnet, die die erforderlichen Ressourceninformationen beschreibt. Daher werden die erweiterten Opcodetoken, die in Anweisungen zum Beschreiben von Ressourceninformationen verwendet werden, nicht mehr ausgegeben.

In Anweisungen ohne Deklaration ist ein Ressourcenopernd für Sampler, SRVs und UAVs ein 2D-Operand. Der erste Index ist eine Literalkonstante, die die Bereichs-ID angibt. Der zweite Index stellt den linearisierten Wert des Indexes dar. Der Wert wird relativ zum Anfang des entsprechenden Registerbereichs (nicht relativ zum Anfang des logischen Bereichs) berechnet, um besser mit der Stammsignatur zu korrelieren und den Aufwand des Treibercompilers beim Anpassen des Indexes zu verringern.

Ein Ressourcenopernd für CBVs ist ein 3D-Operand, der Folgendes enthält: Literal-ID des Bereichs, Index des Konstantenpuffers, Offset in die jeweilige instance des Konstantenpuffers.

Beispiel-HLSL-Deklarationen

HLSL-Programme müssen nichts über Stammsignaturen wissen. Sie können dem virtuellen "Register"-Bindungsraum Bindungen zuweisen, t# für SRVs, u# für UAVs, b# für CBVs, s# für Sampler oder sich auf den Compiler verlassen, um Zuweisungen zu auswählen (und die resultierenden Zuordnungen anschließend mithilfe der Shaderreflektion abzufragen). Die Stammsignatur ordnet diesem virtuellen Registerbereich Deskriptortabellen, Stammdeskriptoren und Stammkonstanten zu.

Im Folgenden sind einige Beispieldeklarationen aufgeführt, die ein HLSL-Shader aufweisen kann. Beachten Sie, dass es keine Verweise auf Stammsignaturen oder Deskriptortabellen gibt.

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)