Wann ist ein Compute-Shader effizienter als ein Pixel-Shader für die Bildfilterung?


37

Bildfilterungsoperationen wie Unschärfen, SSAO, Blühen usw. werden normalerweise unter Verwendung von Pixel-Shadern und "Sammeln" -Operationen durchgeführt, wobei jeder Pixel-Shader-Aufruf eine Anzahl von Texturabrufen ausgibt, um auf die benachbarten Pixelwerte zuzugreifen, und den Wert eines einzelnen Pixels berechnet das Ergebnis. Dieser Ansatz hat eine theoretische Ineffizienz dahingehend, dass viele redundante Abfragen durchgeführt werden: In der Nähe befindliche Shader-Aufrufe rufen viele der gleichen Texel erneut ab.

Eine andere Möglichkeit ist die Verwendung von Compute-Shadern. Diese haben den potenziellen Vorteil, dass eine Gruppe von Shader-Aufrufen einen kleinen Teil des Arbeitsspeichers gemeinsam nutzen kann. Beispielsweise könnte jeder Aufruf ein Texel abrufen und im gemeinsamen Speicher ablegen und dann die Ergebnisse daraus berechnen. Dies kann schneller sein oder auch nicht.

Die Frage ist, unter welchen Umständen (wenn überhaupt) die Compute-Shader-Methode tatsächlich schneller ist als die Pixel-Shader-Methode. Kommt es auf die Größe des Kernels an, welche Art von Filteroperation es ist, etc.? Natürlich wird die Antwort von einem GPU-Modell zum anderen variieren, aber ich bin daran interessiert zu hören, ob es allgemeine Trends gibt.


Ich denke, die Antwort lautet "immer", wenn der Compute-Shader ordnungsgemäß ausgeführt wird. Dies ist nicht trivial zu erreichen. 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

@bernie Kannst du klären, was benötigt wird, damit der Compute-Shader "richtig gemacht" wird? Vielleicht eine Antwort schreiben? Immer gut, um mehr Perspektiven auf das Thema zu bekommen. :)
Nathan Reed

2
Nun sieh dir an, was du mich dazu gebracht hast! :)
Bernie

Neben der Arbeitsteilung in mehreren Threads ist die Verwendung von asynchronem Computing ein wichtiger Grund für die Verwendung von Computing-Shadern.
JarkkoL

Antworten:


23

Ein architektonischer Vorteil von Compute-Shadern für die Bildverarbeitung besteht darin, dass sie den ROP- Schritt überspringen . Es ist sehr wahrscheinlich, dass Schreibvorgänge mit Pixel-Shadern die gesamte reguläre Mischhardware durchlaufen, auch wenn Sie sie nicht verwenden. Im Allgemeinen verwenden 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 diesem zugeschrieben werden.

Ein architektonischer Nachteil von Compute-Shadern besteht darin, dass die GPU nicht mehr weiß, welche Arbeitselemente sich in welchen Pixeln befinden. Wenn Sie die Pixel-Shading-Pipeline verwenden, hat die GPU die Möglichkeit, die Arbeit in eine Warp- / Wellenfront zu packen, die in einen Bereich des Render-Ziels schreibt, der im Speicher zusammenhängend ist (der in Z-Reihenfolge nebeneinander angeordnet sein kann oder etwas Ähnliches für die Leistung Gründe dafür). Wenn Sie eine Compute-Pipeline verwenden, funktioniert die GPU möglicherweise nicht mehr in optimalen Stapeln, was zu einer höheren Bandbreitennutzung führt.

Sie können diese geänderte Warp- / Wellenfront-Packung möglicherweise 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 der Abtasthardware theoretisch eine Pause geben, indem Sie einen Wert pro Spur abtasten und das Ergebnis in einen gemeinsamen Speicher stellen, auf den andere Spuren ohne Abtastung zugreifen können. Ob dies ein Gewinn ist, hängt davon ab, wie teuer Ihr gemeinsam genutzter Gruppenspeicher ist: Wenn er billiger als der niedrigste Textur-Cache ist, kann dies ein Gewinn sein, aber dafür gibt es keine Garantie. GPUs sind (aus Gründen der Notwendigkeit) bereits recht gut mit hochlokalen Texturabrufen zurecht gekommen.

Wenn Sie Zwischenschritte in der Operation haben, in denen Sie Ergebnisse teilen möchten, ist es möglicherweise sinnvoller, Speicher mit Gruppenfreigabe zu verwenden (da Sie nicht auf die Texturabtastungshardware zurückgreifen können, ohne Ihr Zwischenergebnis tatsächlich in den Speicher geschrieben zu haben). Leider können Sie sich auch nicht darauf verlassen, dass Ergebnisse von einer anderen Thread-Gruppe vorliegen, sodass sich die zweite Phase auf das beschränken müsste, was in derselben Kachel verfügbar ist. 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, das Textur-Upsampling mit einer anderen Operation zu kombinieren (da das Upsampling im Gegensatz zu Downsampling und Unschärfen nicht von Werten außerhalb einer bestimmten Kachel abhängt).


Ich bezweifle ernsthaft, dass die ROP einen zusätzlichen Leistungsaufwand verursacht, wenn das Mischen deaktiviert ist.
GroverManheim

@GroverManheim Kommt auf die Architektur an! Der Schritt "Output Merger / ROP" muss sich auch mit Bestellgarantien befassen, selbst wenn das Mischen deaktiviert ist. Mit einem Vollbild-Dreieck gibt es keine tatsächlichen Bestellrisiken, aber die Hardware weiß das möglicherweise nicht. Es mag spezielle schnelle Wege in der Hardware geben, aber Sie müssen sicher sein, dass Sie sich für diese qualifizieren…
John Calsbeek,

10

John hat bereits eine großartige Antwort geschrieben. Betrachten Sie diese Antwort als Erweiterung seiner.

Derzeit arbeite ich viel mit Compute-Shadern für verschiedene Algorithmen. Im Allgemeinen habe ich festgestellt, dass Compute-Shader viel schneller als ihre entsprechenden Pixel-Shader sein oder auf Feedback basierende Alternativen transformieren können.

Wenn Sie sich einmal mit der Funktionsweise von Computing-Shadern befasst haben, sind sie in vielen Fällen auch viel sinnvoller. Um Pixel-Shader zum Filtern eines Bildes zu verwenden, müssen Sie einen Framebuffer einrichten, Scheitelpunkte senden, mehrere Shader-Stufen verwenden usw. Warum sollte dies zum Filtern eines Bildes erforderlich sein? Das Rendern von Vollbild-Quads für die Bildverarbeitung ist meiner Meinung nach mit Sicherheit der einzige "gültige" Grund, diese weiterhin zu verwenden. Ich bin überzeugt, dass ein Neuling auf dem Gebiet der Compute-Grafik Compute-Shader für die Bildverarbeitung viel natürlicher geeignet sind als das Rendern auf Texturen.

Ihre Frage bezieht sich insbesondere auf die Bildfilterung, damit ich nicht zu viel auf andere Themen eingehen werde. In einigen unserer Tests können allein durch das Einrichten eines Transformations-Feedbacks oder das Umschalten von Framebuffer-Objekten zum Rendern in eine Textur Leistungskosten in der Größenordnung von 0,2 ms entstehen. Denken Sie daran, dass dies jegliches Rendern ausschließt! In einem Fall haben wir den exakt gleichen Algorithmus für die Berechnung von Shadern beibehalten und eine deutliche Leistungssteigerung festgestellt.

Bei der 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:

  • Scheitelpunktassembly (Lesen der Scheitelpunktattribute, Scheitelpunktteiler, Typkonvertierung, Erweiterung auf vec4 usw.)
  • Der Vertex-Shader muss geplant werden, egal wie minimal er ist
  • Der Rasterizer muss eine Liste von Pixeln berechnen, um die Scheitelpunktausgaben zu schattieren und zu interpolieren (wahrscheinlich nur Texturkoordinaten für die Bildverarbeitung).
  • Alle verschiedenen Zustände (Tiefentest, Alphatest, Schere, Mischen) müssen eingestellt und verwaltet werden

Sie könnten argumentieren, dass alle zuvor erwähnten Leistungsvorteile von einem intelligenten Treiber negiert werden könnten. Du hättest recht. Ein solcher Treiber könnte erkennen, dass Sie ein Quad im Vollbildmodus ohne Tiefenprüfung usw. rendern, und einen "schnellen Pfad" konfigurieren, der alle nutzlosen Arbeiten zur Unterstützung von Pixel-Shadern übergeht. Es würde mich nicht überraschen, wenn einige Treiber dies tun, um die Nachbearbeitung in einigen AAA-Spielen für ihre spezifischen GPUs zu beschleunigen. Sie können natürlich eine solche Behandlung vergessen, wenn Sie nicht an einem AAA-Spiel arbeiten.

Was der Treiber jedoch nicht tun kann, ist, bessere Parallelitätsmöglichkeiten zu finden, die die Compute-Shader-Pipeline bietet. Nehmen Sie das klassische Beispiel eines Gaußfilters. Mit Compute-Shadern können Sie Folgendes tun (Filter trennen oder nicht):

  1. Teilen Sie für jede Arbeitsgruppe das Sampling des Quellbilds auf die Größe der Arbeitsgruppe auf und speichern Sie die Ergebnisse im gemeinsamen Gruppenspeicher.
  2. Berechnen Sie die Filterausgabe anhand der im gemeinsamen Speicher abgelegten Probenergebnisse.
  3. Schreiben Sie in die Ausgabe-Textur

Schritt 1 ist hier der Schlüssel. In der Pixel-Shader-Version wird das Quellbild mehrmals pro Pixel abgetastet. In der Compute-Shader-Version wird jedes Quellentexel nur einmal in einer Arbeitsgruppe gelesen. Texture Reads verwenden normalerweise einen kachelbasierten Cache, aber dieser Cache ist immer noch viel langsamer als der gemeinsam genutzte Speicher.

Das Gauß-Filter ist eines der einfacheren Beispiele. Andere Filteralgorithmen bieten andere Möglichkeiten, um Zwischenergebnisse in Arbeitsgruppen mithilfe des gemeinsamen Speichers auszutauschen.

Es gibt jedoch einen Haken. Compute-Shader benötigen explizite Speicherbarrieren, um ihre Ausgabe zu synchronisieren. Es gibt auch weniger Schutzmaßnahmen gegen fehlerhafte Speicherzugriffe. 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, Computing-Shader wie normalen C ++ - Code zu behandeln und langsamen oder falschen Code zu schreiben.

Verweise


3

Ich bin auf diesem Blog gestolpert: Compute Shader Optimizations for AMD

Angesichts der Tricks, die im Compute-Shader ausgeführt werden können (die nur für Compute-Shader gelten), war ich gespannt, ob die parallele Reduzierung im Compute-Shader schneller war als im Pixel-Shader. Ich habe den Autor Wolf Engel per E-Mail gefragt, ob er es mit Pixel-Shader versucht hat. Er antwortete, dass ja und zurück, als er den Blog-Beitrag schrieb, die Compute-Shader-Version wesentlich schneller war als die Pixel-Shader-Version. Er fügte hinzu, dass die Unterschiede heute noch größer sind. Offensichtlich gibt es Fälle, in denen die Verwendung von Compute Shader von großem Vorteil sein kann.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.