Informações sobre renderização, lotes, cartão gráfico, desempenho etc. + XNA?

12

Eu sei que o título é um pouco vago, mas é difícil descrever o que realmente estou procurando, mas aqui vai.

No que diz respeito à renderização da CPU, o desempenho é fácil de estimar e direto, mas quando se trata da GPU devido à minha falta de informações técnicas, não tenho noção. Estou usando o XNA, então seria bom se a teoria pudesse estar relacionada a isso.

Então, o que eu realmente quero saber é: o que acontece quando e onde (CPU / GPU) quando você executa ações de desenho específicas? O que é um lote? Que influência os efeitos, projeções etc. têm? Os dados persistem na placa gráfica ou são transferidos a cada etapa? Quando se fala em largura de banda, você está falando sobre a largura de banda interna da placa de vídeo ou o pipeline da CPU para a GPU?
Nota: Na verdade, não estou procurando informações sobre como o processo de desenho acontece, esse é o negócio da GPU, estou interessado em toda a sobrecarga que antecede isso.

Eu gostaria de entender o que está acontecendo quando eu faço a ação X, para adaptar minhas arquiteturas e práticas a isso.

Todos os artigos (possivelmente com exemplos de código), informações, links, tutoriais que fornecem mais informações sobre como escrever jogos melhores são muito apreciados. Obrigado :)

Aidiakapi
fonte
2
Embora originalmente fosse XNA, adicionei a tag DirectX, pois essa é a tecnologia subjacente - ela pode ajudá-lo a obter melhores respostas. Verifique também esta resposta, que pode lhe dar um bom ponto de partida.
Andrew Russell
@AndrewRussell Muito obrigado :). Na verdade, eu já li vários artigos sobre o assunto, incluindo esse. Mas não cobriu tudo o que eu gostaria de saber.
Aidiakapi 27/09/12

Respostas:

20

Eu gosto de pensar no desempenho em termos de " limites ". É uma maneira útil de conceituar um sistema interconectado bastante complicado. Quando você tem um problema de desempenho, faz a pergunta: "Que limites estou atingindo?" (Ou: "Estou vinculado à CPU / GPU?")

Você pode dividi-lo em vários níveis. No nível mais alto, você tem a CPU e a GPU. Você pode estar ligado à CPU (GPU ociosa aguardando CPU) ou ligada à GPU (CPU está aguardando na GPU). Aqui está um bom post sobre o assunto.

Você pode dividir ainda mais. No lado da CPU , você pode estar usando todos os seus ciclos nos dados já no cache da CPU. Ou você pode ter memória limitada , deixando a CPU ociosa aguardando a entrada de dados da memória principal ( otimize o layout dos dados ). Você poderia decompô-lo ainda mais.

(Enquanto estou fazendo uma ampla visão geral do desempenho do XNA, vou apontar que uma alocação de um tipo de referência ( classnão struct), embora normalmente barata, pode acionar o coletor de lixo, o que queima muitos ciclos - especialmente no Xbox 360 . Veja aqui para mais detalhes).

No lado da GPU , começarei apontando para este excelente post no blog, com muitos detalhes. Se você deseja um nível insano de detalhes no pipeline, leia esta série de postagens no blog . ( Aqui está um mais simples ).

Para simplificar, alguns dos grandes são: " limite de preenchimento " (quantos pixels você pode gravar no backbuffer - geralmente quanto excesso de excesso você pode ter), " limite de sombreador " (quão complicado pode ser seu sombreador e quantos dados você pode enviar através deles), " limite de busca de textura / largura de banda de textura " (quantos dados de textura você pode acessar).

E agora chegamos ao grande problema - que é o que você realmente está perguntando - onde a CPU e a GPU precisam interagir (por meio de várias APIs e drivers). Vagamente, existe o " limite de lote " e a " largura de banda ". (Note-se que a primeira parte da série mencionei anteriormente entra em extensos detalhes.)

Mas, basicamente, um lote ( como você já sabe ) acontece sempre que você chama uma das GraphicsDevice.Draw*funções (ou parte do XNA, como SpriteBatchfaz isso por você). Como você já leu, sem dúvida, você recebe alguns milhares * por quadro. Este é um limite de CPU - portanto, ele compete com seu outro uso de CPU. É basicamente o driver empacotando tudo sobre o que você disse para desenhar e enviando para a GPU.

E depois há a largura de banda para a GPU. É a quantidade de dados brutos que você pode transferir para lá. Isso inclui todas as informações de estado que acompanham os lotes - tudo, desde a definição do estado de renderização e constantes / parâmetros do sombreador (que inclui coisas como matrizes de mundo / exibição / projeto) até vértices ao usar as DrawUser*funções. Ele também inclui todas as chamadas para SetDatae GetDatasobre texturas, buffers de vértice, etc.

Neste ponto, devo dizer que tudo o que você pode chamar SetData(texturas, vértices e buffers de índice, etc), bem como Effects - permanece na memória da GPU. Ele não é constantemente reenviado para a GPU. Um comando de desenho que referencia esses dados é simplesmente enviado com um ponteiro para esses dados.

(Além disso: você só pode enviar comandos de desenho do encadeamento principal, mas SetDataem qualquer encadeamento.)

XNA complica as coisas um pouco com suas prestam aulas estatais ( BlendState, DepthStencilState, etc). Esses dados de estado são enviados por chamada de empate (em cada lote). Não tenho 100% de certeza, mas tenho a impressão de que ele é enviado preguiçosamente (ele envia apenas o estado que muda). De qualquer forma, as mudanças de estado são baratas ao ponto de graça, em relação ao custo de um lote.

Finalmente, a última coisa a mencionar é o pipeline interno da GPU . Você não deseja forçá-lo a liberar escrevendo dados que ainda precisam ler ou lendo dados que ainda precisam gravar. Uma liberação de pipeline significa que aguarda a conclusão das operações, para que tudo fique em um estado consistente quando os dados forem acessados.

Os dois casos específicos a serem observados são: Chamar GetDataqualquer coisa dinâmica - particularmente a RenderTarget2Dque a GPU possa estar escrevendo. Isso é extremamente ruim para o desempenho - não faça isso.

O outro caso está chamando SetDatabuffers de vértice / índice. Se você precisar fazer isso com frequência, use um DynamicVertexBuffer(também DynamicIndexBuffer). Isso permite que a GPU saiba que mudará com frequência e faça alguma mágica de buffer internamente para evitar a descarga do pipeline.

(Observe também que os buffers dinâmicos são mais rápidos que os DrawUser*métodos - mas precisam ser pré-alocados no tamanho máximo necessário.)

... E isso é tudo o que sei sobre o desempenho do XNA :)

Andrew Russell
fonte
Muito obrigado! Isto é exatamente o que eu estava procurando e esperando :).
Aidiakapi 27/09/12
1
Algumas centenas de lotes por quadro parecem excessivamente pessimistas. A regra geral que sempre ouvi é de 2 a 3 mil lotes por quadro. Sabe-se que alguns jogos chegam a 10 mil no PC, mas acho que isso exige muito cuidado.
Nathan Reed
Muito bem. O número "algumas centenas" vem do documento "lote de lote" - que listou "25k lotes / s a ​​100% de uma CPU de 1GHz". Mas esse artigo já faz uma década e os drivers e CPUs melhoraram significativamente desde então. Vou atualizar isso (e meus outros) para ler "alguns milhares".
Andrew Russell