Compartir a través de


Enlace de recursos en HLSL

En este tema se describen algunas características específicas del uso del modelo de sombreador de alto nivel (HLSL) modelo 5.1 con Direct3D 12. Todo el hardware de Direct3D 12 es compatible con Shader Model 5.1, por lo que la compatibilidad con este modelo no depende del nivel de característica de hardware.

Tipos de recursos y matrices

La sintaxis de recursos del modelo de sombreador 5 (SM5.0) usa la register palabra clave para retransmitir información importante sobre el recurso al compilador de HLSL. Por ejemplo, la siguiente instrucción declara una matriz de cuatro texturas enlazadas en las ranuras t3, t4, t5 y t6. t3 es la única ranura de registro que aparece en la instrucción , simplemente siendo la primera en la matriz de cuatro.

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

La sintaxis de recursos del modelo de sombreador 5.1 (SM5.1) en HLSL se basa en la sintaxis de recursos de registro existente para permitir una portabilidad más sencilla. Los recursos de Direct3D 12 en HLSL están enlazados a registros virtuales dentro de espacios de registro lógicos:

  • t: para las vistas de recursos del sombreador (SRV)
  • s: para samplers
  • u: para vistas de acceso desordenadas (UAV)
  • b: para vistas de búfer constante (CBV)

La firma raíz que hace referencia al sombreador debe ser compatible con las ranuras de registro declaradas. Por ejemplo, la siguiente parte de una firma raíz sería compatible con el uso de ranuras de textura t3 a t6, ya que describe una tabla de descriptores con ranuras t0 a t98.

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

Una declaración de recursos puede ser escalar, una matriz 1D o una matriz multidimensional:

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

SM5.1 usa los mismos tipos de recursos y tipos de elementos que SM5.0. Los límites de declaración de SM5.1 son más flexibles y solo están restringidos por los límites de tiempo de ejecución o hardware. La space palabra clave especifica a qué espacio de registro lógico está enlazado la variable declarada. Si se omite la space palabra clave , el índice de espacio predeterminado de 0 se asigna implícitamente al intervalo (por lo que el tex2 intervalo anterior reside en space0). register(t3, space0) nunca entrará en conflicto con register(t3, space1), ni con ninguna matriz en otro espacio que pueda incluir t3.

Un recurso de matriz puede tener un tamaño sin enlazar, que se declara especificando la primera dimensión que se va a estar vacía o 0:

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

La tabla de descriptores coincidente podría ser:

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

Una matriz sin enlazar en HLSL coincide con un número fijo con numDescriptors en la tabla de descriptores y un tamaño fijo en HLSL coincide con una declaración sin enlazar en la tabla descriptor.

Se permiten matrices multidimensionales, incluido un tamaño no enlazado. Estas matrices multidimensionales se aplanan en el espacio de registro.

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

No se permite el alias de los intervalos de recursos. En otras palabras, para cada tipo de recurso (t, s, u, b), los intervalos de registro declarados no se deben superponer. Esto incluye también intervalos sin enlazar. Los intervalos declarados en diferentes espacios de registro nunca se superponen. Tenga en cuenta que unbounded tex2 (above) reside en space0, mientras que unbounded tex3 reside en space1, de modo que no se superponen.

El acceso a los recursos que se han declarado como matrices es tan sencillo como indexarlos.

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

Hay una restricción predeterminada importante sobre el uso de los índices (myMaterialID y samplerID en el código anterior) en que no se les permite variar dentro de una onda. Incluso cambiando el índice en función de los recuentos de instancias como variables.

Si es necesario variar el índice, especifique el NonUniformResourceIndex calificador en el índice, por ejemplo:

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

En algún hardware, el uso de este calificador genera código adicional para aplicar la corrección (incluido entre subprocesos), pero a un costo de rendimiento menor. Si se cambia un índice sin este calificador y dentro de un draw/dispatch, los resultados no están definidos.

Matrices de descriptores y matrices de texturas

Las matrices de texturas están disponibles desde DirectX 10. Las matrices de textura requieren un descriptor, pero todos los segmentos de matriz deben compartir el mismo formato, ancho, alto y recuento mip. Además, la matriz debe ocupar un intervalo contiguo en el espacio de direcciones virtuales. En el código siguiente se muestra un ejemplo de acceso a una matriz de texturas desde un sombreador.

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

En una matriz de texturas, el índice puede variar libremente, sin necesidad de calificadores como NonUniformResourceIndex.

La matriz de descriptores equivalente sería:

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

Tenga en cuenta que el uso incómodo de un valor float para el índice de matriz se reemplaza por myArrayOfTex2D[2]. Además, las matrices de descriptores ofrecen más flexibilidad con las dimensiones. El tipo, Texture2D es este ejemplo, no puede variar, pero el formato, el ancho, el alto y el recuento de mip pueden variar con cada descriptor.

Es legítimo tener una matriz descriptor de matrices de texturas:

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

No es legítimo declarar una matriz de estructuras, no se admite cada estructura que contenga descriptores, por ejemplo, el código siguiente.

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

Esto habría permitido el diseño de memoria abcabcabc...., pero es una limitación de idioma y no se admite. Un método compatible de hacer esto sería el siguiente, aunque el diseño de memoria en este caso es aaa... Bbb... ccc....

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

Para lograr el diseño de memoria abcabcabc.... , use una tabla descriptor sin usar la myStruct estructura .

Alias de recursos

Los intervalos de recursos especificados en los sombreadores HLSL son intervalos lógicos. Se enlazan a intervalos de montón concretos en tiempo de ejecución a través del mecanismo de firma raíz. Normalmente, un intervalo lógico se asigna a un intervalo de montón que no se superpone con otros intervalos de montón. Sin embargo, el mecanismo de firma raíz permite establecer un alias (superposición) de intervalos de montones de tipos compatibles. Por ejemplo, tex2 y tex3 los intervalos del ejemplo anterior se pueden asignar al mismo intervalo de montón (o superpuesto), que tiene el efecto de aplicar alias a las texturas del programa HLSL. Si se desea este alias, el sombreador debe compilarse con D3D10_SHADER_RESOURCES_MAY_ALIAS opción, que se establece mediante la opción /res_may_alias para la herramienta Effect-Compiler Tool (FXC). La opción hace que el compilador genere código correcto evitando determinadas optimizaciones de carga o almacén bajo la suposición de que los recursos pueden alias.

Divergencia y derivados

SM5.1 no impone limitaciones en el índice de recursos; Es decir, tex2[idx].Sample(…) : el idx de índice puede ser una constante literal, una constante cbuffer o un valor interpolado. Aunque el modelo de programación proporciona una gran flexibilidad, hay problemas que se deben tener en cuenta:

  • Si el índice difiere en un quad, las cantidades derivadas calculadas por hardware y derivadas, como LOD, pueden no estar definidas. El compilador HLSL hace el mejor esfuerzo para emitir una advertencia en este caso, pero no impedirá que un sombreador se compile. Este comportamiento es similar a calcular derivados en el flujo de control divergente.
  • Si el índice de recursos es divergente, el rendimiento se reduce en comparación con el caso de un índice uniforme, ya que el hardware debe realizar operaciones en varios recursos. Los índices de recursos que pueden ser divergentes deben marcarse con la NonUniformResourceIndex función en el código HLSL. De lo contrario, los resultados no están definidos.

UAV en sombreadores de píxeles

SM5.1 no impone restricciones en intervalos UAV en sombreadores de píxeles, como era el caso de SM5.0.

Búferes de constantes

La sintaxis de los búferes de constantes (cbuffer) de SM5.1 ha cambiado de SM5.0 para permitir a los desarrolladores indexar búferes de constantes. Para habilitar los búferes de constantes indexables, SM5.1 presenta la ConstantBuffer construcción "template":

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

El código anterior declara la variable myCB1 de búfer de constantes de tipo Foo y tamaño 6, y una variable myCB2de búfer escalar y constante . Ahora se puede indexar una variable de búfer de constantes en el sombreador como:

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

Los campos 'a' y 'b' no se convierten en variables globales, sino que se deben tratar como campos. Para la compatibilidad con versiones anteriores, SM5.1 admite el antiguo concepto de cbuffer para cbuffers escalares. La siguiente instrucción hace que las variables globales "a" y "b" sean de solo lectura como en SM5.0. Sin embargo, este tipo de cbuffer de estilo antiguo no se puede indexar.

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

Actualmente, el compilador de sombreador solo admite la plantilla para estructuras definidas por el ConstantBuffer usuario.

Por motivos de compatibilidad, el compilador de HLSL puede asignar automáticamente registros de recursos para los intervalos declarados en space0. Si se omite "space" en la cláusula register, se usa el valor predeterminado space0 . El compilador usa la heurística de primer agujero para asignar los registros. La asignación se puede recuperar a través de la API de reflexión, que se ha ampliado para agregar el campo Espacio para el espacio, mientras que el campo BindPoint indica el límite inferior del intervalo de registro de recursos.

Cambios de código de bytes en SM5.1

SM5.1 cambia cómo se declaran y hacen referencia a los registros de recursos en las instrucciones. La sintaxis implica declarar una "variable" de registro, similar a cómo se realiza para los registros de memoria compartida de grupo:

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

Esto se desensamblará en:

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

Cada intervalo de recursos del sombreador ahora tiene un identificador (un nombre) que es único para el código de bytes del sombreador. Por ejemplo, la matriz de texturas tex1 (t10) se convierte en "T1" en el código de bytes del sombreador. Proporcionar identificadores únicos a cada intervalo de recursos permite dos cosas:

  • Identifique de forma inequívoca qué intervalo de recursos (consulte dcl_resource_texture2d) se está indizando en una instrucción (consulte la instrucción de ejemplo).
  • Adjuntar un conjunto de atributos a la declaración, por ejemplo, tipo de elemento, tamaño de paso, modo de operación de trama, etc.

Tenga en cuenta que el identificador del intervalo no está relacionado con la declaración de límite inferior de HLSL.

El orden de los enlaces de recursos de reflexión (que se enumeran en la parte superior) y las instrucciones de declaración del sombreador (dcl_*) es el mismo para ayudar a identificar la correspondencia entre variables HLSL e identificadores de código de bytes.

Cada instrucción de declaración de SM5.1 usa un operando 3D para definir: id. de intervalo, límites inferiores y superiores. Se emite un token adicional para especificar el espacio de registro. También se pueden emitir otros tokens para transmitir propiedades adicionales del intervalo, por ejemplo, instrucciones de declaración de búfer cbuffer o búfer estructurado emite el tamaño del cbuffer o la estructura. Los detalles exactos de la codificación se pueden encontrar en d3d12TokenizedProgramFormat.h y D3D10ShaderBinary::CShaderCodeParser.

Las instrucciones de SM5.1 no emitirán información adicional del operando de recursos como parte de la instrucción (como en SM5.0). Esta información se encuentra ahora en las instrucciones de declaración. En SM5.0, las instrucciones para indexar los recursos requieren atributos de recursos que se describen en tokens de código de operación extendidos, ya que la indexación ofuscó la asociación a la declaración. En SM5.1, cada identificador (como "t1") está asociado de forma inequívoca a una única declaración que describe la información de recursos necesaria. Por lo tanto, los tokens de código de operación extendidos que se usan en instrucciones para describir la información de recursos ya no se emiten.

En instrucciones que no son de declaración, un operando de recursos para samplers, SRV y UAV es un operando 2D. El primer índice es una constante literal que especifica el identificador de intervalo. El segundo índice representa el valor linealizado del índice. El valor se calcula con respecto al principio del espacio de registro correspondiente (no relativo al principio del intervalo lógico) para correlacionar mejor con la firma raíz y reducir la carga del compilador del controlador para ajustar el índice.

Un operando de recursos para CBV es un operando 3D que contiene: identificador literal del intervalo, índice del búfer de constantes, desplazamiento en la instancia concreta del búfer de constantes.

Declaraciones HLSL de ejemplo

Los programas HLSL no necesitan saber nada sobre las firmas raíz. Pueden asignar enlaces al espacio de enlace "register" virtual, t# para SRV, u# para UAV, b# para CBV, s# para samplers o confiar en el compilador para seleccionar asignaciones (y consultar las asignaciones resultantes mediante la reflexión del sombreador después). La firma raíz asigna las tablas descriptores, los descriptores raíz y las constantes raíz a este espacio de registro virtual.

A continuación se muestran algunas declaraciones de ejemplo que podría tener un sombreador HLSL. Tenga en cuenta que no hay referencias a las firmas raíz ni a las tablas descriptores.

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)