Otimização da memória de jogos Unreal no Android

O uso da memória é fundamental no desenvolvimento de jogos para Android. Ao desenvolver jogos usando o Unreal Engine, você precisa analisar e acompanhar continuamente o estado da memória para atender a essas restrições, evitando vazamentos e alocações excessivas.

Economia de memória com a redução da permutação de sombreadores

Ao remover opções não usadas em Lighting ou Mobile Shader Permutation Reduction, você pode minimizar o número de permutações de sombreadores. Isso reduz significativamente o tamanho do pacote do app, diminui o número de objetos de estado do pipeline (PSOs) e evita a criação de destinos de renderização não usados ou texturas fictícias, o que leva a uma redução da memória de execução e a um melhor desempenho.

Para mais informações, consulte a seção Misc Lighting and Shader Permutation Reduction Settings for Mobile em Diretrizes de desempenho para dispositivos móveis.

Otimização do carregamento de recursos

Ao carregar recursos usando referências fixas, o conteúdo conectado que nem é usado no jogo é carregado na memória simultaneamente, causando um consumo desnecessário. Em particular, quando a herança é usada de forma inadequada em Blueprints, CDOs (objetos padrão de classe) desnecessários de pais são alocados, desperdiçando uma quantidade significativa de memória. Nesses casos, aplique as técnicas a seguir para otimizar o uso da memória.

  • Definição de código C++ e separação de dados: defina as funcionalidades principais usando UCLASS(Abstract) no código C++, separando-as dos dados reais.
  • Minimize a herança: execute a configuração de dados e o carregamento de recursos apenas em Blueprints de nós folha.
  • Use referências indiretas: aplique referências de propriedades indiretas usando TSoftObjectPtr para evitar referências fixas e carregue recursos de forma assíncrona estritamente quando necessário no momento da execução.
  • Precauções: ao usar o carregamento de recursos assíncrono, verifique se as verificações de validade (IsValid) são realizadas durante o callback de conclusão do carregamento para evitar ponteiros pendentes ou falhas.

Para mais informações, consulte Referenciar recursos e criar jogos para dispositivos móvel com o UE5.

Ponteiros inteligentes e ajuste da coleta de lixo (GC)

O uso de ponteiros inteligentes (como TSharedPtr e TWeakPtr) durante o desenvolvimento em C++ evita ponteiros pendentes e vazamentos de memória. Além disso, o uso de TWeakPtr evita a criação de referências fortes a objetos, para que o coletor de lixo não incremente as contagens de referência ao coletar objetos. Isso evita a sobrevivência desnecessária de objetos e diminui a complexidade do gráfico de referência que o GC precisa percorrer, reduzindo significativamente o overhead do GC. A coleta de lixo pode ser gerenciada de maneira eficaz em ambientes móveis acionando ForceGarbageCollection durante transições de nível ou períodos de inatividade, como quando não há interação do usuário. Além disso, ela pode ser acionada quando o jogo está em um estado perceptível ou em segundo plano (por exemplo, callbacks onStop), garantindo que a coleta de lixo seja realizada sem afetar a experiência de jogo ativa.

Para mais informações, consulte Coleta de lixo e Ponteiro inteligente.

Otimização usando a compactação da tabela de realocação

A compactação da tabela de realocação é um método de otimização que compacta o tamanho da tabela de realocação em binários ELF para reduzir o tamanho binário do Android. Quando o minSDKVersion do Android é 23 ou mais recente, a compactação da tabela de realocação do APS é compatível. Quando o minSDKVersion é 28 ou mais recente, a compactação da tabela de realocação RELR também é compatível, reduzindo significativamente o tamanho do arquivo .so. Isso não apenas diminui o tamanho do download, mas também reduz o consumo de memória do .so mmap no tempo de execução, mitigando a pressão geral da memória física.

AndroidToolChain.cs

if (MinSDKVersion >= 28)
{
  Result += " -Wl,--pack-dyn-relocs=android+relr,--use-android-relr-tags";
}
else if (MinSDKVersion >= 23)
{
  Result += " -Wl,--pack-dyn-relocs=android";
}

Para aplicar essa otimização, verifique se a opção Enable compression of relocation tables nas configurações do projeto ou a variável bEnableAdvancedBinaryCompression está ativada.

Para verificar se a compactação RELR foi aplicada corretamente ao binário, use a ferramenta llvm-readelf do Android NDK.

llvm-readelf -d libUnreal.so | grep RELR

Na prática, a criação do modelo ThirdPerson do Unreal Engine e a medição usando dumpsys meminfo confirmam uma redução no uso de PSS e RSS para .so mmap.

Categoria PSS total Privado sujo Privado limpo Swap sujo RSS total
.so mmap antes da compactação 170.210 24.360 141.564 0 242.204
.so mmap após a compactação 138.031 24.748 108.976 0 210.140

Compactação de textura

Use uma compactação de textura adequada para reduzir o consumo de memória dos recursos gráficos gerais. O uso da Compactação de Textura Escalonável Adaptável (ASTC), o padrão para dispositivos móveis, oferece qualidade visual superior em comparação com o ETC2 no mesmo tamanho ou alcança taxas de compactação mais altas com menor consumo de memória na mesma meta de qualidade visual.

Otimização de streaming de textura

Em ambientes móveis, o uso direto do tamanho do pool de streaming de textura padrão do Unreal Engine pode pré-alocar um pool de memória excessivamente grande em relação às especificações do dispositivo. Portanto, é ideal definir perfis de dispositivos móveis e selecionar um tamanho de pool de streaming de textura adequado de acordo com os limites de memória do dispositivo. Além disso, use o recurso Statistic do editor para verificar as estatísticas de textura e identificar texturas que consomem muita memória e ocupam resoluções extremas no momento da execução. Combine isso com o recurso Required Texture Resolution para aplicar limites ou reduzir as resoluções máximas em texturas exigentes, mantendo o uso de recursos controlado no buffer de memória de textura alocado.