El almacenamiento en caché manual de PSO requiere ejecutar una compilación del juego para recopilar información PSO (Pipeline State Object) en una caché empaquetada. La precaché de PSO realiza la recopilación automática de PSO y la compilación asíncrona de todos los PSO que podrían utilizarse durante el renderizado.
Cómo configurar la precaché de PSO
Las siguientes variables de consola controlan la precaché del PSO:
| Variable de consola | Descripción | Estado por defecto |
|---|---|---|
| Variable de consola global para activar la precaché de PSO. Se basa en el indicador RHI | Activado |
| Precachés de PSO usadas por los componentes. | Activado |
| Precaché de PSO usadas por todos los recursos ( | Deshabilitado |
| Espera a que se cree el proxy del componente hasta que se hayan compilado todos los PSO necesarios. Si aún se están compilando al crear el proxy, esos PSO se marcan como de alta prioridad. | Activado |
| Cuando los PSO aún se están compilando durante la creación del proxy del componente, se añade una opción para reemplazar el material con el material por defecto. Se basa en | 0 (ver más abajo) |
| Solo espera a los PSO de alta prioridad durante la carga. Todos los PSO no esenciales se seguirán compilando durante la jugabilidad. Un PSO se marca como de alta prioridad cuando un proxy lo necesita y aún no ha terminado de compilar. | Deshabilitado |
| Determina si se realiza una precaché del cálculo global y los PSO de gráficos al iniciar el motor. | Activado |
Precaché de PSO de sombreador global
Ciertos PSO de sombreadores globales se almacenan en la caché porque pueden provocar enganches en el tiempo de ejecución la primera vez que se usan. Estos PSO se compilan al iniciar el motor y se activan por defecto con la variable de consola r.PSOPrecache.GlobalShaders.
Todas las permutaciones de los sombreadores de cálculo globales que puedes usar el juego se almacenan en la caché.
static EShaderPermutationPrecacheRequest ShouldPrecachePermutation(const FShaderPermutationParameters& Parameters)Esto solía verificar si una permutación dada se puede usar en tiempo de ejecución comprobando los ajustes actuales de las variables de consola para excluir ciertas combinaciones. Por defecto, usa ShouldCompilePermutation, por lo que la permutación de precaché debería ser un subconjunto de las permutaciones compiladas.
La mayoría de los PSO de gráficos globales se crean durante los primeros fotogramas justo después de la carga y se pueden notar enganches en estos fotogramas. Usar una caché de PSO muy pequeña para recopilar estos PSO de gráficos globales puede ayudar. Sin embargo, también se crean y compilan ciertas permutaciones globales de PSO de gráficos en tiempo de ejecución, por lo que también deberían almacenarse en la caché. Para los PSO de gráficos globales, se necesitan recopiladores de PSO específicos para recopilar todo el estado de renderizado correcto, que es necesario para compilar el PSO de gráficos.
Actualmente, la precaché de PSO está implementada para los siguientes tipos de sombreadores globales:
Slate
Luces diferidas
Simulación de partículas de Cascade
Niebla volumétrica
Compilar todos los PSO globales al inicio lleva algo de tiempo y normalmente se hace mientras se navega por el menú principal. Aunque esto no bloquea el motor al iniciar, debería formar parte de la fase de espera de la compilación de PSO durante la pantalla de carga inicial.
Precaché de PSO de componentes
Los componentes primitivos (UPrimitiveComponent) precargan todos los PSO necesarios para el renderizado inmediatamente después de la carga (PostLoad). La precaché recopila toda la información del estado de la canalización necesaria para compilar los PSO, a saber:
Materiales
Generadores de vértices
Información de elementos de vértice
Parámetros específicos de precaché
Unreal Engine usa esta información para iterar sobre todos los procesadores de pases de malla posibles donde se puede renderizar el componente. Cada procesador de pase de malla añade los posibles inicializadores de PSO que pueda necesitar durante el renderizado. Las tareas en segundo plano comprueban una caché de PSO compartida para asegurarse de que los datos necesarios no se están prealmacenando en la caché y compilan estas solicitudes de forma asíncrona.
Un componente puede necesitar muchos PSO para renderizar correctamente en todos los pases, como base, profundidad personalizada, profundidad, distorsión, sombra, mapa de sombras virtuales o velocidad, entre otros. Es importante que todos estos PSO estén listos antes de que el componente esté preparado para no introducir artefactos gráficos porque, por ejemplo, podrían renderizarse en una pasada y en otra no.
Cuando UE crea un proxy primitivo para un componente primitivo y sus PSO necesarios aún están compilando, hay varias opciones disponibles:
Retrasar la creación del proxy hasta que finalice la compilación del PSO (por defecto). De esta forma, se omitirá el trazado hasta que el PSO esté listo.
Reemplazar el material con el material por defecto del motor.
Continuar con posibles tirones. El trazado se bloqueará en la compilación del PSO.
Estrategia de retraso de creación de proxy
La variable de consola r.PSOPrecache.ProxyCreationDelayStrategy se basa en la variable de consola r.PSOPrecache.ProxyCreationWhenPSOReady. Si se establece ProxyCreationWhenPSOReady en 1 (activado), ProxyCreationDelayStrategy ejecutará los siguientes comportamientos en función de su valor:
| Valor | Comportamiento |
|---|---|
0 | Omite el trazado hasta que el PSO esté listo. |
1 | Vuelve al material por defecto del motor hasta que el PSO esté listo. |
Pantalla de carga
Recomendamos encarecidamente que configures tu pantalla de carga inicial teniendo en cuenta las solicitudes de precaché de PSO. .
Al configurar la pantalla de carga inicial de tu juego, la deberías tener esperando a todas las solicitudes de precaché de PSO pendientes. De lo contrario, podrías ver algunos elementos visuales emergentes notables e incluso algunos fallos de tiempo de ejecución para los componentes que no son compatibles con la creación del proxy de retraso, como el terreno de paisaje, donde no sería recomendable sustituir estas mallas por un material por defecto o no renderizarlas.
FShaderPipelineCache::NumPrecompilesRemaining() es útil para comprobar el número de compilaciones de precaché de PSO pendientes tanto para la caché empaquetada como para la precaché de PSO. Puedes modificar la lógica de tu pantalla de carga para comprobar este número y mantener la pantalla de carga activa hasta que llegue a cero. En la mayoría de los casos, el tiempo inicial de compilación de PSO con la caché del controlador vacía debería ser inferior a un minuto en CPU con especificaciones medias.
Gestión de los recursos del sistema
La precaché de PSO se basa en la compilación asíncrona con subprocesos en segundo plano y tiene un impacto en la memoria del sistema y en el rendimiento. En esta sección se explican las opciones disponibles para ajustar y optimizar el uso de estos recursos en función de tu proyecto.
Memoria
Para ahorrar memoria del sistema en tiempo de ejecución, UE elimina los PSO compilados para precaché después de la compilación. Esto se debe a que si la cantidad de PSO almacenados en la caché por tu aplicación es muy alta, puede aumentar drásticamente el consumo de memoria de la aplicación a menos que se limpien (hablamos de cientos de megabytes o incluso gigabytes).
La precaché de PSO se basa en la existencia de una caché del controlador comprimida subyacente. Incluso cuando los PSO se eliminan después de la precaché, se conservan en la caché del controlador. Si se necesita un PSO en tiempo de ejecución, el controlador de gráficos lo cargará desde su caché de controlador comprimida. Sin embargo, esto también puede consumir muchos recursos, y las primeras recuperaciones de estas cachés pueden tardar unos milisegundos. Puedes desactivar la eliminación de los PSO prealmacenados en la caché en D3D12 con D3D12.PSOPrecache.KeepLowLevel.
Crear PSO a partir de la caché del controlador puede ser lento en algunos proveedores de hardware independientes (IHV). En NVIDIA, existe una opción para mantener una cierta cantidad de gráficos prealmacenados en la caché y calcular os PSO en la memoria con r.PSOPrecache.KeepInMemoryUntilUsed, que mantiene los últimos n PSO almacenados en la memoria y evita que el rendimiento de la caché del controlador se vea afectado. La cantidad de PSO retenidos en la memoria se puede ajustar para computación y gráficos por separado con r.PSOPrecache.KeepInMemoryGraphicsMaxNum y r.PSOPrecache.KeepInMemoryComputeMaxNum. Si decides usar esta opción, te recomendamos que pruebes el coste de memoria resultante para tu aplicación con distintos ajustes para determinar un equilibrio aceptable entre el rendimiento de creación de PSO y la sobrecarga de memoria.
Rendimiento
Por defecto, Unreal Engine usa un grupo de subprocesos de precaché de PSO para compilar el PSO de forma asíncrona. Si se establece r.PSO.PrecompileThreadPoolSize o r.PSO.PrecompileThreadPoolPercentOfHardwareThreads, se usará el grupo de subprocesos. De lo contrario, la compilación de PSO volverá a usar tareas regulares en segundo plano, que se programan con el resto de las cargas de trabajo del motor.
| Variable de consola | Descripción | Estado por defecto |
|---|---|---|
| Establece la cantidad exacta de subprocesos que se usarán en el grupo | 0 |
| Establece que el tamaño del grupo de subprocesos sea un porcentaje de los subprocesos de hardware disponibles y crea un grupo de subprocesos con ese tamaño. El tamaño por defecto es 75, lo que representa el 75 % de los subprocesos de hardware. | 75 |
| La cantidad mínima de subprocesos que se usarán en el grupo de subprocesos de PSO. | 2 |
| La cantidad máxima de subprocesos que se usarán en el grupo de subprocesos de PSO. El valor por defecto es | INT_Max |
Notas adicionales:
Con una cantidad ilimitada de subprocesos máximos en el grupo de subprocesos, es posible que se agote la memoria del sistema al compilar PSO en sistemas con muchos procesadores pero poca memoria. Cada subproceso de compilación de PSO puede usar hasta 2 GB de memoria, por lo que limitar esta cantidad puede ser una buena opción para tu proyecto.
Durante el juego, un 75 % de subprocesos de hardware puede ser una cantidad elevada. La contención con los subprocesos habituales en primer plano puede notarse y provocar caídas de fotogramas menores. Aumentar este valor durante el tiempo de carga y volver a reducirlo durante el juego puede ayudar, pero podría retrasar la compilación de PSO y aumentar el retraso en la creación del proxy (no debería introducir tirones en tiempo de ejecución).
Puedes usar el argumento de la línea de comandos -clearPSODriverCache para forzar el borrado de la caché del controlador y recomendamos que así sea para probar la experiencia de iniciar el juego por primera vez.
Al probar en PC con una gran cantidad de núcleos, también recomendamos limitar el número de núcleos a 8 (u otro número típico de núcleos para un PC de uso doméstico), usando el argumento de la línea de comandos -corelimit=n, donde n es la cantidad de núcleos y -processaffinity=n el valor que garantizará que Windows solo programe el juego en n núcleos físicos. Así se garantiza replicar con más precisión la experiencia final del usuario.
Usa el interruptor -clearPSODriverCache de forma coherente en todas las ejecuciones de prueba que evalúen la fluidez de tu juego. Si no, los tirones podrían quedar enmascarados por la caché de PSO creada por el controlador gráfico y permanecer presentes desde ejecuciones anteriores.
Validación y seguimiento
Hay varias opciones para validar y hacer un seguimiento del rendimiento del sistema de precaché de PSO.
Puedes activar la validación usando r.PSOPrecache.Validation con los siguientes valores:
| Variable de consola | Descripción |
|---|---|
0 | Deshabilitado |
1 | Seguimiento ligero solo con números de alto nivel. Esto tiene un impacto mínimo en el rendimiento y se puede usar en títulos enviados. |
2 | Registro y seguimiento detallado de los fallos en la precaché de PSO. |
Cuando la validación de precaché de PSO está activa, puedes inspeccionar las estadísticas recopiladas con el comando de consola stat PSOPrecache.
Las estadísticas recopiladas por el sistema de validación de precaché de PSO. Usa el comando de consola `stat PSOPrecache` para verlos.
Las estadísticas se dividen en tres grupos:
| Grupo | Descripción |
|---|---|
PSO exclusivos de sombreadores | Estas estadísticas solo rastrean los sombreadores RHI usados e ignoran el resto de información de estado en los PSO. Esto es útil para ver si al menos todos los sombreadores se han prealmacenado en la caché y si falta algo o hay fallos en los otros estados de renderizado. Requiere |
PSO mínimos | Contiene los sombreadores y todas las estadísticas de renderizado, así como la información de los elementos de vértice, excepto la información de objetivo de renderizado. La información del objetivo de renderizado solo está disponible para su validación en el momento del trazado, pero las estadísticas de PSO mínimos se pueden actualizar y comprobar durante la compilación de `MeshDrawCommand`. Requiere |
PSO completos | El estado de PSO completo requerido en tiempo de ejecución usado por la API de gráficos. Es lo mismo que los PSO mínimos, pero incluye información adicional del objetivo de renderizado. |
Para cada grupo, se hace un seguimiento de los siguientes parámetros:
| Parámetro | Descripción |
|---|---|
Fallido | El número de PSO que no se prealmacenaron en la caché, pero que sí deberían haberlo hecho porque se necesitaban en el momento del trazado o envío. Posibles motivos: sombreador incorrecto, estado del objetivo de renderizado, atributos de vértice, información del objetivo de renderizado. |
No rastreado | El número de PSO para los que la precaché no está activa. Posibles motivos: validación desactivada, material global, generador de vértices no compatible, tipo de procesador de malla no compatible. En las compilaciones para envío, donde cierta información de depuración no está disponible, los PSO no rastreados aparecerán como fallidos. |
Resultado | El número de PSO usados en tiempo de ejecución que se prealmacenaron en la caché correctamente. |
Demasiado tarde | El número de PSO que se pusieron en cola para la precaché, pero que no se compilaron a tiempo para cuando se necesitaron. |
Usado | El número de PSO usados durante la ejecución (la suma de todos los anteriores). |
Prealmacenado en caché | El número de PSO que se prealmacenaron en la caché (pero no necesariamente se usaron). |
La caché para la canalización de sombreadorestambién proporciona información sobre cuántos enganches se detectaron durante el tiempo de ejecución debido a la propia compilación de PSO. Una compilación de PSO se marca como un enganche si tardó más de una determinada cantidad de milisegundos en compilar el PSO de tiempo de ejecución. El umbral por defecto es de 20 milisegundos. Puedes modificar esto con r.PSO.RuntimeCreationHitchThreshold, pero conviene que el valor sea lo más pequeño posible.
El valor por defecto de 20 milisegundos es alto, ya que las primeras veces que se accede a la caché del controlador pueden tardar bastante.
Cómo recopilar información sobre la precaché de PSO
Puedes usar el archivo de registro, el depurador de Visual Studio y Unreal Insights para obtener más información sobre la precaché de PSO e investigar por qué ciertos PSO pueden provocar fallos en tiempo de ejecución. Los estados correctos de precaché de PSO solo aparecerán en el registro y en Insights cuando la validación de PSO esté activa (consulta Validación y seguimiento más arriba).
Cuando se produce un fallo de PSO o se realiza demasiado tarde, UE mostrará la siguiente información en el registro:
PSO PRECACHING MISS:
Type: FullPSO
PSOPrecachingState: Missed
Material: M_AdvancedSkyDome
VertexFactoryType: FLocalVertexFactory
MDCStatsCategory: StaticMeshComponent
MeshPassName: SkyPass
Shader Hashes:
VertexShader: EC68796503F829FDEACC56B913C4CA86C6AD3C16
PixelShader: 651BF1ABBAEC0B74C8D2A5E917702A00EF29817B
Insights es una herramienta útil para depurar la precaché de PSO. Si añades los temporizadores PSOPrecache: Fallido y PSOPrecache: Demasiado tarde a la serie de estados de fotogramas del juego, obtendrás una visión general de todos los enganches causados por la compilación de PSO durante un periodo de tiempo determinado. En la captura de pantalla de abajo hay algunos enganches de entre 5 y 10 milisegundos debidos a la precaché de PSO, pero también uno grande de 117 milisegundos que el jugador notará. Los otros enganches importantes no provienen de la compilación de PSO.
Haz clic en la imagen para ampliarla.
Al ampliar, puedes ver que proviene del pase Traslucidez (y el registro debería contener más información al respecto):
Haz clic en la imagen para ampliarla.
Puedes encontrar información más detallada en objetos auxiliares de validación de PSO globales. Cuando la validación se establece con seguimiento completo (r.PSOPrecache.Validation=2), agrupa los números por procesador de paso de malla y tipo de generador de vértices, lo que puede ayudar a rastrear de dónde vienen ciertos fallos. También puede ayudar a dar una idea más clara de dónde vienen todos los PSO prealmacenados en la caché y a encontrar valores atípicos que no deberían almacenar en la caché tantos sombreadores.
Aunque estas estadísticas de fábrica por pase y por vértice no se exponen directamente, puedes inspeccionarlas durante la depuración recorriendo las estructuras de datos que las recopilan. Están disponibles en PSOPrecacheValidation.cpp:
FullPSOPrecacheStatsCollectorShadersOnlyPSOPrecacheStatsCollectorMinimalPSOPrecacheStatsCollector
La siguiente captura de pantalla muestra un ejemplo.
Haz clic en la imagen para ampliarla.
Ampliación de la precaché de PSO con funciones del motor
Esta sección proporciona información sobre cómo ampliar los objetos auxiliares para la precaché de PSO.
UPrimitiveComponent
UPrimitiveComponent recopila toda la información necesaria para configurar el inicializador de PSO. Necesita la instancia de material, el generador de vértices (con un posible conjunto de elementos de vértice) y el conjunto de parámetros que podrían influir en el sombreador final o el estado de renderizado usado en FMeshPassProcessor.
Los parámetros se almacenan en FPSOPrecacheParams y los valores por defecto correctos se configuran en UPrimitiveComponent::SetupPrecachePSOParams.
La función de entrada base para la precaché de PSO es:
/** Precache all PSOs which can be used by the primitive component */
ENGINE_API virtual void PrecachePSOs();En la mayoría de los casos, el componente derivado no necesita implementar esta función y simplemente puede anular la función de recopilación del parámetro de precaché:
/**
* Collect all the data required for PSO precaching
*/
struct FComponentPSOPrecacheParams
{
EPSOPrecachePriority Priority = EPSOPrecachePriority::Medium;
Puedes encontrar un ejemplo con todas las funciones en UStaticMeshComponent::CollectPSOPrecacheData, y un caso de uso más sencillo en WaterMeshComponent::CollectPSOPrecacheData.
FVertexFactory
Un nuevo generador de vértices debe indicar que es compatible con la precaché de PSO con el indicador EVertexFactoryFlags::SupportsPSOPrecaching, que puede proporcionarse con la macro de declaración de generador de vértices IMPLEMENT_VERTEX_FACTORY_TYPE.
A continuación, el generador de vértices debe implementar la siguiente función:
static void GetPSOPrecacheVertexFetchElements(EVertexInputStreamType VertexInputStreamType, FVertexDeclarationElementList& Elements);FVertexFactory::GetPSOPrecacheVertexFetchElements se usa durante la precaché de PSO si no se proporciona un conjunto explícito de elementos de vértice.
El conjunto de elementos de vértice fijos será válido si el indicador EVertexFactoryFlags::SupportsManualVertexFetch está activado en el generador de vértices o si se usa un conjunto de elementos de vértice fijos en el sombreador.
Si la lista de elementos de vértice depende de los datos del búfer de vértices de la malla, será necesario proporcionar el conjunto correcto en FPSOPrecacheVertexFactoryData. Esto debería ocurrir durante UPrimitiveComponent::CollectPSOPrecacheData. Echa un vistazo a UStaticMeshComponent::CollectPSOPrecacheData y FLocalVertexFactory::GetVertexElements para ver ejemplos.
FMeshPassProcessor
El procesador de pases de malla tiene que implementar la siguiente función para recopilar todos los PSO que pueden usarse al trazar un material determinado con FPSOPrecacheParams dados:
virtual void CollectPSOInitializers(const FSceneTexturesConfig& SceneTexturesConfig, const FMaterial& Material, const FPSOPrecacheVertexFactoryData& VertexFactoryData, const FPSOPrecacheParams& PreCacheParams, TArray<FPSOPrecacheData>& PSOInitializers) override {}La lógica es básicamente la misma que la de AddMeshBatch (e idealmente se puede compartir en parte), pero mientras que se llama a AddMeshBatch durante la compilación de MeshDrawCommand, el sistema de precaché de PSO intenta recopilar la información mucho antes (después de la carga del componente).
Para ver un ejemplo sencillo, consulta FDistortionMeshProcessor::CollectPSOInitializers. Para ver un ejemplo más completo, consulta FBasePassMeshProcessor::CollectPSOInitializers.
IPSOCollector
No todos los sombreadores de materiales pasan por un procesador de pases de malla ni tienen definido el tipo EMeshPass::Type (como las actualizaciones dinámicas de geometría con pelo, Nanite o trazado de rayos). En estos casos, podría ser necesario derivar directamente de la interfaz base.
IPSOCollector tiene una sola función virtual que necesita 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;También es necesario registrar el recopilador de PSO para crearlo a través de una función FRegisterPSOCollectorCreateFunction global. Hay algunos ejemplos sencillos en el motor: FTranslucentLightingMaterialPSOCollector, FRayTracingDynamicGeometryPSOCollector…
GlobalPSOCollector
Como hemos mencionado anteriormente en esta página, algunos PSO de gráficos globales ya se prealmacenan en la caché durante el inicio, por lo que sabemos que pueden compilar permutaciones en tiempo de ejecución. Para ello se usa GlobalPSOCollector. Es una versión simplificada de IPSOCollector. Es necesario declarar un objeto global FRegisterGlobalPSOCollectorFunction que proporciona la función de recopilador de PSO global:
typedef void (*GlobalPSOCollectorFunction)(const FSceneTexturesConfig& SceneTexturesConfig, int32 GlobalPSOCollectorIndex, TArray<FPSOPrecacheData>& PSOInitializers);Consulta DeferredLightGlobalPSOCollector o RegisterVolumetricFogGlobalPSOCollector para ver algunos ejemplos de uso.
Cómo depurar un fallo de precachés de PSO
Para depurar de dónde proviene el fallo de precaché del PSO anterior, debes recurrir a la depuración manual en Visual Studio.
Depurar fallos en el estado de PSO mínimos es sencillo, ya que se pueden activar durante la compilación de `MeshDrawCommand` y no en el momento del trazado. La información del objetivo de renderizado final (necesaria para calcular el PSO completo) solo está disponible durante el trazado, lo que dificulta la depuración.
La función LogPSOMissInfo es un buen lugar para romper con el depurador cuando se produce un fallo en tiempo de ejecución. La pila de llamadas y la ventana de inspección pueden dar más información sobre el material usado, los pases de renderizado, el generador de vértices y FPrimitiveSceneProxy. También puedes obtener información sobre el componente UPrimitiveComponent con el miembro ComponentForDebuggingOnly. La mayoría de estos datos también se incluyen en el archivo de registro cuando se encuentra un fallo (recopilados en esta función).
Sin embargo, cuando se ejecuta LogPSOMissInfo, normalmente ya se ha prealmacenado en la caché el PSO en ese componente. Si quieres averiguar por qué se ha usado un sombreador o un estado de renderizado incorrectos durante la precaché de PSO de ese componente, necesitas añadir un punto de ruptura durante la precaché de PSO para ese componente o material para el pase dado.
r.PSOPrecache.BreakOnMaterialName sirve para interrumpir durante la precaché de PSO cuando se encuentra un material con un nombre dado. Esto puede ayudar a averiguar por qué cierto estado de renderizado es diferente en comparación con el estado de tiempo de ejecución. También puedes usar r.PSOPrecache.BreakOnPassName y r.PSOPrecache.BreakOnShaderHash para acotar el PSO problemático. Esta información se encuentra en el registro, tal y como hemos mencionado antes.
r.PSOPrecache.UseBackgroundThreadForCollection sirve para desactivar las tareas de subprocesos en segundo plano para la recopilación de inicializadores de PSO con el fin de facilitar el rastreo de la información de los componentes u otros estados mientras se depura un fallo de precaché de PSO.
Es posible que también tengas que comprobar los valores de FPSOPrecacheParams, ya que estos también podrían influir en el sombreador y el estado de renderizado usados en el PSO.