CPU - fluxo de dados da memória GPU [fechado]

16

Sou um programador de gráficos iniciante e tenho me perguntado recentemente - como os dados do modelo (malhas e materiais) fluem do aplicativo (memória da CPU) para a placa gráfica (memória da GPU?)? Digamos que eu tenha um modelo estático (por exemplo, um prédio) que eu carrego e configurei uma vez e não mude ao longo da vida útil do aplicativo.

  • Seus dados são enviados para a memória da GPU apenas uma vez e ficam lá para sempre?
  • Quando o modelo é realmente renderizado em cada quadro, os processadores da GPU precisam buscar seus dados sempre da memória da GPU? O que eu quero dizer é - se eu tivesse dois modelos renderizados várias vezes cada - importaria se eu renderizasse o primeiro várias vezes e depois o segundo várias vezes ou se eu renderizasse o primeiro apenas uma vez, o segundo apenas uma vez e manteve intercalando assim? Eu poderia chamar essa pergunta de "fluxo de dados interno da GPU" nesse sentido.
  • Obviamente, as placas gráficas têm RAM limitada - quando não pode conter todos os dados de modelo necessários para renderizar 1 quadro, acho que ele continua buscando (alguns) da RAM da CPU em cada quadro, está correto?

Sei que há muitos livros e outras coisas sobre isso na internet, mas talvez você tenha algumas orientações gerais rápidas sobre como gerenciar esse fluxo de dados (quando enviar o que e quanto, quando e como renderizar)?

Edit: Eu esqueci de fazer uma distinção: há o envio dos dados para a GPU e há a configuração / ligação dos buffers como atuais . O último causa algum fluxo de dados?

Edit2: Depois de ler o post de Raxvan, gostaria de distinguir algumas ações:

  • criação de buffer com inicialização (como ele disse que eu posso armazenar os dados na CPU ou na GPU)
  • atualização de dados do buffer (que eu acredito que é simples quando os dados são mantidos na memória RAM da CPU e requer a busca da GPU na memória RAM da CPU (e depois de volta) quando mantidos na memória RAM da GPU)
  • vincular o buffer como ativo (é apenas uma maneira de informar à API que eu quero que esse buffer seja renderizado na próxima chamada de empate e não faz nada sozinho ?)
  • Chamada de chamada da API (aqui, eu gostaria de ouvir de você o que realmente acontece lá)
NPS
fonte
Eu não sou especialista, de forma alguma, mas se você estiver usando o OpenGL moderno (ou seja, não imediato) com VAOs e VBOs, os dados serão enviados para a GPU e armazenados na VRAM sempre que você usar um dos comandos da família glBuffer. Então, toda vez que você o desenha, os vértices relevantes são buscados no VRAM e renderizados. Se for um modelo que se move, você tende a armazená-lo estaticamente e usar matrizes para passar do espaço do modelo para o mundo / espaço da câmera. Quanto ao último ponto, não tenho idéia do que acontece se você ficar sem memória RAM. Meu palpite é que, se você ficar sem VRAM, os dados simplesmente não serão enviados, possivelmente com um código de erro.
Polar
@ Polar - não exatamente. Na verdade, o GL não especifica em qual memória um objeto de buffer é armazenado e é livre para movê-lo em tempo de execução com base no padrão de uso. O GL4.4 aborda isso um pouco, mas observa que, no final, o melhor que pode oferecer é "uma daquelas dicas tolas"; veja opengl.org/registry/specs/ARB/buffer_storage.txt e especialmente as questões 2 e 9.
Maximus Minimus
1
@ JimmyShelter Ah, obrigada - seria bom se tivéssemos menos "coisas insensatas" e uma especificação mais concreta.
Polar
@Polar - o que é irritante é que o ARB_buffer_storage poderia ter evitado incluir mais uma dica, mas os designers perderam a oportunidade. Oh, bem, talvez 4.5 finalmente consiga acertar.
Maximus Minimus 29/11
2
Por favor, não edite suas perguntas para "responder" às respostas. Poste uma nova pergunta.

Respostas:

12

Seus dados são enviados para a memória da GPU apenas uma vez e ficam lá para sempre?

Geralmente sim, mas o driver é livre para fazer o que é "ideal", os dados podem ser armazenados na VRAM ou na RAM ou apenas armazenados em cache. Aqui está um atrículo que explica o que realmente acontece com o fluxo VBO .

Por exemplo, se foi sinalizado como um buffer dinâmico openGL (por exemplo, VBO), é mais provável que seja armazenado na RAM. A GPU usa acesso direto à memória (DMA) para acessar o ram diretamente, sem a intervenção da CPU; isso é controlado pelo controlador DMA na placa gráfica e no driver gráfico e é executado no modo kernel.

Quando o modelo é realmente renderizado em cada quadro, os processadores GPU precisam buscar seus dados sempre da memória da GPU, mesmo que um modelo esteja processando várias vezes sequenciais?

Assim como as CPUs, as GPUs têm permissão para reordenar as instruções da GPU e as operações de acesso à memória (leia-se: execução fora de ordem ); portanto, é provável que a GPU lide com o cenário mencionado acessando a memória que está no cache (normalmente acessada recentemente ), mas às vezes não pode fazer isso.

Obviamente, as placas gráficas têm RAM limitada - quando não pode conter todos os dados de modelo necessários para renderizar 1 quadro, acho que ele continua buscando (alguns) da RAM da CPU em cada quadro, está correto?

Você não quer que isso aconteça. Mas, independentemente disso, a GPU começará a mover a memória entre a RAM e a VRAM (o processador de comando na GPU é responsável por isso), o que tornará a renderização muito mais lenta, o que fará com que a GPU pare, porque precisará aguardar os dados para ser copiado de / para V / RAM.

Há o envio dos dados para a GPU e a configuração / ligação dos buffers como atuais. O último causa algum fluxo de dados?

As GPUs contêm um buffer de comando e todos os comandos da API são submetidos a esse buffer; observe que isso pode acontecer simultaneamente com os dados que estão sendo copiados para a GPU. O buffer do anel de comando é uma fila de comunicação entre a CPU e a GPU ; qualquer comando que precise ser executado precisa ser enviado à fila para que possa ser executado pela GPU. Assim como qualquer operação que vincule novos buffers, deve ser enviada à gpu para que ela possa acessar algum local da memória.

Essa é uma das razões pelas quais o glBegin / glEnd foi reprovado, o envio de novos comandos precisa de sincronização de fila (usando barreiras / cercas de memória).

insira a descrição da imagem aqui

Quanto aos seus outros pontos:

Criação de buffer com inicialização

Você pode alocar um buffer sem inicialização e mantê-lo para uso posterior. Ou você pode alocar um buffer e copiar dados ao mesmo tempo (falando sobre o nível da API).

atualização de dados do buffer

Você pode usar o glMapBuffer para atualizar a memória no lado da GPU. se a memória será copiada da / para a RAM não é realmente um padrão do padrão e variará bastante, dependendo do fornecedor, tipo de GPU e driver.

Chamada de empate da API (aqui eu gostaria de ouvir de você o que realmente acontece lá).

Meu segundo ponto na questão principal cobre isso.

vincular o buffer como ativo (é apenas uma maneira de dizer à API que eu quero que esse buffer seja renderizado na próxima chamada de empate e ele não faz nada sozinho?)

Pense na ligação como usar o thisponteiro em qualquer linguagem orientada a objetos, embora não seja exatamente a mesma, quaisquer chamadas de API consequentes serão relativas a esse buffer de ligação.

concept3d
fonte
3

Em geral, o limite e o envolvimento da CPU versus GPU são específicos na plataforma, mas a maioria deles segue este modelo: a CPU possui alguma ram, a GPU também e você pode mover a memória (em alguns casos, a ram é compartilhada, exceto para o por uma questão de simplicidade, vamos nos ater aos carneiros).

primeiro ponto : os dados que você inicializa pode optar por mantê-los na RAM da CPU ou na GPU, e as vantagens são para ambas. Quando você renderiza algo, a GPU precisa fazer o trabalho pesado, portanto, é óbvio que os dados que já estão no mem GPU fornecerão melhor desempenho. para a CPU, ele deve primeiro enviar os dados para a GPU (que pode optar por mantê-los por um tempo) e depois executar a renderização.

segundo ponto : há muitos truques na renderização, mas a principal maneira de fazer é com polígonos. Em um quadro, a gpu renderiza os objetos feitos de polígonos um por um e, após terminar, a GPU envia a imagem para a tela. Não existe um conceito como objetos, existem apenas polígonos e a maneira como você os reúne criará uma imagem. o trabalho da GPU é projetar esses polígonos de 3d para 2d e aplicar efeito (se desejado). Os polígonos seguem apenas o caminho CPU-> GPU-> SCREEN ou GPU-> SCREEN diretamente (se os polígonos já estiverem na ram da GPU)

terceiro ponto : quando você renderiza animações, por exemplo, é melhor manter os dados próximos à CPU, porque lá ele faz o trabalho pesado, não seria ideal manter os dados na GPU, movê-los para a CPU e voltar a cada quadro. Existem muitos outros exemplos como esse para contar, mas em geral todos os dados permanecerão próximos de quem estiver fazendo os cálculos. Normalmente, você deseja mover o máximo de dados possível para a ram da GPU para obter desempenho.

O envio real dos dados para a gpu é feito pela API que você usa (directx / opengl ou outra) e o conceito de ligação e coisas assim são apenas abstrações, para que a API entenda o que você deseja fazer.

Edite para a edição:

  • buffer creation with initialisation: é como a diferença entre int a = new int[10]e a[0] = 0,a[1] = 1.... etc quando você cria um buffer, abre espaço para os dados e, quando inicia os dados, coloca as coisas que deseja.

  • buffer data updatese estiver no ram da CPU, você vertex * verticespoderá jogar com ele; se não estiver lá, será necessário movê-lo da GPU vertex * vertices = map(buffer_id);(o mapa é uma função mitológica que deve mover os dados da GPU para a ram da CPU, também tem seu oposto buffer_id = create_buffer(vertices);

  • binding the buffer as activeé apenas um conceito que eles chamam de bindingrenderização é um processo complexo e é como chamar uma função com 10000 parâmetros. Vinculação é apenas um termo usado para dizer qual buffer vai para onde. Não existe mágica real por trás desse termo, ele não converte, move ou realoca buffers, apenas informa ao driver que, na próxima chamada de empate, use esse buffer.

  • API draw callDepois de toda a amarração e fixação, esse é o lugar onde a borracha encontra a estrada. A chamada de empate pegará todos os dados (ou os IDs que apontam para os dados) que você especificou, os enviou para a GPU (se necessário) e informará a GPU para começar a triturar os números. Isso não é inteiramente verdade em todas as plataformas, existem muitas diferenças, mas, para simplificar, o sorteio diz à GPU para ... empatar.

Raxvan
fonte
2

A resposta mais correta é: depende de como você a programa, mas é uma boa coisa para se preocupar. Embora as GPUs tenham se tornado incrivelmente rápidas, a largura de banda de e para a RAM da GPU não é e será o gargalo mais frustrante.

Seus dados são enviados para a memória da GPU apenas uma vez e ficam lá para sempre?

Espero que sim. Para velocidade de renderização, você deseja que o máximo possível de dados fique na GPU, em vez de reenviá-lo a cada quadro. Os VBOs atendem exatamente a esse propósito. Existem VBO's estáticas e dinâmicas, sendo a primeira melhor para modelos estáticos e a segunda melhor para modelos cujos vértices mudam a cada quadro (por exemplo, um sistema de partículas). Mesmo quando se trata de VBOs dinâmicos, você não deseja reenviar todos os vértices a cada quadro; apenas os que estão mudando.

No caso de sua construção, os dados do vértice permaneceriam lá, e a única coisa que muda são suas matrizes (modelo / mundo, projeção e exibição).

No caso de um sistema de partículas, criei um VBO dinâmico grande o suficiente para armazenar o número máximo de partículas que jamais existirão para esse sistema. Cada quadro eu envio os dados para as partículas emitidas, juntamente com alguns uniformes, e é tudo. Quando desenho, posso especificar um ponto inicial e final nesse VBO, para não precisar excluir dados de partículas. Só posso dizer que não desenhe isso.

Quando o modelo é realmente renderizado em cada quadro, os processadores da GPU precisam buscar seus dados sempre da memória da GPU? O que eu quero dizer é - se eu tivesse dois modelos renderizados várias vezes cada - importaria se eu renderizasse o primeiro várias vezes e depois o segundo várias vezes ou se eu renderizasse o primeiro apenas uma vez, o segundo apenas uma vez e manteve intercalando assim?

O ato de enviar várias chamadas de draw em vez de apenas uma é um limite muito maior. Confira a renderização instanciada; isso pode ajudá-lo bastante e tornar a resposta a essa pergunta inútil. Eu tive alguns problemas com o driver que ainda não resolvi, mas se você conseguir fazê-lo funcionar, o problema será resolvido.

Obviamente, as placas gráficas têm RAM limitada - quando não pode conter todos os dados de modelo necessários para renderizar 1 quadro, acho que ele continua buscando (alguns) da RAM da CPU em cada quadro, está correto?

Você não quer ficar sem RAM da GPU. Se você fizer isso, mude as coisas para não mudar. No cenário hipotético em que você se esgota, provavelmente cairá de alguma forma, mas nunca vi isso acontecer, por isso sinceramente não sei.

Esqueci de fazer uma distinção: há o envio dos dados para a GPU e há a configuração / ligação dos buffers como atuais. O último causa algum fluxo de dados?

Não há fluxo de dados significativo, não. Existe algum custo para isso, mas isso é verdade para todas as linhas de código que você escreve. Descobrir quanto custa você é, novamente, para que serve o perfil.

criação de buffer com inicialização

A resposta de Raxvan parece boa, mas não é muito precisa. No OpenGL, a criação do buffer não reserva nenhum espaço. Se você deseja reservar espaço sem passar nenhum dado, pode chamar glBufferData e apenas passar nulo. (Veja a seção de notas aqui .)

atualização de dados do buffer

Eu estou supondo que você quer dizer glBufferData, ou outras funções como essa, certo? É aqui que a transferência real de dados ocorre. (A menos que você passe nulo, como acabei de dizer no último parágrafo.)

vincular o buffer como ativo (é apenas uma maneira de dizer à API que eu quero que esse buffer seja renderizado na próxima chamada de empate e não faz nada sozinho?)

Sim, mas pode fazer um pouco mais do que isso. Por exemplo, se você vincular um VAO (objeto de matriz de vértices), vincular um VBO, esse VBO se tornará vinculado ao VAO. Mais tarde, se você vincular esse VAO novamente e chamar glDrawArrays, ele saberá qual VBO desenhar.

Observe que, embora muitos tutoriais façam você criar um VAO para cada VBO, fui informado que esse não é o uso pretendido. Supostamente, você deve criar um VAO e usá-lo em todos os VBO que possuam os mesmos atributos. Ainda não tentei isso, então não posso dizer com certeza se é melhor ou pior.

Chamada de empate da API

O que acontece aqui é bem direto (da nossa perspectiva). Digamos que você vincule um VAO e chame glDrawArrays. Você especifica um ponto inicial e uma contagem, e ele executa seu sombreador de vértice para cada vértice nesse intervalo, que por sua vez passa suas saídas pela linha. Todo esse processo é outro ensaio, no entanto.

Desafio gelado
fonte
"então problema resolvido" Sim, instanciar ajudaria muito, mas sem ele eu ainda teria que fazer um call para cada objeto. O mesmo valor em ambos os casos. Então, eu me pergunto se a ordem importa.
NPS
@ NPS - Importa um pouco . Se eles forem encomendados para que você não precise mudar suas ligações, sim, provavelmente será uma quantia minúscula mais rápida. Mas se você tiver que se esforçar para classificá-las, isso provavelmente será muito, muito mais caro. Existem muitas variáveis ​​dependentes da sua implementação para dizer muito mais do que isso.
Icy Defiance