O que você está procurando?
Engine & platform

Remoção de variantes de shader com script

CHRISTOPHE RICCIO / UNITY TECHNOLOGIESContributor
May 14, 2018|12 Min
Remoção de variantes de shader com script
Esta página da Web foi automaticamente traduzida para sua conveniência. Não podemos garantir a precisão ou a confiabilidade do conteúdo traduzido. Se tiver dúvidas sobre a precisão do conteúdo traduzido, consulte a versão oficial em inglês da página da Web.

Reduza enormemente o tempo de compilação do Player e o tamanho dos dados, permitindo que os desenvolvedores controlem quais variantes de Shader são tratadas pelo compilador Unity Shader e incluídas nos dados do Player.

O tempo de construção do player e o tamanho dos dados aumentam junto com a complexidade do projeto devido ao número crescente de variantes de shader.

Com a remoção de variantes de shader com script, introduzida na versão beta 2018.2, o senhor pode gerenciar o número de variantes de shader geradas e, portanto, reduzir drasticamente o tempo de criação do Player e o tamanho dos dados.

Esse recurso permite que o senhor elimine todas as variantes de shader com caminhos de código inválidos, elimine variantes de shader para recursos não utilizados ou crie configurações de compilação de shader, como "debug" e "release", sem afetar o tempo de iteração ou a complexidade da manutenção.

Nesta postagem do blog, primeiro definimos alguns dos termos que usamos. Em seguida, nos concentramos na definição de variantes de shader para explicar por que podemos gerar tantas. Em seguida, há uma descrição da remoção automática de variantes de sombreador e de como a remoção de variantes de sombreador com script é implementada na arquitetura de pipeline de sombreador do Unity. Em seguida, examinamos a API de remoção de variantes de shader com script antes de discutir os resultados da demonstração do Fountainbleau e concluir com algumas dicas sobre como escrever scripts de remoção.

Aprender a remover as variantes de shader com script não é uma tarefa trivial, mas pode levar a um grande aumento na eficiência da equipe!

Conceitos

Para entender o recurso de remoção de variantes de shader com script, é importante ter uma compreensão precisa dos diferentes conceitos envolvidos.

  • Ativo de sombreamento: O código-fonte completo do arquivo com propriedades, sub-shader, passes e HLSL.
  • Trecho de shader: O código de entrada HLSL com dependências para um único estágio de shader.
  • Estágio do sombreador: Um estágio específico no pipeline de renderização da GPU, normalmente um estágio de sombreador de vértice e um estágio de sombreador de fragmento.
  • Palavra-chave Shader: Um identificador de pré-processador para ramificações em tempo de compilação entre shaders.
  • Conjunto de palavras-chave do sombreador: Um conjunto específico de palavras-chave do shader que identifica um caminho de código específico.
  • Variante de sombreamento: O código de shader específico da plataforma gerado pelo compilador de shader do Unity, para um único estágio de shader para uma camada gráfica específica, passagem, conjunto de palavras-chave de shader, etc.
  • Uber shader: Uma fonte de shader que pode produzir muitas variantes de shader.

No Unity, os uber shaders são gerenciados pelos sub shaders, passes e tipos de shader do ShaderLab, bem como pelas diretivas do pré-processador#pragma multi_compile e #pragma shader_feature.

Contagem do número de variantes de shader geradas

Para usar a remoção de variantes de shader com script, o senhor precisa entender claramente o que é uma variante de shader e como as variantes de shader são geradas pelo pipeline de criação de shader. O número de variantes de shader geradas é diretamente proporcional ao tempo de construção e ao tamanho dos dados da variante de shader do Player. Uma variante de shader é uma saída do pipeline de criação de shader.

As palavras-chave do shader são um dos elementos que causam a geração de variantes de shader. O uso desconsiderado de palavras-chave de shader pode levar rapidamente a uma explosão de contagem de variantes de shader e, portanto, a um tempo de construção extremamente longo.

Para ver como as variantes de shader são geradas, o shader simples a seguir conta quantas variantes de shader ele produz:

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
		}
	}
}

O número total de variantes de shader em um projeto é determinístico e dado pela seguinte equação:

Equação

O exemplo trivial ShaderVariantStripping a seguir esclarece essa equação. É um único shader que simplifica a equação da seguinte forma:

Equação

Da mesma forma, esse shader tem um único sub shader e uma única passagem, o que simplifica ainda mais a equação:

Equação

As palavras-chave na equação referem-se às palavras-chave da plataforma e do shader. Uma camada de gráficos é uma combinação específica de conjunto de palavras-chave de plataforma.

O ShaderVariantStripping/Pass tem duas diretivas de compilação múltipla. A primeira diretiva define 4 palavras-chave (COLOR_ORANGE, COLOR_VIOLET, COLOR_GREEN, COLOR_GRAY) e a segunda diretiva define 3 palavras-chave (OP_ADD, OP_MUL, OP_SUB). Por fim, a passagem define dois estágios de sombreamento: um estágio de sombreamento de vértice e um estágio de sombreamento de fragmento.

Esse total de variantes de shader é fornecido para uma única API gráfica compatível. No entanto, para cada API gráfica suportada no projeto, precisamos de um conjunto dedicado de variantes de shader. Por exemplo, se criarmos um Android Player compatível com OpenGL ES 3 e Vulkan, precisaremos de dois conjuntos de variantes de shader. Como resultado, o tempo de criação do Player e o tamanho dos dados do shader são diretamente proporcionais ao número de APIs gráficas compatíveis.

Pipeline de criação de sombreadores

O pipeline de compilação de shader no Unity é uma caixa preta em que cada shader no projeto é analisado para extrair trechos de shader antes de coletar as instruções de pré-processamento variantes, como multi_compile e shader_feature. Isso produz uma lista de parâmetros de compilação, um por variante de shader.

Esses parâmetros de compilação incluem o snippet do shader, a camada gráfica, o tipo de shader, o conjunto de palavras-chave do shader, o tipo e o nome da passagem. Cada um dos parâmetros de compilação definidos é usado para produzir uma única variante de shader.

Consequentemente, a Unity executa uma passagem automática de remoção da variante do sombreador com base em duas heurísticas. Em primeiro lugar, a remoção se baseia nas Configurações do projeto; por exemplo, se o Suporte à realidade virtual estiver desativado, as variantes do shader de VR serão sistematicamente removidas. Em segundo lugar, a remoção automática se baseia na configuração da seção Shader Stripping das Configurações de gráficos.

Opções de remoção automática de variantes de shader nas GraphicsSettings.
Opções de remoção automática de variantes de shader nas GraphicsSettings.

A remoção automática de variantes de shader baseia-se em restrições de tempo de compilação. A Unity não pode selecionar automaticamente apenas as variantes de shader necessárias no momento da compilação porque essas variantes de shader dependem da execução do C# em tempo de execução. Por exemplo, se um script C# adicionar uma luz pontual, mas não houver luzes pontuais no momento da compilação, não há como o pipeline de compilação de sombreamento descobrir que o Player precisaria de uma variante de sombreamento que faça sombreamento de luz pontual.

Aqui está uma lista de variantes de shader com palavras-chave ativadas que são removidas automaticamente:

Modos de mapa de luz: LIGHTMAP_ON, DIRLIGHTMAP_COMBINED, DYNAMICLIGHTMAP_ON, LIGHTMAP_SHADOW_MIXING, SHADOWS_SHADOWMASK

Modos de neblina: FOG_LINEAR, FOG_EXP, FOG_EXP2

Variantes de instanciamento: INSTANCING_ON

Além disso, quando o suporte à Realidade Virtual está desativado, as variantes de sombreador com as seguintes palavras-chave incorporadas ativadas são removidas:

STEREO_INSTANCING_ON, STEREO_MULTIVIEW_ON, STEREO_CUBEMAP_RENDER_ON, UNITY_SINGLE_PASS_STEREO

Quando a remoção automática é feita, o pipeline de compilação do sombreador usa os conjuntos de parâmetros de compilação restantes para agendar a compilação da variante do sombreador em paralelo, iniciando tantas compilações simultâneas quanto a plataforma tiver threads de núcleo de CPU.

Aqui está uma representação visual desse processo:

Arquitetura do pipeline do sombreador com integração de remoção de variantes de sombreador com script em laranja.
Arquitetura do pipeline do sombreador com integração de remoção de variantes de sombreador com script em laranja.

Na versão beta do Unity 2018.2, a arquitetura do pipeline de shader introduz um novo estágio logo antes do agendamento da compilação da variante de shader, permitindo que os usuários controlem a compilação da variante de shader. Esse novo estágio é exposto por meio de retornos de chamada C# para o código do usuário, e cada retorno de chamada é executado por trecho de shader.

API de remoção de variantes de shader com script

Como exemplo, o script a seguir permite a remoção de todas as variantes de shader que seriam associadas a uma configuração "DEBUG", identificada por uma palavra-chave "DEBUG" usada na compilação do Player de desenvolvimento.

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 é chamado logo antes do agendamento da compilação da variante do shader.

Cada combinação de um Shader, um ShaderSnippetData e instâncias de ShaderCompilerData é um identificador de uma única variante de shader que o compilador de shader produzirá. Para remover essa variante de shader, basta removê-la da lista ShaderCompilerData.

Cada variante de shader que o compilador de shader deve gerar aparecerá nessa chamada de retorno. Ao trabalhar no script de remoção das variantes do shader, o senhor precisa primeiro descobrir quais variantes precisam ser removidas, pois não são úteis para o projeto.

Resultados
Remoção de variantes de sombreador para um pipeline de renderização

Um caso de uso para a remoção de variantes de shader com script é remover sistematicamente as variantes de shader inválidas de um pipeline de renderização devido às várias combinações de palavras-chave de shader.

Um script de remoção de variantes de shader incluído no pipeline de renderização HD permite que o senhor reduza sistematicamente o tempo de criação e o tamanho de um projeto usando o pipeline de renderização HD. Esse script se aplica aos seguintes shaders:

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

O script produz os seguintes resultados:

                                 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
Captura de tela da demonstração de fotogrametria de Fontainebleau usando o Pipeline de Renderização HD a partir da resolução padrão de 1920x1080 do PlayStation 4.
Captura de tela da demonstração de fotogrametria de Fontainebleau usando o Pipeline de Renderização HD a partir da resolução padrão de 1920x1080 do PlayStation 4.

Além disso, o pipeline de renderização Lightweight para Unity 2018.2 tem uma interface de usuário para alimentar automaticamente um script de remoção que pode remover automaticamente até 98% das variantes de shader, o que esperamos que seja particularmente valioso para projetos móveis.

Remoção de variantes de shader para um projeto

Outro caso de uso é um script para remover todos os recursos de renderização de um pipeline de renderização que não são usados para um projeto específico. Usando uma demonstração de teste interno para o pipeline de renderização Lightweight, obtivemos os seguintes resultados para todo o projeto:

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

Como podemos ver, o uso da remoção de variantes de shader com script pode levar a resultados significativos e, com mais trabalho no script de remoção, poderíamos ir ainda mais longe.

Captura de tela de uma demonstração do pipeline Lightweight.
Captura de tela de uma demonstração do pipeline Lightweight.
Dicas para escrever o código de remoção de variantes de shader
Aprimoramento do design do código do shader

Um projeto pode rapidamente entrar em uma explosão de contagem de variantes de shader, levando a um tempo de compilação insustentável e ao tamanho dos dados do Player. O shader stripping com script ajuda a lidar com esse problema, mas o senhor deve reavaliar como está usando as palavras-chave do shader para gerar variantes de shader mais relevantes. Podemos contar com o #pragma skip_variants para testar palavras-chave não utilizadas no editor.

Por exemplo, em ShaderStripping/Color Shader, as diretivas de pré-processamento são declaradas com o seguinte código:

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


Essa abordagem implica que todas as combinações de palavras-chave de cor e palavras-chave de operador serão geradas.

Digamos que queiramos renderizar a seguinte cena:

COLOR_ORANGE + OP_ADD, COLOR_VIOLET + OP_MUL, COLOR_GREEN + OP_MUL.
COLOR_ORANGE + OP_ADD, COLOR_VIOLET + OP_MUL, COLOR_GREEN + OP_MUL.

Primeiro, devemos nos certificar de que cada palavra-chave seja realmente útil. Nessa cena, COLOR_GRAY e OP_SUB nunca são usados. Se pudermos garantir que essas palavras-chave nunca serão usadas, então devemos removê-las.

Em segundo lugar, devemos combinar palavras-chave que produzam efetivamente um único caminho de código. Neste exemplo, a operação "add" é sempre usada exclusivamente com a cor "laranja". Portanto, podemos combiná-las em uma única palavra-chave e refatorar o código conforme mostrado abaixo.

#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


É claro que nem sempre é possível refatorar palavras-chave. Nesses casos, a remoção de variantes de shader com script é uma ferramenta valiosa!

Usando callbackOrder para remover variantes de shader em várias etapas

Para cada snippet, todos os scripts de remoção de variantes de shader são executados. Podemos ordenar a execução dos scripts ordenando o valor retornado pela função membro callbackOrder. O pipeline de criação do sombreador executará as callbacks em ordem crescente de callbackOrder, de modo que a mais baixa primeiro e a mais alta por último.

Um caso de uso de vários scripts de shader stripping é separar os scripts por finalidade. Por exemplo:

  • Script 1: Remove sistematicamente todas as variantes de shader com caminhos de código inválidos.
  • Script 2: Remove todas as variantes do shader de depuração.
Variantes
  • Script 3: Remove todas as variantes de shader na base de código que não são necessárias para o projeto atual.
  • Script 4: Registra as variantes de shader restantes e remove todas elas para acelerar o tempo de iteração nos scripts de remoção.
Processo para escrever um script de remoção de variantes de shader

A remoção de variantes de sombreador é extremamente avançada, mas requer muito trabalho para obter bons resultados.

Na visualização Project (Projeto), filtre todos os shaders.

Selecione um shader e, no Inspector, clique em Show para abrir a lista de palavras-chave/ variantes desse shader. Haverá uma lista de palavras-chave que sempre são incluídas na compilação.

Certifique-se de que o senhor sabe quais recursos gráficos específicos o projeto usa.

Verifique se as palavras-chave são usadas em todos os estágios do sombreador. Apenas uma variante é necessária para os estágios que não usam essas palavras-chave.

Retire as variantes do shader no script.

Verifique os recursos visuais na compilação.

Repita as etapas 2 a 6 para cada shader.

Faça o download do projeto de amostra

O projeto de exemplo usado para ilustrar esta postagem do blog pode ser baixado aqui. Ele requer o Unity 2018.2.0b1.

Saiba mais sobre como otimizar o tamanho da implantação binária na Unite Berlin

Venha para a palestra de Jonas Echterhoff no dia 21 de junho e conheça todas as novas ferramentas que lhe dão mais controle sobre o que acaba em sua construção!