Quando um sombreador de computação é mais eficiente que um sombreador de pixel para filtragem de imagens?


37

As operações de filtragem de imagem, como borrões, SSAO, bloom e assim por diante, geralmente são feitas usando pixel shaders e operações de "coleta", em que cada chamada de pixel shader emite várias buscas de textura para acessar os valores vizinhos de pixel e calcula o valor de um único pixel de o resultado. Essa abordagem tem uma ineficiência teórica, pois muitas buscas redundantes são feitas: invocações de sombreadores próximas buscarão novamente muitos dos mesmos texels.

Outra maneira de fazer isso é com shaders de computação. Eles têm a vantagem potencial de poder compartilhar uma pequena quantidade de memória em um grupo de invocações de shader. Por exemplo, você pode fazer com que cada chamada busque um texel e armazene-o na memória compartilhada e calcule os resultados a partir daí. Isso pode ou não ser mais rápido.

A questão é: em que circunstâncias (se alguma vez) o método de computação-shader é realmente mais rápido que o método de pixel-shader? Depende do tamanho do kernel, que tipo de operação de filtragem é etc.? Claramente, a resposta varia de um modelo de GPU para outro, mas estou interessado em saber se há alguma tendência geral.


Acho que a resposta é "sempre" se o sombreador de computação for feito corretamente. Isso não é trivial de alcançar. Um sombreador de computação também é uma correspondência melhor do que um sombreador de pixel conceitualmente para algoritmos de processamento de imagem. Um sombreador de pixel, no entanto, fornece menos margem de manobra para escrever filtros com desempenho insatisfatório.
Bernie

@bernie Você pode esclarecer o que é necessário para que o shader de computação seja "feito corretamente"? Talvez escreva uma resposta? Sempre bom ter mais perspectivas sobre o assunto. :)
Nathan Reed

2
Agora veja o que você me fez fazer! :)
Bernie

Além de compartilhar o trabalho entre threads, a capacidade de usar computação assíncrona é um dos principais motivos para usar shaders de computação.
JarkkoL

Respostas:


23

Uma vantagem arquitetural dos sombreadores de computação para processamento de imagem é que eles pulam a etapa ROP . É muito provável que as gravações dos pixel shaders passem por todo o hardware de mesclagem regular, mesmo que você não o use. De um modo geral, os sombreadores de computação passam por um caminho diferente (e geralmente mais direto) da memória, para evitar um gargalo que você teria. Ouvi falar de vitórias de desempenho bastante consideráveis ​​atribuídas a isso.

Uma desvantagem arquitetônica dos sombreadores de computação é que a GPU não sabe mais quais itens de trabalho se aposentam para quais pixels. Se você estiver usando o pipeline de sombreamento de pixel, a GPU terá a oportunidade de compactar o trabalho em uma frente de onda / deformação que gravará em uma área do destino de renderização que seja contígua na memória (que pode ser organizada em ordem Z ou algo semelhante ao desempenho razões). Se você estiver usando um pipeline de computação, a GPU poderá não funcionar mais em lotes ideais, levando a um maior uso da largura de banda.

Você pode transformar novamente esse empacotamento warp / frente de onda alterado em vantagem, se souber que sua operação específica possui uma subestrutura que pode ser explorada ao empacotar o trabalho relacionado no mesmo grupo de encadeamentos. Como você disse, em teoria, você poderia dar uma pausa no hardware de amostragem, amostrando um valor por faixa e colocando o resultado na memória compartilhada em grupo para outras faixas acessarem sem amostragem. Se essa é uma vitória depende de quanto a memória compartilhada é cara: se for mais barata que o cache de textura de nível mais baixo, pode ser uma vitória, mas não há garantia disso. As GPUs já lidam muito bem com buscas de textura altamente locais (por necessidade).

Se você tiver um estágio intermediário na operação em que deseja compartilhar resultados, pode fazer mais sentido usar memória compartilhada em grupo (já que você não pode voltar ao hardware de amostragem de textura sem ter realmente gravado o resultado intermediário na memória). Infelizmente, você também não pode depender de resultados de qualquer outro grupo de encadeamentos, portanto, o segundo estágio precisaria se limitar apenas ao que está disponível no mesmo bloco. Eu acho que o exemplo canônico aqui é calcular a luminância média da tela para exposição automática. Eu também poderia imaginar combinar a amostragem de textura com alguma outra operação (uma vez que a amostragem, ao contrário da amostragem reduzida e dos borrões, não depende de nenhum valor fora de um determinado bloco).


Eu duvido seriamente que o ROP adicione qualquer sobrecarga de desempenho se a mistura estiver desativada.
GroverManheim #

@GroverManheim Depende da arquitetura! A etapa de fusão / ROP de saída também tem que lidar com garantias de pedidos, mesmo se a mistura estiver desativada. Com um triângulo de tela cheia, não existem riscos reais para pedidos, mas o hardware pode não saber disso. Pode haver caminhos rápidos especiais em hardware, mas sabendo com certeza que você se qualificar para eles ...
John Calsbeek

10

João já escreveu uma ótima resposta, então considere essa resposta uma extensão dele.

Atualmente, estou trabalhando muito com shaders de computação para diferentes algoritmos. Em geral, descobri que os sombreadores de computação podem ser muito mais rápidos que seus sombreadores de pixel equivalentes ou transformar alternativas baseadas em feedback.

Depois de entender como os shaders de computação funcionam, eles também fazem muito mais sentido em muitos casos. O uso de pixels shaders para filtrar uma imagem requer a configuração de um buffer de quadro, o envio de vértices, o uso de vários estágios de shader etc. Por que isso é necessário para filtrar uma imagem? Estar acostumado a renderizar quads em tela cheia para processamento de imagem é certamente o único motivo "válido" para continuar usando-os na minha opinião. Estou convencido de que um iniciante no campo de computação gráfica consideraria os sombreadores de computação um ajuste muito mais natural para o processamento de imagens do que renderizar para texturas.

Sua pergunta refere-se à filtragem de imagens em particular, para que não seja elaborado muito sobre outros tópicos. Em alguns de nossos testes, apenas a configuração de um feedback de transformação ou a troca de objetos de buffer de estrutura para renderizar em uma textura pode resultar em custos de desempenho em torno de 0,2ms. Lembre-se de que isso exclui qualquer renderização! Em um caso, mantivemos exatamente o mesmo algoritmo portado para calcular shaders e observamos um aumento notável no desempenho.

Ao usar sombreadores de computação, mais silício na GPU pode ser usado para fazer o trabalho real. Todas essas etapas adicionais são necessárias ao usar a rota do sombreador de pixels:

  • Montagem de vértice (lendo os atributos de vértice, divisores de vértice, conversão de tipo, expandindo-os para vec4 etc.)
  • O sombreador de vértice precisa ser agendado, não importa quão mínimo seja
  • O rasterizador precisa calcular uma lista de pixels para sombrear e interpolar as saídas de vértice (provavelmente apenas cordas de textura para processamento de imagem)
  • Todos os diferentes estados (teste de profundidade, teste alfa, tesoura, mistura) devem ser definidos e gerenciados

Você pode argumentar que todas as vantagens de desempenho mencionadas anteriormente podem ser negadas por um driver inteligente. Você estaria certo. Esse driver pode identificar que você está renderizando um quad em tela cheia sem teste de profundidade etc. e configurar um "caminho rápido" que ignora todo o trabalho inútil feito para oferecer suporte a pixel shaders. Não ficaria surpreso se alguns drivers fizessem isso para acelerar os passes de pós-processamento em alguns jogos AAA para suas GPUs específicas. É claro que você pode esquecer qualquer tratamento se não estiver trabalhando em um jogo AAA.

No entanto, o que o motorista não pode fazer é encontrar melhores oportunidades de paralelismo oferecidas pelo pipeline do shader de computação. Veja o exemplo clássico de um filtro gaussiano. Usando sombreadores de computação, você pode fazer algo assim (separando o filtro ou não):

  1. Para cada grupo de trabalho, divida a amostragem da imagem de origem pelo tamanho do grupo de trabalho e armazene os resultados para agrupar a memória compartilhada.
  2. Calcule a saída do filtro usando os resultados da amostra armazenados na memória compartilhada.
  3. Gravar na textura de saída

Etapa 1 é a chave aqui. Na versão do pixel shader, a imagem de origem é amostrada várias vezes por pixel. Na versão do shader de computação, cada texel de origem é lido apenas uma vez dentro de um grupo de trabalho. As leituras de textura geralmente usam um cache baseado em bloco, mas esse cache ainda é muito mais lento que a memória compartilhada.

O filtro gaussiano é um dos exemplos mais simples. Outros algoritmos de filtragem oferecem outras oportunidades para compartilhar resultados intermediários dentro de grupos de trabalho usando memória compartilhada.

No entanto, há um problema. Os sombreadores de computação exigem barreiras explícitas à memória para sincronizar sua saída. Também há menos salvaguardas para proteger contra acessos de memória incorretos. Para programadores com bom conhecimento de programação paralela, os sombreadores de computação oferecem muito mais flexibilidade. Essa flexibilidade, no entanto, significa que também é mais fácil tratar shaders de computação como código C ++ comum e escrever código lento ou incorreto.

Referências


O paralelismo aprimorado de amostragem que você descreve é ​​intrigante - eu tenho um sim fluido que já foi implementado com sombreadores de computação com muitas instâncias de várias amostras por pixel. Usar a memória compartilhada de grupo para fazer amostragem única com uma barreira de memória, como você descreve, parece ótimo, mas estou desligado um pouco - como faço para acessar os pixels vizinhos quando eles se enquadram em um grupo de trabalho diferente? por exemplo, se eu tiver um domínio de simulação de 64x64, espalhado por um envio (2,2,1) de numthreads (16,16,1), como o pixel com id.xy == [15,15] obteria os pixels vizinhos ?
Tossrock 21/11

Nesse caso, vejo duas opções principais. 1) aumente o tamanho do grupo acima de 64 e grave apenas os resultados para os pixels de 64x64. 2) primeiro exemplo 64 + nX64 + n dividido de alguma forma no seu grupo de trabalho de 64x64 e, em seguida, use a grade de "entrada" maior para os cálculos. A melhor solução depende das suas condições específicas, é claro, e sugiro que você escreva outra pergunta para obter mais informações, pois os comentários são pouco adequados para isso.
bernie

3

Eu tropecei neste blog: Otimizações de computação de sombreador para AMD

Dados os truques que podem ser feitos no sombreador de computação (que são específicos apenas para sombreadores de computação), fiquei curioso se a redução paralela no sombreador de computação era mais rápida do que no sombreador de pixels. Enviei um e-mail para o autor, Wolf Engel, perguntando se ele havia tentado pixel shader. Ele respondeu que sim e quando escreveu a postagem no blog, a versão do computador shader era substancialmente mais rápida que a versão do pixel shader. Ele também acrescentou que hoje as diferenças são ainda maiores. Aparentemente, existem casos em que o uso do shader de computação pode ser de grande vantagem.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.