Fazendo a portabilidade do Direct3D 11 para o Direct3D 12

Esta seção fornece algumas diretrizes sobre a portabilidade de um mecanismo gráfico Direct3D 11 personalizado para o Direct3D 12.

Criação de dispositivo

O Direct3D 11 e o Direct3D 12 compartilham um padrão de criação de dispositivo semelhante. Os drivers existentes do Direct3D 12 são todos D3D_FEATURE_LEVEL_11_0 ou melhores, portanto, você pode ignorar os níveis de recursos mais antigos e as limitações associadas.

Lembre-se também de que, com o Direct3D 12, você deve enumerar explicitamente as informações do dispositivo usando interfaces DXGI. No Direct3D 11, você pode encadear de volta para o dispositivo DXGI do dispositivo Direct3D e isso não tem suporte para Direct3D 12.

A criação de um dispositivo de software WARP no Direct3D 12 é feita fornecendo um adaptador explícito obtido de IDXGIFactory4::EnumWarpAdapter. O dispositivo WARP para Direct3D 12 está disponível apenas em sistemas com o recurso opcional Ferramentas de Gráficos habilitado.

Observação

Não há equivalente a D3D11CreateDeviceAndSwapChain. Mesmo com o Direct3D 11, desencorajamos o uso dessa função, pois geralmente é melhor criar o dispositivo e trocar a cadeia em etapas distintas.

Recursos confirmados

Objetos criados com as interfaces a seguir no Direct3D 11, traduzem para o que são chamados de "recursos confirmados" no Direct3D 12. Um recurso confirmado é um recurso que tem espaço de endereço virtual e páginas físicas associadas a ele. Esse é um conceito do Modelo de Memória do WDD2 (Microsoft Windows Device Driver 2), no qual o Direct3D 12 se baseia.

Recursos do Direct3D 11:

No Direct3D 12, todos eles são representados por ID3D12Resource e ID3D12Device::CreateCommittedResource.

Recursos reservados

Os recursos reservados são recursos em que apenas o espaço de endereço virtual foi alocado, a memória física não é alocada até que haja uma chamada para ID3D12Device::CreateHeap. Esse é essencialmente o mesmo conceito que recursos lado a lado no Direct3D 11.

Os sinalizadores (D3D11_RESOURCE_MISC_FLAG) usados no Direct3D 11 para configurar recursos lado a lado e mapeá-los para a memória física.

  • D3D11_RESOURCE_MISC_TILED
  • D3D11_RESOURCE_MISC_TILE_POOL

Carregando dados

No Direct3D 11, há a aparência de um único linha do tempo (chamadas após uma sequência, como dados inicializados com D3D11_SUBRESOURCE_DATA, em seguida, uma chamada é feita para ID3D11DeviceContext::UpdateSubresource e, em seguida, uma chamada para ID3D11DeviceContext::Map). O número de cópias criadas dos dados não é óbvio para um desenvolvedor do Direct3D 11.

No Direct3D 12, há duas linhas do tempo, o linha do tempo de GPU (configurado por chamadas para CopyTextureRegion e CopyBufferRegion da memória maplicaível) e o linha do tempo de CPU (determinado por chamadas para Map). As funções auxiliares são fornecidas (no arquivo d3dx12.h) chamadas Updatesubresources que usam um linha do tempo compartilhado. Há várias variações dessa função auxiliar, uma que usa ID3D12Device::GetCopyableFootprints, outra que usa um mecanismo de alocação de heap e outra que usa um mecanismo de alocação de pilha. Essas funções auxiliares copiam recursos para a GPU e a CPU por meio de uma área de preparo intermediária da memória.

Normalmente, a GPU e a CPU têm sua própria cópia de um recurso vinculada aos seus próprios linha do tempo. A abordagem de linha do tempo compartilhada também mantém duas cópias.

Sombreadores e objetos de sombreador

No Direct3D 11, há muita criação de objetos de sombreador e estado e definição do estado desses objetos, usando os métodos de criação ID3D11Device e os métodos de conjunto ID3D11DeviceContext . Normalmente, um grande número de chamadas são feitas a esses métodos, que são combinados no momento do desenho pelo driver para definir o estado correto do pipeline.

No Direct3D 12, essa configuração do estado do pipeline foi combinada em um único objeto (CreateComputePipelineState para um mecanismo de computação e CreateGraphicsPipelineState para um mecanismo gráfico), que é anexado a uma lista de comandos antes da chamada de desenho com uma chamada para SetPipelineState.

Essas chamadas substituem todas as chamadas individuais para definir sombreadores, layout de entrada, estado de combinação, estado do rasterizador, estado do estêncil de profundidade e assim por diante, no Direct3D 11

  • Métodos do dispositivo 11: CreateInputLayout, CreateXShader, CreateDepthStencilStatee CreateRasterizerState.
  • Métodos de Contexto de Dispositivo 11: IASetInputLayout, xxSetShader, OMSetBlendState, OMSetDepthStencilStatee RSSetState.

Embora o Direct3D 12 possa dar suporte a blobs de sombreador compilados mais antigos, os sombreadores devem ser criados usando o Modelo de Sombreador 5.1 com as APIs FXC/D3DCompile ou usando o Modelo de Sombreador 6 usando o compilador DXC DXIL. Você deve validar o suporte ao Modelo de Sombreador 6 com CheckFeatureSupport e D3D12_FEATURE_SHADER_MODEL.

Enviando trabalho para a GPU

no Direct3D 11, há pouco controle sobre como o trabalho é enviado, ele é tratado em grande parte pelo driver, embora algum controle seja habilitado por meio das chamadas ID3D11DeviceContext::Flush e IDXGISwapChain1::P resent1 .

No Direct3D 12, o envio de trabalho é muito explícito e controlado pelo aplicativo. O constructo principal para enviar trabalho é o ID3D12GraphicsCommandList, que é usado para registrar todos os comandos de aplicativos (e é bastante semelhante no conceito ao contexto adiado ID3D11). O repositório de backup para uma lista de comandos é fornecido pelo ID3D12CommandAllocator, que permite que o aplicativo gerencie a utilização de memória da lista de comandos expondo de fato a memória que o driver direct3D 12 usará para armazenar a lista de comandos.

Por fim, ID3D12CommandQueue é uma fila de primeiro a entrar, que armazena a ordem correta das listas de comandos para envio à GPU. Somente quando uma lista de comandos tiver concluído a execução na GPU, a próxima lista de comandos da fila será enviada pelo driver.

No Direct3D 11, não há nenhum conceito explícito de uma fila de comandos. Na configuração comum do Direct3D 12, a lista de comandos D3D12_COMMAND_LIST_TYPE_DIRECT atualmente aberta para o quadro atual pode ser considerada análoga ao contexto imediato do Direct3D 11. Isso fornece muitas das mesmas funções.

D3D11DeviceContext ID3D12GraphicsCommand List
ClearDepthStencilView ClearDepthStencilView
ClearRenderTargetView ClearRenderTargetView
ClearUnorderedAccess* ClearUnorderedAccess*
Draw, DrawInstanced DrawInstanced
DrawIndexed, DrawIndexedInstanced DrawIndexedInstanced
Dispatch Dispatch
IASetInputLayout, xxSetShader etc. SetPipelineState
OMSetBlendState OMSetBlendFactor
OMSetDepthStencilState OMSetStencilRef
OMSetRenderTargets OMSetRenderTargets
RSSetViewports RSSetViewports
RSSetScissorRects RSSetScissorRects
IASetPrimitiveTopology IASetPrimitiveTopology
IASetVertexBuffers IASetVertexBuffers
IASetIndexBuffer IASetIndexBuffer
ResolveSubresource ResolveSubresource
CopySubresourceRegion CopyBufferRegion
UpdateSubresource CopyTextureRegion
CopyResource CopyResource

Observação

Uma lista de comandos criada com D3D12_COMMAND_LIST_TYPE_BUNDLE é simliar para um contexto adiado. O Direct3D 12 também dá suporte à abiilty para acessar alguns recursos de um contexto imediato simultâneo à renderização por meio de tipos de lista de comandos D3D12_COMMAND_LIST_TYPE_COPY e D3D12_COMMAND_LIST_TYPE_COMPUTE .

Sincronização de CPU/GPU

No Direct3D 11, a sincronização de CPU/GPU era em grande parte automática e não havia necessidade de o aplicativo manter o status da memória física.

no Direct3D 12, o aplicativo deve gerenciar as duas linhas do tempo (CPU e GPU) explicitamente. Isso requer que as informações precisem ser mantidas, pelo aplicativo, sobre quais recursos são exigidos pela GPU e por quanto tempo. Isso também significa que o aplicativo é responsável por garantir que o conteúdo dos recursos (recursos confirmados, heaps, alocadores de comando, por exemplo) não seja alterado até que a GPU termine de usá-los.

O objeto main para sincronizar as linhas do tempo é o objeto ID3D12Fence. A operação de cercas é bastante simples, ela permite que a GPU sinalize quando tiver concluído uma tarefa. A GPU e a CPU podem sinalizar e podem esperar em cercas.

Normalmente, a abordagem é que, ao enviar uma lista de comandos para execução, um sinal de isolamento é transmitido pela GPU após a conclusão (quando termina de ler os dados), permitindo que a CPU reutilize ou destrua os recursos.

No Direct3D 11, o sinalizador ID3D11DeviceContext::Map D3D11_MAP_WRITE_DISCARD essencialmente tratou cada recurso como uma oferta infinita de memória que o aplicativo poderia gravar (um processo conhecido como "renomeação"). No Direct3D 12 novamente, o processo é explícito: a memória adicional precisa ser alocada e as cercas devem ser usadas para sincronizar as operações. Buffers de anéis (compostos por buffers grandes) podem ser uma boa técnica para isso, consulte o cenário de buffer de anéis no Gerenciamento de Recursos Baseado em Cerca.

usando um buffer de anéis

Associação de Recursos

Exibições no Direct3D 11 (exibições de recursos de sombreador, exibições de destino de renderização e assim por diante) foram amplamente substituídas no Direct3D 12 pelo conceito de um descritor. Os métodos de criação ainda existem no Direct3D 12 (como CreateShaderResourceView e CreateRenderTargetView), que são chamados após a criação do heap do descritor, para gravar os dados no heap. A associação no Direct3D 12 agora é tratada por identificadores de descritor descritos em uma assinatura raiz e enviados usando os métodos SetGraphicsRootDescriptorTable ou SetComputeRootDescriptorTable .

As assinaturas raiz detalham mapeamentos entre o número do slot de assinatura raiz e as tabelas do descritor, em que a tabela de descritores pode conter referências a recursos disponíveis para sombreadores de vértice, sombreadores de pixel e outros sombreadores, como buffers constantes, exibições de recursos de sombreador e exemplores. Essa flexibilidade desconecta o espaço de registro HLSL do espaço de associação de API no Direct3D 12, ao contrário do Direct3D 11, em que há um mapeamento de um para um entre eles.

Uma das implicações desse sistema é que o aplicativo é responsável por renomear tabelas de descritores, o que permite que os desenvolvedores entendam o custo de desempenho de alterar até mesmo um único descritor por chamada de desenho.

Um novo recurso do Direct3D 12 é que um aplicativo pode controlar quais descritores são compartilhados entre quais estágios de sombreador. No Direct3D 11, recursos como UAVs são compartilhados entre todos os estágios do sombreador. Ao habilitar que os descritores sejam desabilitados para determinados estágios de sombreador, os registros usados por descritores que foram desabilitados estão disponíveis para serem usados por descritores habilitados para um estágio de sombreador específico.

A tabela a seguir mostra um exemplo de assinatura raiz.

Slot de parâmetro raiz Entrada de tabela do descritor
0 Intervalo do Descritor do VS b0-b13
1 Intervalo do Descritor do VS t0-t127
2 Intervalo de descritores do VS s0-s16
3 Intervalo do Descritor do PS b0-b13
...
14 Intervalo de descritores DS s0-16
15 Intervalo de descritor compartilhado u0-u63

 

Estado do recurso

No estado do recurso direct3D 11 não é mantido pelo aplicativo, mas pelo driver.

No Direct3D 12 manter o estado do recurso torna-se responsabilidade do aplicativo, para habilitar o paralelismo completo na gravação de listas de comandos: o aplicativo deve lidar com as linhas do tempo de gravação para listas de comandos (que podem ser feitas em paralelo) e as linhas do tempo de execução que devem ser sequenciais.

Uma transição de estado de recurso é tratada pelo método ResourceBarrier . Principalmente, o aplicativo deve informar o driver quando o uso de recursos está sendo alterado. Por exemplo, se um recurso estiver sendo usado como um destino de renderização e, em seguida, ele deve ser usado como entrada para um sombreador de vértice na próxima chamada de desenho, isso pode exigir uma pequena parada na operação de GPU para concluir a operação de destino de renderização antes de manipular o sombreador de vértice.

Esse sistema permite a sincronização de granularidade fina (as paradas de GPU) do pipeline gráfico, bem como liberações de cache e, possivelmente, algumas alterações de layout de memória (como a exibição de destino de renderização para a descompactação da exibição de estêncil de profundidade).

Isso é conhecido como uma barreira de transição. Há outros tipos de barreiras, no Direct3D 11 a ID3D11DeviceContext2::TiledResourceBarrier habilitou a mesma memória física a ser usada por dois recursos lado a lado diferentes. No Direct3D 12, isso é chamado de "barreira de alias". As barreiras de alias podem ser usadas para recursos lado a lado e colocados no Direct3D 12. Além disso, há a barreira UAV. No Direct3D 11, todas as operações de expedição e desenho do UAV precisam ser serializadas, mesmo que essas operações possam ser em pipeline ou funcionar em paralelo. Para o Direct3D 12, essa restrição é removida pela adição de uma barreira UAV. Uma barreira UAV garante que as operações UAV sejam sequenciais, portanto, se uma segunda operação exigir que a primeira seja concluída, a segunda será forçada a aguardar pela adição da barreira. A operação padrão para UAVs é simplesmente que as operações continuarão o mais rápido possível.

Claramente, haverá ganhos de desempenho se uma carga de trabalho puder ser paralelizada.

Swapchains

A cadeia de troca DXGI é a base para cadeias de troca no Direct3D 11 e 12. Há algumas pequenas diferenças, no Direct3D 11, os três tipos de cadeia de troca são SEQUENCIAL, DISCARD e FLIP_SEQUENTIAL. Para o Direct3D 12, há apenas dois tipos: FLIP_SEQUENTIAL e FLIP_DISCARD. Conforme observado acima, você deve criar explicitamente sua cadeia de troca por meio de IDXGIFactory4 ou posterior e usar a mesma interface para qualquer enumeração de adaptador.

No Direct3D 11, há rotação automática de backbuffer: apenas uma exibição de destino de renderização é necessária para o buffer de fundo 0. Na rotação de buffer do Direct3D 12 é explícita, é necessário que haja uma exibição de destino de renderização para cada buffer de fundo. Use o método IDXGISwapChain3::GetCurrentBackBufferIndex para selecionar para qual renderizar. Novamente, essa flexibilidade adicional permite maior paralelização.

Observação

Embora haja várias maneiras de configurar seu aplicativo, geralmente os aplicativos têm um ID3D12CommandAllocator por buffer de cadeia de troca. Isso permite que o aplicativo continue criando um conjunto de comandos para o próximo quadro enquanto a GPU renderiza o anterior.

Renderização de função fixa

No Direct3D 11, havia alguns métodos que simplificavam várias operações de nível superior, como GenerateMips (criando cadeias de mip completas) e DrawAuto (usando a saída de fluxo como entrada de sombreador sem entrada adicional do aplicativo). Esses métodos não estão disponíveis no Direct3D 12, o aplicativo precisa lidar com essas operações criando sombreadores para executá-las.

Probabilidades e términos

A tabela a seguir mostra vários recursos que são semelhantes entre o Direct3D 11 e o 12, mas não são idênticos.

Direct3D 11 Direct3D 12
ID3D11Query ID3D12QueryHeap permite que as consultas sejam agrupadas, reduzindo o custo.
ID3D11Predicate A precação agora está habilitada por ter dados em um buffer totalmente transparente. O objeto Direct3D 11 ID3D11Predicate é substituído por ID3D12Resource::Map, que deve seguir uma chamada para ResolveQueryData e uma operação de sincronização de GPU usando uma cerca para aguardar os dados estarem prontos. Consulte Predication.
Contador oculto UAV/SO O aplicativo é responsável pela alocação e pelo gerenciamento de contadores SO/UAV. Consulte Contadores de Saída de Fluxo e Contadores UAV.
MinLOD dinâmico do recurso (nível mínimo de detalhes) Isso foi movido para o MinLOD estático do descritor SRV.
Draw*Indirect/DispatchIndirect Os métodos indiretos de desenho são todos mesclados em um método ExecuteIndirect .
Os formatos DepthStencil são intercalados Os formatos DepthStencil são planar. Por exemplo, um formato de 24 bits de profundidade, 8 bits de estêncil seriam armazenados no formato 24/8/24/8... etc no Direct3D 11, mas como 24/24/24... seguido por 8/8/8... no Direct3D 12. Observe que cada plano é seu próprio sub-recurso em D3D12 (consulte Sub-recursos).
ResizeTilePool Os recursos reservados podem ser mapeados para vários heaps. Quando um pool de blocos teria sido cultivado em D3D11, um heap adicional pode ser alocado em D3D12.

 

Tutoriais de vídeo de aprendizado avançado do DirectX: Guia de portabilidade do DirectX 11 para o DirectX 12

Introdução ao Direct3D 12

Como trabalhar com o Direct2D, o Direct3D 10 e o Direct3D 11