Performance: Unterschied zwischen den Versionen
K (Offline-Bearbeitung entfernt) |
|||
Zeile 1: | Zeile 1: | ||
− | |||
− | |||
__TOC__ | __TOC__ | ||
Version vom 2. April 2009, 17:03 Uhr
Inhaltsverzeichnis
Dieser Artikel liefert einige Tipps dazu wie man eine OpenGL-Anwendung optimieren kann. Einige der hier genannten Tipps schließen sich gegenseitig aus. Welche Vorgehensweise die beste ist hängt immer davon ab wo gerade der Engpass liegt. Es bringt zum Beispiel wenig den Shader zu optimieren, wenn eigentlich die zu langsame CPU das Problem ist.
Grundlagen
Die folgenden Tipps sind eigentlich Pflicht. Beherzigt man diese überhaupt nicht, muss man sich nicht über eine unglaublich langsame Anwendung wundern. Die Reihenfolge gibt so ungefähr die Wichtigkeit an.
- Schließe nicht sichtbare Polygone so schnell wie möglich vom Rendering aus.
- Aktiviere Backface Culling.
- Vor allem bei statischer Geometrie (die sich nicht bewegt) bieten sich die etablierten Raumunterteilungstechniken wie BSP-Tree, Quad-Tree, usw. an.
- Vermeide Kommunikation zwischen CPU und GPU.
- Lade nicht deine Texturen/Vertexdaten in jedem Frame neu auf die Grafikkarte hoch. Vermeide den Immediate Mode, also glBegin() und glEnd(). Sofern du aber nur ein, zwei einzelne Dreiecke rendern möchtest ist der Immediate Mode akzeptabel.
- Verwende Vertexbuffer-Objects oder Displaylisten.
- Verwende glDraw***-Aufrufe sparsam. Hiermit sind ALLE Befehle gemeint die irgendetwas rendern, z.B. glDrawArrays, glDrawElements, ..., aber auch glMultiDrawArrays, usw...
- Lieber ein paar Polygone mehr rendern, wenn du dadurch glDraw***-Aufrufe einsparen kannst.
- Es spielt so gut wie keine Rolle, ob du 500 oder 1 Polygon renderst, da ein glDraw***-Aufrufe alleine schon ziemlich viel Zeit braucht.
- Nutze den Z-Buffer aus.
- Sortiere nicht deine Polygone einzeln nach der Entfernung zur Kamera (Painters-Algorithm), sondern verwende den Z-Buffer.
- Sofern du aufwendige Shader oder viele Texturen verwendest, rendere deine Objekte (nicht Polygone) von vorne nach hinten. Also grob sortieren und den Rest den Z-Buffer machen lassen.
- Wenn du transparente Objekte hast, rendere zunächst von die undurchsichtigen Objekte. Die transparenten Objekte renderst du dann sortiert von hinten nach vorne.
- Vermeide wenn möglich häufige Shader-, Textur-, Material- und Statewechsel.
Fortgeschrittene Techniken
Die folgenden Tipps sind nicht so einfach zu realisieren oder erfordern Kenntnisse über fortgeschrittene Features wie Shader, Instancing. Die Reihenfolge der Tipps ist hier nicht von Bedeutung.
- Ein glDraw*** wird nicht sofort ausgeführt, d.h. die CPU erhält die Kontrolle zurück bevor die GPU fertig mit rendern ist. Erst beim vertauschen von Front- und Backbuffer ("SwapBuffers") oder einem expliziten glFinish wird synchronisiert. Folglich gebe zuerst der Grafikkarte was zu arbeiten, rechne dann deine Spiellogik, Physik, etc. auf der CPU und rufe dann erst SwapBuffers auf. Sowas geht natürlich nicht immer, aber man kann beispielsweise Physik und Rendern in zwei Frames aufspilten. Also du berechnest immer die Physik für das nächste Frame, während die GraKa das aktuelle Frame rendert. Siehe auch glFlush.
- Überlege ob du Grafikspeicher sparen kannst indem du z.B. Texturkoordinaten oder Normalen zur Laufzeit im Shader berechnest.
(Beispiel: Heightmap-Terrain) - Wenn du viele identische Objekte renderst, überlege ob du Instancing einsetzen kannst. Erfordert allerdings halbwegs aktuelle Grafikhardware.
- Überlege ob du aufwendige Berechnungen, z.B. ein Partikelsystem, nicht besser vollständig auf der Grafikkarte realisierst. Stichworte: Framebuffer-Objects und Transform-Feedback.
- Verzichte auf einen Geometryshader, wenn du ihn nicht unbedingt benötigst.
Shader
Die folgenden Tipps beschäftigen sich mit der Optimierung von Shadern. Auch hier ist die Reihenfolge nicht von Bedeutung.
- Vermeide den Universal-Shader. Ein Shader-Wechsel ist zwar relativ sind zwar aufwendig, aber eine gigantische if-Verzweigung die für jeden Vertex und jeden Pixel (!) ausgeführt werden muss ist noch wesentlich aufwendiger. Optimiere deinen Shader für die Aufgabe die er erfüllen muss.
- Vermeide aufwendige Berechnungen im Shader. Möglicherweise ist es sinnvoll komplexe Berechnungen im voraus zu berechnen und im Shader eine Lookup-Textur zu verwenden. Sofern eine komplexe Berechnung nur von Uniform-Variablen abhängig ist, ist es oft sinnvoll einfach eine weitere Uniform-Variable zu spendieren und einmal auf der CPU zu berechnen.
- Verwende nach Möglichkeit die in GLSL integrierten Funktionen. Diese Funktionen können zum Teil wesentlich schneller sein da sie zum Teil direkt in der Hardware implementiert sind.
- Optimiere deine Shader so weit wie möglich. Effizienz hat hier Vorrang vor der Lesbarkeit, da die Operationen möglicherweise millionenfach pro Frame ausgeführt werden. Im Fall der Fälle dann einfach mal etwas ausführlicher kommentieren.
- Versuche insbesondere aufwendige Operationen wie zum Beispiel eine Wurzel zu vermeiden. Bedenke das sich solche Operationen auch in eingebauten Funktionen wie beispielsweise length, distance und normalize verstecken können.
- Vermeide Random-Access auf Texturen. Zwei nebeneinander liegende Texel einer Textur können üblicherweise schneller aus dem Speicher geladen werden als zwei Texel an völlig unterschiedlichen Positionen in der Textur. Nutze den GPU-Cache!
- Versuche nicht Speicher zu sparen in dem du Variablen im Shader zusammenfasst. Beispielsweise macht eine Variable vec4 positionAndSize wenig Sinn, wenn Position und Größe nur wenig miteinander zu tun haben und du z.B. ständig mit positionAndSize.xyz arbeitest. Verwende lieber separate Variablen, also vec3 position und float size. So kann der Compiler besser optimieren.
Dies gilt natürlich nur für lokale Variablen im Shader. Wenn du mit dieser Methode Werte in einer Textur zusammenfassen kannst ist dies natürlich sehr sinnvoll!