Moyen le plus rapide de rendre les lignes avec AA, épaisseur variable dans DirectX

14

Je fais donc du développement DirectX, en utilisant SharpDX sous .NET pour être exact (mais les solutions API DirectX / C ++ sont applicables). Je recherche le moyen le plus rapide de rendre les lignes dans une projection orthogonale (par exemple, simuler un dessin au trait 2D pour des applications scientifiques) à l'aide de DirectX.

Voici une capture d'écran des types de tracés que j'essaie de rendre: entrez la description de l'image ici

Il n'est pas rare que ces types de tracés aient des lignes avec des millions de segments, d'épaisseur variable, avec ou sans anticrénelage par ligne (ou en plein écran AA activé / désactivé). J'ai besoin de mettre à jour les sommets des lignes très fréquemment (par exemple 20 fois / seconde) et de décharger autant que possible sur le GPU.

Jusqu'à présent, j'ai essayé:

  1. Rendu logiciel, par exemple GDI + en fait pas de mauvaises performances mais est évidemment lourd sur le CPU
  2. API Direct2D - plus lente que GDI, en particulier avec l'anticrénelage activé
  3. Direct3D10 utilisant cette méthode pour émuler AA en utilisant les couleurs de sommet et la pavage côté CPU. Aussi lent (je l'ai profilé et 80% du temps est consacré au calcul des positions des sommets)

Pour la 3ème méthode, j'utilise des tampons Vertex pour envoyer une bande triangulaire au GPU et la mise à jour toutes les 200 ms avec de nouveaux sommets. J'obtiens un taux de rafraîchissement d'environ 5 images par seconde pour 100 000 segments de ligne. J'ai besoin de millions idéalement!

Maintenant, je pense que le moyen le plus rapide serait de faire la tessellation sur le GPU, par exemple dans un Geometry Shader. Je pourrais envoyer les sommets sous forme de liste de lignes ou regrouper dans une texture et décompresser dans un Geometry Shader pour créer les quads. Ou, envoyez simplement des points bruts à un pixel shader et implémentez le dessin Bresenham Line entièrement dans un pixel shader. Mon HLSL est rouillé, shader model 2 de 2006, donc je ne sais pas ce que les GPU modernes peuvent faire de fou.

La question est donc: - quelqu'un a-t-il déjà fait cela, et avez-vous des suggestions à essayer? - Avez-vous des suggestions pour améliorer les performances avec une mise à jour rapide de la géométrie (par exemple une nouvelle liste de sommets toutes les 20 ms)?

MISE À JOUR 21 janvier

J'ai depuis implémenté la méthode (3) ci-dessus en utilisant des shaders de géométrie en utilisant LineStrip et Dynamic Vertex Buffers. Maintenant, j'obtiens 100FPS à 100k points et 10FPS à 1000000 points. C'est une énorme amélioration, mais maintenant je suis à taux de remplissage et à calcul limité, j'ai donc pensé à d'autres techniques / idées.

  • Qu'en est-il de l'instanciation matérielle d'une géométrie de segment de ligne?
  • Qu'en est-il de Sprite Batch?
  • Qu'en est-il des autres méthodes orientées (Pixel shader)?
  • Puis-je éliminer efficacement le GPU ou le CPU?

Vos commentaires et suggestions très appréciés!

Dr. ABT
la source
1
Qu'en est-il des AA natifs dans Direct3D?
API-Beast
5
Pouvez-vous montrer quelques exemples de courbes que vous souhaitez tracer? Vous mentionnez un million de sommets mais votre écran n'a probablement pas plus d'un million de pixels , cette quantité est-elle vraiment nécessaire? Il me semble que vous n'aurez pas besoin de cette densité de données complète partout. Avez-vous envisagé des niveaux de détail?
sam hocevar
1
La deuxième approche que vous avez liée semble bonne, utilisez-vous un tampon de sommet dynamique que vous mettez à jour ou en créez-vous un à chaque fois?
1
Vous devriez probablement utiliser un tampon de vertex dynamique (pas beaucoup de travail, dites simplement à votre tampon de vertex d'être dynamique, mappez le tampon, copiez vos données, démappez), mais si votre goulot d'étranglement est la génération de vertex en premier lieu, cela a probablement gagné n'aide pas beaucoup en ce moment. Ne pensez pas qu'il y a quelque chose de fou à utiliser un GS pour alléger la charge sur le CPU
1
Si le nombre de sommets finit par être trop grand pour le GPU, vous pouvez toujours essayer de sous-échantillonner vos entrées - vous n'avez pas vraiment besoin de centaines de millions de segments de ligne pour une seule courbe lorsque votre écran aura une largeur de quelques milliers de pixels au plus.

Réponses:

16

Si vous allez rendre Y = f(X) graphiques uniquement, je vous suggère d'essayer la méthode suivante.

Les données de courbe sont transmises en tant que données de texture , ce qui les rend persistantes et permet des mises à jour partielles, par glTexSubImage2Dexemple. Si vous avez besoin de faire défiler, vous pouvez même implémenter un tampon circulaire et ne mettre à jour que quelques valeurs par image. Chaque courbe est rendue sous forme de quadruple plein écran et tout le travail est effectué par le pixel shader.

Le contenu de la texture à un composant pourrait ressembler à ceci:

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

Le travail du pixel shader est le suivant:

  • trouver la coordonnée X du fragment actuel dans l'espace du jeu de données
  • prendre par exemple. les 4 points de données les plus proches qui ont des données; par exemple , si la valeur de X est 41.3elle choisirait 40, 41, 42et 43.
  • interroger la texture pour les 4 valeurs Y (assurez-vous que l'échantillonneur ne fait aucune interpolation d'aucune sorte)
  • convertir le X,Y paires en espace d'écran
  • calculer la distance du fragment actuel à chacun des trois segments et quatre points
  • utiliser la distance comme valeur alpha pour le fragment actuel

Vous pouvez remplacer 4 par des valeurs plus grandes en fonction du niveau de zoom potentiel.

J'ai écrit un shader GLSL très rapide et sale implémentant cette fonctionnalité . Je peux ajouter la version HLSL plus tard, mais vous devriez pouvoir la convertir sans trop d'effort. Le résultat peut être vu ci-dessous, avec différentes tailles de lignes et densités de données:

courbes

Un avantage évident est que la quantité de données transférées est très faible et que le nombre d'appels n'est qu'un.

sam hocevar
la source
C'est une technique assez cool - j'ai déjà fait du GPGPU, donc je sais que les textures avec des données sont quelque chose que je connais, mais pas quelque chose que j'ai envisagé pour cela. Quelle est la plus grande taille (par exemple, le plus grand ensemble de données que vous pouvez télécharger?). Je vais télécharger votre code si cela ne vous dérange pas et jouer avec - je suis impatient de voir la performance.
Dr ABT
@ Dr.ABT La taille de l'ensemble de données n'est limitée que par la taille de texture maximale. Je ne vois pas pourquoi des millions de points ne fonctionneraient pas avec une bonne disposition des données. Notez que mon code n'est certainement pas de qualité de production mais n'hésitez pas à me contacter en privé s'il y a des problèmes.
sam hocevar
Cheers @SamHocevar - Je vais essayer. Cela fait un moment que je n'ai pas compilé un exemple d'OpenGL! :-)
Dr. ABT
Marquer comme réponse, car bien que je n'utilise pas la charge de texture, vous avez présenté un véritable exemple OpenGL qui pourrait tracer des lignes sur un pixel shader et nous mettre dans la bonne direction !! :)
Dr.ABT
4

Il y avait un chapitre GPU Gems sur le rendu des lignes anti-crénelées: Fast Prefiltered Lines . L'idée de base est de rendre chaque segment de ligne en quadruple et de calculer, à chaque pixel, une fonction gaussienne de la distance du centre du pixel par rapport au segment de ligne.

Cela signifie dessiner chaque segment de ligne dans le graphique comme un quadruple séparé, mais dans D3D11, vous pouvez certainement utiliser un ombrage de géométrie et / ou instancier pour générer les quads, réduisant la quantité de données à transférer vers le GPU aux seuls points de données se. J'aurais probablement configuré les points de données en tant que StructuredBuffer à lire par le vertex / geometry shader, puis effectuer un appel de dessin en spécifiant le nombre de segments à dessiner. Il n'y aurait pas de tampons de vertex réels; le vertex shader utilise simplement SV_VertexID ou SV_InstanceID pour déterminer les points de données à examiner.

Nathan Reed
la source
Hé merci - oui je suis au courant de cet article, pas de code source avec lui malheureusement :-( La prémisse de GeoShader + FragShader semble être un gagnant pour ce type de travail. Obtenir les données vers le GPU efficacement va être le partie délicate!
Dr. ABT
Hey @NathanReed pour en revenir à cela - si j'ai utilisé Geometry Shader ou instancié pour dessiner des quads, alors Point1 / Point2 sont des sommets opposés d'un quad, comment dans le Pixel Shader puis-je calculer la distance jusqu'à la ligne entre Pt1 et Pt2? Le pixel shader a-t-il une connaissance de la géométrie d'entrée?
Dr ABT
@ Dr.ABT Les paramètres de la ligne mathématique doivent être envoyés au pixel shader via des interpolateurs. Le pixel shader n'a pas d'accès direct à la géométrie.
Nathan Reed
Je vois, cela peut-il être fait en envoyant des sorties de GeometryShader ou en instanciant? Pour un crédit supplémentaire (si vous êtes intéressé), j'ai ajouté un Q ici: gamedev.stackexchange.com/questions/47831/…
Dr. ABT
@ Dr.ABT C'est exactement de la même manière que les coordonnées de texture, les vecteurs normaux et tout ce genre de choses sont normalement envoyés au pixel shader - en écrivant les sorties du vertex / geometry shader qui sont interpolées et entrées dans le pixel shader.
Nathan Reed
0

@ Dr.ABT - Toutes mes excuses, c'est une question, pas une réponse. Je n'ai pas trouvé de moyen de le demander dans la réponse de Sam Hocevar ci-dessus.

Pourriez-vous partager plus de détails sur la façon dont vous avez finalement implémenté les lignes de votre graphique? J'ai le même besoin d'une application WPF. Merci d'avance pour tout détail ou code que vous pouvez partager à ce sujet.

ErcGeek
la source
comme vous le savez, ce n'est pas une réponse, veuillez le modifier et le mettre en commentaire
MephistonX
J'essayais de le faire, mais il n'y a pas de bouton "ajouter un commentaire" sur le message d'origine.
ErcGeek
Salut ErcGeek, Désolé, je ne peux pas partager la solution finale car ces informations sont la propriété de mon entreprise. Bien que les exemples et suggestions ci-dessus nous mettent sur la bonne voie. Bonne chance!
Dr. ABT
Vous ne pouvez pas publier de commentaires avant d'avoir atteint une certaine réputation. Et tous ces downvotes ne vous fourniront certainement pas cette opportunité.
Mathias Lykkegaard Lorenzen
Le gars ne devrait pas être pénalisé pour avoir voulu connaître le résultat final et pour l'avoir demandé de la seule manière dont il disposait. A voté la réponse.
David Ching