Wann ist ein Compute-Shader für die Bildfilterung effizienter als ein Pixel-Shader?
On Februar 1, 2021 by adminBildfilterungsvorgänge wie Unschärfen, SSAO, Bloom usw. werden normalerweise mit Pixel-Shadern und „Gather“ -Operationen ausgeführt, bei denen jeder Pixel-Shader-Aufruf a ausgibt Anzahl der Texturabrufe, um auf die benachbarten Pixelwerte zuzugreifen, und Berechnung des Ergebnisses eines einzelnen Pixels. Dieser Ansatz weist eine theoretische Ineffizienz dahingehend auf, dass viele redundante Abrufe durchgeführt werden: In der Nähe befindliche Shader-Aufrufe rufen viele der gleichen Texel erneut ab.
Eine andere Möglichkeit besteht darin, Shader zu berechnen. Diese haben den potenziellen Vorteil, dass sie eine kleine Menge an Speicher für eine Gruppe von Shader-Aufrufen gemeinsam nutzen können. Beispielsweise könnte jeder Aufruf ein Texel abrufen und speichern Sie es im gemeinsamen Speicher, und berechnen Sie dann die Ergebnisse von dort. Dies kann schneller sein oder auch nicht.
Die Frage ist, unter welchen Umständen (wenn überhaupt) die Compute-Shader-Methode tatsächlich
Kommentare
- Ich denke, die Antwort lautet “ immer “ , wenn der Compute-Shader ordnungsgemäß ausgeführt wird. Dies ist nicht trivial Ein Compute-Shader passt auch konzeptionell besser zu Bildverarbeitungsalgorithmen als ein Pixel-Shader. Ein Pixel-Shader bietet jedoch weniger Spielraum zum Schreiben von Filtern mit schlechter Leistung.
- @bernie Können Sie klarstellen, was ‚ wird benötigt, damit der Compute-Shader “ ordnungsgemäß ausgeführt wird „? Vielleicht eine Antwort schreiben? Es ist immer gut, mehr Perspektiven zu diesem Thema zu erhalten. 🙂
- Schauen Sie sich jetzt an, was Sie mich dazu gebracht haben! 🙂
- Zusätzlich zur gemeinsamen Arbeit mit Threads können Sie auch asynchrone Berechnungen verwenden ist ein wichtiger Grund für die Verwendung von Compute-Shadern.
Antwort
Ein architektonischer Vorteil von Compute-Shadern für die Bildverarbeitung besteht darin, dass sie den Schritt ROP überspringen. Es ist sehr wahrscheinlich, dass Schreibvorgänge von Pixel-Shadern die gesamte reguläre Mischhardware durchlaufen, selbst wenn Sie sie nicht verwenden. Im Allgemeinen durchlaufen Compute-Shader einen anderen (und häufig direkteren) Pfad zum Speicher, sodass Sie möglicherweise einen Engpass vermeiden, den Sie sonst hätten. Ich habe von ziemlich beträchtlichen Leistungsgewinnen gehört, die darauf zurückgeführt werden.
Ein architektonischer Nachteil von Compute-Shadern besteht darin, dass die GPU nicht mehr weiß, welche Arbeitselemente auf welche Pixel zurückgezogen werden Wenn die Pixel-Shading-Pipeline verwendet wird, hat die GPU die Möglichkeit, Arbeit in eine Warp- / Wellenfront zu packen, die in einen Bereich des Render-Ziels schreibt, der im Speicher zusammenhängend ist (dies kann sein Z-Reihenfolge gekachelt oder ähnliches aus Leistungsgründen). Wenn Sie eine Compute-Pipeline verwenden, kann es sein, dass die GPU nicht mehr in optimalen Stapeln arbeitet, was zu einer höheren Bandbreitennutzung führt.
Sie Möglicherweise können Sie diese veränderte Warp- / Wellenfront-Packung wieder in einen Vorteil verwandeln, wenn Sie wissen, dass Ihre bestimmte Operation eine Unterstruktur aufweist, die Sie ausnutzen können, indem Sie verwandte Arbeiten in dieselbe Thread-Gruppe packen. Wie Sie sagten, könnten Sie dies theoretisch tun Geben Sie der Abtasthardware eine Pause, indem Sie einen Wert pro Spur abtasten und das Ergebnis für ot in den gemeinsam genutzten Gruppenspeicher legen ihre Fahrspuren ohne Probenahme zu erreichen. Ob dies ein Gewinn ist, hängt davon ab, wie teuer Ihr gemeinsam genutzter Gruppenspeicher ist: Wenn er billiger als der Textur-Cache der niedrigsten Ebene ist, ist dies möglicherweise ein Gewinn, aber es gibt keine Garantie dafür. GPUs können bereits sehr gut mit hochlokalen Texturabrufen umgehen (notwendigerweise).
Wenn Sie Zwischenphasen in der Operation haben, in denen Sie Ergebnisse teilen möchten, ist es möglicherweise sinnvoller, Groupshared Memory zu verwenden (da Sie Sie können nicht auf die Textur-Sampling-Hardware zurückgreifen, ohne Ihr Zwischenergebnis tatsächlich in den Speicher geschrieben zu haben. Leider können Sie sich auch nicht darauf verlassen, Ergebnisse von einer anderen Thread-Gruppe zu haben, sodass sich die zweite Stufe nur auf das beschränken müsste ist in der gleichen Kachel erhältlich. Ich denke, das kanonische Beispiel hier ist die Berechnung der durchschnittlichen Luminanz des Bildschirms für die automatische Belichtung. Ich könnte mir auch vorstellen, Textur-Upsampling mit einer anderen Operation zu kombinieren (da Upsampling im Gegensatz zu Downsampling und Unschärfen nicht von Werten außerhalb einer bestimmten Kachel abhängt).
Kommentare
- Ich bezweifle ernsthaft, dass die ROP einen Leistungsaufwand verursacht, wenn das Mischen deaktiviert ist.
- @GroverManheim Abhängig von der Architektur! Der Output Merger / ROP-Schritt muss sich auch mit Bestellgarantien befassen, selbst wenn das Mischen erfolgt ist deaktiviert. Bei einem Vollbilddreieck gibt es ‚ keine tatsächlichen Bestellrisiken, aber die Hardware weiß dies möglicherweise nicht.Es gibt möglicherweise spezielle schnelle Pfade in der Hardware, aber Sie müssen sicher sein, dass Sie sich für diese qualifizieren …
Antwort
John hat bereits eine großartige Antwort geschrieben . Betrachten Sie diese Antwort als Erweiterung seiner.
Ich arbeite derzeit viel mit Compute-Shadern für Verschiedene Algorithmen. Im Allgemeinen habe ich festgestellt, dass Compute-Shader viel schneller sein können als der entsprechende Pixel-Shader oder Feedback-basierte Alternativen transformieren können.
Sobald Sie sich mit der Funktionsweise von Compute-Shadern beschäftigt haben, erstellen sie auch a in vielen Fällen viel sinnvoller. Die Verwendung von Pixel-Shadern zum Filtern eines Bildes erfordert das Einrichten eines Framebuffers, das Senden von Scheitelpunkten, das Verwenden mehrerer Shader-Stufen usw. Warum sollte dies erforderlich sein, um ein Bild zu filtern? Das Rendern von Vollbild-Quads für die Bildverarbeitung ist meiner Meinung nach sicherlich der einzige „gültige“ Grund, sie weiterhin zu verwenden. Ich bin davon überzeugt, dass ein Neuling auf dem Gebiet der Computergrafik Compute-Shader für die Bildverarbeitung viel natürlicher finden würde als das Rendern in Texturen.
Ihre Frage bezieht sich insbesondere auf die Bildfilterung, daher werde ich nicht näher darauf eingehen zu viel zu anderen Themen. In einigen unserer Tests kann das Einrichten einer Transformationsrückkopplung oder das Umschalten von Framebuffer-Objekten zum Rendern in eine Textur Leistungskosten von etwa 0,2 ms verursachen. Beachten Sie, dass dies jegliches Rendern ausschließt! In einem Fall haben wir genau den gleichen Algorithmus für Compute-Shader portiert und eine spürbare Leistungssteigerung festgestellt.
Bei Verwendung von Compute-Shadern kann mehr Silizium auf der GPU für die eigentliche Arbeit verwendet werden. Alle diese zusätzlichen Schritte sind erforderlich, wenn Sie die Pixel-Shader-Route verwenden:
- Scheitelpunkt-Assembly (Lesen der Scheitelpunktattribute, Scheitelpunktteiler, Typkonvertierung, Erweitern auf vec4 usw.)
- Der Vertex-Shader muss geplant werden, egal wie minimal er ist.
- Der Rasterizer muss eine Liste von Pixeln berechnen, um die Vertex-Ausgaben zu schattieren und zu interpolieren (wahrscheinlich nur Texturkoordinaten für die Bildverarbeitung)
- Alle verschiedenen Zustände (Tiefentest, Alpha-Test, Schere, Mischen) müssen eingestellt und verwaltet werden.
Sie könnten argumentieren, dass alle zuvor genannten Leistungsvorteile durch negiert werden könnten ein kluger Fahrer. Du hättest recht. Ein solcher Treiber könnte erkennen, dass Sie „ein Vollbild-Quad ohne Tiefenprüfung usw. rendern und einen“ schnellen Pfad „konfigurieren, der alle nutzlosen Arbeiten zur Unterstützung von Pixel-Shadern überspringt. Ich wäre nicht überrascht, wenn einige Treiber dies tun würden Dies beschleunigt die Nachbearbeitungsdurchläufe in einigen AAA-Spielen für ihre spezifischen GPUs. Sie können eine solche Behandlung natürlich vergessen, wenn Sie nicht an einem AAA-Spiel arbeiten.
Der Treiber kann jedoch keine besseren Parallelitätsmöglichkeiten finden, die die Compute-Shader-Pipeline bietet. Nehmen Sie das klassische Beispiel eines Gaußschen Filters. Mit Compute-Shadern können Sie Folgendes tun (Filter trennen oder nicht):
- Teilen Sie für jede Arbeitsgruppe die Stichprobe des Quellbilds auf die Arbeitsgruppengröße auf und speichern Sie die Ergebnisse in Gruppen-Shared-Memory.
- Berechnen Sie die Filterausgabe anhand der im Shared-Memory gespeicherten Beispielergebnisse.
- Schreiben Sie in die Ausgabetextur
Schritt 1 ist der Schlüssel hier. In der Pixel-Shader-Version wird das Quellbild mehrmals pro Pixel abgetastet. In der Compute-Shader-Version wird jedes Quelltexel innerhalb einer Arbeitsgruppe nur einmal gelesen. Texturlesevorgänge verwenden normalerweise einen kachelbasierten Cache, aber dieser Cache ist immer noch viel langsamer als der gemeinsam genutzte Speicher.
Der Gauß-Filter ist eines der einfacheren Beispiele. Andere Filteralgorithmen bieten andere Möglichkeiten, Zwischenergebnisse innerhalb von Arbeitsgruppen mithilfe des gemeinsam genutzten Speichers auszutauschen.
Es gibt jedoch einen Haken. Compute-Shader benötigen explizite Speicherbarrieren, um ihre Ausgabe zu synchronisieren. Es gibt auch weniger Schutzmaßnahmen zum Schutz vor fehlerhaften Speicherzugriffen. Für Programmierer mit guten Kenntnissen in der parallelen Programmierung bieten Compute-Shader viel mehr Flexibilität. Diese Flexibilität bedeutet jedoch, dass es auch einfacher ist, Compute-Shader wie normalen C ++ – Code zu behandeln und langsamen oder falschen Code zu schreiben.
Referenzen
- OpenGL Compute Shaders-Wiki-Seite
- DirectCompute: Optimierungen und Best Practices, Eric Young, NVIDIA Corporation, 2010 [pdf]
- Effizientes Compute Shader Proramming, Bill Bilodeau, AMD, 2011? [pps]
- DirectCompute for Gaming – Laden Sie Ihre Engine mit Compute Shadern auf, Layla Mah & Stephan Hodes, AMD, 2013, [pps]
- Shader-Optimierungen für berechnen AMD-GPUs: Parallele Reduktion, Wolfgang Engel, 2014
Kommentare
- Die von Ihnen beschriebene verbesserte Stichprobenparallelität ist faszinierend – Ich habe eine flüssige Sim, die bereits mit Compute-Shadern mit vielen Instanzen mehrerer Samples pro Pixel implementiert ist. Die Verwendung von Groupshared Memory für einzelne Samples mit einer von Ihnen beschriebenen Speicherbarriere scheint großartig, aber ich ‚ Ich habe ein Bit aufgelegt – wie greife ich auf benachbarte Pixel zu, wenn diese in eine andere Arbeitsgruppe fallen würden? Wenn ich beispielsweise eine 64×64-Simulationsdomäne habe, die über einen Versand (2,2,1) von Nummernköpfen (16,16,1) verteilt ist, wie würde das Pixel mit id.xy == [15,15] seine benachbarten Pixel erhalten ?
- In diesem Fall sehe ich zwei Hauptoptionen. 1) Erhöhen Sie die Gruppengröße über 64 und schreiben Sie nur Ergebnisse für die 64×64 Pixel. 2) Probieren Sie zuerst 64 + nX64 + n aus, das irgendwie in Ihrer 64×64-Arbeitsgruppe aufgeteilt ist, und verwenden Sie dann das größere “ -Eingabe “ -Gitter für die Berechnungen . Die beste Lösung hängt natürlich von Ihren spezifischen Bedingungen ab, und ich schlage vor, dass Sie eine weitere Frage für weitere Informationen aufschreiben, da Kommentare dafür schlecht geeignet sind.
Antwort
Ich bin auf diesen Blog gestoßen: Shader-Optimierungen für AMD berechnen
Angesichts der möglichen Tricks Ich war neugierig, ob die parallele Reduzierung beim Compute-Shader schneller war als beim Pixel-Shader. Ich habe dem Autor Wolf Engel eine E-Mail geschickt, um ihn zu fragen, ob er Pixel Shader ausprobiert hat. Er antwortete, dass die Compute Shader-Version wesentlich schneller war als die Pixel Shader-Version Heute sind die Unterschiede noch größer. Anscheinend gibt es Fälle, in denen die Verwendung von Compute Shader von großem Vorteil sein kann.
Schreibe einen Kommentar