Partilhar via


Vinculação de recursos em HLSL

Este tópico descreve alguns recursos específicos do uso da linguagem HLSL (High Level Shader Language) Shader Model 5.1 com Direct3D 12. Todo o hardware Direct3D 12 suporta Shader Model 5.1, portanto, o suporte para este modelo não depende do nível de recurso de hardware.

Tipos de recursos e matrizes

A sintaxe do recurso Shader Model 5 (SM5.0) usa a palavra-chave register para retransmitir informações importantes sobre o recurso para o compilador HLSL. Por exemplo, a instrução a seguir declara uma matriz de quatro texturas ligadas nos slots t3, t4, t5 e t6. T3 é o único slot de registro que aparece na instrução, simplesmente sendo o primeiro na matriz de quatro.

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

A sintaxe de recurso do Shader Model 5.1 (SM5.1) no HLSL é baseada na sintaxe de recurso de registro existente, para permitir uma portabilidade mais fácil. Os recursos do Direct3D 12 no HLSL estão vinculados a registros virtuais dentro de espaços de registro lógicos:

  • t – para exibições de recursos de sombreador (SRV)
  • s – para amostradores
  • u – para vistas de acesso não ordenado (UAV)
  • b – para visualizações de buffer constantes (CBV)

A assinatura raiz que faz referência ao sombreador deve ser compatível com os slots de registro declarados. Por exemplo, a seguinte parte de uma assinatura raiz seria compatível com o uso de slots de textura t3 a t6, pois descreve uma tabela de descritores com slots t0 a t98.

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

Uma declaração de recurso pode ser escalar, matriz 1D ou matriz multidimensional:

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

O SM5.1 usa os mesmos tipos de recursos e tipos de elementos que o SM5.0. Os limites de declaração SM5.1 são mais flexíveis e limitados apenas pelos limites de tempo de execução/hardware. A palavra-chave space especifica a qual espaço de registro lógico a variável declarada está vinculada. Se a palavra-chave space for omitida, o índice de espaço padrão de 0 será atribuído implicitamente ao intervalo (portanto, o intervalo de tex2 acima reside em space0). register(t3, space0) nunca entrará em conflito com register(t3, space1), nem com qualquer array em outro espaço que possa incluir t3.

Um recurso de matriz pode ter um tamanho não limitado, que é declarado especificando a primeira dimensão a ser vazia, ou 0:

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

A tabela do descritor correspondente pode ser:

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

Uma matriz não limitada em HLSL corresponde a um conjunto de números fixos com numDescriptors na tabela de descritores, e um tamanho fixo no HLSL corresponde a uma declaração não limitada na tabela de descritores.

Matrizes multidimensionais são permitidas, inclusive de tamanho ilimitado. Essas matrizes multidimensionais são achatadas no espaço de registro.

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

A sobreposição de intervalos de recursos não é permitida. Em outras palavras, para cada tipo de recurso (t, s, u, b), os intervalos de registro declarados não devem se sobrepor. Isso também inclui intervalos ilimitados. Os intervalos declarados em diferentes espaços de registo nunca se sobrepõem. Observe que o tex2 ilimitado (acima) reside em space0, enquanto o tex3 ilimitado reside em space1, de modo que eles não se sobrepõem.

Acessar recursos que foram declarados como matrizes é tão simples quanto indexá-los.

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

Há uma restrição padrão importante sobre o uso dos índices (myMaterialID e samplerID no código acima) na medida em que eles não podem variar dentro de uma onda . Alterar o índice com base na contagem de instanciação também conta como variação.

Se for necessário variar o índice, especifique o qualificador de NonUniformResourceIndex no índice, por exemplo:

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

Em alguns hardwares, o uso desse qualificador gera código adicional para impor a correção (inclusive entre threads), mas com um custo de desempenho menor. Se um índice é alterado sem este qualificador e dentro de um empate/despacho, os resultados são indefinidos.

Matrizes descritoras e matrizes de textura

As matrizes de textura estão disponíveis desde o DirectX 10. As matrizes de textura requerem um descritor, no entanto, todas as fatias de matriz devem compartilhar o mesmo formato, largura, altura e contagem de mip. Além disso, a matriz deve ocupar um intervalo contíguo no espaço de endereçamento virtual. O código a seguir mostra um exemplo de acesso a uma matriz de textura de um 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);

Em uma matriz de textura, o índice pode ser variado livremente, sem qualquer necessidade de qualificadores como NonUniformResourceIndex.

A matriz descritora equivalente seria:

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

Observe que o uso estranho de um float para o índice de matriz é substituído por myArrayOfTex2D[2]. Além disso, as matrizes descritoras oferecem mais flexibilidade com as dimensões. O tipo, Texture2D é este exemplo, não pode variar, mas o formato, a largura, a altura e a contagem de mip podem variar com cada descritor.

É legítimo ter uma matriz descritora de matrizes de textura:

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

Não é legítimo declarar uma matriz de estruturas, cada estrutura contendo descritores, por exemplo, o código a seguir não é suportado.

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

Isso teria permitido o layout de memória abcabcabc...., mas é uma limitação de idioma e não é suportado. Um método suportado para fazer isso seria o seguinte, embora o layout de memória neste caso seja aaa... BBB... CCC....

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

Para alcançar o abcabcabc.... layout de memória, use uma tabela descritora sem o uso da estrutura myStruct.

Aliasing de recursos

Os intervalos de recursos especificados nos sombreadores HLSL são intervalos lógicos. Eles são associados a intervalos específicos de heap em tempo de execução através do mecanismo de assinatura raiz. Normalmente, um intervalo lógico mapeia para um intervalo de heap que não se sobrepõe a outros intervalos de heap. No entanto, o mecanismo de assinatura raiz torna possível sobrepor intervalos de heap de tipos compatíveis. No exemplo acima, os intervalos de tex2 e tex3 podem ser mapeados para o mesmo (ou sobreposto) intervalo de heap, o que tem o efeito de aliasar texturas no programa HLSL. Se esse aliasing for desejado, o sombreador deve ser compilado com D3D10_SHADER_RESOURCES_MAY_ALIAS opção, que é definida usando a opção /res_may_alias para o Effect-Compiler Tool (FXC). A opção faz com que o compilador produza código correto, impedindo certas otimizações de carregamento/armazenamento sob a suposição de que os recursos podem ser alias.

Divergência e derivados

O SM5.1 não impõe limitações ao índice de recursos; Ou seja, tex2[idx].Sample(…) – O índice IDX pode ser uma constante literal, uma constante cbuffer ou um valor interpolado. Embora o modelo de programação ofereça uma flexibilidade tão grande, há questões a ter em conta:

  • Se o índice divergir em um quad, a derivada calculada por hardware e as quantidades derivadas, como LOD, podem ser indefinidas. O compilador HLSL faz o melhor esforço para emitir um aviso neste caso, mas não impedirá que um sombreador compile. Esse comportamento é semelhante ao cálculo de derivadas em fluxo de controle divergente.
  • Se o índice de recursos for divergente, o desempenho será diminuído em comparação com o caso de um índice uniforme, porque o hardware precisa executar operações em vários recursos. Os índices de recursos que podem ser divergentes devem ser marcados com a função NonUniformResourceIndex no código HLSL. Caso contrário, os resultados são indefinidos.

UAVs em sombreadores de pixel

A SM5.1 não impõe limites aos alcances de UAV nos sombreadores de pixel, como acontecia com a SM5.0.

Buffers constantes

A sintaxe dos buffers constantes (cbuffer) do SM5.1 foi alterada do SM5.0 para permitir que os desenvolvedores indexem buffers constantes. Para habilitar buffers constantes indexáveis, o SM5.1 introduz a construção "template" ConstantBuffer:

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

O código anterior declara a variável de buffer constante myCB1 do tipo Foo e tamanho 6 e uma variável de buffer escalar e constante myCB2. Uma variável de buffer constante agora pode ser indexada no sombreador como:

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

Os campos «a» e «b» não se tornam variáveis globais, devendo antes ser tratados como campos. Para compatibilidade com versões anteriores, o SM5.1 suporta o antigo conceito de cbuffer para cbuffers escalares. A declaração a seguir torna 'a' e 'b' variáveis globais de leitura única de acordo com o SM5.0. No entanto, tal cbuffer de estilo antigo não pode ser indexado.

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

Atualmente, o compilador de sombreador suporta o modelo ConstantBuffer apenas para estruturas definidas pelo usuário.

Por motivos de compatibilidade, o compilador HLSL pode atribuir automaticamente registros de recursos para intervalos declarados em space0. Se o «espaço» for omitido na cláusula de registo, é utilizado o space0 predefinido. O compilador usa a heurística first-hole-fits para atribuir os registros. A atribuição pode ser obtida através da API de reflexão, que foi ampliada para adicionar o campo Espaço para espaço, enquanto o campo BindPoint indica o limite inferior da gama de registos de recursos.

Alterações de bytecode no SM5.1

O SM5.1 altera a forma como os registos de recursos são declarados e referenciados nas instruções. A sintaxe envolve declarar um registro "variável", semelhante a como é feito para registros de memória compartilhada 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;
}

Isso será desmontado para:

// 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 de sombreador agora tem uma ID (um nome) que é exclusiva para o bytecode do sombreador. Por exemplo, a matriz de textura tex1 (t10) torna-se 'T1' no bytecode do sombreador. Dar IDs exclusivos para cada intervalo de recursos permite duas coisas:

  • Identifique inequivocamente qual intervalo de recursos (consulte dcl_resource_texture2d) está sendo indexado em uma instrução (consulte a instrução de exemplo).
  • Anexar um conjunto de atributos à declaração, por exemplo, tipo de elemento, tamanho do passo, modo de operação de rasterização, etc.

Observe que a ID do intervalo não está relacionada à declaração de limite inferior HLSL.

A ordem das ligações de recursos de reflexão (listadas no topo) e instruções de declaração do sombreador (dcl_*) é a mesma para ajudar a identificar a correspondência entre variáveis HLSL e identificadores de bytecode.

Cada instrução de declaração no SM5.1 usa um operando 3D para definir: ID do intervalo, limites inferior e superior. Um token adicional é emitido para especificar o espaço de registro. Outros tokens também podem ser emitidos para transmitir propriedades adicionais do intervalo, por exemplo, cbuffer ou instrução de declaração de buffer estruturada emite o tamanho do cbuffer ou estrutura. Os detalhes exatos da codificação podem ser encontrados em d3d12TokenizedProgramFormat.h e D3D10ShaderBinary::CShaderCodeParser.

As instruções do SM5.1 deixarão de emitir informações adicionais sobre operandos de recursos como parte da instrução, ao contrário do SM5.0. Esta informação encontra-se agora nas instruções da declaração. No SM5.0, as instruções de indexação de recursos exigiam que os atributos de recursos fossem descritos em tokens opcode estendidos, uma vez que a indexação ofuscava a associação à declaração. No SM5.1, cada ID (como 't1') é inequivocamente associado a uma única declaração que descreve as informações de recurso necessárias. Portanto, os tokens de opcode estendidos usados em instruções para descrever informações de recursos não são mais emitidos.

Em instruções que não sejam de declaração, um operando de recurso para amostradores, SRVs e UAVs é um operando 2D. O primeiro índice é uma constante literal que especifica o ID do intervalo. O segundo índice representa o valor linearizado do índice. O valor é calculado em relação ao início do espaço de registo correspondente (não em relação ao início do intervalo lógico) para melhor se alinhar com a assinatura raiz e reduzir o esforço do compilador do driver de ajustar o índice.

Um operando de recurso para CBVs é um operando 3D, contendo: ID literal do intervalo, índice do buffer constante, deslocamento para a instância específica do buffer constante.

Exemplo de declarações HLSL

Os programas HLSL não precisam saber nada sobre assinaturas raiz. Eles podem atribuir ligações ao espaço de vinculação virtual "register", t# para SRVs, u# para UAVs, b# para CBVs, s# para samplers, ou confiar no compilador para selecionar atribuições (e consultar os mapeamentos resultantes usando reflexão de sombreador depois). A assinatura raiz mapeia tabelas de descritores, descritores de raiz e constantes de raiz para esse espaço de registro virtual.

A seguir estão alguns exemplos de declarações que um sombreador HLSL pode ter. Observe que não há referências a assinaturas raiz ou tabelas de descritores.

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)