Os programadores devem ter uma boa idéia do custo de certas operações: por exemplo, o custo de uma instrução na CPU, o custo de uma falta de cache L1, L2 ou L3, o custo de um LHS.
Quando se trata de gráficos, percebo que tenho pouca ou nenhuma idéia do que são. Tenho em mente que, se as ordenarmos por custo, as alterações de estado serão algo como:
- Mudança uniforme do sombreador.
- Alteração ativa do buffer de vértice.
- Alteração ativa da unidade de textura.
- Alteração ativa do programa shader.
- Alteração do buffer do quadro ativo.
Mas essa é uma regra muito grosseira, pode até não estar correta, e não tenho idéia de quais são as ordens de magnitude. Se tentarmos colocar unidades, ns, ciclos de relógio ou número de instruções, de quanto estamos falando?
fonte
O custo real de qualquer mudança de estado em particular varia com tantos fatores que uma resposta geral é quase impossível.
Primeiro, toda mudança de estado pode potencialmente ter um custo do lado da CPU e um do lado da GPU. O custo da CPU pode, dependendo do driver e da API de gráficos, ser pago inteiramente no thread principal ou parcialmente em um thread em segundo plano.
Segundo, o custo da GPU pode depender da quantidade de trabalho em voo. As GPUs modernas têm muitos pipelines e adoram trabalhar bastante ao mesmo tempo, e a maior desaceleração que você pode conseguir é estagnar o pipeline para que tudo o que está em andamento no momento se aposente antes que o estado mude. O que pode causar uma paralisação do pipeline? Bem, isso depende da sua GPU!
O que você realmente precisa saber para entender o desempenho aqui é: o que o driver e a GPU precisam fazer para processar sua alteração de estado? Obviamente, isso depende da sua GPU e também dos detalhes que os ISVs geralmente não compartilham publicamente. No entanto, existem alguns princípios gerais .
As GPUs geralmente são divididas em front-end e back-end. O front-end lida com um fluxo de comandos gerados pelo driver, enquanto o back-end faz todo o trabalho real. Como eu disse antes, o back-end adora ter muito trabalho em andamento, mas precisa de algumas informações para armazenar informações sobre esse trabalho (talvez preenchidas pelo front-end). Se você chutar lotes pequenos o suficiente e usar todo o silício para acompanhar o trabalho, o front-end terá que parar mesmo que haja muita potência não utilizada. Portanto, um princípio aqui: quanto mais mudanças de estado (e pequenos empates), maior a probabilidade de você passar fome no back-end da GPU .
Enquanto um empate está realmente sendo processado, você basicamente está executando programas de sombreador, que estão acessando a memória para buscar seus uniformes, seus dados de buffer de vértice, suas texturas, mas também as estruturas de controle que informam às unidades de sombreador onde seus vértices se amortecem e suas texturas são. E a GPU também possui caches na frente desses acessos à memória. Portanto, sempre que você lançar novos uniformes ou novas ligações de textura / buffer na GPU, ele provavelmente sofrerá um erro de cache na primeira vez que tiver que lê-los. Outro princípio: a maioria das alterações de estado causará uma falha no cache da GPU. (Isso é mais significativo quando você gerencia os buffers constantes: se você mantém os buffers constantes iguais entre os sorteios, é mais provável que eles permaneçam em cache na GPU.)
Uma grande parte do custo das alterações de estado dos recursos de sombreador é o lado da CPU. Sempre que você define um novo buffer constante, o driver provavelmente está copiando o conteúdo desse buffer constante em um fluxo de comando para a GPU. Se você definir um único uniforme, é muito provável que o driver o transforme em um grande buffer constante nas suas costas; portanto, é necessário procurar o deslocamento desse uniforme no buffer constante, copiar o valor e marcar o buffer constante. tão sujo que possa ser copiado no fluxo de comandos antes da próxima chamada de empate. Se você vincular uma nova textura ou buffer de vértice, o driver provavelmente estará copiando uma estrutura de controle para esse recurso. Além disso, se você estiver usando uma GPU discreta em um sistema operacional multitarefa, o driver precisará rastrear todos os recursos usados e quando você começar a usá-lo para que o kernel ' s O gerenciador de memória da GPU pode garantir que a memória desse recurso seja residente na VRAM da GPU quando o sorteio ocorrer. Princípio:alterações de estado fazem o driver embaralhar a memória para gerar um fluxo de comandos mínimo para a GPU.
Quando você altera o shader atual, provavelmente está causando uma falha no cache da GPU (eles também têm um cache de instruções!). Em princípio, o trabalho da CPU deve limitar-se a colocar um novo comando no fluxo de comandos dizendo "use o shader". Na realidade, porém, há toda uma confusão de compilação de shader para lidar. Os drivers de GPU geralmente compilam shaders com preguiça, mesmo que você tenha criado o shader com antecedência. Mais relevante para este tópico, no entanto, alguns estados não são suportados nativamente pelo hardware da GPU e, em vez disso, são compilados no programa shader. Um exemplo popular são os formatos de vértices: eles podem ser compilados no sombreador de vértices em vez de serem um estado separado no chip. Portanto, se você usar formatos de vértice que você nunca usou com um shader de vértice específico antes, agora você pode estar pagando muitos custos de CPU para corrigir o shader e copiar o programa para a GPU. Além disso, o driver e o compilador de sombreador podem conspirar para fazer todo tipo de coisa para otimizar a execução do programa de sombreador. Isso pode significar otimizar o layout de memória de seus uniformes e estruturas de controle de recursos para que eles sejam compactados de maneira adequada nos registros adjacentes de memória ou shader. Portanto, quando você altera os sombreadores, pode fazer com que o driver verifique tudo o que você já vinculou ao pipeline e o reembale em um formato totalmente diferente para o novo sombreador e copie-o no fluxo de comando. Princípio: Isso pode significar otimizar o layout de memória de seus uniformes e estruturas de controle de recursos para que eles sejam compactados de maneira adequada nos registros adjacentes de memória ou shader. Portanto, quando você altera os sombreadores, pode fazer com que o driver verifique tudo o que você já vinculou ao pipeline e o reembale em um formato totalmente diferente para o novo sombreador e copie-o no fluxo de comando. Princípio: Isso pode significar otimizar o layout de memória de seus uniformes e estruturas de controle de recursos para que eles sejam compactados de maneira adequada nos registros adjacentes de memória ou shader. Portanto, quando você altera os sombreadores, pode fazer com que o driver verifique tudo o que você já vinculou ao pipeline e o reembale em um formato totalmente diferente para o novo sombreador e copie-o no fluxo de comando. Princípio:alterar shaders pode causar muita confusão na memória da CPU.
As alterações no buffer do quadro são provavelmente as mais dependentes da implementação, mas geralmente são bastante caras na GPU. Sua GPU pode não ser capaz de lidar com várias chamadas de draw para diferentes destinos de renderização ao mesmo tempo, portanto, pode ser necessário interromper o pipeline entre essas duas chamadas de draw. Pode ser necessário liberar caches para que o destino de renderização possa ser lido posteriormente. Pode ser necessário resolver o trabalho adiado durante o desenho. (É muito comum acumular uma estrutura de dados separada, juntamente com buffers de profundidade, destinos de renderização MSAA e muito mais. Isso pode precisar ser finalizado quando você se afasta desse destino de renderização. Se você estiver em uma GPU baseada em blocos , como muitas GPUs móveis, uma quantidade bastante grande de trabalho de sombreamento real pode precisar ser liberada quando você sai de um buffer de quadro.) Princípio:alterar alvos de renderização é caro na GPU.
Tenho certeza de que tudo é muito confuso e, infelizmente, é difícil ser muito específico, porque os detalhes geralmente não são públicos, mas espero que seja uma visão quase decente de algumas das coisas que realmente estão acontecendo quando você chama algum estado. mudança de função na sua API gráfica favorita.
fonte