Ich arbeite mit einem Team an einem kleinen Kachel- / Sprite-basierten PC-Spiel, und es treten Leistungsprobleme auf. Das letzte Mal, dass ich OpenGL verwendet habe, war um 2004, also habe ich mir selbst beigebracht, wie man das Kernprofil verwendet, und ich bin ein wenig verwirrt.
Ich muss in der Nähe von 250-750 48x48 Kacheln jeden Frame auf den Bildschirm zeichnen, sowie vielleicht rund 50 Sprites. Die Kacheln ändern sich nur, wenn ein neues Level geladen wird, und die Sprites ändern sich ständig. Einige der Kacheln bestehen aus vier 24x24-Teilen, und die meisten (aber nicht alle) Sprites haben die gleiche Größe wie die Kacheln. Viele der Kacheln und Sprites verwenden Alpha-Blending.
Im Moment mache ich das alles im Sofortmodus, von dem ich weiß, dass es eine schlechte Idee ist. Trotzdem, wenn eines unserer Teammitglieder versucht, es auszuführen, erhält es sehr schlechte Bildraten (~ 20-30 fps), und es ist viel schlimmer, wenn es mehr Kacheln gibt, besonders wenn viele dieser Kacheln von der Art sind, wie sie sind werden in Stücke geschnitten. Das alles lässt mich glauben, dass das Problem in der Anzahl der getätigten Draw Calls liegt.
Ich habe mir ein paar mögliche Lösungen ausgedacht, aber ich wollte sie von einigen Leuten leiten lassen, die wissen, wovon sie sprechen, damit ich meine Zeit nicht auf etwas Dummes verschwenden kann:
FLIESEN:
- Wenn ein Level geladen ist, ziehe alle Kacheln einmal in einen Framebuffer, der mit einer großen Huptextur verbunden ist, und zeichne einfach in jedem Frame ein großes Rechteck mit dieser Textur.
- Legen Sie alle Kacheln beim Laden des Levels in einen statischen Vertex-Puffer und zeichnen Sie sie auf diese Weise. Ich weiß nicht, ob es eine Möglichkeit gibt, Objekte mit unterschiedlichen Texturen mit einem einzigen Aufruf von glDrawElements zu zeichnen, oder ob ich das überhaupt tun möchte. Vielleicht einfach alle Kacheln in eine riesige Textur legen und lustige Texturkoordinaten in der VBO verwenden?
SPRITES:
- Zeichnen Sie jedes Sprite mit einem separaten Aufruf von glDrawElements. Dies scheint eine Menge Texturwechsel mit sich zu bringen, von denen mir gesagt wurde, dass sie schlecht sind. Sind Texturarrays hier vielleicht nützlich?
- Benutze irgendwie einen dynamischen VBO. Gleiche Texturfrage wie Nummer 2 oben.
- Sprites zeigen? Das ist wahrscheinlich albern.
Ist eine dieser Ideen sinnvoll? Gibt es eine gute Implementierung, über die ich nachsehen könnte?
Antworten:
Die schnellste Methode zum Rendern der Kacheln besteht darin, die Eckendaten in ein statisches VBO mit Indizes zu packen (wie glDrawElements angibt). Das Schreiben in ein anderes Bild ist völlig unnötig und erfordert nur viel mehr Speicher. Textur Schalt ist sehr teuer, so dass Sie wahrscheinlich alle Fliesen in einen so genannten packen wollen Texture Atlas und jedes Dreieck geben in der VBO der richtigen Texturkoordinaten. Auf dieser Basis sollte es kein Problem sein, je nach Hardware 1000 oder sogar 100000 Kacheln zu rendern.
Der einzige Unterschied zwischen Tile-Rendering und Sprite-Rendering besteht wahrscheinlich darin, dass Sprites dynamisch sind. Um die beste und dennoch leicht zu erreichende Leistung zu erzielen, können Sie die Koordinaten für die Sprite-Scheitelpunkte einfach in ein Stream-Draw-VBO für jeden Frame einfügen und mit glDrawElements zeichnen. Packe auch alle Texturen in einen Texturatlas. Wenn sich Ihre Sprites nur selten bewegen, können Sie auch versuchen, ein dynamisches VBO zu erstellen und zu aktualisieren, wenn sich ein Sprite bewegt. Dies ist jedoch ein völliger Overkill, da Sie nur einige Sprites rendern möchten.
Sie können sich einen kleinen Prototyp ansehen, den ich in C ++ mit OpenGL: Particulate erstellt habe
Ich rendere ungefähr 10000 Punkt-Sprites mit einer durchschnittlichen Geschwindigkeit von 400 fps auf einem normalen Computer (Quad Core bei 2,66 GHz). Es ist CPU-begrenzt, was bedeutet, dass die Grafikkarte noch mehr rendern kann. Beachten Sie, dass ich hier keine Texturatlanten verwende, da ich nur eine einzige Textur für die Partikel habe. Die Partikel werden mit GL_POINTS gerendert und die Shader berechnen dann die tatsächliche Quad-Größe, aber ich denke, es gibt auch einen Quad-Renderer.
Oh, und ja, es sei denn, Sie haben ein Quadrat und verwenden Shader für das Textur-Mapping, dann ist GL_POINTS ziemlich albern. ;)
quelle
Selbst bei dieser Anzahl von Draw-Anrufen sollte die Leistung nicht abnehmen. Der unmittelbare Modus ist zwar langsam, aber nicht so langsam (als Referenz kann sogar das alte Quake mehrere tausend Anrufe im unmittelbaren Modus pro Frame verwalten, ohne zu fallen so schlecht runter).
Ich vermute, dass hier etwas Interessanteres vor sich geht. Das erste, was Sie tun müssen, ist, einige Zeit in die Profilerstellung Ihres Programms zu investieren. Andernfalls besteht ein enormes Risiko für eine Neugestaltung, basierend auf einer Annahme, die zu einem Leistungsgewinn von Null führen kann. Sehen Sie sich einmal so grundlegende Dinge wie GLIntercept an und finden Sie heraus, wohin Ihre Zeit geht. Basierend auf den Ergebnissen werden Sie in der Lage sein, das Problem mit einigen wirklichen Informationen über Ihre primären Engpässe anzugehen .
quelle
Okay, da meine letzte Antwort hier irgendwie außer Kontrolle geraten ist, handelt es sich um eine neue, die vielleicht nützlicher ist.
Über 2D-Performance
Zunächst einige allgemeine Ratschläge: 2D stellt keine Anforderungen an die aktuelle Hardware, selbst weitgehend nicht optimierter Code wird funktionieren. Das bedeutet jedoch nicht, dass Sie den Zwischenmodus verwenden sollten. Stellen Sie zumindest sicher, dass Sie den Status nicht ändern, wenn dies nicht erforderlich ist (binden Sie beispielsweise keine neue Textur mit glBindTexture, wenn dieselbe Textur bereits gebunden ist, und überprüfen Sie die CPU auf Tonnen schneller als ein glBindTexture-Aufruf) und nichts völlig Falsches und Dummes wie glVertex zu benutzen (sogar glDrawArrays werden viel schneller sein und sind nicht schwieriger zu benutzen, aber nicht sehr "modern"). Mit diesen beiden sehr einfachen Regeln sollte die Frame-Zeit mindestens 10 ms (100 fps) betragen. Um noch mehr Geschwindigkeit zu erreichen, ist der nächste logische Schritt das Batching, z. B. das Bündeln von beliebig vielen Draw-Aufrufen in einem. Hierzu sollten Sie Texturatlanten implementieren. So können Sie die Anzahl der Texturbindungen minimieren und somit die Anzahl der Rechtecke, die Sie mit einem Aufruf zeichnen können, auf eine große Menge erhöhen. Wenn du jetzt nicht auf ungefähr 2ms (500fps) bist, machst du etwas falsch :)
Karten kacheln
Durch die Implementierung des Zeichencodes für Kachelkarten wird das Gleichgewicht zwischen Flexibilität und Geschwindigkeit hergestellt. Sie können statische VBOs verwenden, aber das funktioniert nicht mit animierten Kacheln, oder Sie können einfach die Scheitelpunktdaten für jeden Frame generieren und die oben erläuterten Regeln anwenden. Das ist sehr flexibel, aber bei weitem nicht so schnell.
In meiner vorherigen Antwort hatte ich ein anderes Modell eingeführt, in dem der Fragment-Shader die gesamte Texturierung übernimmt. Es wurde jedoch darauf hingewiesen, dass dies eine abhängige Textur-Suche erfordert und daher möglicherweise nicht so schnell ist wie die anderen Methoden. (Die Idee ist im Grunde, dass Sie nur die Kachelindizes hochladen und im Fragment-Shader die Texturkoordinaten berechnen, was bedeutet, dass Sie die gesamte Karte mit nur einem Rechteck zeichnen können.)
Sprites
Sprites erfordern viel Flexibilität, was es sehr schwierig macht, sie zu optimieren, abgesehen von den im Abschnitt "Über 2D-Leistung" beschriebenen. Und wenn Sie nicht gleichzeitig zehntausende Sprites auf dem Bildschirm haben möchten, lohnt sich die Mühe wahrscheinlich nicht.
quelle
Wenn alle Stricke reißen...
Richten Sie eine Flip-Flop-Zeichenmethode ein. Aktualisieren Sie jeweils nur jedes andere Sprite. Selbst mit VisualBasic6 und einfachen Bit-Blit-Methoden können Sie Tausende von Sprites pro Frame aktiv zeichnen. Vielleicht sollten Sie sich diese Methoden genauer ansehen, da Ihre direkte Methode, nur Sprites zu zeichnen, anscheinend fehlschlägt. (Klingt eher so, als würden Sie eine "Rendermethode" verwenden, aber versuchen, sie wie eine "Spielemethode" zu verwenden. Beim Rendern geht es um Klarheit, nicht um Geschwindigkeit.)
Es besteht die Möglichkeit, dass Sie ständig den gesamten Bildschirm neu zeichnen. Anstatt nur die geänderten Bereiche neu zu zeichnen. Das ist eine Menge Aufwand. Das Konzept ist einfach, aber nicht leicht zu verstehen.
Verwenden Sie einen Puffer für den jungfräulichen statischen Hintergrund. Dies wird niemals selbst gerendert, es sei denn, auf dem Bildschirm befinden sich keine Sprites. Dies wird ständig verwendet, um "zurückzusetzen", wo ein Sprite gezeichnet wurde, um das Sprite beim nächsten Aufruf zu entzeichnen. Sie benötigen auch einen Puffer zum "Zeichnen", bei dem es sich nicht um den Bildschirm handelt. Sie zeichnen dort, und wenn Sie alle gezeichnet haben, klappen Sie das Bild einmal auf den Bildschirm. Das sollte ein Screen-Call für alle deine Sprites sein. (Im Gegensatz dazu, jedes Sprite einzeln auf dem Bildschirm zu zeichnen oder zu versuchen, alles auf einmal zu machen, schlägt die Alpha-Überblendung fehl.) Das Schreiben in den Speicher ist schnell und erfordert keine Bildschirmzeit zum Zeichnen ". Jeder Draw-Call wartet auf ein Return-Signal, bevor er erneut versucht, zu zeichnen. (Kein V-Sync, ein tatsächlicher Hardware-Tick, der viel langsamer ist als die Wartezeit, die der RAM hat.)
Ich stelle mir vor, dass dies ein Grund dafür ist, dass Sie dieses Problem nur auf einem Computer sehen. Oder es wird auf das Software-Rendering von ALPHA-BLEND zurückgegriffen, das nicht von allen Karten unterstützt wird. Prüfen Sie, ob diese Funktion von der Hardware unterstützt wird, bevor Sie versuchen, sie zu verwenden? Haben Sie ein Fallback (Nicht-Alpha-Mischmodus), wenn sie es nicht haben? Offensichtlich haben Sie keinen Code, der die Grenzen (Anzahl der gemischten Dinge) festlegt, da ich davon ausgehe, dass dies Ihren Spielinhalt beeinträchtigen würde. (Im Gegensatz dazu, wenn es sich nur um Partikeleffekte handelt, die alle Alpha-gemischt sind, und daher, warum Programmierer sie einschränken, da sie auf den meisten Systemen selbst mit Hardware-Unterstützung eine hohe Belastung darstellen.)
Zuletzt würde ich vorschlagen, das, was Sie Alpha-Blending sind, nur auf Dinge zu beschränken, die es brauchen. Wenn alles es braucht ... Sie haben keine andere Wahl, als von Ihren Benutzern bessere Hardwareanforderungen zu fordern oder das Spiel für die gewünschte Leistung zu verschlechtern.
quelle
Erstellen Sie ein Sprite-Blatt für Objekte und ein Geländeset wie in anderen 2D-Spielen. Es ist nicht erforderlich, die Texturen zu wechseln.
Das Rendern von Kacheln kann schmerzhaft sein, da jedes Dreieckspaar seine eigenen Texturkoordinaten benötigt. Für dieses Problem gibt es eine Lösung, die als instanziiertes Rendern bezeichnet wird .
Solange Sie Ihre Daten so sortieren können, dass Sie beispielsweise eine Liste mit Grasplättchen und deren Positionen haben, können Sie jedes Grasplättchen mit einem einzigen Zeichenaufruf rendern. Sie müssen lediglich ein Array bereitstellen von Modell zu Weltmatrizen für jedes Plättchen. Das Sortieren Ihrer Daten auf diese Weise sollte auch mit dem einfachsten Szenendiagramm kein Problem sein.
quelle