Partilhar via


Passos de renderização do Direct3D 12

O recurso de passes de renderização é novo para o Windows 10, versão 1809 (10.0; Build 17763) e introduz o conceito de um passo de renderização Direct3D 12. Um passo de renderização consiste em um subconjunto dos comandos que você grava em uma lista de comandos.

Para declarar onde cada passo de renderização começa e termina, aninhar os comandos pertencentes a essa passagem dentro de chamadas para ID3D12GraphicsCommandList4::BeginRenderPass e EndRenderPass. Consequentemente, qualquer lista de comandos contém zero, um ou mais passos de renderização.

Cenários

Os passos de renderização podem melhorar o desempenho do renderizador se ele for baseado em Tile-Based Renderização Adiada (TBDR), entre outras técnicas. Mais especificamente, a técnica ajuda o renderizador a melhorar a eficiência da GPU, reduzindo o tráfego de memória de/para a memória off-chip, permitindo que seu aplicativo identifique melhor os requisitos de pedidos de renderização de recursos e as dependências de dados.

Um driver de vídeo escrito expressamente para aproveitar o recurso de passes de renderização oferece os melhores resultados. Mas as APIs de passes de renderização podem ser executadas mesmo em drivers pré-existentes (embora, não necessariamente, com melhorias de desempenho).

Estes são os cenários nos quais os passes de renderização são projetados para fornecer valor.

Permita que seu aplicativo evite cargas/armazenamentos desnecessários de recursos de/para a memória principal em um Tile-Based arquitetura de renderização adiada (TBDR)

Uma das propostas de valor dos passos de renderização é que ele fornece um local central para indicar as dependências de dados do seu aplicativo para um conjunto de operações de renderização. Essas dependências de dados permitem que o driver de vídeo inspecione esses dados em tempo de ligação/barreira e emita instruções que minimizam as cargas/armazenamentos de recursos de/para a memória principal.

Permita que sua arquitetura TBDR persista oportunisticamente recursos no cache on-chip em passos de renderização (mesmo em listas de comandos separadas)

Observação

Especificamente, esse cenário é limitado aos casos em que você está gravando no(s) mesmo(s) destino(s) de renderização em várias listas de comandos.

Um padrão de renderização comum é que seu aplicativo seja renderizado para o(s) mesmo(s) destino(s) de renderização em várias listas de comandos em série, mesmo que os comandos de renderização sejam gerados em paralelo. Seu uso de passos de renderização neste cenário permite que esses passos sejam combinados de tal forma (uma vez que o aplicativo sabe que retomará a renderização na lista de comandos subsequente imediata) que o driver de vídeo pode evitar uma liberação para a memória principal nos limites da lista de comandos.

Responsabilidades da sua candidatura

Mesmo com o recurso de passes de renderização, nem o tempo de execução do Direct3D 12 nem o driver de vídeo assumem a responsabilidade de deduzir oportunidades para reordenar/evitar cargas e armazenamentos. Para aproveitar corretamente o recurso de passos de renderização, seu aplicativo tem essas responsabilidades.

  • Identificar corretamente as dependências de dados/ordenação para suas operações.
  • Encomende os seus envios de uma forma que minimize as descargas (assim, minimize a sua utilização de _PRESERVE sinalizadores).
  • Faça uso correto das barreiras de recursos e rastreie o estado do recurso.
  • Evite cópias/limpezas desnecessárias. Para ajudar a identificá-los, você pode usar os avisos de desempenho automatizados do PIX no Windowsferramenta .

Usando o recurso de passo de renderização

O que é um render pass?

Um passo de renderização é definido por esses elementos.

  • Um conjunto de ligações de saída que são fixadas para a duração do passo de renderização. Essas associações são para uma ou mais visualizações de destino de renderização (RTVs) e/ou para uma exibição de estêncil de profundidade (DSV).
  • Uma lista de operações de GPU que visam esse conjunto de ligações de saída.
  • Metadados que descrevem as dependências de carga/armazenamento para todas as ligações de saída direcionadas pelo passo de renderização.

Declarar suas ligações de saída

No início de um passo de renderização, você declara ligações ao(s) alvo(s) de renderização e/ou ao buffer de profundidade/estêncil. É opcional vincular ao(s) destino(s) de renderização, e é opcional vincular a um buffer de profundidade/estêncil. Mas você deve ligar a pelo menos um dos dois, e no exemplo de código abaixo nós nos ligamos a ambos.

Você declara essas associações em uma chamada para ID3D12GraphicsCommandList4::BeginRenderPass.

void render_passes(::ID3D12GraphicsCommandList4 * pIGCL4,
    D3D12_CPU_DESCRIPTOR_HANDLE const& rtvCPUDescriptorHandle,
    D3D12_CPU_DESCRIPTOR_HANDLE const& dsvCPUDescriptorHandle)
{
    const float clearColor4[]{ 0.f, 0.f, 0.f, 0.f };
    CD3DX12_CLEAR_VALUE clearValue{ DXGI_FORMAT_R32G32B32_FLOAT, clearColor4 };

    D3D12_RENDER_PASS_BEGINNING_ACCESS renderPassBeginningAccessClear{ D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR, { clearValue } };
    D3D12_RENDER_PASS_ENDING_ACCESS renderPassEndingAccessPreserve{ D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE, {} };
    D3D12_RENDER_PASS_RENDER_TARGET_DESC renderPassRenderTargetDesc{ rtvCPUDescriptorHandle, renderPassBeginningAccessClear, renderPassEndingAccessPreserve };

    D3D12_RENDER_PASS_BEGINNING_ACCESS renderPassBeginningAccessNoAccess{ D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_NO_ACCESS, {} };
    D3D12_RENDER_PASS_ENDING_ACCESS renderPassEndingAccessNoAccess{ D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_NO_ACCESS, {} };
    D3D12_RENDER_PASS_DEPTH_STENCIL_DESC renderPassDepthStencilDesc{ dsvCPUDescriptorHandle, renderPassBeginningAccessNoAccess, renderPassBeginningAccessNoAccess, renderPassEndingAccessNoAccess, renderPassEndingAccessNoAccess };

    pIGCL4->BeginRenderPass(1, &renderPassRenderTargetDesc, &renderPassDepthStencilDesc, D3D12_RENDER_PASS_FLAG_NONE);
    // Record command list.
    pIGCL4->EndRenderPass();
    // Begin/End further render passes and then execute the command list(s).
}

Você define o primeiro campo da estrutura D3D12_RENDER_PASS_RENDER_TARGET_DESC para o identificador do descritor da CPU correspondente a uma ou mais exibições de destino de renderização (RTVs). Da mesma forma, D3D12_RENDER_PASS_DEPTH_STENCIL_DESC contém o identificador do descritor da CPU correspondente a uma visualização de estêncil de profundidade (DSV). Esses identificadores de descritor de CPU são os mesmos que você passaria para ID3D12GraphicsCommandList::OMSetRenderTargets. E, assim como acontece com OMSetRenderTargets, os descritores de CPU são encaixados de seus respetivos heaps (descritor de CPU) no momento da chamada para BeginRenderPass.

Os RTVs e DSV não são herdados no passo de renderização. Pelo contrário, têm de ser definidas. Nem os RTVs e DSV declarados em BeginRenderPass propagados para a lista de comandos. Em vez disso, eles estão em um estado indefinido após o passo de renderização.

Renderizar passes e cargas de trabalho

Você não pode aninhar passos de renderização e não pode ter um passo de renderização em mais de uma lista de comandos (eles devem começar e terminar durante a gravação em uma única lista de comandos). As otimizações projetadas para permitir a geração multi-threaded eficiente de passos de renderização são discutidas na seção Sinalizadores de passagem de renderização, abaixo.

Uma gravação que você faz de dentro de um passo de renderização não é válido para você ler até um passo de renderização subsequente. Isso impede alguns tipos de barreiras dentro do passo de renderização — por exemplo, barreiras de RENDER_TARGET para SHADER_RESOURCE no destino de renderização vinculado no momento. Para obter mais informações, consulte a seção Render passes e barreiras de recursos, abaixo.

A única exceção à restrição de leitura-gravação mencionada acima envolve as leituras implícitas que ocorrem como parte do teste de profundidade e da mistura de destino de renderização. Portanto, essas APIs não são permitidas dentro de um passo de renderização (o tempo de execução principal remove a lista de comandos se qualquer uma delas for chamada durante a gravação).

Renderizar passes e barreiras de recursos

Você não pode ler ou consumir uma gravação que ocorreu dentro do mesmo passo de renderização. Certas barreiras não estão de acordo com essa restrição, por exemplo, de D3D12_RESOURCE_STATE_RENDER_TARGET para *_SHADER_RESOURCE no destino de renderização atualmente vinculado (e a camada de depuração errará nesse sentido). Mas, essa mesma barreira em um destino de renderização que foi escrito fora o passo de renderização atual é conforme, porque as gravações serão concluídas antes do início do passo de renderização atual. Você pode se beneficiar de saber sobre certas otimizações que um driver de vídeo pode fazer a esse respeito. Dada uma carga de trabalho conforme, um driver de vídeo pode mover quaisquer barreiras encontradas em seu passo de renderização para o início do passo de renderização. Lá, eles podem ser aglutinados (e não interferem em nenhuma operação de telha/encadernação). Esta é uma otimização válida desde que todas as suas gravações tenham sido concluídas antes do início do passo de renderização atual.

Aqui está um exemplo mais completo de otimização de driver, que pressupõe que você tenha um mecanismo de renderização que tenha um design de vinculação de recursos no estilo pré-Direct3D 12 — fazendo barreiras sob demanda com base em como os recursos são vinculados. Ao gravar em uma visualização de acesso não ordenado (UAV) no final de um quadro (a ser consumido no quadro seguinte), o mecanismo pode deixar o recurso no estado D3D12_RESOURCE_STATE_UNORDERED_ACCESS na conclusão do quadro. No quadro a seguir, quando o mecanismo for vincular o recurso como uma exibição de recurso de sombreador (SRV), ele descobrirá que o recurso não está no estado correto e emitirá uma barreira de D3D12_RESOURCE_STATE_UNORDERED_ACCESS para D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE. Se essa barreira ocorrer dentro do passo de renderização, então o driver de vídeo é justificado em assumir que todas as gravações já ocorreram fora desse passo de renderização atual e, consequentemente (e aqui é onde entra a otimização) o driver de vídeo pode mover barreira até o início do passo de renderização. Novamente, isso é válido, desde que seu código esteja em conformidade com a restrição de gravação-leitura descrita nesta seção e na última.

Estes são exemplos de barreiras conformes.

  • D3D12_RESOURCE_STATE_UNORDERED_ACCESS para D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT.
  • D3D12_RESOURCE_STATE_COPY_DEST para *_SHADER_RESOURCE.

E estes são exemplos de barreiras não conformes.

  • D3D12_RESOURCE_STATE_RENDER_TARGET a qualquer estado de leitura em RTVs/DSVs atualmente vinculados.
  • D3D12_RESOURCE_STATE_DEPTH_WRITE a qualquer estado de leitura em RTVs/DSVs atualmente vinculados.
  • Qualquer barreira de aliasing.
  • Barreiras de vista de acesso não ordenado (UAV). 

Declaração de acesso ao recurso

Ao BeginRenderPass tempo, além de declarar todos os recursos que estão servindo como RTVs e/ou DSV dentro desse passe, você também deve especificar seu início e fim acesso características. Como você pode ver no exemplo de código na seção Declare suas ligações de saída acima, você faz isso com as estruturas D3D12_RENDER_PASS_RENDER_TARGET_DESC e D3D12_RENDER_PASS_DEPTH_STENCIL_DESC.

Para obter mais detalhes, consulte as estruturas D3D12_RENDER_PASS_BEGINNING_ACCESS e D3D12_RENDER_PASS_ENDING_ACCESS e as enumerações D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE e D3D12_RENDER_PASS_ENDING_ACCESS_TYPE.

Sinalizadores de passagem de renderização

O último parâmetro passado para BeginRenderPass é um sinalizador de passo de renderização (um valor da enumeração D3D12_RENDER_PASS_FLAGS).

enum D3D12_RENDER_PASS_FLAGS
{
    D3D12_RENDER_PASS_FLAG_NONE = 0,
    D3D12_RENDER_PASS_FLAG_ALLOW_UAV_WRITES = 0x1,
    D3D12_RENDER_PASS_FLAG_SUSPENDING_PASS = 0x2,
    D3D12_RENDER_PASS_FLAG_RESUMING_PASS = 0x4
};

UAV grava dentro de um passo de renderização

As gravações de exibição de acesso não ordenado (UAV) são permitidas dentro de um passo de renderização, mas você deve indicar especificamente que emitirá gravações de UAV dentro do passo de renderização, especificando D3D12_RENDER_PASS_FLAG_ALLOW_UAV_WRITES, para que o driver de vídeo possa desativar o mosaico, se necessário.

Os acessos UAV devem seguir a restrição write-read descrita acima (gravações em um passo de renderização não são válidas para leitura até um passo de renderização subsequente). Barreiras de UAV não são permitidas dentro de um passe de renderização.

As ligações UAV (por meio de tabelas raiz ou descritores raiz) são herdadas em passos de renderização e são propagadas a partir de passes de renderização.

Passes de suspensão e passes de retomada

Você pode indicar um passo de renderização inteiro como sendo um passo de suspensão e/ou um passo de retomada. Um par suspending-followed-by-retomang deve ter visualizações/sinalizadores de acesso idênticos entre os passos e pode não ter nenhuma operação de GPU interveniente (por exemplo, draws, dispatches, descartes, clears, copies, update-tile-mappings, write-buffer-immediates, queries, query resolves) entre o passo de renderização de suspensão e o passo de renderização de retomada.

O caso de uso pretendido é a renderização multi-threaded, onde digamos que quatro listas de comandos (cada uma com seus próprios passos de renderização) podem ter como alvo os mesmos destinos de renderização. Quando os passos de renderização são suspensos/retomados em listas de comandos, as listas de comandos devem ser executadas na mesma chamada para ID3D12CommandQueue::ExecuteCommandLists.

Um passo de renderização pode ser retomado e suspenso. No exemplo multi-threaded que acabamos de dar, as listas de comandos 2 e 3 seriam retomadas a partir de 1 e 2, respectivamente. E, ao mesmo tempo, 2 e 3 seriam suspensos para 3 e 4, respectivamente.

Consulta para suporte ao recurso de passos de renderização

Você pode chamar ID3D12Device::CheckFeatureSupport para consultar até que ponto um driver de dispositivo e/ou o hardware suporta eficientemente passagens de renderização.

D3D12_RENDER_PASS_TIER get_render_passes_tier(::ID3D12Device * pIDevice)
{
    D3D12_FEATURE_DATA_D3D12_OPTIONS5 featureSupport{};
    winrt::check_hresult(
        pIDevice->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS5, &featureSupport, sizeof(featureSupport))
    );
    return featureSupport.RenderPassesTier;
}
...
    D3D12_RENDER_PASS_TIER renderPassesTier{ get_render_passes_tier(pIDevice) };

Devido à lógica de mapeamento do tempo de execução, os passes de renderização sempre funcionam. Mas, dependendo do suporte a recursos, eles nem sempre fornecerão um benefício. Você pode usar um código semelhante ao exemplo de código acima para determinar se/quando vale a pena emitir comandos à medida que a renderização passa, e quando definitivamente não é um benefício (ou seja, quando o tempo de execução está apenas mapeando para a superfície da API existente). Realizar essa verificação é particularmente importante se você estiver usando D3D11On12).

Para obter uma descrição das três camadas de suporte, consulte a enumeração D3D12_RENDER_PASS_TIER.