Performance

Aus DGL Wiki
Wechseln zu: Navigation, Suche

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, insbesondere da sie meist leicht zu realisieren sind. Beherzigt man diese überhaupt nicht, muss man sich nicht über eine unglaublich langsame Anwendung wundern. Die Reihenfolge gibt so ungefähr die Wichtigkeit an.

  1. Schließe nicht sichtbare Polygone so schnell wie möglich vom Rendering aus.
  2. 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.
  3. 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.
  4. Optimiere deine Modelle. Nutze den Mesh-Optimierer in deiner 3D-Modellierungssoftware. Dies bringt zwar meist nicht viel, ist aber ja auch nicht mehr als ein Knopfdruck.
  5. Die Grafikkarte hat einen Cache für bereits vom Vertexshader verarbeitete Vertices. Dieser wird genutzt, wenn du Indices verwendest. Der Vertexshader muss also im optimalen Fall für jeden Vertex nur einmal ausgeführt werden, auch wenn der Vertex in mehreren Dreiecken verwendet wird. In jedem geschlossenen und 2-mannigfaltigen Dreiecksnetz wird jeder Vertex im Durchschnitt von 6 Dreiecken verwendet. Hier lässt sich also so einiges an Speicher und Rechenleistung sparen.
  6. 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.
  7. 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.

  1. 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.
  2. Überlege ob du Grafikspeicher sparen kannst indem du z.B. Texturkoordinaten oder Normalen zur Laufzeit im Shader berechnest.
    (Beispiel: Heightmap-Terrain)
  3. Wenn du viele identische Objekte renderst, überlege ob du Instancing einsetzen kannst. Erfordert allerdings halbwegs aktuelle Grafikhardware.
  4. Überlege ob du aufwendige Berechnungen, z.B. ein Partikelsystem, nicht besser vollständig auf der Grafikkarte realisierst. Stichworte: Framebuffer-Objects und Transform-Feedback.
  5. Verzichte auf einen Geometryshader, wenn du ihn nicht unbedingt benötigst.
  6. Mit Triangle Strips lässt sich die Anzahl der notwendigen Vertices (bzw. Indices) bis auf ein Drittel reduzieren. Auch hier kommt der Vertexcache der Grafikkarte zum Einsatz.
  7. Überlege ob du deine Modelle immer in höchster Detailstufe ( mit Texturierung, Beleuchtung und anderen teuren Effekten ) zeichnen musst, wenn nicht, verwende LOD ( Level-Of-Detail ).
  8. Überlege ob du bei statischen Szenen komplexe Beleuchtungsberechnungen durch Lightmaps ersetzen kannst. Lightmaps benötigen zwar einiges an Grafikspeicher, jedoch müssen Berechnungen z.B. für Schatten nur einmal durchgeführt werden. Dies funktioniert sowohl bei Shadow Maps als auch bei volumetrischen Stencilschatten. Auch Reflexionen oder Kaustiken stellen kein Problem dar.

Shader

Die folgenden Tipps beschäftigen sich mit der Optimierung von Shadern. Auch hier ist die Reihenfolge nicht von Bedeutung.

  1. Vermeide den Universal-Shader. Shader-Wechsel 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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!
  7. 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!
  8. Es gibt diverse Tools mit denen du feststellen kannst wo genau deine Anwendung die meiste Zeit benötigt bzw. wo du optimieren solltest.

Quellen