A maneira mais rápida de renderizar linhas com AA, variando a espessura no DirectX

14

Então, estou desenvolvendo o DirectX, usando o SharpDX no .NET para ser exato (mas as soluções da API DirectX / C ++ são aplicáveis). Estou procurando a maneira mais rápida de renderizar linhas em uma projeção ortogonal (por exemplo, simulando o desenho de linhas 2D para aplicativos científicos) usando o DirectX.

Segue uma captura de tela dos tipos de plotagens que estou tentando renderizar: insira a descrição da imagem aqui

Não é incomum que esse tipo de plotagem tenha linhas com milhões de segmentos, de espessura variável, com ou sem antialiasing por linha (ou AA de tela cheia ativada / desativada). Preciso atualizar os vértices para as linhas com muita frequência (por exemplo, 20 vezes / segundo) e descarregar o máximo possível para a GPU.

Até agora eu tentei:

  1. Renderização de software, por exemplo, GDI +, na verdade, não apresenta desempenho ruim, mas obviamente é pesado na CPU
  2. API Direct2D - mais lenta que a GDI, especialmente com Antialiasing em
  3. Direct3D10 usando esse método para emular AA usando cores de vértice e mosaico no lado da CPU. Também lento (criei um perfil e 80% do tempo é gasto computando posições de vértice)

Para o terceiro método, estou usando os Vertex Buffers para enviar uma faixa triangular para a GPU e atualizando a cada 200ms com novos vértices. Estou obtendo uma taxa de atualização de cerca de 5FPS para 100.000 segmentos de linha. Eu preciso de milhões idealmente!

Agora, estou pensando que a maneira mais rápida seria fazer o mosaico na GPU, por exemplo, em um Geometry Shader. Eu poderia enviar os vértices como uma lista de linhas ou empacotar em uma textura e descompactar em um Geometry Shader para criar os quadríceps. Ou envie apenas pontos brutos para um pixel shader e implemente o desenho da linha Bresenham inteiramente em um pixel shader. Meu HLSL é um modelo de shader enferrujado 2 de 2006, então eu não sei sobre as coisas loucas que as GPUs modernas podem fazer.

Portanto, a pergunta é: - alguém já fez isso antes e você tem alguma sugestão para tentar? - Você tem alguma sugestão para melhorar o desempenho com a atualização rápida da geometria (por exemplo, nova lista de vértices a cada 20ms)?

ATUALIZAÇÃO 21 de jan

Desde então, implementei o método (3) acima usando shaders de geometria usando LineStrip e Dynamic Vertex Buffers. Agora estou recebendo 100FPS em 100k pontos e 10FPS em 1.000.000 pontos. Essa é uma grande melhoria, mas agora estou com taxa de preenchimento e computação limitadas, então pensei em outras técnicas / idéias.

  • E a instância de hardware de uma geometria de segmento de linha?
  • E o Sprite Batch?
  • E quanto a outros métodos orientados (pixel shader)?
  • Posso selecionar com eficiência a GPU ou CPU?

Seus comentários e sugestões muito apreciados!

Dr. ABT
fonte
1
E o AA nativo no Direct3D?
API-Beast
5
Você pode mostrar alguns exemplos de curvas que deseja plotar? Você mencionou um milhão de vértices, mas sua tela provavelmente não possui muito mais que um milhão de pixels . Essa quantidade é realmente necessária? Parece-me que você não precisará dessa densidade total de dados em todos os lugares. Você já considerou LODs?
Sam Hocevar
1
A segunda abordagem que você vinculou parece boa, você está usando um buffer de vértice dinâmico que você atualiza ou cria um novo a cada vez?
1
Você provavelmente deve usar um buffer de vértice dinâmico (não há muito trabalho, basta dizer ao seu buffer de vértice que seja dinâmico, mapear o buffer, copiar seus dados, remover o mapeamento), mas se o seu gargalo for a geração de vértices em primeiro lugar, provavelmente ganhou ajuda muito agora. Não acho que haja algo louco sobre como usar um GS para aliviar a carga sobre a CPU
1
Se o número de vértices acabar sendo muito grande para a GPU, você ainda pode tentar reduzir a amostragem de suas entradas - você realmente não precisa de centenas de milhões de segmentos de linha para uma única curva quando sua tela tiver alguns milhares de pixels de largura no máximo.

Respostas:

16

Se você for renderizar Y = f(X)apenas gráficos, sugiro tentar o seguinte método.

Os dados da curva são passados ​​como dados de textura , tornando-os persistentes e permitindo atualizações parciais, glTexSubImage2Dpor exemplo. Se você precisar rolar, poderá implementar um buffer circular e atualizar apenas alguns valores por quadro. Cada curva é renderizada como um quad de tela cheia e todo o trabalho é feito pelo pixel shader.

O conteúdo da textura de um componente pode ficar assim:

+----+----+----+----+
| 12 | 10 |  5 | .. | values for curve #1
+----+----+----+----+
| 55 | 83 | 87 | .. | values for curve #2
+----+----+----+----+

O trabalho do pixel shader é o seguinte:

  • encontre a coordenada X do fragmento atual no espaço do conjunto de dados
  • tome por exemplo. os 4 pontos de dados mais próximos que possuem dados; por exemplo, se o valor X é 41.3ele escolheria 40, 41, 42e 43.
  • consulte a textura quanto aos valores 4 Y (verifique se o amostrador não faz interpolação de qualquer tipo)
  • converter os X,Ypares em espaço na tela
  • calcular a distância do fragmento atual a cada um dos três segmentos e quatro pontos
  • use a distância como um valor alfa para o fragmento atual

Você pode substituir 4 por valores maiores, dependendo do nível de zoom em potencial.

Eu escrevi um shader GLSL muito rápido e sujo implementando esse recurso . Posso adicionar a versão HLSL posteriormente, mas você poderá convertê-la sem muito esforço. O resultado pode ser visto abaixo, com diferentes tamanhos de linha e densidades de dados:

curvas

Uma vantagem clara é que a quantidade de dados transferidos é muito baixa e o número de chamadas de chamada é apenas um.

sam hocevar
fonte
Essa é uma técnica bem legal - eu já fiz GPGPU antes, então empacotar texturas com dados é algo que eu sei, mas não algo que eu considerei para isso. Qual é o maior tamanho (por exemplo, o maior conjunto de dados que você pode carregar?). Vou baixar o seu código se você não se importa e brincar com ele - estou ansioso para ver o desempenho.
Dr. ABT 01/01
@ Dr.ABT O tamanho do conjunto de dados é limitado apenas pelo tamanho máximo da textura. Não vejo por que milhões de pontos não funcionariam, devido a um layout de dados adequado. Observe que meu código certamente não é de qualidade de produção, mas fique à vontade para entrar em contato comigo em particular, se houver algum problema.
Sam Hocevar
Cheers @SamHocevar - vou tentar. Já faz um tempo desde que eu compilei uma mente de exemplo do OpenGL! :-)
Dr. ABT 01/01
Marcando como resposta, como se eu não estivesse usando a carga de textura, você apresentou um exemplo real do OpenGL que poderia desenhar linhas em um pixel shader e nos colocar na direção certa !! :)
Dr. ABT
4

Havia um capítulo da GPU Gems sobre a renderização de linhas sem suavização de linhas: Linhas pré-filtradas rápidas . A idéia básica é renderizar cada segmento de linha como um quad e calcular, em cada pixel, uma função gaussiana da distância do centro de pixels ao segmento de linha.

Isso significa desenhar cada segmento de linha no gráfico como um quad separado, mas no D3D11 você certamente poderia usar um shader de geometria e / ou instanciamento para gerar os quads, reduzindo a quantidade de dados a serem transferidos para a GPU para apenas os pontos de dados si mesmos. Provavelmente eu configuraria os pontos de dados como um StructuredBuffer para ser lido pelo sombreador de vértice / geometria e, em seguida, faria uma chamada de empate especificando o número de segmentos a serem desenhados. Não haveria nenhum buffer de vértice real; o sombreador de vértice usaria apenas SV_VertexID ou SV_InstanceID para determinar quais pontos de dados examinar.

Nathan Reed
fonte
Ei, obrigado - sim, eu estou ciente desse artigo, infelizmente, nenhum código-fonte com ele :-( A premissa do GeoShader + FragShader parece ser a vencedora desse tipo de trabalho. Obter os dados para a GPU com eficiência será o parte complicada!
Dr. ABT
Hey @NathanReed voltando a isso - se eu usei o Geometry Shader ou instanciando para desenhar quads, então Point1 / Point2 são vértices opostos de um quad, como no Pixel Shader posso calcular a distância da linha entre Pt1 e Pt2? O sombreador de pixels tem conhecimento de geometria de entrada?
Dr. ABT
@ Dr.ABT Os parâmetros da linha matemática devem ser enviados ao pixel shader por interpoladores. O pixel shader não tem acesso direto à geometria.
Nathan Reed
Entendo, isso pode ser feito enviando saídas do GeometryShader ou instanciando? Para crédito extra (se você estiver interessado), adicionei um Q aqui: gamedev.stackexchange.com/questions/47831/…
Dr. ABT
@ Dr.ABT É exatamente da mesma maneira que coordenadas de textura, vetores normais e todo esse tipo de material são normalmente enviados para o pixel shader - escrevendo as saídas do shader de vértice / geometria que são interpoladas e inseridas no shader de pixel.
Nathan Reed
0

@ Dr.ABT - Desculpas por isso é uma pergunta, não uma resposta. Não encontrei uma maneira de fazer isso na resposta de Sam Hocevar acima.

Você poderia compartilhar mais detalhes sobre como finalmente implementou as linhas do seu gráfico? Tenho a mesma necessidade de um aplicativo WPF. Agradecemos antecipadamente por todos os detalhes ou códigos que você pode compartilhar sobre isso.

ErcGeek
fonte
como você sabe que não é uma resposta Edite e colocá-lo como um comentário
MephistonX
Eu estava tentando fazer isso, mas não há botão "adicionar comentário" na postagem original.
ErcGeek
Olá ErcGeek, Desculpe, não consigo compartilhar a solução final, pois essas informações são de propriedade da minha empresa. Embora os exemplos e sugestões acima nos colocem no caminho certo. Muito bem sucedida!
Dr. ABT
Você não pode postar comentários antes de atingir uma certa reputação. E todas essas votações negativas com certeza não fornecerão a você essa oportunidade.
Mathias Lykkegaard Lorenzen
O cara não deve ser penalizado por querer saber o resultado final e pedir da única maneira disponível. Votou na resposta.
David Ching