So verbessern Sie die Dosierleistung

9

Ich entwickle ein Sprite-basiertes 2D-Spiel für mobile Plattformen und verwende OpenGL (eigentlich Irrlicht) zum Rendern von Grafiken. Zuerst habe ich das Sprite-Rendering auf einfache Weise implementiert: Jedes Spielobjekt wird als Quad mit einem eigenen GPU-Draw-Aufruf gerendert. Wenn ich also 200 Spielobjekte hätte, hätte ich 200 Draw-Aufrufe pro Frame durchgeführt. Natürlich war dies eine schlechte Wahl und mein Spiel war vollständig CPU-gebunden, da bei jedem GPU-Draw-Aufruf ein kleiner CPU-Overhead anfällt. Die GPU blieb die meiste Zeit im Leerlauf.

Jetzt dachte ich, ich könnte die Leistung verbessern, indem ich Objekte in großen Stapeln sammle und diese Stapel mit nur wenigen Zeichenaufrufen rendere. Ich habe Batching implementiert (so dass jedes Spielobjekt mit derselben Textur im selben Batch gerendert wird) und dachte, dass meine Probleme weg sind ... nur um herauszufinden, dass meine Framerate noch niedriger war als zuvor.

Warum? Nun, ich habe 200 (oder mehr) Spielobjekte und sie werden 60 Mal pro Sekunde aktualisiert. Für jeden Frame muss ich eine neue Position (Translation und Rotation) für Scheitelpunkte in der CPU neu berechnen (GPU auf mobilen Plattformen unterstützt keine Instanzierung, daher kann ich dies dort nicht tun) und diese Berechnung 48000 pro Sekunde (200 * 60 * 4 seitdem) durchführen Jedes Sprite hat 4 Eckpunkte) scheint einfach zu langsam zu sein.

Was könnte ich tun, um die Leistung zu verbessern? Alle Spielobjekte bewegen / drehen sich (fast) in jedem Frame, daher muss ich die Scheitelpunktpositionen wirklich neu berechnen. Die einzige Optimierung, die mir einfällt, ist eine Nachschlagetabelle für Rotationen, damit ich sie nicht berechnen muss. Würden Punkt-Sprites helfen? Irgendwelche bösen Hacks? Noch etwas?

Vielen Dank.

user4241
quelle

Antworten:

5

Hast du meinen irrlicht Port für Android benutzt? Für 2D-Sprites auf Android und iPhone verwende ich die gleichen Tricks wie Sie: Batching. Ich probiere viele Lösungen in OpenGL ES 1.x und 2.x aus:

  • Sortieren Sie nach z (Parallaxe) und Textur, führen Sie die Transformationen auf der CPU durch und rufen Sie glDrawArrays oder glDrawElements (schnellster Weg) auf. Verwenden Sie eine große Textur, wenn Sie können.
  • Gleicher Trick mit VBO, nicht schneller, da Sie für jeden Frame alle Informationen aktualisieren. Es kann für statische Sprites nützlich sein.
  • Verwenden Sie OpenGL ES 2.x und verwenden Sie den Vertex-Shader, um Positionen zu berechnen (langsamer).
  • Verwenden Sie PointSprites (keine Lösung, wenn es sich nicht um ein Quadrat handelt und zu viele transparente Pixel die Füllrate töten).
  • benutze die Erweiterung gldrawtexoes ...
  • Verwenden Sie für jedes Sprite einen Drawcall (langsamste Methode)

Wie Sie werden alle Transformationen von der CPU für OGLES 1.x oder OGLES 2.x durchgeführt. Wenn Sie Neonanweisungen haben, können Sie diese verwenden, um Ihre Berechnungen zu beschleunigen.

Ps: Auf iPhone- oder Android-Geräten bin ich nicht auf die CPU, sondern auf die Füllrate beschränkt. Daher ist es sehr wichtig, die Überziehung zu begrenzen.

Ellis
quelle
Ausgezeichnet, das habe ich gesucht. Ihr Irrlicht-Port war mir nicht bekannt, aber meine Version von Irrlicht läuft bereits unter iOS. Sie sagen, Sie sind nicht CPU-begrenzt - wie viele Sprites zeichnen Sie? Und wie hoch sind beispielsweise Ihre Frameraten für 100 Sprites auf dem iPhone? Wenn ich 200 Objekte habe, mache ich am Ende 48000 Berechnungen pro Sekunde. Ihr Standpunkt zur Füllrate ist gut.
user4241
Statische Sprites (Hintergrund) sind in VBO. Ich benutze einen VBO pro Parallaxe. Ansonsten habe ich 100 bis 200 Sprites auf Moblox. Auf allen iPhones einschließlich des 3G habe ich mehr als 30 fps (wie ich mich erinnere). Aber große Sprites sind sehr teuer (Füllratenproblem) ....
Ellis
Ich arbeite an einer Partikel-Engine, die ich mit bis zu 20 000 Partikeln verwenden kann, wobei alle Positionsberechnungen auf der CPU durchgeführt werden, und ich habe 10 fps mit extremen Einstellungen (auf 3GS und iPhone4). 1000 Sprites müssen also auf 3GS oder iPhone4 mit guter Framerate möglich sein.
Ellis
Vielen Dank, sehr hilfreich! Wie implementieren Sie Ihre Partikelmaschine? Ich nehme an, du spielst mit Shadern herum?
user4241
Ich benutze Shader, weil ich gl_PointSize brauche, um jede Partikelgröße einzurichten. Ich arbeite nicht mehr mit OGLES 1.x, weil alte Telefone nicht mein Ziel sind. Zuerst war mein gesamter Code OGLES 1.x, dann OGLES 1.x und OGLES 2.x (keine Leistungsverbesserung) und jetzt OGLES 2.x (Rendering-Verbesserung).
Ellis
1

Ich würde empfehlen, ein VBO zu haben, wobei jeder Scheitelpunkt die Position / Drehung jedes gerenderten Objekts enthält und die Stapelung basierend auf der Textur erfolgt, wie Sie es tun. Ich bin mit ogl ES nicht sehr vertraut, daher bin ich mir nicht sicher, welche Version von glsl unterstützt wird, aber Sie können möglicherweise sogar anhand einer Reihe von Texturen stapeln und speichern, welche der vier oder mehr Texturen Sie übergeben in würden Sie innerhalb des Scheitelpunkts verwenden. Punkt-Sprites würden definitiv Ihre Leistung verbessern, da dies die Datenmenge, die Sie senden, drastisch reduzieren würde und das Stapeln niemals die Leistung verringern sollte, wenn Sie es richtig machen. Sie können die Leistung auch ein wenig verbessern, indem Sie die Rotation auf dem Shader berechnen und nur einen int / float-Wert an die Parameter oder innerhalb des Scheitelpunkts selbst übergeben. (Parameter wären schneller,

Sringer
quelle
Vielen Dank für Ihre Antwort. Ihr Vorschlag zur Rotationsberechnung im Shader ist hervorragend, aber leider verwende ich OpenGL ES 1, das keine Shader unterstützt, sodass ich bei einer festen Pipeline stecke. Ich werde Punkt-Sprites ausprobieren, aber ich kann sie nicht in allen Fällen verwenden, da es eine Obergrenze für ihre Größe gibt. Ich bin immer noch ein bisschen pessimistisch in Bezug auf VBO. Wenn ich die Position jedes Scheitelpunkts in jedem Frame neu berechne, wie hilft VBO?
user4241
Dadurch können Ihre Scheitelpunktdaten auf der GPU verbleiben, wodurch sich die Datenmenge verringert, die Sie in jedem Frame an die GPU senden müssen. Sie benötigen keine Shader, um dies zu nutzen. Sie sollten die Scheitelpunktdaten überhaupt nicht ändern müssen. Wenn Sie für jedes Sprite eine Basisposition (z. B. den Ursprung) haben, können Sie die Weltmatrix einfach um ändern Es wird transformiert, bevor Draw aufgerufen wird. Dies kann jedoch beim Chargen schwierig sein. Wenn Sie eine feste Funktion verwenden, ist es wahrscheinlich vorteilhafter, einfach zu VBOs zu wechseln und die Stapelverarbeitung zumindest vorerst zu löschen, was Ihnen definitiv einen Schub gibt.
Sringer
Ich verstehe dein Argument. Sie sprechen also nicht vom Stapeln, sondern verwenden einfach einen Draw-Aufruf, um ein Spielobjekt zu zeichnen. Ich werde auf jeden Fall testen, wie sich VBO ohne Batching auf FPS in meinem Spiel auswirkt, aber immer noch 200 Draw Calls pro Frame klingen zu groß ... aber ich denke, ich muss dann damit leben. Ich werde Ihre Antwort akzeptieren, wenn keine anderen Antworten angezeigt werden.
user4241
1

Sie erwähnen mobile Plattformen ohne Instanz. Aber Sie haben immer noch Vertex-Shader, nicht wahr?

In diesem Fall können Sie immer noch Pseudo-Instanzen durchführen, was ebenfalls sehr schnell ist. Erstellen Sie ein VBO (GL_STATIC_DRAW) mit den Eckpunkten (relativ zum Mittelpunkt des Sprites, z. B. -1 / -1, 1 / -1, 1/1, -1/1) und allen erforderlichen Texturkoordinaten .
Setzen Sie dann eines der generischen Scheitelpunktattribute für jeden Zeichenaufruf auf den Mittelpunkt des Sprites und zeichnen Sie die beiden Dreiecke mit dem gebundenen Puffer. Lesen Sie im Vertex-Shader das generische Vertex-Attribut und fügen Sie die Koordinaten des Vertex hinzu.

Das erspart Ihnen das Blockieren einer Datenübertragung für jedes Sprite und sollte viel schneller sein. Die tatsächliche Anzahl der Draw Calls ist nicht so schrecklich wichtig, das Blockieren / Abwürgen dazwischen ist.

dm.skt
quelle
Dies klingt nach einer guten Lösung für OpenGL ES 2.0. Leider verwende ich ES 1, das überhaupt keine Shader hat.
user4241
0

Das Problem liegt in der Datenmenge, die Sie in jedem Frame an die GPU senden. Erstellen Sie einfach einen VBO für jeden Stapel und füllen Sie ihn einmal aus. Wenden Sie dann beim Zeichnen der Stapel die entsprechenden Transformationsmatrizen an (über glMultMatrix oder einen Shader, wenn Sie ES 2.0 verwenden).

r2d2rigo
quelle
Ich verstehe nicht, wie dies hilft, wenn ich 200 separate Spielobjekte mit einzigartigen Transformationen habe. Die Verwendung von glMultMatrix würde dieselbe Transformation auf alle Objekte anwenden, was nicht das ist, was ich will. Das Senden von Daten an die GPU ist ebenfalls kein Engpass. Wenn ich CPU-seitige Transformationen entferne, ist die Leistung sehr gut.
user4241
Ja, aber ein VBO kann bei korrekter Anwendung die Leistung verbessern. Wie rendern Sie derzeit Ihre 200 Objekte? Verwenden Sie glBegin / glEnd?
TheBuzzSaw
1
Ich verwende die Irrlicht 3D-Engine mit einem benutzerdefinierten Szenenknoten, daher verwende ich OpenGL nicht direkt (aber ich nehme an, dass in diesem Fall einfaches glBegin / glEnd verwendet wird). Würde VBO wirklich helfen, da ich den gesamten Puffer in jedem Frame ändern müsste? Dies löst auch nicht das grundlegende Problem der CPU-Bindung aufgrund von Vertex-Transformationsberechnungen. Aber trotzdem danke für deine Antworten!
user4241