Melhorias nos tempos de construção do shader e no uso de memória no 2021 LTS

À medida que o conjunto de recursos disponíveis do Scriptable Render Pipeline (SRP) do Unity continua a crescer, também aumenta a quantidade de variantes de shader sendo processadas e compiladas no momento da compilação. Além do suporte contínuo para APIs gráficas adicionais e uma seleção cada vez maior de plataformas de destino, as melhorias do SRP continuam a se expandir.
Os shaders são compilados e armazenados em cache após uma compilação inicial (“limpa”), acelerando assim futuras compilações incrementais (“quentes”). Embora compilações limpas geralmente demorem mais, longos tempos de compilação a quente podem ser um problema comum durante o desenvolvimento e a iteração do projeto.

Para resolver esse problema, a equipe de gerenciamento de shaders da Unity tem trabalhado arduamente para fornecer soluções significativas e escaláveis. Isso resultou em tempos de construção de shaders e uso de memória de tempo de execução significativamente reduzidos para projetos criados usando o Unity 2021 LTS e versões posteriores.
Para ler mais sobre essas novas otimizações, incluindo versões afetadas, backports e números de nossos testes internos, pule diretamente para as seções que abordam pré-filtragem de variantes de shader e carregamento dinâmico de shader. No final desta postagem do blog, também abordamos nossos planos futuros para refinar ainda mais o gerenciamento de variantes de shader como um todo – na criação, construção e tempo de execução do projeto.
Antes de nos aprofundarmos nas melhorias interessantes feitas no sistema de shaders do Unity, vamos aproveitar a oportunidade para revisar rapidamente os conceitos de compilação de shaders condicional, variantes de shaderse remoção de variantes de shaders.
Os recursos de shader condicional permitem que desenvolvedores e artistas controlem e alterem convenientemente a funcionalidade de um shader usando scripts, configurações de material, bem como configurações de projeto e gráficos. Esses recursos condicionais servem para simplificar a criação de projetos, permitindo que eles sejam dimensionados com eficiência ao minimizar o número de shaders que você precisará criar e manter.

Os recursos de shader condicional podem ser implementados de diferentes maneiras:
- Ramificação estática (tempo de compilação)
- Compilação de variantes de shader
- Ramificação dinâmica (tempo de execução)
Embora a ramificação estática evite a sobrecarga de execução do shader relacionada à ramificação no tempo de execução, ela é avaliada e bloqueada no tempo de compilação e não fornece controle de tempo de execução. A compilação de variantes de shader, por sua vez, é uma forma de ramificação estática que fornece controle adicional de tempo de execução. Isso funciona compilando um programa de shader exclusivo (variante) para cada combinação possível de ramificações estáticas, a fim de manter o desempenho ideal da GPU em tempo de execução.
Essas variantes são criadas declarando e avaliando condicionalmente a funcionalidade do shader por meio das palavras-chave shader_feature e multi_compile . As variantes corretas do shader são carregadas em tempo de execução com base em palavras-chave ativas e configurações de tempo de execução. Declarar e avaliar palavras-chave de shader adicionais pode levar a um aumento no tempo de compilação, no tamanho do arquivo e no uso de memória em tempo de execução.
Ao mesmo tempo, a ramificação dinâmica (baseada em uniforme) evita completamente a sobrecarga da compilação de variantes de shader, resultando em compilações mais rápidas e redução do tamanho do arquivo e do uso de memória. Isso pode gerar uma iteração mais suave e rápida durante o desenvolvimento.
Por outro lado, a ramificação dinâmica pode ter um forte impacto no desempenho da execução do shader com base na complexidade do shader e no dispositivo de destino. Ramificações assimétricas, onde um lado da ramificação é muito mais complexo que o outro, podem impactar negativamente o desempenho. Isso ocorre porque a execução de um caminho mais simples ainda pode incorrer nas penalidades de desempenho do caminho mais complexo.
Ao introduzir recursos de shader condicional em seus próprios shaders, essas abordagens e compensações devem ser mantidas em mente. Para obter informações mais detalhadas, consulte a documentação sobre condicionais de shader, ramificação de shadere variantes de shader .
Para mitigar o aumento no processamento do shader e no tempo de compilação, a remoção de variantes do shader é utilizada. Seu objetivo é excluir variantes de shader desnecessárias da compilação com base em fatores como:
- Materiais incluídos e palavras-chave habilitadas
- Configurações do Pipeline de Projeto e Renderização
- Descascamento programável
Ao enumerar variantes de shader, o Editor filtrará automaticamente quaisquer palavras-chave declaradas com shader_feature que não estejam habilitadas pelos materiais referenciados e incluídos na compilação. Como resultado, essas palavras-chave não gerarão nenhuma variante adicional.
Por exemplo, se a propriedade do material Clear Coat não estiver habilitada por nenhum material que use o Complex Lit URP Shader, todas as variantes de shader que implementam a funcionalidade Clear Coat serão removidas com segurança no momento da construção.
Enquanto isso, as palavras-chave multi_compile solicitam que desenvolvedores e jogadores controlem livremente a funcionalidade do shader em tempo de execução com base nas configurações e scripts disponíveis do Player. O outro lado é que essas palavras-chave não podem ser removidas automaticamente pelo Editor no mesmo grau que as palavras-chave shader_feature . É por isso que eles geralmente produzem um número maior de variantes.
A remoção programável é uma API C# que permite excluir variantes de shader da compilação durante o tempo de construção por meio de palavras-chave e combinações não necessárias no tempo de execução. Os pipelines de renderização utilizam remoção programável para remover variantes desnecessárias de acordo com as configurações do pipeline de renderização do projeto e os ativos de qualidade incluídos na compilação.
Baixa qualidade Alta qualidade Multiplicador de variantes Luz principal/Sombras projetadas: Desligado Ligado 2x Luz principal/Sombras projetadas: Ligado Ligado 1x Luz principal/Sombras projetadas: Desligado Desligado 1x
Para maximizar os efeitos da remoção de variantes de shader do Editor, recomendamos desabilitar todos os recursos relacionados a gráficos e as configurações do Render Pipeline não utilizadas no tempo de execução. Consulte a documentação oficial para obter mais informações sobre remoção de variantes de shader.
A remoção de variantes de shader reduz bastante a quantidade de variantes de shader compiladas, com base em fatores como os ativos de qualidade do pipeline de renderização na compilação. No entanto, a remoção é atualmente realizada no final do estágio de processamento do shader. Simplesmente enumerar todas as variantes possíveis ainda pode levar muito tempo, independentemente da compilação.
Para reduzir os tempos de processamento de variantes de shader (e de construção de projetos), estamos introduzindo uma otimização significativa na remoção de variantes de shader integrada ao mecanismo. Com a pré-filtragem de variantes de shader, os tempos de construção limpos e quentes são significativamente reduzidos.
A otimização funciona introduzindo a exclusão antecipada de palavras-chave multi_compile , de acordo com os atributos de pré-filtragem orientados pelas configurações do Render Pipeline. Isso diminui a quantidade de variantes sendo enumeradas para potencial remoção e compilação, o que, por sua vez, reduz o tempo de processamento do shader – com tempos de construção a quente reduzidos em até 90% nos exemplos mais drásticos.
A pré-filtragem de variantes de shader foi lançada pela primeira vez em 2023.1.0a14e foi retroportada para 2022.2.0b15 e 2021.3.15f1.


A pré-filtragem de variantes também ajuda a reduzir os tempos de construção inicial/limpa aplicando o mesmo princípio.


Historicamente, o tempo de execução do Unity carregava todos os objetos de shader do disco para a memória da CPU durante o carregamento de cenas e recursos. Na maioria dos casos, um projeto e uma cena criados incluem muito mais variantes de shader do que o necessário em qualquer momento durante o tempo de execução do aplicativo. Para projetos que usam uma grande quantidade de shaders, isso geralmente resulta em alto uso de memória do shader em tempo de execução.
O carregamento dinâmico de shaders resolve o problema fornecendo controle refinado ao usuário sobre o comportamento de carregamento de shaders e o uso de memória. Essa otimização facilita o streaming de blocos de dados do shader para a memória, bem como a remoção de dados do shader que não são mais necessários no tempo de execução, com base em um orçamento de memória controlado pelo usuário. Isso permite que você reduza significativamente o uso de memória do shader em plataformas com orçamentos de memória limitados.
As novas configurações de carregamento de variantes de shader agora podem ser acessadas nas configurações do playerdo editor. Use-os para substituir o número máximo de blocos de shader carregados e o tamanho de bloco por shader (MB).

Com a seguinte API C# agora disponível, você pode substituir as configurações de carregamento de variantes do shader usando scripts do editor, como:
- PlayerSettings.SetDefaultShaderChunkCount e PlayerSettings.SetDefaultShaderChunkSizeInMB para substituir as configurações de carregamento de shader padrão do projeto
- PlayerSettings.SetShaderChunkCountForPlatform e PlayerSettings.SetShaderChunkSizeInMBForPlatformpara substituir essas configurações por plataforma
Você também pode substituir a quantidade máxima de blocos de shader carregados em tempo de execução usando a API C# via Shader.maximumChunksOverride. Isso permite que você substitua o orçamento de memória do shader com base em fatores como a memória total disponível do sistema e da placa de vídeo consultada em tempo de execução.
O carregamento dinâmico de shaders chegou em 2023.1.0a11 e foi retroportado para 2022.2.0b10, 2022.1.21f1e 2021.3.12f. No caso do Boat Attackdo Universal Render Pipeline (URP), observamos uma redução de 78,8% no uso de memória de tempo de execução para shaders, de 315 MiB (padrão) para 66,8 MiB (carregamento dinâmico). Você pode ler mais sobre essa otimização no anúncio oficial.

Além das mudanças críticas mencionadas acima, estamos trabalhando para aprimorar a geração e remoção de variantes de shader do Universal Render Pipeline. Também estamos investigando melhorias adicionais no gerenciamento de variantes de shader do Unity em geral. O objetivo final é facilitar o crescente conjunto de recursos do mecanismo, ao mesmo tempo em que garante o mínimo de sobrecarga de tempo de execução e construção de shaders.
Algumas de nossas investigações em andamento envolvem a desduplicação de recursos de shader em variantes semelhantes, bem como melhorias gerais nas palavras-chave de shader e nas APIs de coleção de variantes de shader. O objetivo é fornecer mais flexibilidade e controle sobre o processamento de variantes de shader e o desempenho do tempo de execução.
Olhando para o futuro, também estamos explorando a possibilidade de ferramentas no Editor para rastreamento e análise de variantes de shader para fornecer os seguintes detalhes sobre o uso de variantes de shader:
- Quais shaders e palavras-chave produzem mais variantes?
- Quais variantes são compiladas, mas não utilizadas em tempo de execução?
- Quais variantes são removidas, mas solicitadas em tempo de execução?
Seu feedback tem sido fundamental até agora, pois nos ajuda a priorizar as soluções mais significativas. Confira nosso roteiro público para votar nos recursos que melhor atendem às suas necessidades. Se houver alterações adicionais que você gostaria de ver, sinta-se à vontade para enviar uma solicitação de recursoou entre em contato com a equipe diretamente neste fórum de shaders.