Verbesserungen bei Shader-Build-Zeiten und Speichernutzung in 2021 LTS

Da der verfügbare Funktionsumfang der Scriptable Render Pipeline (SRP) von Unity weiter wächst, steigt auch die Anzahl der Shader-Varianten, die zur Build-Zeit verarbeitet und kompiliert werden. Neben der fortlaufenden Unterstützung zusätzlicher Grafik-APIs und einer ständig wachsenden Auswahl an Zielplattformen werden die Verbesserungen des SRP ständig erweitert.
Shader werden nach einem ersten („sauberen“) Build kompiliert und zwischengespeichert, wodurch weitere inkrementelle („warme“) Builds beschleunigt werden. Während saubere Builds normalerweise am längsten dauern, können lange Warm-Build-Zeiten ein häufiger Schwachpunkt während der Projektentwicklung und -iteration sein.

Um dieses Problem zu lösen, hat das Shader-Management-Team von Unity hart daran gearbeitet, sinnvolle und skalierbare Lösungen bereitzustellen. Dies hat zu deutlich reduzierten Shader-Build-Zeiten und Laufzeitspeichernutzung für Projekte geführt, die mit Unity 2021 LTS und späteren Versionen erstellt wurden.
Um mehr über diese neuen Optimierungen zu erfahren, einschließlich der betroffenen Versionen, Backports und Zahlen aus unseren internen Tests, springen Sie direkt zu den Abschnitten über die Vorfilterung von Shader-Varianten und das dynamische Laden von Shadern. Am Ende dieses Blogbeitrags gehen wir auch auf unsere Zukunftspläne ein, das Shader-Variantenmanagement insgesamt weiter zu verfeinern – über Projekterstellung, Build und Laufzeit hinweg.
Bevor wir uns mit den spannenden Verbesserungen am Shader-System von Unity befassen, wollen wir die Gelegenheit auch nutzen, um kurz die Konzepte der bedingten Shader-Kompilierung, der Shader-Variantenund des Strippings von Shader-Variantendurchzugehen.
Mithilfe der bedingten Shader-Funktionen können Entwickler und Künstler die Funktionalität eines Shaders bequem mithilfe von Skripten, Materialeinstellungen sowie Projekt- und Grafikeinstellungen steuern und ändern. Solche bedingten Funktionen dienen dazu, die Projekterstellung zu vereinfachen und Projekte effizient zu skalieren, indem die Anzahl der Shader, die Sie erstellen und verwalten müssen, minimiert wird.

Bedingte Shader-Funktionen können auf verschiedene Arten implementiert werden:
- Statisches (zur Kompilierungszeit) Branching
- Zusammenstellung der Shadervarianten
- Dynamische (Laufzeit-)Verzweigung
Während durch statische Verzweigung der mit der Verzweigung verbundene Mehraufwand bei der Shader-Ausführung zur Laufzeit vermieden wird, wird er zur Kompilierungszeit ausgewertet und gesperrt und bietet keine Laufzeitsteuerung. Die Shader-Variantenkompilierungist eine Form der statischen Verzweigung, die zusätzliche Laufzeitkontrolle bietet. Dies funktioniert, indem für jede mögliche Kombination statischer Zweige ein einzigartiges Shaderprogramm (Variante) kompiliert wird, um zur Laufzeit eine optimale GPU-Leistung aufrechtzuerhalten.
Solche Varianten werden durch bedingtes Deklarieren und Auswerten der Shader-Funktionalität über die Shader-Schlüsselwörter shader_feature und multi_compile erstellt. Die richtigen Shader-Varianten werden zur Laufzeit basierend auf aktiven Schlüsselwörtern und Laufzeiteinstellungen geladen. Das Deklarieren und Auswerten zusätzlicher Shader-Schlüsselwörter kann zu einer Erhöhung der Build-Zeit, der Dateigröße und der Laufzeitspeichernutzung führen.
Gleichzeitig wird durch dynamisches (uniformbasiertes) Branching der Overhead der Shader-Varianten-Kompilierung vollständig vermieden, was zu schnelleren Builds und einer geringeren Dateigröße und Speichernutzung führt. Dies kann zu reibungsloseren und schnelleren Iterationen während der Entwicklung führen.
Andererseits kann die dynamische Verzweigung je nach Komplexität des Shaders und Zielgerät einen starken Einfluss auf die Leistung der Shader-Ausführung haben. Asymmetrische Verzweigungen, bei denen eine Seite der Verzweigung wesentlich komplexer ist als die andere, können sich negativ auf die Leistung auswirken. Dies liegt daran, dass die Ausführung eines einfacheren Pfades immer noch die Leistungseinbußen des komplexeren Pfades mit sich bringen kann.
Wenn Sie bedingte Shader-Funktionen in Ihre eigenen Shader einführen, sollten Sie diese Ansätze und Kompromisse berücksichtigen. Ausführlichere Informationen finden Sie in der Dokumentation zu Shader-Bedingungen, Shader-Verzweigungund Shader-Varianten .
Um den Anstieg der Shader-Verarbeitungs- und Kompilierungszeit zu verringern, wird die Entfernung von Shader-Varianten genutzt. Ziel ist es, unnötige Shader-Varianten von der Kompilierung auszuschließen, basierend auf Faktoren wie:
- Enthaltene Materialien und aktivierte Schlüsselwörter
- Projekt- und Render-Pipeline-Einstellungen
- Skriptfähiges Stripping
Beim Aufzählen von Shader-Varianten filtert der Editor automatisch alle mit „shader_feature“ deklarierten Schlüsselwörter heraus, die nicht durch referenzierte und im Build enthaltene Materialien aktiviert sind. Daher werden durch diese Schlüsselwörter keine weiteren Varianten generiert.
Wenn beispielsweise die Materialieneigenschaft „Clear Coat“ von keinem Material aktiviert wird, das den Complex Lit URP Shaderverwendet, werden alle Shadervarianten, die die „Clear Coat“-Funktionalität implementieren, zur Build-Zeit sicher entfernt.
In der Zwischenzeit fordern multi_compile -Schlüsselwörter Entwickler und Spieler auf, die Funktionalität des Shaders zur Laufzeit basierend auf verfügbaren Player-Einstellungen und Skripten frei zu steuern. Die Kehrseite besteht darin, dass solche Schlüsselwörter vom Editor nicht im gleichen Maße automatisch entfernt werden können wie Shader_Feature- Schlüsselwörter. Deshalb produzieren sie im Allgemeinen eine größere Anzahl von Varianten.
Scriptable Stripping ist eine C#-API , mit der Sie Shader-Varianten während der Build-Zeit über Schlüsselwörter und Kombinationen von der Kompilierung ausschließen können, die zur Laufzeit nicht benötigt werden. Die Render-Pipelines nutzen skriptbasiertes Stripping, um unnötige Varianten entsprechend den Render-Pipeline-Einstellungen des Projekts und den im Build enthaltenen Qualitätsressourcen zu entfernen.
Niedrige Qualität Hohe Qualität Variantenmultiplikator Hauptlicht/Schattenwurf: Aus An 2x Hauptlicht/Schattenwurf: An An 1x Hauptlicht/Schattenwurf: Aus Aus 1x
Um die Effekte der Shader-Variantenentfernung des Editors zu maximieren, empfehlen wir, alle grafikbezogenen Funktionen und Render-Pipeline-Einstellungen zu deaktivieren, die zur Laufzeit nicht verwendet werden. Weitere Informationen zum Stripping von Shader-Variantenfinden Sie in der offiziellen Dokumentation.
Durch das Entfernen von Shader-Varianten wird die Anzahl der kompilierten Shader-Varianten erheblich reduziert, basierend auf Faktoren wie den Render Pipeline Quality Assets im Build. Das Stripping wird derzeit jedoch am Ende der Shader-Verarbeitungsphase durchgeführt. Das einfache Aufzählen aller möglichen Varianten kann unabhängig von der Kompilierung immer noch lange dauern.
Um die Verarbeitungszeit der Shader-Varianten (und die Zeit für die Projekterstellung) zu verkürzen, führen wir jetzt eine wesentliche Optimierung beim integrierten Shader-Varianten-Stripping der Engine ein. Durch die Vorfilterung der Shadervarianten werden sowohl die Clean- als auch die Warm-Build-Zeiten erheblich reduziert.
Die Optimierung funktioniert durch die frühzeitige Ausgrenzung von Multi_Compile- Schlüsselwörtern gemäß den durch die Render-Pipeline-Einstellungengesteuerten Vorfilterattributen . Dadurch verringert sich die Anzahl der Varianten, die zum potenziellen Entfernen und Kompilieren aufgezählt werden, was wiederum die Shader-Verarbeitungszeit verkürzt – wobei sich die Warm-Build-Zeiten in den drastischsten Beispielen um bis zu 90 % verkürzen.
Die Vorfilterung von Shader-Varianten wurde erstmals in 2023.1.0a14eingeführt und auf 2022.2.0b15 und 2021.3.15f1zurückportiert.


Durch die Anwendung des gleichen Prinzips trägt die Variantenvorfilterung auch dazu bei, die anfänglichen/sauberen Build-Zeiten zu verkürzen.


In der Vergangenheit hat die Unity-Laufzeitumgebung beim Laden von Szenen und Ressourcen alle Shader-Objekte vorab von der Festplatte in den CPU-Speicher geladen. In den meisten Fällen enthält ein erstelltes Projekt und eine Szene viel mehr Shader-Varianten, als zu einem bestimmten Zeitpunkt während der Laufzeit der Anwendung benötigt werden. Bei Projekten mit einer großen Anzahl von Shadern führt dies häufig zu einer hohen Shader-Speichernutzung zur Laufzeit.
Das dynamische Laden von Shadern behebt das Problem, indem es dem Benutzer eine verfeinerte Kontrolle über das Ladeverhalten und die Speichernutzung der Shader bietet. Diese Optimierung erleichtert das Streaming von Shader-Datenblöcken in den Speicher sowie das Auslagern von Shader-Daten, die zur Laufzeit nicht mehr benötigt werden, basierend auf einem vom Benutzer gesteuerten Speicherbudget. Dadurch können Sie den Shader-Speicherverbrauch auf Plattformen mit begrenztem Speicherbudget erheblich reduzieren.
Auf die neuen Ladeeinstellungen für Shader-Varianten kann jetzt über die Player-Einstellungendes Editors zugegriffen werden. Verwenden Sie sie, um die maximale Anzahl geladener Shader-Chunks und die Chunk-Größe pro Shader (MB) zu überschreiben.

Mit der folgenden jetzt verfügbaren C#-API können Sie die Ladeeinstellungen für Shader-Varianten mithilfe von Editor-Skripten überschreiben, beispielsweise:
- PlayerSettings.SetDefaultShaderChunkCount und PlayerSettings.SetDefaultShaderChunkSizeInMB zum Überschreiben der Standard-Shader-Ladeeinstellungen des Projekts
- PlayerSettings.SetShaderChunkCountForPlatform und PlayerSettings.SetShaderChunkSizeInMBForPlatform, um diese Einstellungen plattformspezifisch zu überschreiben.
Sie können die maximale Anzahl geladener Shader-Chunks zur Laufzeit auch mithilfe der C#-API über Shader.maximumChunksOverrideüberschreiben. Auf diese Weise können Sie das Shader-Speicherbudget basierend auf Faktoren wie dem insgesamt verfügbaren System- und Grafikspeicher , der zur Laufzeit abgefragt wird, überschreiben.
Das dynamische Laden von Shadern ist in 2023.1.0a11 gelandet und wurde auf 2022.2.0b10, 2022.1.21f1und 2021.3.12f zurückportiert. Im Fall von Boat Attackder Universal Render Pipeline (URP) konnten wir eine Reduzierung der Laufzeitspeichernutzung für Shader um 78,8 % von 315 MiB (Standard) auf 66,8 MiB (dynamisches Laden) feststellen. Weitere Informationen zu dieser Optimierung finden Sie in der offiziellen Ankündigung.

Über die oben genannten wichtigen Änderungen hinaus arbeiten wir daran, die Generierung und Entfernung von Shadervarianten der Universal Render Pipeline zu verbessern. Wir untersuchen außerdem weitere Verbesserungen für die Shader-Variantenverwaltung von Unity im Allgemeinen. Das ultimative Ziel besteht darin, den wachsenden Funktionsumfang der Engine zu ermöglichen und gleichzeitig einen minimalen Mehraufwand beim Shader-Build und bei der Laufzeit sicherzustellen.
Einige unserer laufenden Untersuchungen betreffen die Deduplizierung von Shader-Ressourcen über ähnliche Varianten hinweg sowie allgemeine Verbesserungen der Shader-Schlüsselwörter und der Shader-Variantensammlungs-APIs. Ziel ist es, mehr Flexibilität und Kontrolle über die Verarbeitung von Shadervarianten und die Laufzeitleistung zu bieten.
Mit Blick auf die Zukunft untersuchen wir auch die Möglichkeit, im Editor Tools für die Verfolgung und Analyse von Shader-Varianten bereitzustellen, um die folgenden Details zur Verwendung von Shader-Varianten bereitzustellen:
- Welche Shader und Schlüsselwörter erzeugen die meisten Varianten?
- Welche Varianten werden kompiliert, zur Laufzeit aber nicht verwendet?
- Welche Varianten werden entfernt, aber zur Laufzeit angefordert?
Ihr Feedback war bisher von entscheidender Bedeutung, da es uns dabei hilft, die sinnvollsten Lösungen zu priorisieren. Bitte sehen Sie sich unsere öffentliche Roadmap an, um über die Funktionen abzustimmen, die Ihren Anforderungen am besten entsprechen. Wenn Sie weitere Änderungen sehen möchten, können Sie gerne eine Funktionsanfragesenden oder das Team direkt in diesem Shader-Forumkontaktieren.
