O cache de SO manual requer executar uma compilação do seu jogo para coletar informações de PSO em um cache empacotado. O Pré-cache de PSO realiza a coleta automática de PSOs e compilação assíncrona para todos os PSOs que podem ser usados durante a renderização.
Configuração do pré-cache de PSO
As seguintes variáveis de console controlam o pré-cache de PSO:
| Variável do console | Descrição | Estado padrão |
|---|---|---|
| Variável de console global para habilitar pré-cache de PSO. Depende do sinalizador de RHI | Habilitado |
| PSOs de pré-cache usados pelos componentes. | Habilitado |
| PSOs de pré-cache usados por todos os recursos ( | Desabilitado |
| Aguarda a criação do proxy de componente até que todos os PSOs necessários sejam compilados. Se o usuário ainda estiver compilando ao criar o proxy, esses PSOs serão marcados como de alta prioridade. | Habilitado |
| Quando PSOs ainda estão compilando durante a criação de proxy do componente, isso adiciona uma opção para substituir o material pelo material padrão. Isso depende de | 0 (veja abaixo) |
| Aguarda apenas PSOs de alta prioridade durante o carregamento. Todos os PSOs não essenciais serão compilados durante a jogabilidade. Um PSO é marcado como de alta prioridade quando é necessário por um proxy e ainda não terminou de ser compilado. | Desabilitado |
| Indica se deve colocar PSOs globais de computação e gráficos em pré-cache também durante a inicialização da engine. | Habilitado |
Pré-cache de PSO de shader global
Alguns PSOs de shader global são colocados em pré-cache porque podem introduzir engasgos de tempo de execução no primeiro uso. Esses PSOs são compilados na inicialização da engine e são habilitados com a variável de console r.PSOPrecache.GlobalShaders por padrão.
Todas as permutações globais de shaders de computação que podem ser usadas pelo jogo são colocadas em pré-cache.
static EShaderPermutationPrecacheRequest ShouldPrecachePermutation(const FShaderPermutationParameters& Parameters)Isso costumava verificar se uma determinada permutação pode ser usada em tempo de execução, checando as configurações atuais das variáveis do console para excluir certas combinações. Por padrão, usa ShouldCompilePermutation, de modo que a permutação pré-cache deve ser um subconjunto das permutações compiladas.
A maioria dos PSOs gráficos globais é criada durante os primeiros quadros logo após o carregamento, e engasgos podem ser percebidos durante esses quadros. Usar um cache empacotado de PSO bem pequeno para coletar esses PSOs gráficos globais pode ajudar. Mas certas permutações de PSO gráfico global também são criadas e compiladas em tempo de execução, portanto, elas também devem ser colocadas em pré-cache. Para PSOs gráficos globais, são necessários coletores de PSO específicos para coletar todo o estado de renderização correto, o que é necessário para compilar o PSO gráfico.
No momento, o pré-cache de PSO está implementado para os seguintes tipos globais de shader:
Slate
Luzes diferidas
Simulação de partículas em cascata
neblina volumétrica
Compilar todos os PSOs globais na inicialização leva algum tempo e, normalmente, ocorre durante a navegação pelo menu principal. Embora isso não bloqueie a engine na inicialização, deve fazer parte da fase de espera da compilação do PSO durante a tela de carregamento inicial.
Pré-cache do PSO do componente
Os componentes primitivos (UPrimitiveComponent) faz o pré-cache de todos os PSOs necessários para a renderização imediatamente após o carregamento (durante o PostLoad). O pré-cache coleta todas as informações de estado do pipeline necessárias para compilar os PSOs, incluindo:
Materiais
Fábricas de vértices
Informações de elementos de vértice
Parâmetros específicos de pré-cache
A Unreal Engine usa essas informações para iterar em todos os processadores de etapas de malha possíveis nos quais o componente pode ser renderizado. Cada processador de etapa de malha adiciona os possíveis inicializadores de PSO que podem ser necessários durante a renderização. As tarefas em segundo plano verificam um cache de PSO compartilhado para garantir que os dados necessários já não estejam sendo colocados em pré-cache e compilam essas solicitações de forma assíncrona.
Um único componente pode precisar de muitos PSOs para ser renderizado corretamente em todas as etapas diferentes, como base, profundidade personalizada, profundidade, distorção, sombra, mapa de sombra virtual, velocidade e assim por diante. É importante que todos esses PSOs estejam prontos antes do componente estar pronto para não introduzir artefatos gráficos porque, por exemplo, ele pode ser renderizado em uma etapa e não em outra.
Quando a UE cria um proxy primitivo para um componente primitivo e os PSOs necessários ainda estão compilando, há várias opções disponíveis:
Atrase a criação do proxy até a conclusão da compilação do PSO (padrão). Isso fará com que a renderização seja pulada até que o PSO esteja pronto.
Substitua o material pelo material padrão da engine.
Continue e tenha um possível engasgo. A renderização será bloqueada na compilação do PSO.
Estratégia de atraso para criação de proxy
A variável de console r.PSOPrecache.ProxyCreationDelayStrategy depende da variável de console r.PSOPrecache.ProxyCreationWhenPSOReady. Se ProxyCreationWhenPSOReady estiver definido como 1 (habilitado), ProxyCreationDelayStrategy executará os seguintes comportamentos, dependendo do valor:
| Valor | Comportamento |
|---|---|
0 | Pular a renderização até que o PSO esteja pronto. |
1 | Utilizar o material padrão da engine até que o PSO esteja pronto. |
Tela de carregamento
Recomendamos configurar a tela de carregamento inicial considerando as solicitações de pré-cache do PSO. .
Ao configurar a tela de carregamento inicial para o seu jogo, você deve aguardar todas as solicitações de pré-cache de PSO pendentes no momento. Caso contrário, pode haver um aparecimento visual perceptível e até travamentos em tempo de execução para componentes que não suportam o atraso na criação do proxy, como o terreno da paisagem, onde talvez não seja recomendável substituir essas malhas por um material padrão ou deixar de renderizá-las.
FShaderPipelineCache::NumPrecompilesRemaining() é útil para verificar o número de compilações de pré-cache de PSO pendentes para o cache empacotado e o pré-cache de PSO. Você pode modificar a lógica da tela de carregamento para verificar esse número e manter a tela de carregamento ativa até que chegue a zero. Na maioria dos casos, o tempo de compilação inicial do PSO com um cache de driver vazio deve levar menos de um minuto em CPUs de especificações intermediárias.
Gerenciamento de recursos do sistema
O pré-cache de PSO depende da compilação assíncrona usando threads em segundo plano e afeta a memória e o desempenho do sistema. Esta seção explica as opções disponíveis para ajustar e otimizar o uso desses recursos de acordo com seu projeto.
Memória
Para economizar memória do sistema no tempo de execução, a UE exclui os PSOs compilados para pré-cache após a compilação. Isso ocorre porque, se a quantidade de PSOs colocados em pré-cache pelo seu aplicativo for muito alta, isso pode aumentar drasticamente o consumo de memória do aplicativo, a menos que eles sejam liberados (podendo chegar a centenas de megabytes ou até gigabytes).
O pré-cache de PSO depende da existência de um cache de driver comprimido subjacente. Mesmo quando os PSOs são excluídos após o pré-cache, eles são mantidos no cache do driver. Se for necessário um PSO no tempo de execução, o driver gráfico o carregará a partir do cache de driver comprimido. No entanto, isso também pode exigir muitos recursos de computação, e as primeiras recuperações desses caches podem levar alguns milissegundos. Você pode desabilitar a exclusão dos PSOs em pré-cache no D3D12 com D3D12.PSOPrecache.KeepLowLevel.
A criação de PSOs pelo cache do driver pode ser lenta em alguns fornecedores independentes de hardware (IHVs). Para NVIDIA, há uma opção para manter uma certa quantidade de gráficos em pré-cache e computar PSOs na memória com r.PSOPrecache.KeepInMemoryUntilUsed, que mantém os últimos N PSOs em pré-cache na memória e evita o impacto no desempenho do cache do driver. O número de PSOs retidos na memória pode ser ajustado para computação e gráficos separadamente com r.PSOPrecache.KeepInMemoryGraphicsMaxNum e r.PSOPrecache.KeepInMemoryComputeMaxNum. Se você decidir usar essa opção, é recomendável testar o custo de memória resultante para sua aplicação com diferentes configurações, para determinar um equilíbrio aceitável entre desempenho na criação de PSOs e sobrecarga de memória.
Desempenho
Por padrão, a Unreal Engine usa um pool de threads de pré-cache de PSO para compilar o PSO de forma assíncrona. Se r.pso.PrecompileThreadPoolSize ou r.pso.PrecompileThreadPoolPercentOfHardwareThreads estiver definido, esse pool de threads será usado. Caso contrário, a compilação de PSO volta a usar tarefas regulares em segundo plano, que são agendadas com o restante das cargas de trabalho da engine.
| Variável do console | Descrição | Estado padrão |
|---|---|---|
| Define a quantidade exata de threads a serem usadas no pool. | 0 |
| Define o tamanho do pool de threads como uma porcentagem dos threads de hardware disponíveis e cria um pool de threads com esse tamanho. O tamanho padrão é 75, que representa 75% dos threads de hardware. | 75 |
| A quantidade mínima de threads a serem usadas no pool de threads de PSO. | 2 |
| A quantidade máxima de threads a serem usados no pool de threads de PSO. O padrão é | INT_Max |
Observações adicionais:
Com uma quantidade ilimitada de threads máximos no pool, pode ser possível ficar sem memória do sistema durante a compilando de PSOs em sistemas com muitos processadores, mas sem muita memória. Cada thread que compila PSOs pode usar até 2 Gbs de memória. Portanto, limitar essa quantidade pode fazer sentido para o projeto.
Durante a jogabilidade, 75 por cento dos threads de hardware podem ser muito. A contenção com threads regulares em primeiro plano pode ser perceptível, causando pequenas quedas de quadros. Aumentar este valor durante o tempo de carregamento e diminui-lo novamente durante a jogabilidade pode ajudar, mas isso pode atrasar a compilação do PSO e aumentar a criação atrasada do proxy — isso não deve causar engasgos no tempo de execução.
Você pode usar o argumento de linha de comando -clearPSODriverCache para forçar a limpar o cache do driver. Isso é recomendado para testar a experiência de inicialização do jogo.
Ao testar em PCs com uma grande quantidade de núcleos, também recomendamos limitar a contagem de núcleos a 8, ou outra contagem típica de PCs domésticos, usando o argumento de linha de comando -corelimit=n, em que n é o número de núcleos e -processaffinity=n, garantindo que o Windows agende o jogo apenas em n núcleos físicos. Isso garante uma replicação mais precisa da experiência do usuário final.
Use a opção -clearPSODriverCache de forma consistente em todas as execuções de teste para avaliar a suavidade do jogo. Sem ela, problemas podem ser mascarados pelo cache de PSO criado pelo driver da placa de vídeo e deixados pelas execuções anteriores.
Validação e rastreamento
Existem diversas opções para validar e rastrear o desempenho do sistema de pré-cache de PSO.
Você pode habilitar a validação com r.PSOPrecache.Validation usando os seguintes valores:
| Variável do console | Descrição |
|---|---|
0 | Desabilitado |
1 | Rastreamento leve apenas com números de alto nível. Isso tem um impacto mínimo no desempenho e pode ser usado em títulos enviados. |
2 | Rastreamento e registro detalhados de erros de pré-cache de PSO. |
Quando a validação de pré-cache de PSO está ativa, você pode inspecionar as estatísticas coletadas usando o comando de console stat PSOPrecache.
As estatísticas coletadas pelo sistema de validação de pré-cache de PSO. Use o comando de console stat PSOPrecache para visualizá-los.
As estatísticas são divididas em 3 grupos:
| Grupo | Descrição |
|---|---|
PSOs somente para shader | Essas estatísticas rastreiam apenas os shaders de RHI usados e ignoram todas as outras informações de estado nos PSOs. Isso é útil para conferir se pelo menos todos os shaders foram colocados em pré-cache e se algo está faltando ou errado com os outros estados de renderização. Exige |
PSOs mínimos | Contém os shaders e todas as estatísticas de renderização e informações do elemento de vértice, exceto as informações do alvo de renderização. As informações do alvo de renderização só estão disponíveis para validação no momento da renderização, mas as estatísticas mínimas de PSO podem ser atualizadas e verificadas durante a construção do MeshDrawCommand. Exige |
PSOs completos | O estado de PSO completo necessário em tempo de execução usado pela API gráfica. É igual ao PSO mínimo, mas com informações extras do alvo de renderização. |
Para cada grupo, os seguintes parâmetros são rastreados:
| Parâmetro | Descrição |
|---|---|
Perdidos | O número de PSOs que não foram colocados em pré-cache, mas deveriam ter sido porque eram necessários na hora da renderização ou expedição. Possíveis motivos: shader errado, estado do alvo de renderização, atributos de vértice, informações do alvo de renderização. |
Não rastreados | O número de PSOs para os quais o pré-cache não está habilitado. Possíveis motivos: validação desabilitada, material global, fábrica de vértices não compatível, tipo de processador de etapa de malha não compatível. Em compilações de lançamento, em que determinadas informações de depuração não estão disponíveis, os PSOs não rastreados aparecerão como perdidos. |
Acertos | O número de PSOs usados no tempo de execução que foram colocados em pré-cache com sucesso. |
Tarde demais | O número de PSOs que foram colocados na fila para pré-cache, mas não compilados a tempo quando eram necessários. |
Usados | O número de PSOs usados no tempo de execução (soma de todos os itens acima). |
Pré-cache | O número de PSOs que foram armazenados em pré-cache (mas não necessariamente usados). |
O cache do pipeline do shader também informa quantos problemas reais de tempo de execução foram detectados pela própria compilação de PSO. Uma compilação de PSO é marcada como um engasgo se levou mais de um determinado número de milissegundos para compilar o PSO no tempo de execução. O limite padrão é de 20 milissegundos. Você pode modificar isso com r.PSO.RuntimeCreationHitchThreshold, mas mantenha-o o mais pequeno possível.
O valor padrão de 20 milissegundos é alto, pois as primeiras ocorrências no cache do driver podem levar muito tempo.
Coleta de informações sobre no pré-cache de PSO
Você pode usar o arquivo de logs, o depurador do Visual Studio e o Unreal Insights para obter mais informações sobre pré-cache de PSO e investigar por que alguns PSOs ainda podem causar engasgos no tempo de execução. Os estados corretos de pré-cache de PSO só aparecerão no log e no Insights quando a validação de PSO estiver habilitada (confira Validação e rastreamento acima).
Quando um PSO é perdido ou é encontrado tarde demais, a UE imprimirá as seguintes informações no log:
PSO PRECACHING MISS:
Type: FullPSO
PSOPrecachingState: Missed
Material: M_AdvancedSkyDome
VertexFactoryType: FLocalVertexFactory
MDCStatsCategory: StaticMeshComponent
MeshPassName: SkyPass
Shader Hashes:
VertexShader: EC68796503F829FDEACC56B913C4CA86C6AD3C16
PixelShader: 651BF1ABBAEC0B74C8D2A5E917702A00EF29817B
Insights é uma ferramenta útil para depurar pré-cache de PSO. Adicionar os temporizadores "PSOPrecache: Missed" e "PSOPrecache: Too Late" à série de estados de quadro do jogo fornece uma visão geral útil de todos os engasgos causados pela compilação de PSO em um determinado período. Na captura de tela a seguir, há alguns engasgos de 5 a 10 ms de erros de pré-cache de PSO, mas também há um grande problema de 117 ms que será perceptível pelo jogador. Os outros grandes engasgos não vêm da compilação de PSO.
Clique para ampliar a imagem.
Ao ampliarmos, podemos ver que está vindo da etapa de Translucidez (e o log deve ter mais informações sobre isso):
Clique para ampliar a imagem.
Encontre informações mais detalhadas sobre objetos auxiliares de validação global de PSO. Quando a validação está definida para rastreamento completo (r.PSOPrecache.Validation=2), ela agrupa números por processador de etapas de malha e tipo de fábrica de vértices, o que pode ajudar a rastrear de onde vêm alguns erros. Também dá uma ideia mais clara da origem de todos os PSOs armazenados em pré-cache e pode ajudar a encontrar exceções que não devem ter tantos shaders em pré-cache.
Embora essas estatísticas de fábrica por etapa e por vértice não sejam expostas diretamente, podem ser inspecionadas durante a depuração navegando pelas estruturas de dados que as coletam. Elas estão em PSOPrecacheValidation.cpp:
FullPSOPrecacheStatsCollectorShadersOnlyPSOPrecacheStatsCollectorMinimalPSOPrecacheStatsCollector
A captura de tela a seguir mostra um exemplo.
Clique para ampliar a imagem.
Aumento da capacidade de pré-cache de PSO com funcionalidades da engine
Esta seção fornece informações sobre como estender os objetos de suporte para pré-cache de PSO.
UPrimitiveComponent
O UPrimitiveComponent coleta todas as informações necessárias para definir o inicializador de PSO. Ele precisa da instância de material, da fábrica de vértices (com possível conjunto de elementos de vértice) e do conjunto de parâmetros que podem influenciar o shader final ou o estado de renderização usado no FMeshPassProcessor.
Os parâmetros são armazenados em FPSOPrecacheParams e os valores padrão corretos são configurados em UPrimitiveComponent::SetupPrecachePSOParams.
A função de entrada básica para pré-cache de PSO é:
/** Precache all PSOs which can be used by the primitive component */
ENGINE_API virtual void PrecachePSOs();Na maioria dos casos, o componente derivado não precisa implementar essa função e pode simplesmente substituir a função de coleção de parâmetros de pré-cache:
/**
* Collect all the data required for PSO precaching
*/
struct FComponentPSOPrecacheParams
{
EPSOPrecachePriority Priority = EPSOPrecachePriority::Medium;
Um exemplo completo pode ser encontrado em UStaticMeshComponent::CollectPSOPrecacheData. Um caso de uso mais simples pode ser encontrado em WaterMeshComponent::CollectPSOPrecacheData.
FVertexFactory
Uma nova fábrica de vértices precisa sinalizar que é compatível com pré-cache de PSO usando a sinalização EVertexFactoryFlags::SupportsPSOPrecaching, que pode ser fornecida com o macro de declaração da fábrica de vértices IMPLEMENT_VERTEX_FACTORY_TYPE.
Em seguida, a fábrica de vértices deve implementar a seguinte função:
static void GetPSOPrecacheVertexFetchElements(EVertexInputStreamType VertexInputStreamType, FVertexDeclarationElementList& Elements);FVertexFactory::GetPSOPrecacheVertexFetchElements será usado durante o pré-cache de PSO se não for fornecido um conjunto explícito de elementos de vértice.
O conjunto fixo de elementos de vértice será válido se o sinalizador EVertexFactoryFlags::SupportsManualVertexFetch estiver definido na fábrica de vértices ou se um conjunto fixo de elementos de vértice for usado no shader.
Se a lista de elementos de vértice depender dos dados do buffer de vértices da malha, o conjunto correto deverá ser fornecido em FPSOPrecacheVertexFactoryData. Isso deve acontecer durante UPrimitiveComponent::CollectPSOPrecacheData. Veja UStaticMeshComponent::CollectPSOPrecacheData e FLocalVertexFactory::GetVertexElements para elementos.
FMeshPassProcessor
O processador de etapas de malha deve implementar a seguinte função para coletar todos os PSOs que podem ser usados ao renderizar um determinado material com os FPSOPrecacheParams fornecidos:
virtual void CollectPSOInitializers(const FSceneTexturesConfig& SceneTexturesConfig, const FMaterial& Material, const FPSOPrecacheVertexFactoryData& VertexFactoryData, const FPSOPrecacheParams& PreCacheParams, TArray<FPSOPrecacheData>& PSOInitializers) override {}A lógica é basicamente igual à de AddMeshBatch (e pode ser compartilhada parcialmente). No entanto, embora AddMeshBatch seja chamada durante a criação do MeshDrawCommand, o sistema de pré-cache do PSO tenta coletar as informações muito antes (PostLoad do componente).
Para obter um exemplo simples, consulte FDistortionMeshProcessor::CollectPSOInitializers. Para obter um exemplo mais abrangente, consulte FBasePassMeshProcessor::CollectPSOInitializers.
IPSOCollector
Nem todos os shaders de material passam por um processador de etapas de malha ou têm EMeshPass::Type definido (como atualizações de geometria dinâmica de cabelo, Nanite ou traçado de raios). Para esses casos, pode ser necessário derivar diretamente da interface base.
O IPSOCollector tem uma única função virtual que precisa ser implementada:
// Collect all PSO for given material, vertex factory & params
virtual void CollectPSOInitializers(const FSceneTexturesConfig& SceneTexturesConfig, const FMaterial& Material, const FPSOPrecacheVertexFactoryData& VertexFactoryData, const FPSOPrecacheParams& PreCacheParams, TArray<FPSOPrecacheData>& PSOInitializers) = 0;O coletor de PSO também precisa ser registrado para criação por meio de um FRegisterPSOCollectorCreateFunction global. Há alguns exemplos simples na engine: FTranslucentLightingMaterialPSOCollector, FRayTracingDynamicGeometryPSOCollector,…
GlobalPSOCollector
Como mencionado anteriormente nesta página, alguns PSOs gráficos globais são armazenados em pré-cache na inicialização e sabemos que eles podem compilar permutações em tempo de execução. Para isso, o GlobalPSOCollector é usado. É uma versão simplificada do IPSOCollector. É preciso declarar um objeto global FRegisterGlobalPSOCollectorFunction, que fornece a função de coletor de PSO global:
typedef void (*GlobalPSOCollectorFunction)(const FSceneTexturesConfig& SceneTexturesConfig, int32 GlobalPSOCollectorIndex, TArray<FPSOPrecacheData>& PSOInitializers);Confira DeferredLightGlobalPSOCollector ou RegisterVolumetricFogGlobalPSOCollector para ver alguns exemplos de uso.
Depuração de uma falha de pré-cache de PSO
Para depurar de onde vem o erro de pré-cache de PSO acima, você precisa usar a depuração manual no Visual Studio.
Depurar perdas no estado mínimo de PSO é simples porque elas podem ser acionadas durante a construção do MeshDrawCommand e não no momento da renderização. As informações finais do alvo de renderização, necessárias para computar o PSO completo, só estão disponíveis durante a renderização, o que dificulta a depuração.
A função LogPSOMissInfo é um local conveniente para pausar com o depurador quando ocorre uma perda no tempo de execução. A pilha de chamadas e a janela de observação podem fornecer mais informações sobre o material usado, a etapa de renderização, a fábrica de vértices e o FPrimitiveSceneProxy. Você também pode obter informações sobre o UPrimitiveComponent usando o membro ComponentForDebuggingOnly. A maioria desses dados também é impressa no arquivo de logs quando uma falha é encontrada (coletado nessa função).
No entanto, quando LogPSOMissInfo é executado, o pré-cache de PSO geralmente já ocorreu nesse componente. Se você estiver tentando descobrir por que um shader ou estado de renderização incorreto foi usado durante o pré-cache para os PSOs desse componente, precisa adicionar uma pausa de depuração durante o pré-cache de PSO para esse componente e/ou material para a etapa específica.
r.PSOPrecache.BreakOnMaterialName é útil para interromper o pré-cache de PSO quando ele encontra um material com um determinado nome — isso pode ajudar a descobrir por que determinado estado de renderização é diferente em relação ao estado do tempo de execução. Também é possível usar r.PSOPrecache.BreakOnPassName e r.PSOPrecache.BreakOnShaderHash para tentar descobrir qual é o PSO problemático. Essas informações podem ser encontradas no log, conforme mencionado acima.
r.PSOPrecache.UseBackgroundThreadForCollection é útil para desabilitar as tarefas em thread de segundo plano para a coleta do inicializador de PSO, facilitando a localização de informações do componente ou outro estado durante a depuração de uma perda no pré-cache de PSO.
Talvez seja necessário verificar os valores de FPSOPrecacheParams, pois eles também podem influenciar o shader e o estado de renderização usados no PSO.