Copiando e acessando dados de recursos

Os sinalizadores de uso indicam como o aplicativo pretende usar os dados do recurso, para colocar os recursos na área de memória com o melhor desempenho possível. Os dados do recurso são copiados entre os recursos para que a CPU ou GPU possa acessá-los sem afetar o desempenho.

Não é necessário pensar nos recursos como sendo criados na memória de vídeo ou na memória do sistema, ou decidir se o runtime deve ou não gerenciar a memória. Com a arquitetura do WDDM (Windows Display Driver Model), os aplicativos criam recursos do Direct3D com diferentes sinalizadores de uso para indicar como o aplicativo pretende usar os dados do recurso. Esse modelo de driver virtualiza a memória usada pelos recursos; É responsabilidade do sistema operacional/driver/gerenciador de memória colocar os recursos na área de memória com melhor desempenho possível, considerando o uso esperado.

O caso padrão é que os recursos estejam disponíveis para a GPU. Há momentos em que os dados do recurso precisam estar disponíveis para a CPU. Copiar dados de recursos para que o processador apropriado possa acessá-los sem afetar o desempenho requer algum conhecimento de como os métodos de API funcionam.

Copiando dados de recursos

Os recursos são criados na memória quando o Direct3D executa uma chamada Create. Eles podem ser criados na memória de vídeo, memória do sistema ou qualquer outro tipo de memória. Como o modelo de driver WDDM virtualiza essa memória, os aplicativos não precisam mais acompanhar em que tipo de recursos de memória são criados.

Idealmente, todos os recursos estariam localizados na memória de vídeo para que a GPU possa ter acesso imediato a eles. No entanto, às vezes é necessário que a CPU leia os dados do recurso ou que a GPU acesse os dados do recurso nos quais a CPU gravou. O Direct3D lida com esses diferentes cenários solicitando que o aplicativo especifique um uso e, em seguida, oferece vários métodos para copiar dados de recursos quando necessário.

Dependendo de como o recurso foi criado, nem sempre é possível acessar diretamente os dados subjacentes. Isso pode significar que os dados do recurso devem ser copiados do recurso de origem para outro recurso que seja acessível pelo processador apropriado. Em termos de Direct3D, os recursos padrão podem ser acessados diretamente pela GPU, os recursos dinâmicos e de preparo podem ser acessados diretamente pela CPU.

Depois que um recurso é criado, seu uso não pode ser alterado. Em vez disso, copie o conteúdo de um recurso para outro recurso que foi criado com um uso diferente. Você copia dados de recursos de um recurso para outro ou copia dados da memória para um recurso.

Existem dois tipos principais de recursos: mapeáveis e não mapeáveis. Os recursos criados com usos dinâmicos ou de preparo são mapeáveis, enquanto os recursos criados com usos padrão ou imutáveis não são mapeáveis.

A cópia de dados entre recursos não mapeáveis é muito rápida porque esse é o caso mais comum e foi otimizado para ter um bom desempenho. Como esses recursos não são diretamente acessíveis pela CPU, eles são otimizados para que a GPU possa manipulá-los rapidamente.

Copiar dados entre recursos mapeáveis é mais problemático porque o desempenho dependerá do uso com o qual o recurso foi criado. Por exemplo, a GPU pode ler um recurso dinâmico rapidamente, mas não pode gravar neles, e a GPU não pode ler ou gravar diretamente em recursos de preparo.

Os aplicativos que desejam copiar dados de um recurso com uso padrão para um recurso com uso de preparo (para permitir que a CPU leia os dados - ou seja, o problema de leitura da GPU) devem fazê-lo com cuidado. Consulte Acesso a dados de recursos, abaixo.

Acessando dados de recursos

O acesso a um recurso requer o mapeamento do recurso; mapeamento significa essencialmente que o aplicativo está tentando dar à CPU acesso à memória. O processo de mapeamento de um recurso para que a CPU possa acessar a memória subjacente pode causar alguns gargalos de desempenho e, por esse motivo, deve-se tomar cuidado sobre como e quando executar essa tarefa.

O desempenho pode ser interrompido se o aplicativo tentar mapear um recurso no momento errado. Se o aplicativo tentar acessar os resultados de uma operação antes que essa operação seja concluída, ocorrerá uma paralisação do pipeline.

Executar uma operação de mapa no momento errado pode causar uma queda severa no desempenho, forçando a GPU e a CPU a sincronizar entre si. Essa sincronização ocorrerá se o aplicativo quiser acessar um recurso antes que a GPU termine de copiá-lo para um recurso que a CPU possa mapear.

Considerações sobre desempenho

É melhor pensar em um PC como uma máquina rodando como uma arquitetura paralela com dois tipos principais de processadores: uma ou mais CPUs e uma ou mais GPUs. Como em qualquer arquitetura paralela, o melhor desempenho é alcançado quando cada processador é agendado com tarefas suficientes para evitar que ele fique ocioso e quando o trabalho de um processador não está esperando o trabalho de outro.

O pior cenário para o paralelismo GPU/CPU é a necessidade de forçar um processador a aguardar os resultados do trabalho feito por outro. O Direct3D remove esse custo tornando os métodos de cópia assíncronos; A cópia não foi necessariamente executada no momento em que o método retorna.

O benefício disso é que o aplicativo não paga o custo de desempenho de realmente copiar os dados até que a CPU acesse os dados, que é quando Map é chamado. Se o método Map for chamado depois que os dados tiverem sido realmente copiados, não ocorrerá perda de desempenho. Por outro lado, se o método Map for chamado antes que os dados tenham sido copiados, ocorrerá uma paralisação do pipeline.

As chamadas assíncronas no Direct3D (que são a grande maioria dos métodos e, especialmente, as chamadas de renderização) são armazenadas no que é chamado de buffer de comando. Esse buffer é interno ao driver gráfico e é usado para chamadas em lote para o hardware subjacente para que a dispendiosa alternância do modo de usuário para o modo kernel no Microsoft Windows ocorra o mais raramente possível.

O buffer de comando é liberado, causando assim uma alternância de modo de usuário/kernel, em uma das quatro situações, que são as seguintes.

  1. Presente é chamado.
  2. Flush é chamado.
  3. O buffer de comando está cheio; seu tamanho é dinâmico e é controlado pelo sistema operacional e pelo driver gráfico.
  4. A CPU requer acesso aos resultados de um comando aguardando para ser executado no buffer de comando.

Das quatro situações acima, a número quatro é a mais crítica para o desempenho. Se o aplicativo emitir uma chamada para copiar um recurso ou sub-recurso, essa chamada será enfileirada no buffer de comandos.

Se o aplicativo tentar mapear o recurso de preparo que foi o destino da chamada de cópia antes que o buffer de comando tenha sido liberado, ocorrerá uma paralisação de pipeline, pois não apenas a chamada do método Copy precisa ser executada, mas todos os outros comandos em buffer no buffer de comando também devem ser executados. Isso fará com que a GPU e a CPU sejam sincronizadas porque a CPU estará aguardando para acessar o recurso de preparo enquanto a GPU está esvaziando o buffer de comando e, finalmente, preenchendo o recurso de que a CPU precisa. Depois que a GPU terminar a cópia, a CPU começará a acessar o recurso de preparo, mas durante esse tempo, a GPU ficará ociosa.

Fazer isso com frequência em tempo de execução degradará gravemente o desempenho. Por esse motivo, o mapeamento de recursos criados com uso padrão deve ser feito com cuidado. O aplicativo precisa aguardar o tempo suficiente para que o buffer de comando seja esvaziado e, portanto, fazer com que todos esses comandos terminem de ser executados antes de tentar mapear o recurso de preparo correspondente.

Quanto tempo o aplicativo deve esperar? Pelo menos dois quadros, pois isso permitirá que o paralelismo entre as CPUs e a GPU seja aproveitado ao máximo. A maneira como a GPU funciona é que, enquanto o aplicativo está processando o quadro N enviando chamadas para o buffer de comando, a GPU está ocupada executando as chamadas do quadro anterior, N-1.

Portanto, se um aplicativo quiser mapear um recurso que se origina na memória de vídeo e copia um recurso no quadro N, essa chamada começará a ser executada no quadro N+1, quando o aplicativo estiver enviando chamadas para o próximo quadro. A cópia deve ser concluída quando o aplicativo estiver processando o quadro N+2.

Frame GPU/CPU Status
N
  • Problemas de CPU renderizam chamadas para o quadro atual.
N+1
  • GPU executando chamadas enviadas da CPU durante o quadro N.
  • Problemas de CPU renderizam chamadas para o quadro atual.
N+2
  • A GPU terminou de executar as chamadas enviadas da CPU durante o quadro N. Resultados prontos.
  • GPU executando chamadas enviadas da CPU durante o quadro N+1.
  • Problemas de CPU renderizam chamadas para o quadro atual.
N+3
  • A GPU terminou de executar as chamadas enviadas da CPU durante o quadro N+1. Resultados prontos.
  • GPU executando chamadas enviadas da CPU durante o quadro N+2.
  • Problemas de CPU renderizam chamadas para o quadro atual.
N+4 ...

 

Recursos