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 ( class
nã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 SpriteBatch
faz 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 SetData
e GetData
sobre 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 Effect
s - 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 SetData
em 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 GetData
qualquer coisa dinâmica - particularmente a RenderTarget2D
que a GPU possa estar escrevendo. Isso é extremamente ruim para o desempenho - não faça isso.
O outro caso está chamando SetData
buffers 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 :)