Schnellste Methode zum Rendern von Linien mit AA, wobei die Stärke in DirectX variiert

14

Also mache ich eine DirectX-Entwicklung, und zwar mit SharpDX unter .NET (aber DirectX / C ++ - API-Lösungen sind anwendbar). Ich bin auf der Suche nach der schnellsten Möglichkeit, Linien in einer orthogonalen Projektion (z. B. Simulation von 2D-Linien für wissenschaftliche Apps) mit DirectX zu rendern.

Ein Screenshot der Arten von Plots, die ich zu rendern versuche, folgt: Bildbeschreibung hier eingeben

Es ist nicht ungewöhnlich, dass diese Arten von Plots Linien mit Millionen von Segmenten und variabler Dicke aufweisen, mit oder ohne zeilenweisem Antialiasing (oder Vollbild-AA ein / aus). Ich muss die Eckpunkte für die Linien sehr häufig aktualisieren (z. B. 20 Mal pro Sekunde) und so viel wie möglich auf die GPU auslagern.

Bisher habe ich versucht:

  1. Software-Rendering, z. B. GDI +, ist eigentlich keine schlechte Leistung, belastet aber offensichtlich die CPU
  2. Direct2D-API - langsamer als GDI, insbesondere mit aktiviertem Antialiasing
  3. Direct3D10 emuliert mit dieser Methode AA mit Vertexfarben und Tessellation auf der CPU-Seite. Auch langsam (ich habe ein Profil erstellt und 80% der Zeit wird für die Berechnung der Scheitelpunktpositionen aufgewendet)

Bei der dritten Methode verwende ich Vertex-Puffer, um einen Dreiecksstreifen an die GPU zu senden und alle 200 ms mit neuen Vertices zu aktualisieren. Ich erhalte eine Bildwiederholfrequenz von ca. 5 Bildern pro Sekunde für 100.000 Liniensegmente. Ich brauche im Idealfall Millionen!

Jetzt denke ich, dass der schnellste Weg wäre, die Tessellation auf der GPU durchzuführen, z. B. in einem Geometry Shader. Ich könnte die Eckpunkte als Linienliste senden oder in eine Textur packen und in einen Geometrie-Shader entpacken, um die Quads zu erstellen. Oder senden Sie einfach Rohpunkte an einen Pixel-Shader und implementieren Sie das Zeichnen von Bresenham-Linien vollständig in einem Pixel-Shader. Mein HLSL ist rostig, Shader Model 2 aus dem Jahr 2006, also weiß ich nicht, was moderne GPUs für verrückte Sachen können.

Die Frage ist also: - Hat das schon jemand gemacht und haben Sie Vorschläge, die Sie ausprobieren sollten? - Haben Sie Vorschläge zur Verbesserung der Leistung durch eine schnelle Aktualisierung der Geometrie (z. B. alle 20 ms eine neue Scheitelpunktliste)?

UPDATE 21. Jan

Ich habe seitdem Methode (3) oben mit Geometry Shader mit LineStrip und Dynamic Vertex Buffers implementiert. Jetzt erhalte ich 100 FPS bei 100.000 Punkten und 10 FPS bei 1.000.000 Punkten. Dies ist eine enorme Verbesserung, aber jetzt bin ich auf Füllrate und Rechenleistung beschränkt, sodass ich über andere Techniken / Ideen nachdenke.

  • Was ist mit der Hardware-Instanzierung einer Liniensegmentgeometrie?
  • Was ist mit Sprite Batch?
  • Was ist mit anderen (Pixel Shader) orientierten Methoden?
  • Kann ich die GPU oder die CPU effizient ausmerzen?

Ihre Kommentare und Vorschläge werden sehr geschätzt!

Dr. ABT
quelle
1
Was ist mit nativer AA in Direct3D?
API-Beast
5
Können Sie einige Beispielkurven anzeigen, die Sie zeichnen möchten? Sie erwähnen eine Million Eckpunkte, aber Ihr Bildschirm hat wahrscheinlich nicht viel mehr als eine Million Pixel . Wird diese Menge wirklich benötigt? Es scheint mir, dass Sie nicht überall die volle Datendichte benötigen. Haben Sie über LODs nachgedacht?
Sam Hocevar
1
Der zweite Ansatz, den Sie verknüpft haben, scheint gut zu sein. Verwenden Sie einen dynamischen Vertex-Puffer, den Sie aktualisieren, oder erstellen Sie jedes Mal einen neuen?
1
Sie sollten wahrscheinlich einen dynamischen Scheitelpunktpuffer verwenden (nicht viel Arbeit, sagen Sie Ihrem Scheitelpunktpuffer einfach, dass er dynamisch sein soll, ordnen Sie den Puffer zu, kopieren Sie Ihre Daten, entfernen Sie die Zuordnung) jetzt nicht viel helfen. Denken Sie nicht, dass es verrückt ist, eine GS zu verwenden, um die CPU zu
1
Wenn die Anzahl der Scheitelpunkte für die GPU zu groß ist, können Sie dennoch versuchen, Ihre Eingaben zu verkleinern. Sie benötigen nicht wirklich Hunderte Millionen Liniensegmente für eine einzelne Kurve, wenn Ihr Bildschirm einige tausend Pixel breit sein soll maximal.

Antworten:

16

Wenn du rendern willst Y = f(X) nur Grafiken möchten, empfehle ich die folgende Methode.

Die Kurvendaten werden als Texturdaten übergeben , wodurch sie dauerhaft sind und teilweise Aktualisierungen beispielsweise durchlaufen können glTexSubImage2D. Wenn Sie scrollen müssen, können Sie sogar einen Ringpuffer implementieren und nur einige Werte pro Frame aktualisieren. Jede Kurve wird als Vollbild-Quad gerendert und die gesamte Arbeit wird vom Pixel-Shader erledigt.

Der Inhalt der Einkomponenten-Textur könnte folgendermaßen aussehen:

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

Die Arbeit des Pixel-Shaders ist wie folgt:

  • Suchen Sie die X-Koordinate des aktuellen Fragments im Datensatzbereich
  • nimm zB. die 4 nächsten Datenpunkte mit Daten; zum Beispiel, wenn der X - Wert 41.3würde es wählen 40, 41, 42und 43.
  • Fragen Sie die Textur nach den 4 Y-Werten ab (stellen Sie sicher, dass der Sampler keinerlei Interpolation durchführt).
  • Wandle die X,YPaare in Bildschirmgröße um
  • Berechnen Sie den Abstand zwischen dem aktuellen Fragment und jedem der drei Segmente und vier Punkte
  • Verwenden Sie den Abstand als Alpha-Wert für das aktuelle Fragment

Je nach möglicher Zoomstufe können Sie 4 durch größere Werte ersetzen.

Ich habe einen sehr schnellen und schmutzigen GLSL-Shader geschrieben, der diese Funktion implementiert . Ich kann die HLSL-Version später hinzufügen, aber Sie sollten sie ohne großen Aufwand konvertieren können. Das Ergebnis ist unten mit unterschiedlichen Liniengrößen und Datendichten zu sehen:

Kurven

Ein klarer Vorteil ist, dass die übertragene Datenmenge sehr gering ist und die Anzahl der Drawcalls nur eins beträgt.

sam hocevar
quelle
Das ist eine ziemlich coole Technik - ich habe GPGPU gemacht, also weiß ich etwas über das Packen von Texturen mit Daten, aber ich habe nicht darüber nachgedacht. Was ist die größte Größe (z. B. der größte Datensatz, den Sie hochladen können?). Ich werde Ihren Code herunterladen, wenn Sie nichts dagegen haben und damit herumspielen - ich bin gespannt auf die Leistung.
Dr. ABT
@ Dr.ABT Die Datensatzgröße ist nur durch die maximale Texturgröße begrenzt. Ich verstehe nicht, warum Millionen von Punkten bei einem angemessenen Datenlayout nicht funktionieren würden. Beachten Sie, dass mein Code mit Sicherheit keine Produktionsqualität hat, aber Sie können mich gerne privat kontaktieren, wenn Probleme auftreten.
Sam Hocevar
Prost @SamHocevar - Ich werde es versuchen. Es ist schon eine Weile her, dass ich ein OpenGL-Beispiel-Mind kompiliert habe! :-)
Dr. ABT
Als Antwort markieren, als würde ich keine Textur laden, aber Sie haben ein echtes OpenGL-Beispiel vorgestellt, das Linien auf einem Pixel-Shader zeichnen und uns in die richtige Richtung bringen könnte !! :)
Dr. ABT
4

Es gab ein GPU Gems-Kapitel zum Rendern von Antialias-Linien: Schnelle vorgefilterte Linien . Die Grundidee besteht darin, jedes Liniensegment als Quad zu rendern und bei jedem Pixel eine Gaußsche Funktion des Abstands des Pixelzentrums vom Liniensegment zu berechnen.

Dies bedeutet, dass jedes Liniensegment im Diagramm als separates Quad dargestellt wird. In D3D11 können Sie jedoch durchaus einen Geometrie-Shader und / oder eine Instanz zum Generieren der Quads verwenden, um die an die GPU zu übertragende Datenmenge auf die Datenpunkte zu reduzieren sich. Ich würde die Datenpunkte wahrscheinlich als StructuredBuffer zum Lesen durch den Vertex / Geometry-Shader einrichten und dann einen Draw-Aufruf ausführen, in dem die Anzahl der zu zeichnenden Segmente angegeben wird. Es gäbe keine tatsächlichen Vertex-Puffer. Der Vertex-Shader verwendet lediglich SV_VertexID oder SV_InstanceID, um zu bestimmen, welche Datenpunkte betrachtet werden sollen.

Nathan Reed
quelle
Hey danke - ja, mir ist dieser Artikel bekannt, leider kein Quellcode :-( Die Prämisse von GeoShader + FragShader scheint ein Gewinner für diese Art von Arbeit zu sein. Die Daten effizient zur GPU zu bringen, wird das sein schwieriger Teil!
Dr. ABT
Hey @ NathanReed, ich gehe noch einmal darauf zurück. Wenn ich Geometry Shader oder Instanzen zum Zeichnen von Quads verwendet habe, dann sind Point1 / Point2 entgegengesetzte Eckpunkte eines Quadrats. Wie kann ich im Pixel Shader die Entfernung zur Linie zwischen Pt1 und Pt2 berechnen? Hat der Pixel Shader Kenntnisse über die Eingabegeometrie?
Dr. ABT
@ Dr.ABT Die Parameter der mathematischen Linie müssen über Interpolatoren an den Pixel-Shader gesendet werden. Der Pixel Shader hat keinen direkten Zugriff auf die Geometrie.
Nathan Reed
Ich verstehe, kann dies durch Senden von Ausgaben aus dem GeometryShader oder durch Instanzen geschehen? Für zusätzliches Guthaben (falls Sie interessiert sind) habe ich hier ein Q hinzugefügt: gamedev.stackexchange.com/questions/47831/…
Dr. ABT
@ Dr.ABT Genau so werden normalerweise Texturkoordinaten, normale Vektoren und alles Mögliche an den Pixel-Shader gesendet, indem Ausgaben des Vertex- / Geometrie-Shaders geschrieben werden, die interpoliert und in den Pixel-Shader eingegeben werden.
Nathan Reed
0

@ Dr.ABT - Entschuldigung, das ist eine Frage, keine Antwort. Ich habe in der Antwort von Sam Hocevar oben keine Möglichkeit gefunden, danach zu fragen.

Können Sie weitere Details darüber mitteilen, wie Sie die Linien für Ihr Diagramm schließlich implementiert haben? Ich habe das gleiche Bedürfnis nach einer WPF-App. Vielen Dank im Voraus für alle Details oder Codes, die Sie dazu mitteilen können.

ErcGeek
quelle
Wie Sie wissen, ist es keine Antwort, bitte bearbeiten und als Kommentar
MephistonX
Ich habe versucht, das zu tun, aber es gibt keinen "Kommentar hinzufügen" -Button für den ursprünglichen Beitrag.
ErcGeek
Hallo ErcGeek, Entschuldigung, ich kann die endgültige Lösung nicht weitergeben, da diese Informationen Eigentum meines Unternehmens sind. Die obigen Beispiele und Vorschläge haben uns auf den richtigen Weg gebracht. Alles Gute!
Dr. ABT
Sie können keine Kommentare veröffentlichen, bevor Sie eine bestimmte Reputation erreicht haben. Und all diese Abstimmungen werden Ihnen diese Gelegenheit sicher nicht bieten.
Mathias Lykkegaard Lorenzen
Der Typ sollte nicht dafür bestraft werden, das Endergebnis wissen zu wollen und danach zu fragen, wie es ihm nur möglich ist. Hat die Antwort positiv bewertet.
David Ching