Por que a segurança do encadeamento é tão importante para as APIs gráficas?

21

Afirma-se que o Vulkan e o DirectX12 podem ser utilizados de maneira segura para threads. As pessoas parecem estar empolgadas com isso.

Por que isso é considerado um recurso tão grande? De qualquer maneira, o processamento "real" é lançado sobre a ponte de memória em uma unidade de processamento separada.

Além disso, se é tão grande, por que não é até agora que uma API gráfica de segmento seguro foi lançada?

catraca arrepiante
fonte
Este artigo é muito mais "gamer focado", mas pode dar-lhe alguns insights ... pcgamer.com/what-directx-12-means-for-gamers-and-developers
glampert

Respostas:

13

O principal ganho seria que seria mais fácil dividir as tarefas da CPU em vários threads, sem precisar resolver todos os problemas difíceis ao acessar a API de gráficos. Normalmente, você teria que tornar o contexto atual (o que pode ter implicações ruins no desempenho) ou fornecer uma fila e chamar a API gráfica em um único encadeamento. Eu não acho que qualquer desempenho seja obtido dessa maneira, porque a GPU realmente os processa sequencialmente de qualquer maneira, mas facilita muito o trabalho do desenvolvedor.

A razão pela qual isso não foi feito até agora provavelmente é porque o directx e o opengl foram criados em uma época em que o multithreading não era realmente aparente. Além disso, o conselho da Khronos é muito conservador ao alterar a API. A visão deles sobre o Vulkan também é que ele coexistirá ao lado do OpenGL, porque ambos servem a propósitos diferentes. Provavelmente não foi até recentemente que o paralismo se tornou tão importante, à medida que os consumidores obtêm acesso a mais e mais processadores.

EDIT: Não quero dizer que nenhum desempenho seja obtido com o trabalho em várias CPUs; não é útil dividir suas chamadas em vários threads para criar texturas / shaders mais rapidamente. Em vez disso, o desempenho é obtido devido ao fato de ter mais processadores ocupados e manter a GPU ocupada com as coisas a serem executadas.

Maurice Laveaux
fonte
1
Como uma observação extra, o OpenGL geralmente só funciona em um encadeamento, portanto, um aplicativo intensivo em gráficos pode maximizar o máximo de um núcleo. Algo como o Vulkan permite que vários threads enviem comandos para uma fila, o que significa que muitas chamadas gráficas podem ser feitas a partir de vários threads.
ensaboado
9

Há muito trabalho necessário na CPU para configurar um quadro para a GPU, e boa parte desse trabalho está dentro do driver gráfico. Antes do DX12 / Vulkan, o trabalho do driver gráfico era essencialmente forçado a ser encadeado pelo design da API.

A esperança é que o DX12 / Vulkan elimine essa restrição, permitindo que o trabalho do driver seja executado em paralelo em vários threads da CPU em um quadro. Isso permitirá o uso mais eficiente de CPUs multicore, permitindo que os mecanismos de jogo forneçam cenas mais complexas sem se tornarem dependentes da CPU. Essa é a esperança - se isso será realizado na prática é algo que teremos que esperar para ver nos próximos anos.

Para elaborar um pouco: a saída de um renderizador de mecanismo de jogo é um fluxo de chamadas da API DX / GL que descrevem a sequência de operações para renderizar um quadro. No entanto, existe uma grande distância entre o fluxo de chamadas da API e os buffers reais de comando binário que o hardware da GPU consome. O driver precisa "compilar" as chamadas da API no idioma da máquina da GPU, por assim dizer. Esse não é um processo trivial - envolve muita conversão de conceitos de API em realidades de hardware de baixo nível, validação para garantir que a GPU nunca seja configurada em um estado inválido, disputando alocações e dados de memória, rastreando alterações de estado para emitir o corrigir comandos de baixo nível, e assim por diante. O driver gráfico é responsável por tudo isso.

Nas APIs DX11 / GL4 e anteriores, esse trabalho geralmente é realizado por um único thread de driver. Mesmo se você chamar a API a partir de vários threads (o que você pode fazer usando as listas de comandos adiadas do DX11, por exemplo), ele adicionará algum trabalho a uma fila para que o thread do driver chegue mais tarde. Uma grande razão para isso é o rastreamento de estado que mencionei antes. Muitos dos detalhes de configuração da GPU no nível de hardware requerem conhecimento do estado atual do pipeline de gráficos, portanto, não há uma boa maneira de dividir a lista de comandos em partes que podem ser processadas em paralelo - cada parte precisa saber exatamente em que estado deve iniciar com, mesmo que o pedaço anterior ainda não tenha sido processado.

Essa é uma das grandes coisas que mudaram no DX12 / Vulkan. Por um lado, eles incorporam quase todo o estado do pipeline gráfico em um objeto e, em outro (pelo menos no DX12), quando você começa a criar uma lista de comandos, deve fornecer um estado inicial do pipeline; o estado não é herdado de uma lista de comandos para a seguinte. Em princípio, isso permite que o driver não precise saber nada sobre as listas de comandos anteriores antes de começar a compilar - e que, por sua vez, permite que o aplicativo divida sua renderização em pedaços paralelizáveis, produzindo listas de comandos totalmente compiladas, que podem ser concatenados juntos e enviados à GPU com um mínimo de confusão.

Obviamente, existem muitas outras mudanças nas novas APIs, mas no que diz respeito ao multithreading, essa é a parte mais importante.

Nathan Reed
fonte
5

As GPUs modernas geralmente têm uma única seção de front-end que processa um fluxo de comandos inteiramente linear da CPU. Se é um projeto de hardware natural ou se simplesmente evoluiu nos dias em que havia um único núcleo de CPU gerando comandos para a GPU, é discutível, mas é a realidade por enquanto. Portanto, se você gerar um único fluxo linear de comandos com estado, é claro que faz sentido gerar esse fluxo linearmente em um único encadeamento na CPU! Direita?

Bem, as GPUs modernas também geralmente têm um back-end unificado muito flexível que pode funcionar em várias coisas diferentes ao mesmo tempo. De um modo geral, a GPU trabalha com vértices e pixels com granularidade bastante fina. Não há muita diferença entre uma GPU processando 1024 vértices em um desenho e 512 + 512 vértices em dois desenhos diferentes.

Isso sugere uma maneira bastante natural de fazer menos trabalho: em vez de lançar um grande número de vértices na GPU em uma única chamada, divida seu modelo em seções, faça seleções grosseiras baratas nessas seções e envie cada pedaço individualmente se ele passar o teste de seleção. Se você fizer isso com a granularidade certa, deverá obter uma boa aceleração!

Infelizmente, na realidade atual da API gráfica, as chamadas de chamadas são extremamente caras na CPU. Uma explicação simplificada do porquê: as alterações de estado na GPU podem não corresponder diretamente às chamadas da API gráfica, muitas chamadas da API gráfica simplesmente definem algum estado dentro do driver, e a chamada de empacotamento que dependeria desse novo estado vai e examina todas as O estado marcado como alterado desde o último sorteio, grava-o no fluxo de comandos da GPU e, na verdade, inicia o sorteio. Todo esse trabalho é feito na tentativa de obter um fluxo de comandos enxuto e médio para a unidade de front-end da GPU.

O que isso se resume é que você tem um orçamento para as chamadas de saque, que é inteiramente imposto pela sobrecarga do motorista . (Acho que ouvi dizer que hoje em dia você pode obter cerca de 5.000 por quadro para um título de 60 FPS.) Você pode aumentar isso em uma grande porcentagem construindo esse fluxo de comando em pedaços paralelos.

Também existem outros motivos (por exemplo, timewarp assíncrono para aprimoramentos de latência de VR), mas esse é um grande problema para jogos vinculados a gráficos e outro software pesado para chamadas (como pacotes de modelagem 3D).

John Calsbeek
fonte