Share via


Associação de recursos no HLSL

Este tópico descreve alguns recursos específicos do uso do HLSL (High Level Shader Language) Shader Model 5.1 com Direct3D 12. Todo o hardware do Direct3D 12 dá suporte ao Sombreador Modelo 5.1, portanto, o suporte para esse modelo não depende do nível do recurso de hardware.

Tipos de recursos e matrizes

A sintaxe de recurso SM5.0 (Shader Model 5) usa o register palavra-chave 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 associadas aos 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 Modelo de Sombreador 5.1 (SM5.1) no HLSL baseia-se na sintaxe de recurso de registro existente, para permitir uma portabilidade mais fácil. Os recursos do Direct3D 12 no HLSL estão associados a registros virtuais em espaços de registro lógico:

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

A assinatura raiz que referencia o sombreador deve ser compatível com os slots de registro declarados. Por exemplo, a parte a seguir de uma assinatura raiz seria compatível com o uso de slots de textura t3 a t6, pois descreve uma tabela de descritor com slots t0 a t98.

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

Uma declaração de recurso pode ser um escalar, uma matriz 1D ou uma 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 do SM5.1 são mais flexíveis e restritos apenas pelos limites de runtime/hardware. O space palavra-chave especifica a qual espaço de registro lógico a variável declarada está associada. Se o space palavra-chave for omitido, o índice de espaço padrão de 0 será atribuído implicitamente ao intervalo (portanto, o tex2 intervalo acima reside em space0). register(t3, space0) nunca entrará em conflito com register(t3, space1), nem com qualquer matriz em outro espaço que possa incluir t3.

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

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

A tabela de descritor correspondente pode ser:

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

Uma matriz não associada no HLSL corresponde a um conjunto de números fixo com numDescriptors na tabela descritor e um tamanho fixo no HLSL corresponde a uma declaração não associada na tabela do descritor.

Matrizes multidimensionais são permitidas, incluindo um tamanho não associado. Essas matrizes multidimensionais são niveladas 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)

O aliasing de intervalos de recursos não é permitido. 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 não associados. Intervalos declarados em espaços de registro diferentes nunca se sobrepõem. Observe que não associado tex2 (acima) reside em space0, enquanto não tex3 associado 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) em que eles não têm permissão para variar dentro de uma onda. Mesmo alterando o índice com base em contagens de instanciação como variáveis.

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

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

Em algum hardware, o uso desse qualificador gera código adicional para impor a correção (inclusive entre threads), mas a um custo de desempenho menor. Se um índice for alterado sem esse qualificador e dentro de um sorteio/expedição, os resultados serão indefinidos.

Matrizes de descritor e matrizes de textura

As matrizes de textura estão disponíveis desde o DirectX 10. As matrizes de textura exigem 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ço virtual. O código a seguir mostra um exemplo de como acessar 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 a necessidade de qualificadores como NonUniformResourceIndex.

A matriz de descritor 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]. As matrizes de descritor também 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 de descritores de matrizes de textura:

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

Não é legítimo declarar uma matriz de estruturas, cada estrutura que contém descritores, por exemplo, não há suporte para o código a seguir.

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 linguagem e não tem suporte. Um método com suporte para fazer isso seria o seguinte, embora o layout de memória neste caso seja aaaa... Bbb... ccc....

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

Para obter o layout de memória abcabcabc.... , use uma tabela de descritor sem o uso da myStruct estrutura.

Aliasing de recursos

Os intervalos de recursos especificados nos sombreadores HLSL são intervalos lógicos. Eles são associados a intervalos de heap concretos em runtime por meio do mecanismo de assinatura raiz. Normalmente, um intervalo lógico é mapeado para um intervalo de heap que não se sobrepõe a outros intervalos de heap. No entanto, o mecanismo de assinatura raiz possibilita alias (sobreposição) de intervalos de heap de tipos compatíveis. Por exemplo, tex2 os intervalos e tex3 do exemplo acima podem ser mapeados para o mesmo intervalo de heap (ou sobreposto), que tem o efeito de texturas de alias no programa HLSL. Se esse aliasing for desejado, o sombreador deverá ser compilado com D3D10_SHADER_RESOURCES_MAY_ALIAS opção , que é definida usando a opção /res_may_alias para a FXC ( Effect-Compiler Tool ). A opção faz com que o compilador produza o código correto impedindo determinadas otimizações de carga/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 idx de índice pode ser uma constante literal, uma constante cbuffer ou um valor interpolado. Embora o modelo de programação forneça uma flexibilidade tão grande, há problemas a serem considerados:

  • Se o índice divergir em um quadriciclo, as quantidades derivadas e derivadas computadas por hardware, como LOD, poderão ser indefinidas. O compilador HLSL faz o melhor esforço para emitir um aviso nesse caso, mas não impedirá que um sombreador compile. Esse comportamento é semelhante aos derivados de computação no fluxo de controle divergente.
  • Se o índice de recursos for divergente, o desempenho será reduzido em comparação com o caso de um índice uniforme, pois o hardware precisa executar operações em vários recursos. Índices de recursos que podem ser divergentes devem ser marcados com a NonUniformResourceIndex função no código HLSL. Caso contrário, os resultados são indefinidos.

UAVs em sombreadores de pixel

O SM5.1 não impõe restrições a intervalos UAV em sombreadores de pixel, como foi o caso do SM5.0.

Buffers constantes

A sintaxe de buffers constantes SM5.1 (cbuffer) foi alterada de SM5.0 para permitir que os desenvolvedores indexem buffers constantes. Para habilitar buffers constantes indexáveis, o SM5.1 introduz o ConstantBuffer constructo "template":

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 myCB1 de buffer constante do tipo Foo e tamanho 6 e uma variável myCB2de buffer escalar e constante . 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, mas devem ser tratados como campos. Para compatibilidade com versões anteriores, o SM5.1 dá suporte ao antigo conceito cbuffer para cbuffers escalares. A instrução a seguir faz variáveis "a" e "b" globais, somente leitura, como no SM5.0. No entanto, esse cbuffer de estilo antigo não pode ser indexável.

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

Atualmente, o compilador de sombreador dá suporte apenas ao ConstantBuffer modelo 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 'space' for omitido na cláusula register, o padrão space0 será usado. O compilador usa a heurística first-hole-fits para atribuir os registros. A atribuição pode ser recuperada por meio da API de reflexão, que foi estendida para adicionar o campo Espaço para espaço, enquanto o campo BindPoint indica o limite inferior do intervalo de registro de recursos.

Alterações de código de byte no SM5.1

O SM5.1 altera a forma como os registros de recursos são declarados e referenciados nas instruções. A sintaxe envolve declarar uma "variável" de registro, semelhante à forma como ela é feita para registros de memória compartilhada em 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 desmontará 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 do sombreador agora tem uma ID (um nome) exclusiva para o código de byte do sombreador. Por exemplo, a matriz de textura tex1 (t10) torna-se 'T1' no código de byte do sombreador. Fornecer IDs exclusivas para cada intervalo de recursos permite duas coisas:

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

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

A ordem das associações de recursos de reflexão (listagem na parte superior) e instruções de declaração de sombreador (dcl_*) é a mesma para ajudar a identificar a correspondência entre variáveis HLSL e IDs de código de byte.

Cada instrução de declaração no SM5.1 usa um operando 3D para definir: ID de 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 estruturado emite o tamanho do cbuffer ou da estrutura. Os detalhes exatos da codificação podem ser encontrados em d3d12TokenizedProgramFormat.h e D3D10ShaderBinary::CShaderCodeParser.

As instruções do SM5.1 não emitirão informações adicionais de operando de recurso como parte da instrução (como no SM5.0). Essas informações agora estão nas instruções de declaração. No SM5.0, as instruções de indexação de recursos exigiam que os atributos de recurso fossem descritos em tokens opcode estendidos, já que a indexação ofuscava a associação à declaração. No SM5.1, cada ID (como 't1') é associada sem ambiguidade a uma única declaração que descreve as informações de recurso necessárias. Portanto, os tokens opcode estendidos usados nas instruções para descrever informações de recurso não são mais emitidos.

Em instruções de não declaração, um operando de recurso para samplers, SRVs e UAVs é um operando 2D. O primeiro índice é uma constante literal que especifica a 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 registro correspondente (não relativo ao início do intervalo lógico) para correlacionar melhor com a assinatura raiz e reduzir a carga 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 associações ao espaço de associação virtual "register", t# para SRVs, u# para UAVs, b# para CBVs, s# para samplers ou depender do compilador para escolher atribuições (e consultar os mapeamentos resultantes usando reflexão de sombreador posteriormente). A assinatura raiz mapeia tabelas de descritores, descritores raiz e constantes raiz para esse espaço de registro virtual.

Veja a seguir algumas declarações de exemplo que um sombreador HLSL pode ter. Observe que não há referências a assinaturas raiz ou tabelas de descritor.

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)