Lista de conteúdo
- Introdução
- Sincronização da GPU no XBOX Series X|S
- Barreiras em detalhes
- Configuração
- Diferenças em relação à especificação do PC
- Exemplos de Barreiras Aprimoradas
- Compatibilidade com as Barreiras de Recursos herdadas
- Suporte e validação do PIX
Introdução
As Barreiras Aprimoradas (Enhanced Barriers, EB) são um novo conjunto de APIs do D3D12 projetado para resolver alguns dos problemas introduzidos pela API herdada de Barreiras de Recursos (Resource Barriers), como conceitos confusos de promoção e decaimento de estado de recursos, problemas de design com as Barreiras de Aliasing, operações de flush excessivas etc., além de oferecer aos desenvolvedores controle independente sobre a sincronização do trabalho da GPU. Elas oferecem um tipo de barreira mais expressivo do que era possível com as Barreiras de Recursos herdadas. Os recursos das Barreiras Aprimoradas incluem:
- Redução da latência de sincronização e de flushes de cache excessivos.
- Nenhuma regra misteriosa de promoção e decaimento.
- Aliasing de recursos rápido e flexível.
- Descarte durante a transição da barreira.
- Leitura/gravação simultânea e autocópia.
Sincronização da GPU no XBOX Series X|S
Antes de prosseguir com as Barreiras Aprimoradas, é necessária uma introdução à sincronização no XBOX Series X|S. É importante entender os conceitos e mecanismos explicados nessa seção, pois eles são os blocos de construção das Barreiras. Sincronização da GPU no XBOX Series X/S Agora podemos nos aprofundar nas Barreiras Aprimoradas e explicar como elas funcionam em mais detalhes.
Barreiras em detalhes
As Barreiras Aprimoradas introduzem três conceitos úteis para decidir como fazer a transição de qualquer recurso (ou memória). São eles: Sync, Access e Layout. De certa forma, eles substituem o conceito anterior de Resource State usado nas Barreiras de Recursos. No entanto, eles não são equivalentes, pois sempre é possível expressar um estado de recurso como uma combinação de Sync, Access e Layout, mas nem sempre é possível expressar uma combinação de Sync, Access e Layout como um estado de recurso. A nova API é mais expressiva. Sync (Before-After) é usado para informar ao driver qual estágio do pipeline precisamos aguardar até a conclusão e qual mecanismo deve ser impedido de executar até terminarmos as operações da barreira.
- A tabela a seguir mostra como alguns sinalizadores de sync se traduzem em requisitos para que estágios sejam drenados ou bloqueados (consulte a seção de pipelining). Vale observar que alguns sinalizadores de sync de Barreira se referem a um estágio amplo (como cópia), mas, internamente e com base nos dados fornecidos por access e layout, são mapeados para estágios específicos (origem da cópia/destino da cópia), gerando requisitos de drenagem/bloqueio mais adequados. Além disso, a tabela abaixo considera o caso de texturas (para Buffer, alguns estágios seriam diferentes).
- Esses requisitos são posteriormente analisados e passados, junto com outras primitivas, para as funções SyncBottomOfPipe/SyncTopOfPipe para determinar as ações finais de sincronização (por exemplo, D3D12XBOX_SYNC_FLAG_PS_PARTIAL_FLUSH ou D3D12XBOX_SYNC_FLAG_IDLE). Esses sinalizadores não devem ser confundidos com os sinalizadores D3D12_BARRIER_SYNC, que são usados nas chamadas de Barreiras Aprimoradas.
- Esses requisitos não conhecem os mecanismos usados em última instância para atendê-los, então eles indicam explicitamente qual componente de hardware precisa ser drenado/bloqueado. Componentes posteriores podem perceber, por exemplo, que várias drenagens podem ser realizadas por um único evento EoP.
- No contexto das Barreiras, é útil saber quais recursos são mapeados para quais caches. Por exemplo, se tivermos um AccessBefore de D3D12_ACCESS_RENDER_TARGET e um access after de D3D12_ACCESS_SHADER_RESOURCE, queremos saber quais caches invalidar e/ou liberar (se necessário). A tabela a seguir mostra alguns tipos de recursos usados com frequência e qual memória eles utilizam.
- Os tipos de recurso não serão o único fator determinante para quais caches o recurso usa. O tipo de operação (escalar vs. vetorial) também importa, e isso é algo que o compilador decide. Uma busca em um buffer de constantes, por exemplo, pode passar pelo caminho $K-L2 se for uma operação escalar (SMEM), ou pelo caminho L0-L1-L2 se for uma operação vetorial (VMEM).
Layout (before-after) descreve o layout de memória do recurso. Ele determinará se os recursos precisarão ser descompactados/compactados na operação da barreira, bem como se a inicialização de metadados é necessária (para rendertargets e depthstencils). Por exemplo, um rendertarget com DCC habilitado pode precisar executar um DCC-Eliminate ao fazer a transição para SRV, passando assim por uma descompactação.
- Determinados sinalizadores de recurso e de barreira alteram os requisitos resultantes de compactação/descompactação:
- O driver mantém duas tabelas que mapeiam o tipo de recurso para operações de compactação/descompactação. Uma dessas tabelas é para recursos criados com D3D12XBOX_RESOURCE_FLAG_FORCE_TEXTURE_COMPATIBILITY, enquanto a outra é para recursos não TC.
- As EB introduzem o D3D12_TEXTURE_BARRIER_FLAG_DISCARD que, quando passado como argumento para o método Barrier, força a inicialização de metadados. Antes das EB, isso podia ser feito com uma operação de limpeza executada antes do primeiro uso do recurso.
- Rendertargets e depthstencils precisam ser criados com D3D12_RESOURCE_FLAGS_ALLOW_RENDER_TARGET e D3D12_RESOURCE_FLAGS_ALLOW_DEPTH_STENCIL, respectivamente, para a alocação correta das superfícies de metadados.
- Se você quiser usar DCC, o recurso precisa ser criado com o sinalizador D3D12XBOX_RESOURCE_FLAG_ALLOW_DCC. O DCC implicará operações de descompactação ao fazer a transição de um estado de gravação para um estado de leitura, mas não quando o recurso também for criado com D3D12XBOX_RESOURCE_FLAG_FORCE_TEXTURE_COMPATIBILITY.
- A tabela a seguir, que mapeia sinalizadores de layout para sinalizadores de estado de compactação, ajuda a entender melhor como as transições de entrada e saída podem causar compactações/descompactações: por exemplo, passar de rendertarget (rt) para shader resource (sr) em recursos não TC requer uma descompactação, pois rt tem vários sinalizadores de compactação, enquanto sr está marcado como uncompressedAll. Mas a mesma transição para um recurso TC não acionaria uma descompactação, já que sr tem um superconjunto dos sinalizadores de estado de compactação de rt.
Definir before e after para layout, access e sync nos dá grande flexibilidade para fazer coisas que não conseguíamos ao usar ResourceBarriers (exceto com barreiras divididas, falaremos mais sobre isso adiante), além de melhorar os casos em que as Barreiras de Recursos podiam resultar em Partial Flushes desnecessários (barreiras UAV) e, no pior caso, levar a corrupção de memória (barreiras de aliasing, ao tentar uma descompactação sem ter certeza do layout atual da memória). Isso também corresponde melhor à forma como a sincronização funciona na GPU, oferecendo um controle mais refinado ao desenvolvedor.
A especificação também define três tipos de barreiras:
- Barreiras globais:
- Aplicam-se a toda a memória. A definição não recebe um ponteiro de recurso.
- Nenhuma informação de layout é passada como argumento.
- Barreiras de buffer
- Como o nome indica, as Barreiras de buffer são usadas para fazer a transição de recursos de buffer.
- Nenhum layout ou sub-recurso é especificado como parâmetro (já que os buffers não usam nenhum dos dois).
- Barreiras de textura
- Transição para recursos de textura (rendertargets, depthstencils, texturas SRV, texturas UAV etc.).
- O layout é obrigatório, portanto essa barreira pode acionar descompactações e compactações.
- É possível especificar um sub-recurso específico ou um intervalo.
- Para sub-recursos, há limitações em termos de descompactação, pois não é possível descompactar apenas parte de um recurso.
- LayoutBefore, LayourAfter: layout de memória antes e depois da barreira. Determinará o que será compactado/descompactado. Usado somente por D3D12_TEXTURE_BARRIER.
- SyncBefore, SyncAfter: estágio a aguardar até ser drenado e estágio a bloquear antes de prosseguir.
- AccessBefore, AccessAfter: informa de quais caches deixaremos de fazer uso e quais passaremos a usar na transição.
- Subresource range — Permite definir um intervalo de sub-recursos dos quais a barreira fará a transição. Usado somente por D3D12_TEXTURE_BARRIER.
- Resource — O proprietário da memória da qual estamos fazendo a transição. Usado por D3D12_TEXTURE_BARRIER e D3D12_BUFFER_BARRIER.
- Texture Barrier flags — Os valores podem ser D3D12_TEXTURE_BARRIER_FLAG_NONE e D3D12_TEXTURE_BARRIER_FLAG_DISCARD. Usado somente por D3D12_TEXTURE_BARRIER.
- Usamos o sinalizador de descarte ao ativar um recurso, para que seus metadados sejam inicializados corretamente.
- O uso desse sinalizador também permite agrupar a inicialização de recursos. As limpezas podem se sobrepor, e as operações de sync da barreira são agrupadas.
Configuração
Para usar as barreiras aprimoradas, você precisa do seguinte:
- GDK de março de 2023 ou mais recente.
- ID3D12GraphicsCommandList7: necessário para chamar o método Barrier. Recebe uma struct D3D12_BARRIER_GROUP e o número do grupo.
- De acordo com a especificação, a interoperabilidade entre as APIs de Barreiras aprimoradas e os D3D12_RESOURCE_STATES herdados é compatível. No entanto, sub-recursos em qualquer estado herdado diferente de D3D12_RESOURCE_STATE_COMMON precisam passar por uma transição para D3D12_RESOURCE_STATE_COMMON antes de serem referenciados com uma Barreira aprimorada.
- Crie um arquivo vazio na pasta Loose (diretório de implantação) e nomeie-o como D3D__DisableLegacyResourceBarrierTranslation.
- Implante o jogo e verifique se o arquivo foi implantado no console.
- Observação: ao executar nesse modo, o driver não oferece suporte à combinação de barreiras herdadas com barreiras aprimoradas.
Diferenças em relação à especificação do PC
- No XBOX, o flush no limite de ExecuteCommandList não é emitido, portanto não há suporte para acesso sem barreiras.
- Isso se deve ao desempenho, pois um flush completo da GPU é caro.
- Acesso sem barreiras na especificação
- Sub-recursos. O PC rastreia metadados por sub-recurso, enquanto nós não. Isso significa que o estado de compactação por sub-recurso não pode ser rastreado. Por isso, alterações de layout que envolvem compactação ou descompactação terão resultados diferentes dependendo de o intervalo de recursos abranger (ou não) o recurso inteiro.
- Se um recurso em um layout compactado tiver um ou mais (mas não todos) sub-recursos em transição para um layout não compactado, o recurso inteiro será descompactado.
- Se um recurso em um layout não compactado tiver um ou mais (mas não todos) sub-recursos em transição para um layout compactado, o recurso inteiro permanecerá não compactado.
- Oferecemos suporte a estados de compactação independentes para depth/stencil, o que significa que podemos ter depth compactado e stencil não compactado ou vice-versa.
- Barreiras divididas funcionam, mas não são implementadas como barreiras divididas no driver. Em vez disso, elas são convertidas em barreiras imediatas. Fazemos essa conversão porque as barreiras divididas, conforme definidas pelo D3D12, impõem um custo de memória por recurso e são desnecessariamente ineficientes no nosso hardware. No entanto, a conversão para barreiras imediatas não é tão eficiente quanto poderia ser.
- Isso ocorre porque o driver não recebe informações completas sobre o sync before e after. A conversão para barreira imediata acontece no lado próximo da barreira (o lado distante é ignorado), e por isso o driver precisa ser pessimista e bloquear tanto o CE quanto o PFP, o que pode tornar as barreiras divididas mais lentas do que as barreiras nativamente imediatas.
- Além disso, historicamente, pouquíssimas pessoas usam as barreiras divididas no modelo herdado.
- Uma forma de resolver o custo de memória por recurso no futuro pode ser permitir que os próprios desenvolvedores forneçam essa memória.
- Há planos futuros para revisar a implementação das barreiras divididas.
- Uma recomendação é que os usuários implementem suas próprias cópias de textura em Compute Shaders. Eles precisarão gerenciar as dependências, é claro, mas isso permitiria um trabalho não serializado e mais eficiente.
- Um motivo pelo qual as cópias de textura gerenciadas pelo driver podem ter desempenho inferior é que elas podem usar os caches do CB/DB. Isso gera a necessidade de fazer WB extra e invalidar os caches do CB e do DB ao entrar ou sair de cópias de texturas.
Exemplos de Barreiras Aprimoradas
Embora melhorias automáticas de desempenho em tempo de execução possam ser obtidas pela capacidade do driver de traduzir chamadas de Barreiras de Recursos herdadas em equivalentes de Barreiras Aprimoradas, economias adicionais podem ser obtidas usando as Barreiras Aprimoradas diretamente. A expressividade da API oferece oportunidades para reduzir latências de sincronização e eliminar flushes desnecessários. Os exemplos de código a seguir mostram como expressar chamadas de Barreiras de Recursos herdadas com a API de Barreiras Aprimoradas para aproveitar esses benefícios adicionais.
- Barreiras de Recursos de transição
- Barreiras de Recursos de acesso não ordenado
- Autocópia
- Barreiras de Recursos de aliasing
Barreiras de Recursos de transição
Os recursos de memória em aplicativos gráficos são frequentemente acessados em contextos diferentes, seja como destinos de renderização, exibições de recursos de sombreador, exibições de buffer de vértices ou em outros tipos de estados. Isso gera a necessidade frequente de barreiras de recursos de transição para garantir a sincronização correta e a coerência do cache. No entanto, em alguns casos, as barreiras de recursos de transição podem introduzir lentidões de desempenho desnecessárias, como flush excessivo do pipeline. Considere o seguinte trecho de código de barreira de recursos herdada, que mostra o estado de um recurso fazendo a transição de uma exibição de recurso de sombreador (SRV) para uma exibição de destino de renderização (RTV):


Barreiras de Recursos de acesso não ordenado
As Barreiras de Recursos herdadas criadas para sincronizar leituras e gravações em uma exibição de acesso não ordenado (UAV) também são vítimas de flushes supérfluos do pipeline da GPU, por motivos relacionados ao driver semelhantes aos que existem nas barreiras de recursos de transição herdadas. Considere um cenário em que texturas com mipmaps são geradas a partir de um compute shader, todas gravadas e lidas a partir de uma UAV de textura, e o seguinte código de barreira de recursos de acesso não ordenado é usado para evitar riscos:

Autocópia
Anteriormente, não era possível gravar e ler simultaneamente um sub-recurso, pois o design das Barreiras de Recursos herdadas não permite combinar bits somente leitura e de gravação. Isso significava que mover dados em um recurso entre duas regiões não sobrepostas exigiria a criação de uma textura temporária, mais barreiras de transição para alternar entre D3D12_RESOURCE_STATE_COPY_SOURCE e D3D12_RESOURCE_STATE_COPY_DEST e mais operações de cópia de sub-recursos. Isso é ilustrado no exemplo de código abaixo, que mostra o trabalho necessário para copiar o quarto superior esquerdo de uma textura para o seu quarto inferior direito.

Barreiras de Recursos de aliasing
Como visto no código abaixo, extraído da documentação pública do D3D12 no MSDN, o aliasing de recursos posicionados na API de Barreiras de Recursos herdadas historicamente envolvia apenas especificar os dois recursos que compartilhavam a memória do heap. A API de Barreiras Aprimoradas pode ajudar a evitar a necessidade de bloquear todo o trabalho da GPU e fazer flush de todas as gravações, além de fornecer estados de layout explícitos para garantir que o driver não descompacte memória não compactada (consulte este link para obter mais informações sobre esse último problema em “Aliasing Barriers Are Very Expensive”).Compatibilidade com as Barreiras de Recursos herdadas
A API de barreiras anterior (Barreiras de Recursos) ainda é compatível, mas o driver as traduz internamente em Barreiras Aprimoradas. Vários testes foram executados em capturas do PIX para ver o impacto da tradução (em comparação com o uso da implementação anterior). Os resultados mostraram que a tradução para EB oferece ganhos significativos.

Vale observar que os ganhos dependerão muito da forma como o mecanismo gráfico é construído em termos de sincronização e posicionamento das barreiras.
Exemplos em que a tradução apresentou melhorias em relação às Barreiras de Recursos herdadas:
Exemplo 1 - Geração de partial flush na barreira UAV
Barreira de Recursos herdada

Exemplo 2 - Aliasing
Barreira de Recursos herdada

Tudo isso mostra os ganhos que os desenvolvedores podem obter simplesmente por terem as Barreiras Aprimoradas, sem alterações no código. Para ver os ganhos potenciais ao atualizar para a nova API de Barreiras, consulte a seção Exemplos de Barreiras Aprimoradas.
Suporte e validação do PIX
O PIX oferece suporte tanto às barreiras aprimoradas quanto às Barreiras de Recursos herdadas traduzidas. Para as Barreiras Aprimoradas, será criado um evento do PIX chamado Barrier, que contém os pacotes de CP gerados.





Validação
A validação estará disponível tanto para as barreiras aprimoradas quanto para as barreiras de recursos herdadas que passam pela camada de tradução. A validação que você obtém dependerá de qual API você está usando:- Para as Barreiras Aprimoradas, a paridade de validação se refere aos cenários cobertos. As Barreiras Aprimoradas são diferentes das Barreiras de Recursos, portanto a validação não replicará exatamente o que fazia para as herdadas, mas garantirá que todas as propriedades before e after estejam corretas para a transição. Por exemplo:
- Garantir que os recursos estejam no layout correto ao iniciar uma transição.
- Validar a correção da API (mais detalhes abaixo).
- Para as Barreiras de Recursos herdadas (traduzidas), a validação oferece paridade com a validação atual das Barreiras de Recursos.
- O dispositivo subjacente oferece suporte a barreiras aprimoradas
- Não é uma lista de comandos de pacote (bundle)
- Avisa se alguma contagem (de grupos ou de barreiras por grupo) for zero
- Verifica se os sub-recursos da barreira de textura correspondem aos limites de sub-recursos da textura
- O tipo de barreira corresponde ao tipo de recurso
- LayoutBefore e AccessBefore correspondem ao layout e ao acesso conhecidos ou presumidos do recurso.
- SyncBefore, AccessBefore e LayoutBefore são compatíveis.
- SyncAfter, AccessAfter e LayoutAfter são compatíveis.
- As barreiras de fim de divisão correspondem às barreiras de início de divisão anteriores.
- As barreiras divididas de Buffer e de D3D12_BARRIER_LAYOUT_COMMON não ultrapassam os limites de ExecuteCommandLists.
- Provavelmente um aviso em vez de um erro, devido às permissões herdadas
