Compartilhar via


Como criar uma assinatura raiz

As assinaturas raiz são uma estrutura de dados complexa que contém estruturas aninhadas. Eles podem ser definidos programaticamente usando a definição de estrutura de dados abaixo (que inclui métodos para ajudar a inicializar membros). Como alternativa, eles podem ser criados em HLSL (Linguagem de Sombreamento de Alto Nível) – dando a vantagem de que o compilador validará antecipadamente que o layout é compatível com o sombreador.

A API para criar uma assinatura raiz usa uma versão serializada (autocontida, sem ponteiro) da descrição do layout descrita abaixo. Um método é fornecido para gerar essa versão serializada da estrutura de dados C++, mas outra maneira de obter uma definição de assinatura raiz serializada é recuperá-la de um sombreador que foi compilado com uma assinatura raiz.

Se você quiser aproveitar as otimizações de driver para descritores de assinatura raiz e dados, consulte Assinatura Raiz Versão 1.1

Tipos de associação de tabela de descritor

A enumeração D3D12_DESCRIPTOR_RANGE_TYPE define os tipos de descritores que podem ser referenciados como parte de uma definição de layout de tabela de descritor.

É um intervalo para que, por exemplo, se parte de uma tabela de descritor tiver 100 SRVs, esse intervalo poderá ser declarado em uma entrada em vez de 100. Portanto, uma definição de tabela de descritor é uma coleção de intervalos.

typedef enum D3D12_DESCRIPTOR_RANGE_TYPE
{
  D3D12_DESCRIPTOR_RANGE_TYPE_SRV,
  D3D12_DESCRIPTOR_RANGE_TYPE_UAV,
  D3D12_DESCRIPTOR_RANGE_TYPE_CBV,
  D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER
} D3D12_DESCRIPTOR_RANGE_TYPE;

Intervalo do descritor

A estrutura D3D12_DESCRIPTOR_RANGE define um intervalo de descritores de um determinado tipo (como SRVs) em uma tabela de descritor.

Normalmente D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND , a macro pode ser usada para o OffsetInDescriptorsFromTableStart parâmetro de D3D12_DESCRIPTOR_RANGE. Isso significa acrescentar o intervalo de descritores que está sendo definido após o anterior na tabela do descritor. Se o aplicativo quiser alias de descritores ou, por algum motivo, quiser ignorar slots, ele poderá definir OffsetInDescriptorsFromTableStart como qualquer deslocamento desejado. Definir intervalos sobrepostos de diferentes tipos é inválido.

O conjunto de registros de sombreador especificado pela combinação de RangeType, NumDescriptors, BaseShaderRegistere RegisterSpace não pode entrar em conflito ou se sobrepor a nenhuma declaração em uma assinatura raiz que tenha D3D12_SHADER_VISIBILITY comuns (consulte a seção visibilidade do sombreador abaixo).

Layout da Tabela do Descritor

A estrutura D3D12_ROOT_DESCRIPTOR_TABLE declara o layout de uma tabela de descritor como uma coleção de intervalos de descritores que começam em um determinado deslocamento de um heap de descritor. Exemplos não são permitidos na mesma tabela de descritor que CBV/UAV/SRVs.

Esse struct é usado quando o tipo de slot de assinatura raiz é definido D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLEcomo .

Para definir uma tabela de descritores gráficos (CBV, SRV, UAV, Sampler), use ID3D12GraphicsCommandList::SetGraphicsRootDescriptorTable.

Para definir uma tabela de descritor de computação, use ID3D12GraphicsCommandList::SetComputeRootDescriptorTable.

Constantes raiz

A estrutura D3D12_ROOT_CONSTANTS declara constantes embutidas na assinatura raiz que aparecem em sombreadores como um buffer constante.

Esse struct é usado quando o tipo de slot de assinatura raiz é definido D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTScomo .

Descritor raiz

A estrutura D3D12_ROOT_DESCRIPTOR declara descritores (que aparecem em sombreadores) embutidos na assinatura raiz.

Esse struct é usado quando o tipo de slot de assinatura raiz é definido D3D12_ROOT_PARAMETER_TYPE_CBVcomo ou D3D12_ROOT_PARAMETER_TYPE_SRVD3D12_ROOT_PARAMETER_TYPE_UAV.

Visibilidade do sombreador

O membro de D3D12_SHADER_VISIBILITY enumeração definido no parâmetro de visibilidade do sombreador de D3D12_ROOT_PARAMETER determina quais sombreadores veem o conteúdo de um determinado slot de assinatura raiz. A computação sempre usa _ALL (já que há apenas um estágio ativo). Os gráficos podem escolher, mas se ele usar _ALL, todos os estágios do sombreador verão o que está associado ao slot de assinatura raiz.

Um uso da visibilidade do sombreador é ajudar com sombreadores criados esperando associações diferentes por estágio de sombreador usando um namespace sobreposto. Por exemplo, um sombreador de vértice pode declarar:

Texture2D foo : register(t0);

e o sombreador de pixel também podem declarar:

Texture2D bar : register(t0);

Se o aplicativo fizer uma associação de assinatura raiz para t0 VISIBILITY_ALL, ambos os sombreadores verão a mesma textura. Se o sombreador definir realmente quiser que cada sombreador veja texturas diferentes, ele poderá definir dois slots de assinatura raiz com VISIBILITY_VERTEX e _PIXEL. Não importa qual seja a visibilidade em um slot de assinatura raiz, ela sempre tem o mesmo custo (custo somente dependendo do que é o SlotType) para um tamanho de assinatura raiz máximo fixo.

Em hardware D3D11 de baixa extremidade, SHADER_VISIBILITY também é levado em conta usado ao validar os tamanhos de tabelas de descritor em um layout raiz, já que alguns hardwares D3D11 só podem dar suporte a uma quantidade máxima de associações por estágio. Essas restrições só são impostas ao serem executadas em hardware de baixa camada e não limitam o hardware mais moderno.

Se uma assinatura raiz tiver várias tabelas de descritor definidas que se sobrepõem entre si no namespace (as associações de registro para o sombreador) e qualquer uma delas especificar _ALL para visibilidade, o layout será inválido (a criação falhará).

Definição de assinatura raiz

A estrutura D3D12_ROOT_SIGNATURE_DESC pode conter tabelas de descritor e constantes embutidas, cada tipo de slot definido pela estrutura de D3D12_ROOT_PARAMETER e a D3D12_ROOT_PARAMETER_TYPE de enumeração.

Para iniciar um slot de assinatura raiz, consulte os métodos SetComputeRoot*** e SetGraphicsRoot*** de ID3D12GraphicsCommandList.

Exemplos estáticos são descritos na assinatura raiz usando a estrutura D3D12_STATIC_SAMPLER .

Vários sinalizadores limitam o acesso de determinados sombreadores à assinatura raiz, consulte D3D12_ROOT_SIGNATURE_FLAGS.

Serialização/desserialização da estrutura de dados de assinatura raiz

Os métodos descritos nesta seção são exportados por D3D12Core.dll e fornecem métodos para serializar e desserializar uma estrutura de dados de assinatura raiz.

O formulário serializado é o que é passado para a API ao criar uma assinatura raiz. Se um sombreador tiver sido criado com uma assinatura raiz nele (quando esse recurso for adicionado), o sombreador compilado já conterá uma assinatura raiz serializada.

Se um aplicativo gerar um D3D12_ROOT_SIGNATURE_DESC estrutura de dados de forma processual, ele deverá tornar o formulário serializado usando D3D12SerializeRootSignature. A saída de que pode ser passada para ID3D12Device::CreateRootSignature.

Se um aplicativo já tiver uma assinatura raiz serializada ou tiver um sombreador compilado que contenha uma assinatura raiz e quiser descobrir programaticamente a definição de layout (conhecida como "reflexão"), D3D12CreateRootSignatureDeserializer poderá ser chamado. Isso gera uma interface ID3D12RootSignatureDeserializer , que contém um método para retornar a estrutura de dados D3D12_ROOT_SIGNATURE_DESC desserializada. A interface possui o tempo de vida da estrutura de dados desserializada.

API de criação de assinatura raiz

A API ID3D12Device::CreateRootSignature usa uma versão serializada de uma assinatura raiz.

Assinatura raiz em objetos de estado do pipeline

Os métodos para criar o estado do pipeline (ID3D12Device::CreateGraphicsPipelineState e ID3D12Device::CreateComputePipelineState ) assumem uma interface ID3D12RootSignature opcional como um parâmetro de entrada (armazenado em uma estrutura D3D12_GRAPHICS_PIPELINE_STATE_DESC ). Isso substituirá qualquer assinatura raiz já nos sombreadores.

Se uma assinatura raiz for passada para um dos métodos de estado de criação de pipeline, essa assinatura raiz será validada em relação a todos os sombreadores no PSO para compatibilidade e fornecida ao driver a ser usada com todos os sombreadores. Se qualquer um dos sombreadores tiver uma assinatura raiz diferente, ele será substituído pela assinatura raiz passada na API. Se uma assinatura raiz não for passada, todos os sombreadores passados devem ter uma assinatura raiz e devem corresponder – isso será dado ao driver. Definir um PSO em uma lista de comandos ou pacote não altera a assinatura raiz. Isso é feito pelos métodos SetGraphicsRootSignature e SetComputeRootSignature. Quando draw(graphics)/dispatch(compute) é invocado, o aplicativo deve garantir que o PSO atual corresponda à assinatura raiz atual; caso contrário, o comportamento é indefinido.

Código para definir uma assinatura raiz da versão 1.1

O exemplo a seguir mostra como criar uma assinatura raiz com o seguinte formato:

RootParameterIndex Sumário Valores
[0] Constantes raiz: { b2 } (1 CBV)
 [1] Tabela do descritor: { t2-t7, u0-u3 } (6 SRVs + 4 UAVs)
[2] CBV raiz: { b0 } (1 CBV, dados estáticos)
[3] Tabela descritor: { s0-s1 } (2 Samplers)
[4] Tabela do descritor: { t8 - unbounded } (não associado # de SRVs, descritores voláteis)
[5] Tabela do descritor: { (t0, space1) - unbounded } (não associado # de SRVs, descritores voláteis)
[6] Tabela do descritor: { b1 } (1 CBV, dados estáticos)

 

Se a maioria das partes da assinatura raiz for usada na maioria das vezes, poderá ser melhor do que ter que alternar a assinatura raiz com muita frequência. Os aplicativos devem classificar as entradas na assinatura raiz da alteração mais frequente para o mínimo. Quando um aplicativo altera as associações para qualquer parte da assinatura raiz, o driver pode ter que fazer uma cópia de alguns ou de todo o estado de assinatura raiz, o que pode se tornar um custo não fatal quando multiplicado em muitas alterações de estado.

Além disso, a assinatura raiz definirá um sampler estático que faz a filtragem de textura anisotrópica no registro de sombreador s3.

Depois que essa assinatura raiz é associada, tabelas de descritor, CBV raiz e constantes podem ser atribuídas ao espaço de parâmetro [0..6]. Por exemplo, tabelas de descritor (intervalos em um heap de descritor) podem ser associadas a cada um dos parâmetros raiz [1] e [3..6].

CD3DX12_DESCRIPTOR_RANGE1 DescRange[6];

DescRange[0].Init(D3D12_DESCRIPTOR_RANGE_SRV,6,2); // t2-t7
DescRange[1].Init(D3D12_DESCRIPTOR_RANGE_UAV,4,0); // u0-u3
DescRange[2].Init(D3D12_DESCRIPTOR_RANGE_SAMPLER,2,0); // s0-s1
DescRange[3].Init(D3D12_DESCRIPTOR_RANGE_SRV,-1,8, 0,
                  D3D12_DESCRIPTOR_RANGE_FLAG_DESCRIPTORS_VOLATILE); // t8-unbounded
DescRange[4].Init(D3D12_DESCRIPTOR_RANGE_SRV,-1,0,1,
                  D3D12_DESCRIPTOR_RANGE_FLAG_DESCRIPTORS_VOLATILE); 
                                                            // (t0,space1)-unbounded
DescRange[5].Init(D3D12_DESCRIPTOR_RANGE_CBV,1,1,
                  D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC); // b1

CD3DX12_ROOT_PARAMETER1 RP[7];

RP[0].InitAsConstants(3,2); // 3 constants at b2
RP[1].InitAsDescriptorTable(2,&DescRange[0]); // 2 ranges t2-t7 and u0-u3
RP[2].InitAsConstantBufferView(0, 0, 
                               D3D12_ROOT_DESCRIPTOR_FLAG_DATA_STATIC); // b0
RP[3].InitAsDescriptorTable(1,&DescRange[2]); // s0-s1
RP[4].InitAsDescriptorTable(1,&DescRange[3]); // t8-unbounded
RP[5].InitAsDescriptorTable(1,&DescRange[4]); // (t0,space1)-unbounded
RP[6].InitAsDescriptorTable(1,&DescRange[5]); // b1

CD3DX12_STATIC_SAMPLER StaticSamplers[1];
StaticSamplers[0].Init(3, D3D12_FILTER_ANISOTROPIC); // s3
CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC RootSig(7,RP,1,StaticSamplers);
ID3DBlob* pSerializedRootSig;
CheckHR(D3D12SerializeVersionedRootSignature(&RootSig,pSerializedRootSig)); 

ID3D12RootSignature* pRootSignature;
hr = CheckHR(pDevice->CreateRootSignature(
    pSerializedRootSig->GetBufferPointer(),pSerializedRootSig->GetBufferSize(),
    __uuidof(ID3D12RootSignature),
    &pRootSignature));

O código a seguir ilustra como a assinatura raiz acima pode ser usada em uma lista de comandos gráficos.

InitializeMyDescriptorHeapContentsAheadOfTime(); // for simplicity of the 
                                                 // example
CreatePipelineStatesAhreadOfTime(pRootSignature); // The root signature is passed into 
                                     // shader / pipeline state creation
...

ID3D12DescriptorHeap* pHeaps[2] = {pCommonHeap, pSamplerHeap};
pGraphicsCommandList->SetDescriptorHeaps(2,pHeaps);
pGraphicsCommandList->SetGraphicsRootSignature(pRootSignature);
pGraphicsCommandList->SetGraphicsRootDescriptorTable(
                        6,heapOffsetForMoreData,DescRange[5].NumDescriptors);
pGraphicsCommandList->SetGraphicsRootDescriptorTable(5,heapOffsetForMisc,5000); 
pGraphicsCommandList->SetGraphicsRootDescriptorTable(4,heapOffsetForTerrain,20000);
pGraphicsCommandList->SetGraphicsRootDescriptorTable(
                        3,heapOffsetForSamplers,DescRange[2].NumDescriptors);
pGraphicsCommandList->SetComputeRootConstantBufferView(2,pDynamicCBHeap,&CBVDesc);

MY_PER_DRAW_STUFF stuff;
InitMyPerDrawStuff(&stuff);
pGraphicsCommandList->SetGraphicsRoot32BitConstants(
                        0,RTSlot[0].Constants.Num32BitValues,&stuff,0);

SetMyRTVAndOtherMiscBindings();

for(UINT i = 0; i < numObjects; i++)
{
    pGraphicsCommandList->SetPipelineState(PSO[i]);
    pGraphicsCommandList->SetGraphicsRootDescriptorTable(
                    1,heapOffsetForFooAndBar[i],DescRange[1].NumDescriptors);
    pGraphicsCommandList->SetGraphicsRoot32BitConstant(0,i,drawIDOffset);
    SetMyIndexBuffers(i);
    pGraphicsCommandList->DrawIndexedInstanced(...);
}

Assinaturas raiz

Como especificar assinaturas raiz no HLSL

Como usar uma assinatura raiz