Performance: Unterschied zwischen den Versionen

Aus DGL Wiki
Wechseln zu: Navigation, Suche
K (Quellen / Links: Link aktualisiert, AMD hat das Dokument verschoben.)
K
 
(2 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 11: Zeile 11:
 
# Vermeide Kommunikation zwischen CPU und GPU.
 
# 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.
 
#* 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 [[VBO|Vertexbuffer-Objects]] oder [[Displayliste]]n.
+
#* Verwende [[VBO|Vertexbuffer-Objects]] - am besten in Kombination mit [[Vertex Array Object]]s. In sehr alten OpenGL-Programmen kann man stattdessen [[Displayliste]]n verwenden.
 
# Verwende glDraw***-Aufrufe sparsam. Hiermit sind ALLE Befehle gemeint die irgendetwas rendern, z.B. [[glDrawArrays]], [[glDrawElements]], ..., aber auch [[glMultiDrawArrays]], usw...
 
# 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.
 
#* Lieber ein paar Polygone mehr rendern, wenn du dadurch glDraw***-Aufrufe einsparen kannst.
Zeile 17: Zeile 17:
 
# 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.
 
# 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.
 
# 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 [[Mesh|geschlossenen]] und [[Mesh|2-mannigfaltigen]] Dreiecksnetz wird jeder Vertex im Durchschnitt von 6 Dreiecken verwendet. Hier lässt sich also so einiges an Speicher und Rechenleistung sparen.
 
# 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 [[Mesh|geschlossenen]] und [[Mesh|2-mannigfaltigen]] Dreiecksnetz wird jeder Vertex im Durchschnitt von 6 Dreiecken verwendet. Hier lässt sich also so einiges an Speicher und Rechenleistung sparen.
# Nutze den Z-Buffer aus.
+
# Nutze den [[Z-Buffer]] aus.
 
#* Sortiere nicht deine Polygone einzeln nach der Entfernung zur Kamera (Painters-Algorithm), sondern verwende den Z-Buffer.
 
#* 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.
 
#* 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 sortiert vorne nach hinten die undurchsichtigen Objekte. Die transparenten Objekte renderst du dann anschließend von hinten nach vorne.
 
#* Wenn du transparente Objekte hast, rendere zunächst von sortiert vorne nach hinten die undurchsichtigen Objekte. Die transparenten Objekte renderst du dann anschließend von hinten nach vorne.
 
# Vermeide wenn möglich häufige Shader-, Textur-, Material- und Statewechsel.
 
# Vermeide wenn möglich häufige Shader-, Textur-, Material- und Statewechsel.
 +
# Überlege ob es wirklich notwendig ist, am Anfang jedes Frames den Farbpuffer mittels [[glClear]] zu löschen. Wenn jeder Pixel garantiert mindestens einmal pro Frame überschrieben wird, kannst du so wahrscheinlich etwas Speicherbandbreite sparen.
  
 
===Performance Killer===
 
===Performance Killer===
Zeile 30: Zeile 31:
 
=== Fortgeschrittene Techniken ===
 
=== 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.
 
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]].
+
# Ein glDraw*** wird nicht sofort ausgeführt, d.h. die CPU erhält die Kontrolle zurück, bevor die GPU fertig mit dem 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 aufsplitten. 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.<br>(Beispiel: [[Shader#Beispiel:_Heightmap-Terrain|Heightmap-Terrain]])
+
# Überlege ob du Grafikspeicher sparen kannst, indem du z.B. Texturkoordinaten oder Normalen zur Laufzeit im [[Shader]] berechnest.<br>(Beispiel: [[Shader#Beispiel:_Heightmap-Terrain|Heightmap-Terrain]])
 
# Wenn du viele identische Objekte renderst, überlege ob du [[Instancing]] einsetzen kannst. Erfordert allerdings halbwegs aktuelle Grafikhardware.
 
# 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 [[GLSL_Partikel_2|Partikelsystem]], nicht besser vollständig auf der Grafikkarte realisierst. Stichworte: [[FBO|Framebuffer-Objects]] und [[Transform-Feedback]].
 
# Überlege ob du aufwendige Berechnungen, z.B. ein [[GLSL_Partikel_2|Partikelsystem]], nicht besser vollständig auf der Grafikkarte realisierst. Stichworte: [[FBO|Framebuffer-Objects]] und [[Transform-Feedback]].
 
# Verzichte auf einen [[Geometryshader]], wenn du ihn nicht unbedingt benötigst.
 
# Verzichte auf einen [[Geometryshader]], wenn du ihn nicht unbedingt benötigst.
 
# Mit [[Triangulation|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.
 
# Mit [[Triangulation|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.
# Ü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'' ).
+
# Ü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'').
 
# Ü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 [[GLSL_Licht_und_Schatten|Shadow Maps]] als auch bei [[Volumetrische_Stencilschatten|volumetrischen Stencilschatten]]. Auch [[Reflexion]]en oder [[Kaustik]]en stellen kein Problem dar.
 
# Ü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 [[GLSL_Licht_und_Schatten|Shadow Maps]] als auch bei [[Volumetrische_Stencilschatten|volumetrischen Stencilschatten]]. Auch [[Reflexion]]en oder [[Kaustik]]en stellen kein Problem dar.
  
Zeile 47: Zeile 48:
 
# Versuche insbesondere aufwendige Operationen wie zum Beispiel eine Wurzel zu vermeiden. Bedenke das sich solche Operationen auch in eingebauten Funktionen wie beispielsweise {{INLINE_CODE|length}}, {{INLINE_CODE|distance}} und {{INLINE_CODE|normalize}} verstecken können.
 
# Versuche insbesondere aufwendige Operationen wie zum Beispiel eine Wurzel zu vermeiden. Bedenke das sich solche Operationen auch in eingebauten Funktionen wie beispielsweise {{INLINE_CODE|length}}, {{INLINE_CODE|distance}} und {{INLINE_CODE|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!
 
# 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 {{INLINE_CODE|vec4 positionAndSize}} wenig Sinn, wenn Position und Größe nur wenig miteinander zu tun haben und du z.B. ständig mit {{INLINE_CODE|positionAndSize.xyz}} arbeitest. Verwende lieber separate Variablen, also {{INLINE_CODE|vec3 position}} und {{INLINE_CODE|float size}}. So kann der Compiler besser optimieren.<br>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!
+
# Versuche nicht Speicher zu sparen, indem du Variablen im Shader zusammenfasst. Beispielsweise macht eine Variable {{INLINE_CODE|vec4 positionAndSize}} wenig Sinn, wenn Position und Größe nur wenig miteinander zu tun haben und du z.B. ständig mit {{INLINE_CODE|positionAndSize.xyz}} arbeitest. Verwende lieber separate Variablen, also {{INLINE_CODE|vec3 position}} und {{INLINE_CODE|float size}}. So kann der Compiler besser optimieren.<br>Dies gilt natürlich nur für lokale Variablen im Shader. Wenn du mit dieser Methode Werte in einer Textur oder einem VBO zusammenfassen kannst, ist dies natürlich sehr sinnvoll!
 
# Es gibt diverse Tools mit denen du feststellen kannst wo genau deine Anwendung die meiste Zeit benötigt bzw. wo du optimieren solltest.
 
# Es gibt diverse Tools mit denen du feststellen kannst wo genau deine Anwendung die meiste Zeit benötigt bzw. wo du optimieren solltest.
 
#* [http://developer.nvidia.com/object/nvperfkit_home.html NVIDIA PerfKit]
 
#* [http://developer.nvidia.com/object/nvperfkit_home.html NVIDIA PerfKit]

Aktuelle Version vom 30. März 2014, 19:11 Uhr

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 - am besten in Kombination mit Vertex Array Objects. In sehr alten OpenGL-Programmen kann man stattdessen Displaylisten verwenden.
  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 sortiert vorne nach hinten die undurchsichtigen Objekte. Die transparenten Objekte renderst du dann anschließend von hinten nach vorne.
  7. Vermeide wenn möglich häufige Shader-, Textur-, Material- und Statewechsel.
  8. Überlege ob es wirklich notwendig ist, am Anfang jedes Frames den Farbpuffer mittels glClear zu löschen. Wenn jeder Pixel garantiert mindestens einmal pro Frame überschrieben wird, kannst du so wahrscheinlich etwas Speicherbandbreite sparen.

Performance Killer

  1. Die OpenGL eigene Selektion wird nur schlecht als recht unterstützt. Eine Color-Picking-Selection kann abhilfe leisten.
  2. glPushAttrib und glPopAttrib gelten laut diesem ATI Dokument als "State Evils", welche sich negativ auf die Performance auswirken.
  3. Sofern die Extension GL_ARB_texture_non_power_of_two nicht unterstützt wird, könnte eine Grafikkarte bei Texturen deren Kantenlänge keiner 2er-Potenz entspricht (also z.B. 256, 512, 1024, ...) in den Software-Modus umschalten. Im Software-Modus wird die Grafikkarte komplett von der CPU emuliert. Das dies nicht schnell sein kann dürfte klar sein.

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 dem 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 aufsplitten. 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, indem 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 oder einem VBO 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 / Links