Wie funktioniert das Aktualisieren eines Tiefenpuffers in der GPU?

10

Im Moment versuche ich, eine Art Tiefenpuffer in Software zu implementieren, und ich habe ein großes Problem, wenn ich darauf schreibe. Einen Mutex zu haben ist ein absoluter Overkill. Also habe ich eine Anzahl von Mutexen erstellt, die der Anzahl der Threads entspricht. Ich sperre einen Mutex basierend auf dem aktuellen Pixel (pixel_index% mutexes_number) und dies funktioniert besser, aber immer noch sehr, sehr langsam. Und ich frage mich, wie es in einer echten GPU gemacht wird? Gibt es einen cleveren Algorithmus oder eine Hardware, die damit umgeht?

Nikitablack
quelle

Antworten:

9

Hochspezialisierte Hardware kümmert sich darum. Eine typische Strategie besteht darin, dass eine GPU die Rasterung kachelt und Tiefeninformationen in komprimierten Formaten speichert (z. B. die Z-Gleichung, wenn ein Polygon eine Kachel vollständig bedeckt). Dies ermöglicht das gleichzeitige Testen einer gesamten Kachel. Andere coole HW-Tricks umfassen Tiefentests, bevor der Pixel-Shader ausgeführt wird (vorausgesetzt, die Bedingungen erlauben dies - der Shader kann keinen Tiefenwert schreiben). Sie könnten in der Software etwas Ähnliches in Betracht ziehen, z. B. dass jeder Thread eine Teilmenge von Kacheln "besitzt" und jedes Grundelement unabhängig durchläuft, oder Multi-GPU-Strategien wie alternative Frames oder alternative Rasterlinien nachahmen.

Daniel M Gessel
quelle
11

In einer realen GPU wird der Tiefenpuffer nicht in mehrere Kerne unterteilt, die versuchen, denselben Bereich des Tiefenpuffers zu lesen / schreiben und zwischen ihnen zu synchronisieren, sondern in Kacheln (z. B. 16 × 16 oder 32 × 32) Die Kachel ist einem einzelnen Kern zugeordnet. Dieser Kern ist dann für die gesamte Rasterung in dieser Kachel verantwortlich: Alle Dreiecke, die diese Kachel berühren, werden vom besitzenden Kern (innerhalb dieser Kachel) gerastert. Dann gibt es keine Interferenz zwischen Kernen und sie müssen nicht synchronisiert werden, wenn sie auf ihren Teil des Framebuffers zugreifen.

Dies bedeutet, dass Dreiecke, die mehrere Kacheln berühren, von mehreren Kernen gerastert werden müssen. Es gibt also einen Arbeitsumverteilungsschritt zwischen der Geometrieverarbeitung (Operationen an Scheitelpunkten und Dreiecken) und der Pixelverarbeitung.

In der Geometriephase kann jeder Kern einen Teil der Eingabeprimitive verarbeiten. Dann kann es für jedes Grundelement schnell bestimmen, welche Kacheln das Grundelement berührt (dies wird als "grobe Rasterisierung" bezeichnet), und das Grundelement einer Warteschlange für jeden Kern hinzufügen, der eine der betroffenen Kacheln besitzt.

In der Pixelphase kann dann jeder Kern die Liste der Grundelemente in seiner Warteschlange auslesen, die Pixelabdeckung für die Kacheln des Kerns berechnen und mit dem Tiefentest, der Pixelschattierung und der Aktualisierung des Framebuffers fortfahren, ohne dass eine weitere Koordination erforderlich ist mit anderen Kernen.

Nathan Reed
quelle