Associazione di risorse in HLSL

Questo argomento descrive alcune funzionalità specifiche dell'uso di High Level Shader Language (HLSL) Shader Model 5.1 con Direct3D 12. Tutti gli hardware Direct3D 12 supportano Shader Model 5.1, quindi il supporto per questo modello non dipende dal livello di funzionalità hardware.

Tipi di risorse e matrici

La sintassi delle risorse Shader Model 5 (SM5.0) usa la register parola chiave per inoltrare informazioni importanti sulla risorsa al compilatore HLSL. Ad esempio, l'istruzione seguente dichiara una matrice di quattro trame associate a slot t3, t4, t5 e t6. t3 è l'unico slot di registrazione visualizzato nell'istruzione, semplicemente essendo il primo nella matrice di quattro.

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

La sintassi delle risorse shader Model 5.1 (SM5.1) in HLSL è basata sulla sintassi delle risorse di registrazione esistente, per consentire una portabilità più semplice. Le risorse Direct3D 12 in HLSL sono associate ai registri virtuali all'interno di spazi di registrazione logici:

  • t : per le visualizzazioni delle risorse shader (SRV)
  • s : per gli esempi
  • u : per le visualizzazioni di accesso non ordinate (UAV)
  • b : per le visualizzazioni del buffer costante (CBV)

La firma radice che fa riferimento allo shader deve essere compatibile con gli slot di registrazione dichiarati. Ad esempio, la parte seguente di una firma radice sarebbe compatibile con l'uso degli slot di trama t3-t6, come descrive una tabella descrittore con slot t0-t98.

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

Una dichiarazione di risorsa può essere una matrice scalare, una matrice 1D o una matrice multidimensionale:

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

SM5.1 usa gli stessi tipi di risorse e tipi di elemento di SM5.0. I limiti di dichiarazione SM5.1 sono più flessibili e vincolati solo dai limiti di runtime/hardware. La space parola chiave specifica a quale spazio di registrazione logica è associata la variabile dichiarata. Se la space parola chiave viene omessa, l'indice dello spazio predefinito di 0 viene assegnato in modo implicito all'intervallo ( quindi l'intervallo tex2 precedente risiede in space0). register(t3, space0) non sarà mai in conflitto con register(t3, space1), né con qualsiasi matrice in un altro spazio che potrebbe includere t3.

Una risorsa matrice può avere una dimensione non in uscita, dichiarata specificando la prima dimensione vuota o 0:

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

La tabella descrittore corrispondente può essere:

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

Una matrice non associato in HLSL corrisponde a un set di numeri fissi con numDescriptors nella tabella descrittore e una dimensione fissa nella HLSL corrisponde a una dichiarazione non associata nella tabella descrittore.

Le matrici multidimensionali sono consentite, incluse le dimensioni non collegate. Queste matrici multidimensionali sono appiattite nello spazio di registrazione.

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

L'aliasing degli intervalli di risorse non è consentito. In altre parole, per ogni tipo di risorsa (t, s, u, b), gli intervalli di registro dichiarati non devono sovrapporsi. Ciò include anche intervalli non associati. Gli intervalli dichiarati in spazi di registro diversi non si sovrappongono mai. Si noti che tex2 non si trova in space0, mentre si trova in , mentre tex3 non si trova in space1, in modo che non si sovrapponga.

L'accesso alle risorse dichiarate come matrici è semplice come l'indicizzazione.

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

Esiste una restrizione predefinita importante sull'uso degli indici (myMaterialID e samplerID nel codice precedente) in cui non è consentito variare all'interno di un'onda. Anche modificando l'indice in base al conteggio instancing come variabile.

Se è necessario variare l'indice, specificare il NonUniformResourceIndex qualificatore nell'indice, ad esempio:

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

In alcuni hardware l'uso di questo qualificatore genera codice aggiuntivo per applicare la correttezza (inclusi i thread) ma a un costo di prestazioni minore. Se un indice viene modificato senza questo qualificatore e all'interno di un disegno/invio i risultati non sono definiti.

Matrici di descrittori e matrici di trame

Le matrici di trame sono disponibili da DirectX 10. Le matrici trama richiedono un descrittore, tuttavia tutte le sezioni della matrice devono condividere lo stesso formato, larghezza, altezza e conteggio mip. Inoltre, la matrice deve occupare un intervallo contiguo nello spazio indirizzi virtuale. Il codice seguente mostra un esempio di accesso a una matrice di trama da uno 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 una matrice di trama l'indice può essere variato liberamente, senza alcuna necessità di qualificatori, ad esempio NonUniformResourceIndex.

La matrice di descrittore equivalente sarà:

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

Si noti che l'uso imbarazzante di un float per l'indice della matrice viene sostituito con myArrayOfTex2D[2]. Anche i descrittori offrono maggiore flessibilità con le dimensioni. Il tipo, Texture2D è questo esempio, non può variare, ma il formato, la larghezza, l'altezza e il conteggio mip possono variare tutti con ogni descrittore.

È legittimo disporre di una matrice descrittore di matrici di trame:

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

Non è legittimo dichiarare una matrice di strutture, ogni struttura contenente descrittori, ad esempio il codice seguente non è supportato.

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

Ciò avrebbe consentito il layout della memoria abcabcabc...., ma è una limitazione del linguaggio e non è supportata. Un metodo supportato per eseguire questa operazione sarebbe il seguente, anche se il layout della memoria in questo caso è aaa... Bbb... ccc....

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

Per ottenere il layout abcabcabc.... memoria, usare una tabella descrittore senza usare la myStruct struttura.

Aliasing delle risorse

Gli intervalli di risorse specificati negli shader HLSL sono intervalli logici. Sono associati a intervalli di heap concreti in fase di esecuzione tramite il meccanismo di firma radice. Normalmente, un intervallo logico esegue il mapping a un intervallo heap che non si sovrappone ad altri intervalli heap. Tuttavia, il meccanismo di firma radice consente di aliasre (sovrapporsi) intervalli di tipi compatibili. Ad esempio, tex2 e tex3 gli intervalli dall'esempio precedente possono essere mappati allo stesso intervallo di heap (o sovrapposto), che ha l'effetto delle trame di aliasing nel programma HLSL. Se tale aliasing è desiderato, è necessario compilare lo shader con l'opzione D3D10_SHADER_RESOURCES_MAY_ALIAS, impostata usando l'opzione /res_may_alias per lo strumento del compilatore effetto (FXC). L'opzione rende il compilatore produrre codice corretto impedendo determinate ottimizzazioni di carico/archivio nel presupposto che le risorse possano aliasre.

Differenze e derivati

SM5.1 non impone limitazioni sull'indice delle risorse; ad esempio tex2[idx].Sample(…) : l'idx dell'indice può essere una costante letterale, una costante cbuffer o un valore interpolato. Sebbene il modello di programmazione fornisca una grande flessibilità, esistono problemi di cui tenere presente:

  • Se l'indice si differenzia in un quad, la derivata calcolata hardware e le quantità derivate, ad esempio LOD, potrebbero non essere definite. Il compilatore HLSL fa il massimo sforzo per generare un avviso in questo caso, ma non impedirà la compilazione di uno shader. Questo comportamento è simile al calcolo dei derivati nel flusso di controllo divergente.
  • Se l'indice delle risorse è divergente, le prestazioni vengono ridotte rispetto al caso di un indice uniforme, perché l'hardware deve eseguire operazioni su diverse risorse. Gli indici delle risorse che possono essere divergenti devono essere contrassegnati con la NonUniformResourceIndex funzione nel codice HLSL. In caso contrario, i risultati non sono definiti.

UAV in pixel shader

SM5.1 non impone vincoli sugli intervalli UAV nei pixel shader, come nel caso di SM5.0.

Buffer costanti

La sintassi dei buffer costanti SM5.1 (cbuffer) è cambiata da SM5.0 per consentire agli sviluppatori di indicizzare i buffer costanti. Per abilitare i buffer costanti indicizzati, SM5.1 introduce il ConstantBuffer costrutto "modello":

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

Il codice precedente dichiara la variabile di buffer costante di tipo Foo e dimensione 6 e una variabile myCB1myCB2di buffer costante. Una variabile di buffer costante può ora essere indicizzata nello shader come:

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

I campi 'a' e 'b' non diventano variabili globali, ma devono essere considerati come campi. Per la compatibilità con le versioni precedenti, SM5.1 supporta il concetto di cbuffer precedente per i cbuffer scalari. L'istruzione seguente rende "a" e "b" variabili globali, di sola lettura, come in SM5.0. Tuttavia, non è possibile indicizzare un cbuffer di questo tipo.

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

Attualmente, il compilatore shader supporta il ConstantBuffer modello solo per le strutture definite dall'utente.

Per motivi di compatibilità, il compilatore HLSL può assegnare automaticamente i registri delle risorse per gli intervalli dichiarati in space0. Se 'space' viene omesso nella clausola register, viene usato il valore predefinito space0 . Il compilatore usa l'euristica adatta al primo foro per assegnare i registri. L'assegnazione può essere recuperata tramite l'API reflection, estesa per aggiungere il campo Spazio per lo spazio, mentre il campo BindPoint indica il limite inferiore dell'intervallo di registrazione delle risorse.

Modifiche di Bytecode in SM5.1

SM5.1 modifica il modo in cui i registri delle risorse vengono dichiarati e a cui si fa riferimento nelle istruzioni. La sintassi prevede la dichiarazione di una "variabile" di registrazione, simile a quella eseguita per i registri di memoria condivisa del gruppo:

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

In questo modo verrà rimosso:

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

Ogni intervallo di risorse shader include ora un ID (un nome) univoco per il bytecode shader. Ad esempio, la matrice di trama tex1 (t10) diventa 'T1' nel bytecode shader. L'aggiunta di ID univoci a ogni intervallo di risorse consente due elementi:

  • Identificare in modo ambiguo l'intervallo di risorse (vedere dcl_resource_texture2d) indicizzato in un'istruzione (vedere l'istruzione di esempio).
  • Collegamento di un set di attributi alla dichiarazione, ad esempio tipo di elemento, dimensioni stride, modalità di operazione raster e così via.

Si noti che l'ID dell'intervallo non è correlato alla dichiarazione con associazione inferiore HLSL.

L'ordine delle associazioni di risorse reflection (elenco nella parte superiore) e istruzioni di dichiarazione shader (dcl_*) è lo stesso per facilitare l'identificazione della corrispondenza tra le variabili HLSL e gli ID bytecode.

Ogni istruzione di dichiarazione in SM5.1 usa un operando 3D per definire: ID intervallo, limiti inferiori e superiori. Viene generato un token aggiuntivo per specificare lo spazio di registrazione. Altri token possono essere generati anche per trasmettere proprietà aggiuntive dell'intervallo, ad esempio cbuffer o istruzioni di dichiarazione del buffer strutturato generano le dimensioni del cbuffer o della struttura. I dettagli esatti della codifica sono disponibili in d3d12TokenizedProgramFormat.h e D3D10ShaderBinary::CShaderCodeParser.

Le istruzioni SM5.1 non generano informazioni aggiuntive sull'operando di risorse come parte dell'istruzione (come in SM5.0). Queste informazioni sono ora disponibili nelle istruzioni di dichiarazione. In SM5.0, le istruzioni per l'indicizzazione delle risorse richiedono la descrizione degli attributi delle risorse da descrivere nei token opcode estesi, poiché l'indicizzazione offusca l'associazione alla dichiarazione. In SM5.1 ogni ID ,ad esempio 't1', è associato senza ambiguità a una singola dichiarazione che descrive le informazioni necessarie sulla risorsa. Di conseguenza, i token opcode estesi usati nelle istruzioni per descrivere le informazioni sulle risorse non vengono più generati.

Nelle istruzioni non di dichiarazione, un operando di risorse per campionatori, SRV e UAV è un operando 2D. Il primo indice è una costante letterale che specifica l'ID intervallo. Il secondo indice rappresenta il valore linearizzato dell'indice. Il valore viene calcolato rispetto all'inizio dello spazio del registro corrispondente (non relativo all'inizio dell'intervallo logico) per correlare meglio con la firma radice e ridurre il carico del compilatore del driver per regolare l'indice.

Un operando di risorsa per cbv è un operando 3D contenente: ID letterale dell'intervallo, indice del buffer costante, offset nella particolare istanza del buffer costante.

Dichiarazioni HLSL di esempio

I programmi HLSL non devono conoscere nulla sulle firme radice. Possono assegnare associazioni allo spazio di associazione "register" virtuale, t# per le unità srv, u# per gli UAV, b# per cbv, s# per gli esempi oppure fare affidamento sul compilatore per selezionare le assegnazioni ed eseguire query sui mapping risultanti usando la reflection shader. La firma radice esegue il mapping delle tabelle del descrittore, dei descrittori radice e delle costanti radice a questo spazio di registrazione virtuale.

Di seguito sono riportate alcune dichiarazioni di esempio che potrebbero essere presenti uno shader HLSL. Si noti che non sono presenti riferimenti alle firme radice o alle tabelle del descrittore.

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)