Profiling für mobile Spiele mit Unity und ARM in Angriff nehmen

Erfahren Sie, wie Sie mit den Profiling-Tools von Unity und ARM Leistungsprobleme im mobilen Bereich angehen können. Sie erfahren, wie Sie mit Unity Profile erstellen, wie Sie Leistungseinbußen optimieren können und erhalten Tipps und Tricks, wie Sie das Beste aus Ihren Spiel-Assets herausholen.
In diesem Blog untersuchen wir, wie man Leistungsprobleme in einem mobilen Spiel mit Hilfe von Profiling-Tools von Unity und ARM identifizieren kann. Außerdem stellen wir bewährte Verfahren zur Optimierung von Inhalten für mobile Spiele vor.
Um Leistungsprobleme in Ihrem Spiel zu erkennen, sollten Sie es zunächst auf einer Reihe verschiedener Geräte testen. Am besten ist es, ein Leistungsprofil auf einem echten Gerät zu erstellen. Tools wie der Unity Profiler und der Frame Debugger können Ihnen einen guten Einblick geben, wo die Elemente Ihres Spiels ihre Ressourcen verbrauchen. Darüber hinaus ermöglichen Tools wie Arm Mobile Studio die Erfassung von Performance-Counter-Aktivitätsdaten vom Gerät, so dass Sie genau sehen können, wie Ihr Spiel die CPU- und GPU-Ressourcen nutzt. Das von uns verwendete Gerät hat zwar einen Mali-Grafikprozessor, aber die hier vorgestellten Konzepte gelten auch für andere mobile Grafikprozessoren.
Bei dem von uns getesteten Spiel handelt es sich um ein Action-Rollenspiel, bei dem der Spieler Wellen von ankommenden feindlichen NSCs mit Nahkampf- und Zauberangriffen bekämpfen muss. Diese Art von Spiel kann auf einem mobilen Gerät schnell an die Grenzen des Grafikprozessors stoßen, wenn immer mehr Gegner auf dem Bildschirm erscheinen und mehrere visuelle Partikel- und Post-Processing-Effekte auftreten.
Wir haben das Spiel durch den Unity Profiler laufen lassen, um etwaige Leistungseinbußen zu ermitteln. Wir haben ein paar hochrangige Verdächtige gefunden, Post-Processing und behobene Zeitschritt- und Instanzierungsspitzen.
Die Post-Processing-Effekte waren eine der Hauptursachen für die schlechte CPU-Leistung des Spiels.

Von allen Nachbearbeitungseffekten war der Bloom-Pass, der helle Bereiche in der Szene zum Leuchten bringt, der anstrengendste.
Im obigen Screenshot können Sie sehen, dass die Render-Kamera sehr viel Zeit benötigt und die Frame-Grenze überschreitet. Der Hauptthread wartet dann, bis die Rendering-Befehle abgeschlossen sind, bevor er das nächste Bild vorbereitet. Schauen wir uns den Unity Frame Debugger an, um herauszufinden, was hier los ist.

Das erste, was man im Frame Debugger sieht, ist, dass das Spiel mit der vollen Bildschirmauflösung des Geräts gerendert wird. Für ein durchschnittliches mobiles Gerät bedeutet dies angesichts der Komplexität der Inhalte eine unangemessene Belastung für die GPU des Geräts. Eine Verringerung der Auflösung auf etwas Vernünftigeres wie 1080p oder sogar 720p würde die Kosten für das Rendern des Spiels, insbesondere für die Nachbearbeitungseffekte, erheblich senken.

Die nächste Beobachtung ist, dass der Bloom-Effekt bei 25 Aufrufen der Bloom-Pyramide auftritt. Jeder Zeichenaufruf repräsentiert einen Zielpuffer mit einer Größe, die mit der halben Auflösung des Vollbildgeräts beginnt. Diese Auflösung wird dann bei jeder Iteration halbiert. Die Verringerung der anfänglichen Rendering-Auflösung ist eine Möglichkeit, die Anzahl der möglichen Iterationen zu reduzieren. Eine andere Möglichkeit wäre, den Quellcode des Bloom-Effekts zu ändern, um die Anzahl der Iterationen zu verringern und eine sinnvolle Grenze festzulegen. In diesem Fall wäre es jedoch besser, die Nachbearbeitungseffekte vorerst zu deaktivieren, da die Bearbeitung dieser Effekte sehr viel Zeit in Anspruch nimmt. Zumindest solange, bis der Rest des Spiels mit 30 Bildern pro Sekunde flüssig läuft.
Eine weitere Verbesserung für das Projekt wäre die Verringerung der Häufigkeit der festen Zeitschrittintervalle. Wie wir sehen können, ist sie derzeit kurz genug, um mehrmals pro Frame aufgerufen zu werden; standardmäßig setzt Unity sie auf 0,02 oder 50 Hz. Für mobile Titel, die auf 30 FPS abzielen, können Sie einen festen Timestep-Wert von 0,04 verwenden. Der Grund dafür ist, dass bei 0,333, was 30 FPS entspräche, die Möglichkeit besteht, dass ein Frame zeitlich ausschlägt und man im nächsten Frame zwei Aufrufe hat. Das bedeutet, dass es länger dauert - und Sie können den Zyklus eines etwas längeren Rahmens nie durchbrechen. Der Benutzer kann auch die maximal zulässige Zeitspanne einstellen, um zu verhindern, dass das Aufholen länger als die gewünschte Zeitspanne dauert.
Diese Timestep-Dauer wirkt sich auf Skripte aus, die die FixedUpdate-Funktion verwenden, sowie auf alle Unity-internen Systeme, die im festen Aktualisierungsintervall aktualisiert werden, z. B. Physik und Animation.

Im Rahmen dieses Projekts trugen nur die Physik und die Cinemachine erheblich zur benötigten Zeit bei, nämlich etwa 3 ms pro Aufruf; ein Aufruf bedeutet, dass das System vollständig aktualisiert wurde (obwohl ein zusätzlicher Aufruf 5 Mal bedeutete, dass dies bis zu 15 ms pro Frame an verschwendeter Zeit ausmachen konnte).

Dies ist auf die langsamen Nachbearbeitungseffekte zurückzuführen. Das Ausschalten dieser Funktionen verringert den Zeitaufwand, aber die frühere Empfehlung, die feste Timestep-Frequenz zu verringern, um unnötige Arbeit für die CPU zu vermeiden, bleibt bestehen.
Bei der Profilerstellung waren Spitzen in der Bildzeit zu erkennen. Wenn man sie in der Hierarchieansicht des CPU-Profilers aufspürt, zeigt sich, dass sie von der Instanziierung von NPCs herrühren.

Die gebräuchlichste Lösung hierfür ist, die Zeichen im Voraus zu instanziieren und sie in einer Art Objektpool im Ruhezustand zu halten. Diese NSCs können dann ohne Instanzierungskosten aus dem Pool geholt werden. Wenn mehr benötigt werden, kann der Pool nach Bedarf erweitert werden.
Das gleiche Problem tritt auch bei der Verwendung von Fähigkeiten auf, da diese ebenfalls Objekte instanziieren.

Das Pooling von Objekten ist der einfachste Weg, diese Probleme zu lösen. Dies kann sich zwar auf die Ladezeiten auswirken, ermöglicht aber eine wesentlich flüssigere Bildrate zur Laufzeit, was in diesem Fall das geringere Übel ist.
Wir haben auch Arm Mobile Studio verwendet, um mehr Einblick in das Spielverhalten zu erhalten. Mit den Tools in Mobile Studio können wir Leistungsdaten für die CPU und GPU abrufen, so dass wir genau sehen können, wie das Spiel die Ressourcen des Geräts nutzt.
Sie können Arm Mobile Studio hier kostenlos herunterladen. Es sind 4 Werkzeuge enthalten:
- Performance Advisor - zur Erstellung übersichtlicher Berichte und für Optimierungshinweise
- Streamline - ein umfassender Leistungsprofiler zur Erfassung aller Zähleraktivitäten
- Mali Offline Compiler - um zu prüfen, wie ein Shader-Programm auf einer Mali GPU funktionieren würde
- Graphics Analyzer - zum Debuggen von Grafik-API-Aufrufen und Analysieren der Darstellung von Inhalten
Performance Advisor liefert uns einen schnellen Überblick über die Spielleistung und ist als regelmäßiger Gesundheitscheck gedacht. Ein Bericht ist schnell erstellt, vor allem, wenn Sie ihn in einen kontinuierlichen Integrationsworkflow zusammen mit Ihrem nächtlichen Build-System einbauen. Performance Advisor liefert uns einen schnellen Überblick über die Spielleistung und ist als regelmäßiger Gesundheitscheck gedacht. Ein Bericht ist schnell erstellt, vor allem, wenn Sie ihn in einen kontinuierlichen Integrationsworkflow zusammen mit Ihrem nächtlichen Build-System einbauen.

Während der ersten 2 Minuten des Spiels sagt uns der Performance Advisor, dass wir im Durchschnitt nur 17 Bilder pro Sekunde haben. Der grüne Bereich zu Beginn der Frameratenanalyse zeigt an, wo das Spiel geladen wird. Dann wird die Grafik plötzlich blau und zeigt an, dass das Spiel fragmentiert ist, und so bleibt es auch. Dies bedeutet, dass der Grafikprozessor des Geräts Schwierigkeiten hat, Fragmente zu verarbeiten, was darauf hindeutet, dass das Spiel entweder zu viel Arbeit abverlangt oder die Pixel nicht effizient verarbeitet werden.
Da wir dem Spiel Anmerkungen zu den Regionen hinzugefügt haben, zeigt das Diagramm zur Bildratenanalyse unsere benutzerdefinierten Regionsnamen an. An den Stellen, an denen das Diagramm eine mit "S" beschriftete Markierung zeigt, hat Performance Advisor einen Screenshot des Spiels gemacht, um uns zu zeigen, was an dieser Stelle auf dem Bildschirm passiert. Sie können konfigurieren, dass Bildschirmaufnahmen gemacht werden, wenn die Bildwiederholrate unter einen bestimmten Wert fällt. Da die FPS durchgehend niedrig bleibt, macht Performance Advisor alle 200 Bilder einen Screenshot.
Werfen Sie einen Blick auf die Grafik der GPU-Zyklen pro Frame, wo wir ein Budget von 28 Millionen Zyklen pro Frame für dieses Gerät hinzugefügt haben. Wir haben geschätzt, dass dies die maximale Anzahl von Zyklen ist, die dieses Gerät verarbeiten kann, während es immer noch eine Bildrate von 30 FPS erreicht. Hier können wir sehen, dass die Anzahl der GPU-Zyklen dieses Budget deutlich übersteigt und dass die Anzahl der Zyklen mit der Zeit zunimmt.

Der Performance Advisor gibt Optimierungshinweise, wenn er ein Problem findet. Ein Blick auf das Diagramm der Shader-Zyklen pro Frame zeigt, dass die Anzahl der Zyklen der Ausführungsmaschine hoch ist. In einem Mali-Shader-Kern ist die Ausführungs-Engine für die Verarbeitung arithmetischer Operationen zuständig. Performance Advisor hat dies als Problem erkannt und rät uns, die Berechnungen in Shadern zu reduzieren.

Es gibt eine einfache Lösung für dieses Problem. Sie können die Genauigkeit von Shader-Variablen auf mediump statt auf highp reduzieren, ohne dass sich auf dem Bildschirm etwas ändert. Dadurch werden die Shader-Kosten erheblich gesenkt. Informationen dazu finden Sie unter Shader-Datentypen und Präzision in unserer Dokumentation. Wie wir bereits mit dem Frame Debugger von Unity festgestellt haben, wird das Spiel derzeit in der vollen Bildschirmauflösung des Geräts gerendert. Alle Änderungen, die wir vornehmen, um die Rendering-Auflösung des Spiels zu reduzieren (auf 1080p oder 720p), werden auch die Fragment-Shading-Kosten reduzieren.
Wir hatten für dieses Gerät ein Budget von 500.000 Vertices pro Frame festgelegt. Das Budget wird nach etwa 45 Sekunden überschritten, und die Zahl steigt mit der Zeit stetig an.

Betrachtet man das Diagramm der Primitive pro Frame, so stellt man fest, dass die Gesamtzahl der verarbeiteten Primitive mit der Zeit zunimmt, obwohl die Anzahl der sichtbaren Primitive relativ konstant bleibt. In den ersten 2 Minuten des Spiels sind die einzigen neuen Objekte, die erschaffen werden, die gegnerischen NSCs, die dann von unserem Helden mit einem Blitz zerstört werden. Dies deutet darauf hin, dass die Geometrie der Feinde auch nach ihrer Zerstörung noch vorhanden ist, auch wenn sie nicht sichtbar ist.

Es gibt mehrere Gründe, warum die GPU nicht in der Lage sein könnte, die Anforderungen des Spiels zu bewältigen, daher müssen wir das Profiling-Tool von ARM mit Streamline näher untersuchen. Streamline wird uns mehr über diese schwere Fragment-Arbeitslast verraten, und wenn wir uns die anderen Zähler ansehen, können wir Hinweise finden, wie wir die Last verringern können.
Wenn wir uns denselben Abschnitt des Spiels in Streamline ansehen, können wir eine Reihe von Diagrammen untersuchen, die die GPU-Zähleraktivität für die verschiedenen Phasen der Geometrie- und Pixelverarbeitung zeigen. Dies zeigt, wie der Inhalt des Spiels von der GPU verarbeitet wird und ob es unnötige Prozesse gibt.
Mali-basierte Grafikprozessoren verfolgen einen kachelbasierten Ansatz bei der Verarbeitung von Grafikaufgaben, bei dem der Bildschirm in Kacheln aufgeteilt wird und jede Kachel der Reihe nach abgearbeitet wird. Für jede Kachel wird zuerst die Geometrieverarbeitung durchgeführt, dann werden die Pixel bei der Pixelverarbeitung eingefärbt.

Wir wissen bereits, dass die GPU des Geräts bei Fragmenten an ihre Grenzen stößt, daher müssen wir nach Möglichkeiten suchen, den Druck auf die Pixelverarbeitungsstufe zu verringern.
Eine Möglichkeit, die Belastung durch die Pixelverarbeitung zu reduzieren, besteht darin, die Komplexität der Geometrie, die zur Pixelverarbeitung gesendet wird, zu verringern. Geometrien, die sich vollständig außerhalb des Bildschirms befinden oder nach hinten zeigen, werden vor der Pixelverarbeitung gelöscht, aber kleine Dreiecke, die nur teilweise 2×2-Pixel-Quads abdecken, können die Fragmentierungseffizienz beeinträchtigen und verursachen hohe Bandbreitenkosten pro Ausgabepixel.
Die Diagramme "Mali Geometry Usage" und "Mali Geometry Culling Rate" in Streamline zeigen, wie effizient die GPU Geometrie verarbeitet. Wir können die Anzahl der Primitive sehen, die an die GPU gesendet werden, und wie viele davon während der Geometrieverarbeitung aussortiert werden. Arbeiten, die in diesem Stadium aussortiert werden, werden nicht zur Pixelverarbeitung weitergeleitet. Das ist eine gute Nachricht, aber wir könnten den Inhalt effizienter organisieren, so dass nicht sichtbare Primitive überhaupt nicht durchgereicht werden.

Das Diagramm zur Geometrieverwendung in Mali zeigt, dass im ausgewählten Zeitrahmen (ca. 0,05 Sekunden) 1,07 Millionen Primitive in die Geometrieverarbeitung einfließen (orangefarbene Linie), aber 700.000 Primitive in diesem Stadium aussortiert werden (rote Linie).
Das Diagramm " Mali Geometry Culling Rate" zeigt, warum sie aussortiert werden. Etwa die Hälfte wird durch den Facing-Test (orangefarbene Linie) aussortiert, was zu erwarten ist, da es sich um die nach hinten gerichteten Dreiecke unserer 3D-Objekte handelt. Besorgniserregender ist die Tatsache, dass 31,9 % der Primitive bei der Stichprobenprüfung aussortiert werden (violette Linie) - idealerweise sollte diese Zahl weniger als 5 % betragen. Der Stichprobentest zeigt, dass diese Primitive zu klein waren, um gerastert zu werden, da sie keinen einzigen Abtastpunkt trafen, und daher als unsichtbar betrachtet wurden. Dies kann passieren, wenn Objekte mit komplexen Netzen weit von der Kamera entfernt sind und die Dreiecke im Netz zu klein sind, um sichtbar zu sein. Höhere Zahlen könnten darauf hindeuten, dass die Maschen der Spielobjekte zu komplex für ihre Position auf dem Bildschirm sind.
Dieses Problem verschärft sich bei Primitiven, die groß genug sind, um den Mustertest zu bestehen, aber dennoch nur wenige Pixel abdecken. Diese "Mikrodreiecke" werden an die Pixelverarbeitung weitergeleitet und sind teuer in der Verarbeitung. Das liegt daran, dass beim Fragment-Shading Dreiecke in zwei-mal-zwei-Pixel-Felder, so genannte Quads, aufgerastert werden. Winzige Dreiecke treffen nur eine Teilmenge der Pixel innerhalb eines Vierecks, dennoch muss das gesamte Viereck zur Verarbeitung gesendet werden. Dies bedeutet, dass der Fragment-Shader mit ungenutzten Lanes in der Hardware ausgeführt wird, was die Shader-Ausführung weniger effizient macht.

Um zu überprüfen, ob wir ein Problem mit Mikrodreiecken haben, können wir das Mali Core Workload Property Diagramm in Streamline verwenden, um die Effizienz der Abdeckung zu überwachen. Im Idealfall sollte dieser Wert weniger als 10 % betragen. Wir sehen hier, dass in einigen Abschnitten die Teilerfassungsrate (grüne Linie) sehr hoch ist, nämlich über 70 %. Dieser Wert deutet darauf hin, dass der Inhalt eine hohe Dichte an Mikrodreiecken aufweist, was das Problem bestätigt, das zuvor durch die hohe Ausleserate aufgezeigt wurde.

Geometrien, die auf dem Bildschirm erscheinen, müssen für ihre Position die richtige Größe haben. Ein komplexes Stück Landschaft, das weit entfernt ist, muss nicht sehr detailliert sein, da es nicht viel zur Szene beiträgt. Wir könnten Level Of Detail (LOD) Meshesfür Objekte verwenden, die weiter von der Kamera entfernt sind, um die Komplexität zu reduzieren und Rechenleistung und DRAM-Bandbreite zu sparen. Oder wir könnten anstelle von Geometrie Texturen und Normal Maps verwenden, um Oberflächendetails für Objekte zu erstellen.
Durch den Performance Advisor-Bericht haben wir herausgefunden, dass unsere Shader zu teuer sein könnten und dass wir von einer Verringerung ihrer Präzision profitieren könnten. In Streamline können wir das Mali-Diagramm zur unterschiedlichen Nutzung verwenden, um die Anzahl der Zyklen zu sehen, in denen die 32-Bit-Interpolation (hohe Präzision) oder die 16-Bit-Interpolation (mittlere Präzision) aktiv ist. Hier ist zu erkennen, dass in den meisten Zyklen eine 32-Bit-Interpolation verwendet wird. 16-Bit-Variablen interpolieren doppelt so schnell wie 32-Bit-Variablen und benötigen nur halb so viel Platz in den Shader-Registern, um die Interpolationsergebnisse zu speichern. Es wird daher empfohlen, wann immer möglich mittelgroße (16-Bit-) variierende Eingaben für Fragment-Shader zu verwenden.

Um Shader zu untersuchen, können wir das statische Offline-Compiler-Tool von ARM Mobile Studio verwenden, um eine schnelle Analyse des Shader-Programms zu erstellen.
Dazu müssen Sie den Shader-Code aus der kompilierten Datei holen, die Sie von Unity erhalten, und dann den Mali Offline Compiler auf diese Datei anwenden:
1. Wählen Sie in Unity den Shader aus, den Sie analysieren möchten, entweder direkt aus Ihrem Anlagenordner oder indem Sie ein Material auswählen, auf das Zahnradsymbol klicken und Shader auswählen wählen.
2. Wählen Sie Kompilieren und zeigen Sie den Code im Inspektor an. Der kompilierte Shader-Code wird in Ihrem Standard-Code-Editor geöffnet. Diese Datei enthält mehrere Shader-Code-Varianten.
3. Kopieren Sie entweder eine Vertex- oder Fragment-Shader-Variante aus dieser Datei in eine neue Datei und geben Sie ihr die Erweiterung.vert oder .frag. Vertex-Shader beginnen mit #ifdef VERTEX und Fragment-Shader beginnen mit #ifdef FRAGMENT. Sie enden mit ihrem jeweiligen #endif. (Fügen Sie die Anweisungen #ifdef und #endif nicht in die neue Datei ein).
4. Führen Sie in einem Befehlsterminal den Mali Offline Compiler für diese Datei aus und geben Sie die zu testende GPU an. Zum Beispiel: malioc -c Mali-G72 myshader.frag Weitere Anweisungen finden Sie unter Erste Schritte mit dem Mali Offline Compiler.
Wir haben uns entschieden, den Fragment-Shader zu analysieren, der für den Auflösungseffekt verantwortlich ist, der auftritt, wenn die gegnerischen NSCs sterben. Hier ist der Bericht des Mali Offline Compilers, mit hervorgehobenen Abschnitten von Interesse:

Es wird deutlich, dass nur 2 % der arithmetischen Berechnungen mit 16-Bit-Genauigkeit effizient durchgeführt werden. Der Shader wird effizienter arbeiten, wenn wir die Präzision von highp auf mediump reduzieren. Dadurch werden sowohl der Energieverbrauch als auch der Registerdruck gesenkt, und die Leistung kann verdoppelt werden. Es gibt Situationen, in denen ein hoher p-Wert immer erforderlich ist, z. B. bei Positions- und Tiefenberechnungen, aber in vielen Fällen ist der Unterschied auf dem Bildschirm kaum spürbar, wenn die Genauigkeit auf einen mittleren p-Wert reduziert wird.
Der Bericht enthält eine ungefähre Aufschlüsselung der Zykluskosten für die wichtigsten Funktionseinheiten des Mali-Shader-Kerns. Hier ist zu erkennen, dass das Rechenwerk am stärksten genutzt wird.
Im Abschnitt Shader-Eigenschaften sehen wir, dass dieser Shader einheitliche Berechnungen enthält, die nur von literalen Konstanten oder einheitlichen Werten abhängen. Dies führt zu demselben Ergebnis für jeden Thread in einem Draw Call oder Compute Dispatch. Idealerweise sollte diese Art der einheitlichen Berechnung in die Anwendungslogik der CPU verlagert werden.
Wir können auch sehen, dass der Shader die Fragmentabdeckungsmaske ändern kann, die festlegt, welche Abtastpunkte in jedem Pixel von einem Fragment abgedeckt werden, indem er die Anweisung discard verwendet, um Fragmente unterhalb eines Alpha-Schwellenwerts zu verwerfen. Shader mit modifizierbarer Abdeckung müssen eine späte ZS-Aktualisierung verwenden, was die Effizienz der frühen ZS-Tests und der Fragmentplanung für spätere Fragmente an derselben Koordinate verringern kann. Sie sollten die Verwendung von Discard-Anweisungen und Alpha-to-Coverage in Fragment-Shadern nach Möglichkeit minimieren. Hinweise zur Verwendung von Discard-Anweisungen finden Sie im Arm Mali Best Practices Guide.
Im Graphics Analyzer von ARM Mobile Studio können Sie alle Grafik-API-Aufrufe sehen, die die Anwendung gemacht hat, und sie Schritt für Schritt durchgehen, um zu sehen, wie die Szene aufgebaut ist. Dies hilft bei der Identifizierung von Objekten, die für ihre Größe auf dem Bildschirm und ihren Abstand zur Kamera zu komplex sind. Hier sind ein paar Beispiele, die wir in diesem Spiel gefunden haben:
Das Mauerwerk in der hinteren Ecke der Szene ist mit Geometrie aufgebaut und verwendet 2064 Eckpunkte. Die Details sind in der endgültigen Ausgabe nicht sehr gut sichtbar, so dass es sich um eine unnötige Bearbeitung handelt.

Das gleiche Problem haben wir bei den Bodenkacheln festgestellt - sie haben jeweils 1170 Eckpunkte, aber obwohl sich das Objekt nahe an der Kamera befindet, profitiert die Szene nicht wirklich von dieser Komplexität. Es wäre effizienter, hier eine Normal Map zu verwenden, um die Unebenheiten und kantigen Kanten darzustellen, als sie mit Dreiecken zu erstellen. Außerdem können wir sehen, dass diese Objekte mit separaten Zeichenaufrufen gezeichnet werden. Die Verringerung der Anzahl der Zeichnungsaufrufe durch das Zusammenfassen von Objekten oder die Verwendung von Objektinstanzen könnte die Leistung erhöhen.

Ein weiteres Beispiel sind die Statuen im hinteren Teil der Szene - jeweils 6966 Scheitelpunkte. Sie können sehen, dass das Netz ziemlich komplex ist, was ein großartiges visuelles Ergebnis ergibt, wenn der Spieler nahe an die Statuen herankommt, aber aus dieser Kameraposition sind sie kaum zu erkennen. Es würde eine Menge Rechenleistung sparen, wenn man hier Mesh LODs verwenden würde, um diese Objekte darzustellen, wenn sie so weit von der Kamera entfernt sind.

Denken Sie daran, dass die Verringerung der Komplexität bei vielen ähnlichen Objekten zu einer enormen Einsparung bei der Geometrieverarbeitung führt, was wiederum den Umfang des erforderlichen Fragment-Shadings verringert. Dadurch wird nicht nur die Fragmentierungslast gesenkt und die Anzahl der Bilder pro Sekunde erhöht, sondern auch der Installations-Footprint der APK verringert.
Wir haben mehrere Bereiche aufgedeckt, in denen wir Änderungen am Spiel vornehmen könnten, um die Leistung zu verbessern. Nachfolgend finden Sie die Maßnahmen, die wir eingeführt haben, und wie wir sie umgesetzt haben.
Fixed Timestep ist ein von der Framerate unabhängiges Intervall, das steuert, wann physikalische Berechnungen und FixedUpdate()-Ereignisse ausgeführt werden. Standardmäßig ist diese Funktion auf 50 FPS eingestellt. Während 50 oder sogar 60 FPS auf High-End-Mobilgeräten möglich sind, laufen Mainstream-Geräte mit 30 FPS, worauf dieser Titel abzielt. Gehen Sie zu Bearbeiten > Projekteinstellungen und dann in die Kategorie Zeit, um die Eigenschaft Fester Zeitschritt auf 0,04 zu setzen. Dadurch wird sichergestellt, dass Ihre physikalischen Berechnungen, FixedUpdate() und Aktualisierungen alle synchron laufen.

Nachdem die Anpassungen am festen Timestep in Unity vorgenommen wurden, wurde der feste Aktualisierungsteil der Hauptspielschleife nur noch einmal pro Frame aufgerufen, und zwar für durchschnittlich 1,5 ms. Dies ist eine enorme Verbesserung im Vergleich zu den 12 ms, die es vorher brauchte - und eine einfache Lösung für eine häufige Leistungsfalle.
Beim Start der Anwendung werden die Daten für alle Objekte, auf die in den eingebauten Szenen oder im Ressourcenordner verwiesen wird, in den Instanz-ID-Cache geladen. Diese Assets werden wie ein großes Asset-Bündel behandelt, d. h. es gibt Metadaten und Indizierungsinformationen, die immer in den Speicher geladen werden. Sobald ein Asset aus diesem Bündel verwendet wird, kann es nicht mehr aus dem Speicher entladen werden.
Die empfohlene Methode für den Umgang mit Assets und Ressourcen, um den Speicherverbrauch zu verbessern, ist das Addressable Asset System, das eine effiziente Möglichkeit bietet, nicht mehr benötigte Inhalte aus dem Speicher zu entfernen.
In unserer Umgebung gibt es viele Objekte, die mehrfach vorkommen. Wände, Bodenfliesen und andere Requisiten der Umgebung werden dupliziert, um diese Szene zu gestalten. Wir können Zeichnungsaufrufe einsparen, indem wir GPU-Instanzierung für das Material der Objekte aktivieren. Die GPU-Instanzierung rendert identische Meshes mit einer geringen Anzahl von Zeichenaufrufen und ermöglicht es jeder Instanz, unterschiedliche Parameter wie Farbe oder Skalierung zu haben. Diese Änderung kann die CPU-Leistung erhöhen. Unten sehen Sie die Daten des Performance Advisors, bevor die GPU-Instanzierung aktiviert wurde.

Und hier sehen Sie denselben Teil der Anwendung, aber mit aktivierter GPU-Instanzierung - ein kleiner, aber messbarer Zuwachs in Richtung unseres Ziels von 30 FPS.

Mit Rendertexturen können Sie 3D-Elemente in Ihre Benutzeroberfläche einfügen, aber auch viele andere Anwendungsfälle. Wenn Sie eine Kamera haben, die auf die Rendertextur gerendert wird, stellen Sie sicher, dass Sie die Kamera deaktivieren, wenn sie nicht auf dem Bildschirm ist. Es besteht keine Notwendigkeit, etwas zu rendern, das der Benutzer nicht sehen wird. Verwenden Sie Graphics Analyzer oder den Frame Debugger von Unity, um sicherzustellen, dass diese Texturen nicht außerhalb des Bildschirms aktualisiert werden.
Anstatt die CPU zusätzlich zu belasten, indem Sie dieselben Objekte immer wieder erstellen und zerstören, sollten Sie ein Objekt-Pooling durchführen. Objekt-Pooling ist ein Entwurfsmuster, das Sie dazu auffordert, die benötigten Objekte im Voraus zu erstellen und so die Arbeit der CPU vorwegzunehmen. Anstatt sie zu zerstören, können Sie sie dann dem Pool wieder hinzufügen, um sie wieder zu verwenden, wenn ein Objekt desselben Typs erneut benötigt wird. Dies ist eine fantastische Möglichkeit, die Rechenleistung der CPU zu entlasten, so dass sie frei an wichtigeren Aufgaben für Ihr Spiel arbeiten kann.
Mit der Umstellung auf Objekt-Pooling gibt es keinen Spike bei den auf dem Bildschirm erscheinenden Gegnerwellen, der in den Unity-Profiler-Captures identifiziert werden kann, und auch keine erkennbaren Auswirkungen auf die Framerate.
Wenn ein Mesh auf dem Bildschirm angezeigt wird, verbringt die GPU Zeit damit, alle Dreiecke im Mesh zu rendern, egal wie klein sie sind. In Spielen, in denen sich die Kamera oder die Assets bewegen können, führt dies oft zu einer Situation, in der ein Großteil der GPU-Ressourcen für das Rendern von Mesh-Dreiecken verwendet wird, die zu klein sind, um im Bild gesehen zu werden. Verwenden Sie dazu Level Of Detail (LOD) Meshes . Auf diese Weise kann Ihr Spiel weniger komplexe Meshes nutzen, wenn sich die Kamera von den Assets entfernt, was die Komplexität der Meshes, die der Grafikprozessor rendern muss, verringert und die Anzahl der Vertexe pro Frame reduziert, wodurch mehr Dreiecke für die Pixelverarbeitung zur Verfügung stehen. Dadurch wird nicht nur die Effizienz gesteigert, sondern auch die künstlerische Integrität der Szene erhalten.

Weitere Tipps zur Optimierung von Assets finden Sie in den Game Artist Guides von ARM.
Wenn Sie wissen, dass einige Assets mit denselben Materialeigenschaften in derselben Szene verwendet werden, können Sie sie zusammen stapeln. Kombinieren Sie ihre Texturdaten in einem einzigen Texturatlas, was Zeichnungsaufrufe spart, da sie auf einmal gezeichnet werden, und im Vergleich zu mehreren separaten Dateien einen geringeren Platzbedarf beim Komprimieren ergibt.
Wenn Sie Ihre eigenen benutzerdefinierten Shader schreiben oder Shader Graph verwenden, können Sie entscheiden, welche Genauigkeit Sie verwenden möchten: Float oder Half. Die Wahl der Hälfte, wo immer es möglich ist, wird zu leistungsfähigeren Shadern führen - aber denken Sie daran, dass Sie wahrscheinlich Float für alles verwenden müssen, was mit Weltraumpositionen oder Tiefenberechnungen zu tun hat!

Wenn Sie mit der Planung der Post-Processing-Effekte für Ihr Projekt beginnen, haben Sie zwei Möglichkeiten zur Auswahl: den alten integrierten Funktionssatz oder den neuen Funktionssatz von Post Processing v2. Unten sehen Sie das Spiel mit dem integrierten Funktionsumfang.

Alle 3-4 Frames sehen wir eine Spitze in V-Sync, wo das System auf den zu rendernden Frame wartet. Dies führt dazu, dass das Spiel konstant unter 30 FPS fällt und Strom verschwendet. Hier sehen Sie die Profildaten des Spiels mit denselben Effekten, diesmal mit dem Post Processing v2-Feature-Set.

Diese Profiler-Grafik ist viel besser, da Post Processing v2 für die Ausführung auf mobiler Hardware optimiert ist. Verwenden Sie es in Ihrem Projekt, um die beste Nachbearbeitungsleistung zu erzielen.
Das Hinzufügen von Nachbearbeitungseffekten zu Ihrem Spiel kann Ihrem Projekt einen schönen Schliff und visuelle Tiefe verleihen. Aber es ist auch wichtig, diese Effekte mit der Leistung in Einklang zu bringen. Schließlich können diese Effekte teuer werden. Wenn Sie diese Funktionen bei Massenmarktgeräten ausschalten, können Sie eine Menge Strom sparen und verhindern, dass sich das Gerät in den Händen Ihrer Spieler aufheizt.
Nachdem die anderen Optimierungen vorgenommen worden waren, konnten wir in einigen Bereichen immer noch Spitzenwerte feststellen. Durch binäres Suchen, Ein- und Ausschalten, haben wir schließlich zwei Dinge aufgespürt: Eine davon war der verwendete Post-Processing-Stack. Das half bei der Gesamtzeit, aber die Bildrate pendelte sich schließlich ein, als wir das Anti-Aliasing ausschalteten - so sehr, dass ein Teil der Nachbearbeitung sogar auf den Geräten mit den niedrigsten Spezifikationen, die wir zum Testen verwendeten, weiterlaufen konnte.

Nachdem wir das Spiel optimiert hatten, ließen wir es noch einmal durch Arm Mobile Studio laufen, um nach Unterschieden zu suchen. Der Bericht des Performance Advisors zeigt nun, dass wir eine durchschnittliche FPS von 28,9 (vorher 17) erreicht haben und die Gesamtfragmentbegrenzung reduziert haben. Die Fragmentaktivität ist in einigen Abschnitten des Spiels immer noch hoch, so dass wir noch viel Arbeit vor uns haben, aber mit guten Daten, die uns bei unseren Untersuchungen helfen, sollten wir in der Lage sein, diese Abschnitte zu optimieren und die Leistung weiter zu verbessern.

Die Anzahl der Vertices pro Frame liegt jetzt deutlich unter unserem Budget von 500.000, und man kann regelmäßige Einbrüche sehen, wenn die feindlichen NPCs zerstört werden.

Die Geometrienutzung und das Culling sind jetzt viel effizienter, wobei die Anzahl der sichtbaren Primitive einen viel gesünderen Prozentsatz der Anzahl der eingegebenen Primitive darstellt. Der Facing-Test ist erwartungsgemäß für etwa 50 % der aussortierten Primitive verantwortlich, und die durch den Stichprobentest getöteten Primitive liegen unter 10 %, was zeigt, dass wir die Anzahl der sehr kleinen Dreiecke reduziert haben.

Durch den Einsatz von Unitys Profiler und Frame Debugger in Verbindung mit Arm Mobile Studio konnten wir zahlreiche Möglichkeiten zur Verbesserung der Leistung und zur Verringerung der Belastung von CPU und GPU auf einem mobilen Gerät entdecken. Einige der von uns aufgedeckten Probleme könnten bei künftigen Titeln vermieden werden, wenn man sich an eine Reihe von Best Practices für Inhalte hält.
Wir wollen natürlich nicht, dass die Qualität der Bildschirmdarstellung durch Optimierungen beeinträchtigt wird. Hier sehen Sie, wie die optimierte Version des Spiels neben der Originalversion aussieht.
Leistungstests finden oft erst recht spät im Entwicklungszyklus statt. Es ist großartig, weitere Optimierungsmöglichkeiten zu finden, aber was ist, wenn keine Zeit bleibt, die Probleme vor dem Veröffentlichungstermin zu beheben? Es ist viel praktischer, die Inhalte von Anfang an optimal zu gestalten. Es kann sinnvoll sein, Inhaltsbudgets für Mesh-Komplexität, Shader-Komplexität und Texturkomprimierung festzulegen, damit Ihr Team die besten Chancen hat, effizient für Mobilgeräte zu entwickeln. Hier sind einige Ressourcen, die Ihrem Team helfen können:
- ARM-Leitfaden für Unity-Entwickler
- Leitfäden für Entwickler für bewährte Verfahren auf Mali
- Unity Learn Kurs, 3D Art Optimization for Mobile Applications
Sobald Sie wissen, dass die meisten Ihrer Anwendungen und Assets einer Reihe von Best Practices folgen, können Sie während des gesamten Entwicklungszyklus regelmäßige Leistungstests durchführen, um etwaige Probleme rechtzeitig zu erkennen und zu beheben.
Teams, die ein kontinuierliches Integrationssystem verwenden, können die Vorteile der automatisierten Leistungstests nutzen, die mit der Professional Edition von ARM Mobile Studio verfügbar sind. Diese Edition kann auf mehreren Geräten in einer Gerätefarm ausgeführt werden und macht die manuelle Profilerstellung überflüssig. Die gemeldeten Daten können sogar in eine beliebige JSON-kompatible Datenbank eingespeist werden, so dass Sie visuelle Dashboards und Warnmeldungen erstellen können, um zu überwachen, wie sich die Leistung im Laufe der Zeit verändert, um Probleme früher zu erkennen.
Der in Unity integrierte Profiler ist ein guter Ausgangspunkt. Lesen Sie in der Unity-Dokumentation, wie Sie Ihre Anwendung profilieren können. Oder erkunden Sie den Frame-Debugger, mit dem Sie untersuchen können, wie ein einzelner Frame aufgebaut ist.
Laden Sie Arm Mobile Studio kostenlos von der Arm Developer Website herunter und lesen Sie die Starter Guides für Performance Advisor, Streamline, Mali Offline Compiler und Graphics Analyzer, um schnell loslegen zu können.
Wenn Sie weitere Hilfe bei der Profilerstellung mit dem Unity Profiler und Frame Debugger benötigen, können Sie gerne Fragen in unserem Forum stellen.
Wenn Sie weitere Unterstützung bei der Arbeit mit Mali-Geräten oder Arm Mobile Studio benötigen, besuchen Sie das Graphics and Gaming Forum von Arm, wo Sie Fragen stellen können und Arm Ihnen gerne weiterhilft.
