Que recherchez-vous ?
Engine & platform

Suppression des variantes de shaders scriptables

CHRISTOPHE RICCIO / UNITY TECHNOLOGIESContributor
May 14, 2018|12 Min
Suppression des variantes de shaders scriptables
Cette page a été traduite automatiquement pour faciliter votre expérience. Nous ne pouvons pas garantir l'exactitude ou la fiabilité du contenu traduit. Si vous avez des doutes quant à la qualité de cette traduction, reportez-vous à la version anglaise de la page web.

Réduisez considérablement le temps de création du lecteur et la taille des données en permettant aux développeurs de contrôler les variantes de Shader gérées par le compilateur Unity Shader et incluses dans les données du lecteur.

Le temps de création du joueur et la taille des données augmentent avec la complexité de votre projet en raison du nombre croissant de variantes de shaders.

Avec la suppression des variantes de shader scriptables, introduite dans la version bêta 2018.2, vous pouvez gérer le nombre de variantes de shader générées et donc réduire considérablement le temps de création du lecteur et la taille des données.

Cette fonctionnalité vous permet de supprimer toutes les variantes de shader avec des chemins de code non valides, de supprimer les variantes de shader pour les fonctionnalités inutilisées ou de créer des configurations de build de shader telles que « debug » et « release » sans affecter le temps d'itération ou la complexité de la maintenance.

Dans cet article de blog, nous définissons d’abord certains des termes que nous utilisons. Nous nous concentrons ensuite sur la définition des variantes de shaders pour expliquer pourquoi nous pouvons en générer autant. Ceci est suivi d'une description du décapage automatique des variantes de shader et de la manière dont le décapage des variantes de shader scriptables est implémenté dans l'architecture du pipeline de shader Unity . Ensuite, nous examinons l' API de décapage des variantes de shader scriptables avant de discuter des résultats de la démo de Fountainbleau et de conclure avec quelques conseils sur l'écriture de scripts de décapage.

Apprendre à dénuder les variantes de shaders scriptables n'est pas une tâche triviale, mais cela peut conduire à une augmentation massive de l'efficacité de l'équipe !

Concepts

Pour comprendre la fonctionnalité de suppression des variantes de shaders scriptables, il est important d'avoir une compréhension précise des différents concepts impliqués.

  • Ressource shader: Le code source complet du fichier avec les propriétés, le sous-shader, les passes et HLSL.
  • Extrait de shader : Le code d'entrée HLSL avec les dépendances pour une seule étape de shader.
  • Étape du shader : Une étape spécifique dans le pipeline de rendu GPU, généralement une étape de vertex shader et une étape de fragment shader.
  • Mot-clé du shader : Un identifiant de préprocesseur pour les branches de compilation entre les shaders.
  • Ensemble de mots-clés du shader : Un ensemble spécifique de mots-clés de shader identifiant un chemin de code particulier.
  • Variante de shader : Le code de shader spécifique à la plate-forme généré par le compilateur de shader Unity , pour une seule étape de shader pour un niveau graphique spécifique, une passe, un ensemble de mots-clés de shader, etc.
  • Shader Uber : Une source de shader qui peut produire de nombreuses variantes de shader.

Dans Unity, les uber shaders sont gérés par les sous-shaders, les passes et les types de shaders ShaderLab ainsi que par les directives de préprocesseur#pragma multi_compile et #pragma shader_feature.

Compter le nombre de variantes de shader générées

Pour utiliser le découpage de variantes de shader scriptable, vous devez avoir une compréhension claire de ce qu'est une variante de shader et de la manière dont les variantes de shader sont générées par le pipeline de création de shader. Le nombre de variantes de shader générées est directement proportionnel au temps de construction et à la taille des données de la variante de shader du joueur. Une variante de shader est une sortie du pipeline de construction de shader.

Les mots-clés de shader sont l’un des éléments qui provoquent la génération de variantes de shader. Une utilisation inconsidérée des mots-clés du shader peut rapidement conduire à une explosion du nombre de variantes de shader et donc à un temps de construction extrêmement long.

Pour voir comment les variantes de shader sont générées, le shader simple suivant compte le nombre de variantes de shader qu'il produit :

Shader "ShaderVariantsStripping"
{
	SubShader
	{
		Pass
		{
			Name "ShaderVariantsStripping/Pass"

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma multi_compile COLOR_ORANGE COLOR_VIOLET COLOR_GREEN COLOR_GRAY
			#pragma multi_compile OP_ADD OP_MUL OP_SUB

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
			};

			sampler2D _MainTex;
			float4 _MainTex_ST;

			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = v.uv;
				return o;
			}

			fixed4 get_color()
			{
				#if defined(COLOR_ORANGE)
					return fixed4(1.0, 0.5, 0.0, 1.0);
				#elif defined(COLOR_VIOLET)
					return fixed4(0.8, 0.2, 0.8, 1.0);
				#elif defined(COLOR_GREEN)
					return fixed4(0.5, 0.9, 0.3, 1.0);
				#elif defined(COLOR_GRAY)
					return fixed4(0.5, 0.9, 0.3, 1.0);
				#else
					#error "Unknown 'color' keyword"
				#endif
			}

			fixed4 frag (v2f i) : SV_Target
			{
				fixed4 diffuse = tex2D(_MainTex, i.uv);

				fixed4 color = get_color();

				#if defined(OP_ADD)
					return diffuse + color;
				#elif defined(OP_MUL)
					return diffuse * color;
				#elif defined(OP_SUB)
					return diffuse - color;
				#else
					#error "Unknown 'op' keyword"
				#endif
			}
			ENDCG
		}
	}
}

Le nombre total de variantes de shader dans un projet est déterministe et donné par l'équation suivante :

Equation

L'exemple trivial de ShaderVariantStripping suivant apporte de la clarté à cette équation. Il s'agit d'un shader unique qui simplifie l'équation comme suit :

Equation

De même, ce shader possède un seul sous-shader et un seul passage, ce qui simplifie encore l'équation en :

Equation

Les mots-clés de l'équation font référence à la fois aux mots-clés de la plate-forme et du shader. Un niveau graphique est une combinaison spécifique de mots-clés d'une plateforme.

Le ShaderVariantStripping/Pass possède deux directives de compilation multiple. La première directive définit 4 mots-clés (COLOR_ORANGE, COLOR_VIOLET, COLOR_GREEN, COLOR_GRAY) et la deuxième directive définit 3 mots-clés (OP_ADD, OP_MUL, OP_SUB). Enfin, le pass définit 2 étapes de shader : une étape de vertex shader et une étape de fragment shader.

Ce total de variantes de shader est donné pour une seule API graphique prise en charge. Cependant, pour chaque API graphique prise en charge dans le projet, nous avons besoin d’un ensemble dédié de variantes de shader. Par exemple, si nous créons un lecteur Android qui prend en charge à la fois OpenGL ES 3 et Vulkan, nous avons besoin de deux ensembles de variantes de shader. Par conséquent, le temps de création du lecteur et la taille des données du shader sont directement proportionnels au nombre d’API graphiques prises en charge.

Pipeline de construction de shader

Le pipeline de compilation de shader dans Unity est une boîte noire où chaque shader du projet est analysé pour extraire des extraits de shader avant de collecter les instructions de prétraitement des variantes, telles que multi_compile et shader_feature. Cela produit une liste de paramètres de compilation, un par variante de shader.

Ces paramètres de compilation incluent l'extrait de shader, le niveau graphique, le type de shader, l'ensemble de mots-clés du shader, le type de passe et le nom. Chacun des paramètres de compilation définis est utilisé pour produire une seule variante de shader.

Par conséquent, Unity exécute une passe de suppression automatique des variantes de shader basée sur deux heuristiques. Tout d'abord, le dépouillement est basé sur les paramètres du projet. Par exemple, si la prise en charge de la réalité virtuelle est désactivée, les variantes de shader VR sont systématiquement supprimées. Deuxièmement, le décapage automatique est basé sur la configuration de la section Shader Stripping des Paramètres graphiques.

Options de suppression automatique des variantes de shader dans les paramètres graphiques.
Options de suppression automatique des variantes de shader dans les paramètres graphiques.

La suppression automatique des variantes de shader est basée sur des restrictions de temps de construction. Unity ne peut pas sélectionner automatiquement uniquement les variantes de shader nécessaires au moment de la construction, car ces variantes de shader dépendent de l'exécution C# au moment de l'exécution. Par exemple, si un script C# ajoute une lumière ponctuelle mais qu'il n'y avait pas de lumière ponctuelle au moment de la construction, le pipeline de construction du shader n'a aucun moyen de déterminer que le lecteur aurait besoin d'une variante de shader qui effectue l'ombrage de la lumière ponctuelle.

Voici une liste de variantes de shader avec des mots-clés activés qui sont supprimés automatiquement :

Modes Lightmap : LIGHTMAP_ON, DIRLIGHTMAP_COMBINED, DYNAMICLIGHTMAP_ON, LIGHTMAP_SHADOW_MIXING, OMBRES_SHADOWMASK

Modes de brouillard : FOG_LINEAR, FOG_EXP, FOG_EXP2

Instanciation de variantes : INSTANCING_ON

De plus, lorsque la prise en charge de la réalité virtuelle est désactivée, les variantes de shader avec les mots-clés intégrés suivants activés sont supprimées :

INSTANCATION_STÉRÉO_ACTIVÉE, MULTIVUE_STÉRÉO_ACTIVÉE, RENDU_CUBEMAP_STÉRÉO_ACTIVÉ, UNITY_SINGLE_PASS_STEREO

Une fois le décapage automatique terminé, le pipeline de création de shader utilise les ensembles de paramètres de compilation restants pour planifier la compilation des variantes de shader en parallèle, en lançant autant de compilations simultanées que la plate-forme possède de threads de cœur de processeur.

Voici une représentation visuelle de ce processus :

Architecture de pipeline de shader avec intégration de décapage de variantes de shader scriptables en orange.
Architecture de pipeline de shader avec intégration de décapage de variantes de shader scriptables en orange.

Dans Unity 2018.2 beta, l'architecture du pipeline de shader introduit une nouvelle étape juste avant la planification de la compilation des variantes de shader, permettant aux utilisateurs de contrôler la compilation des variantes de shader. Cette nouvelle étape est exposée via des rappels C# au code utilisateur, et chaque rappel est exécuté par extrait de shader.

API de suppression de variantes de shaders scriptables

À titre d’exemple, le script suivant permet de supprimer toutes les variantes de shader qui seraient associées à une configuration « DEBUG », identifiée par un mot-clé « DEBUG » utilisé dans la version de développement du lecteur.

using System.Collections.Generic;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Rendering;
using UnityEngine;
using UnityEngine.Rendering;

// Simple example of stripping of a debug build configuration
class ShaderDebugBuildProcessor : IPreprocessShaders
{
    ShaderKeyword m_KeywordDebug;

    public ShaderDebugBuildProcessor()
    {
        m_KeywordDebug = new ShaderKeyword("DEBUG");
    }

    // Multiple callback may be implemented.
    // The first one executed is the one where callbackOrder is returning the smallest number.
    public int callbackOrder { get { return 0; } }

    public void OnProcessShader(
        Shader shader, ShaderSnippetData snippet, IList<ShaderCompilerData> shaderCompilerData)
    {
        // In development, don't strip debug variants
        if (EditorUserBuildSettings.development)
            return;

        for (int i = 0; i < shaderCompilerData.Count; ++i)
        {
            if (shaderCompilerData[i].shaderKeywordSet.IsEnabled(m_KeywordDebug))
            {
                shaderCompilerData.RemoveAt(i);
                --i;
            }
        }
    }
}


OnProcessShader est appelé juste avant la planification de la compilation de la variante de shader.

Chaque combinaison d'un Shader, d'une instance de ShaderSnippetData et d'une instance de ShaderCompilerData est un identifiant pour une seule variante de shader que le compilateur de shader produira. Pour supprimer cette variante de shader, il suffit de la supprimer de la liste ShaderCompilerData.

Chaque variante de shader que le compilateur de shader doit générer apparaîtra dans ce rappel. Lorsque vous travaillez sur le script de suppression des variantes de shader, vous devez d'abord déterminer quelles variantes doivent être supprimées, car elles ne sont pas utiles pour le projet.

Résultats
Suppression des variantes de shader pour un pipeline de rendu

Un cas d'utilisation pour la suppression des variantes de shader scriptables consiste à supprimer systématiquement les variantes de shader non valides d'un pipeline de rendu en raison des différentes combinaisons de mots-clés de shader.

Un script de suppression des variantes de shader inclus dans le pipeline de rendu HD vous permet de réduire systématiquement le temps de construction et la taille d'un projet à l'aide du pipeline de rendu HD. Ce script s'applique aux shaders suivants :

HDRenderPipeline/Lit
HDRenderPipeline/LitTessellation
HDRenderPipeline/LayeredLit
HDRenderPipeline/LayeredLitTessellation

Le script produit les résultats suivants :

                                 Unstripped    Stripped 
Player Data Shader Variant Count 24350 (100%)  12122 (49.8%) 
Player Data Size on disk         511 MB        151 MB 
Player Build Time                4864 seconds  1356 seconds
Capture d'écran de la démo de photogrammétrie de Fontainebleau utilisant le pipeline de rendu HD à partir de la résolution standard PlayStation 4 1920x1080.
Capture d'écran de la démo de photogrammétrie de Fontainebleau utilisant le pipeline de rendu HD à partir de la résolution standard PlayStation 4 1920x1080.

De plus, le pipeline de rendu léger pour Unity 2018.2 dispose d'une interface utilisateur permettant d'alimenter automatiquement un script de suppression capable de supprimer automatiquement jusqu'à 98 % des variantes de shader, ce qui, selon nous, sera particulièrement utile pour les projets mobiles.

Suppression des variantes de shader pour un projet

Un autre cas d’utilisation est un script permettant de supprimer toutes les fonctionnalités de rendu d’un pipeline de rendu qui ne sont pas utilisées pour un projet spécifique. En utilisant une démonstration de test interne pour le pipeline de rendu léger, nous avons obtenu les résultats suivants pour l'ensemble du projet :

                                 Unstripped  Stripped 
Player Data Shader Variant Count 31080       7056 
Player Data Size on disk         121         116 
Player Build Time                839 seconds 286 seconds

Comme nous pouvons le voir, l’utilisation d’un script de stripping de variantes de shader peut conduire à des résultats significatifs et avec plus de travail sur le script de stripping, nous pourrions aller encore plus loin.

Capture d'écran d'une démonstration de pipeline léger.
Capture d'écran d'une démonstration de pipeline léger.
Conseils pour écrire du code de suppression de variantes de shader
Améliorer la conception du code du shader

Un projet peut rapidement se retrouver confronté à une explosion du nombre de variantes de shaders, entraînant un temps de compilation et une taille de données du lecteur insoutenables. Le découpage de shaders scriptables permet de résoudre ce problème, mais vous devez réévaluer la manière dont vous utilisez les mots-clés de shaders pour générer des variantes de shaders plus pertinentes. Nous pouvons compter sur le #pragma skip_variants pour tester les mots-clés inutilisés dans l'éditeur.

Par exemple, dans ShaderStripping/Color Shader, les directives de prétraitement sont déclarées avec le code suivant :

#pragma multi_compile COLOR_ORANGE COLOR_VIOLET COLOR_GREEN COLOR_GRAY // color keywords
#pragma multi_compile OP_ADD OP_MUL OP_SUB // operator keywords


Cette approche implique que toutes les combinaisons de mots-clés de couleur et de mots-clés d’opérateur seront générées.

Disons que nous voulons restituer la scène suivante :

COULEUR_ORANGE + OP_ADD, COULEUR_VIOLET + OP_MUL, COULEUR_VERT + OP_MUL.
COULEUR_ORANGE + OP_ADD, COULEUR_VIOLET + OP_MUL, COULEUR_VERT + OP_MUL.

Tout d’abord, nous devons nous assurer que chaque mot-clé est réellement utile. Dans cette scène, COLOR_GRAY et OP_SUB ne sont jamais utilisés. Si nous pouvons garantir que ces mots-clés ne seront jamais utilisés, nous devrions les supprimer.

Deuxièmement, nous devons combiner des mots-clés qui produisent efficacement un chemin de code unique. Dans cet exemple, l'opération « ajouter » est toujours utilisée exclusivement avec la couleur « orange ». Nous pouvons donc les combiner dans un seul mot-clé et refactoriser le code comme indiqué ci-dessous.

#pragma multi_compile ADD_COLOR_ORANGE MUL_COLOR_VIOLET MUL_COLOR_GREEN

#if defined(ADD_COLOR_ORANGE)
	#define COLOR_ORANGE
	#define OP_ADD
#elif defined(MUL_COLOR_VIOLET)
	#define COLOR_VIOLET
	#define OP_MUL
#elif defined(MUL_COLOR_GREEN)
	#define COLOR_GREEN
	#define OP_MUL
#endif


Bien sûr, il n’est pas toujours possible de refactoriser les mots-clés. Dans ces cas, la suppression des variantes de shaders scriptables est un outil précieux !

Utilisation de callbackOrder pour supprimer les variantes de shader en plusieurs étapes

Pour chaque extrait, tous les scripts de suppression de variantes de shader sont exécutés. Nous pouvons ordonner l'exécution des scripts en ordonnant la valeur renvoyée par la fonction membre callbackOrder. Le pipeline de construction du shader exécutera les rappels dans l'ordre croissant de callbackOrder, donc le plus bas en premier et le plus haut en dernier.

Un cas d'utilisation pour l'utilisation de plusieurs scripts de suppression de shaders consiste à séparer les scripts par objectif. Par exemple:

  • Scénario 1 : Supprime systématiquement toutes les variantes de shader avec des chemins de code non valides.
  • Scénario 2 : Supprime toutes les variantes de shader de débogage.
Variantes
  • Scénario 3 : Supprime toutes les variantes de shader dans la base de code qui ne sont pas nécessaires pour le projet en cours.
  • Scénario 4 : Enregistre les variantes de shader restantes et les supprime toutes pour un temps d'itération rapide sur les scripts de suppression.
Processus d'écriture d'un script de suppression de variantes de shader

Le stripping des variantes de shader est extrêmement puissant mais nécessite beaucoup de travail pour obtenir de bons résultats.

Dans la vue Projet, filtrez tous les shaders.

Sélectionnez un shader et, dans l’inspecteur, cliquez sur Afficher pour ouvrir la liste des mots-clés/variantes de ce shader. Il y aura une liste de mots-clés qui sont toujours inclus dans la construction.

Assurez-vous de connaître les fonctionnalités graphiques spécifiques utilisées par le projet.

Vérifiez si les mots-clés sont utilisés dans toutes les étapes du shader. Une seule variante est nécessaire pour les étapes qui n'utilisent pas ces mots-clés.

Supprimer les variantes de shader dans le script.

Vérifiez les visuels dans la construction.

Répétez les étapes 2 à 6 pour chaque shader.

Télécharger l'exemple de projet

L'exemple de projet utilisé pour illustrer cet article de blog peut être téléchargé ici. Il nécessite Unity 2018.2.0b1.

Apprenez-en plus sur l'optimisation de la taille du déploiement binaire sur Unite Berlin

Venez à la conférence de Jonas Echterhoff le 21 juin et découvrez tous les nouveaux outils qui vous donnent plus de contrôle sur ce qui se retrouve dans votre build !