手動の PSO キャッシュ ではゲーム ビルドをプレイして PSO (Pipeline State Object) 情報を収集する必要がありますが、PSO 事前キャッシュ では、コンポーネントの MeshDrawCommand レンダリング中に使用できるすべての PSO について、自動的に PSO の収集と非同期コンパイルを実行します。
PSO 事前キャッシュは、Unreal Engine 5.3 の時点で、D3D12 Windows 上のクック済みビルドでのみ使用可能です。
概要
プリミティブ コンポーネント (UPrimitiveComponent
) では、ロードの直後 (PostLoad の実行中) にレンダリングに必要となるすべての PSO を事前キャッシュします。事前キャッシュでは、次のような PSO のコンパイルに必要なすべてのパイプラインの状態情報を収集します。
-
マテリアル。
-
頂点ファクトリ。
-
頂点要素情報。
-
特定の事前キャッシュ パラメータ。
UE では、コンポーネントをレンダリングできるすべての可能なメッシュ パス プロセッサ全体をイテレートするためにこの情報を使用します。各メッシュ パス プロセッサは、レンダリング中に必要になることがある PSO 初期化子を追加します。バックグラウンドタスクでは、共有されている PSO キャッシュがチェックされ、必要なデータがすでに事前キャッシュされていないことを確認して、これらのリクエストを非同期でコンパイルします。
UE でプリミティブ コンポーネント向けに プリミティブ プロキシ が作成され、それに必要な PSO がまだコンパイルされている場合、次のようないくつかのオプションを利用することができます。
-
PSO のコンパイルが完了するまで、プロキシの作成を遅延する (デフォルト)。これにより、PSO の準備が整うまで描画がスキップされます。
-
マテリアルをエンジンのデフォルト マテリアルに置き換える。
-
実行を継続するため、ヒッチの可能性がある。描画により、PSO のコンパイルがブロックされます。
PSO 事前キャッシュを設定する
次の CVars により、PSO 事前キャッシュを制御します。
CVar | 説明 | デフォルトの状態 |
---|---|---|
r.PSOPrecaching |
PSO 事前キャッシュを有効にするグローバル CVar。RHI フラグ GRHISupportsPSOPrecaching に依存します。 |
有効 |
r.PSOPrecache.Components |
コンポーネントで使用される PSO を事前キャッシュします。 | 有効 |
r.PSOPrecache.Resources |
すべてのリソース (UStaticMesh 、USkinnedMesh など) で使用される PSO を事前キャッシュします。いくつかの状態はコンポーネントからのみ派生するため、これらの PSO には正確なレンダリング状態がない場合があります。ただし、ドライバにコンパイル用の正しいシェーダーを与える必要があります。 |
無効 |
r.PSOPrecache.ProxyCreationWhenPSOReady |
すべての必要な PSO がコンパイルされるまで、コンポーネント プロキシの作成を待機します。プロキシの作成時にまだコンパイルしている場合、それらは優先度の高い PSO としてマークされます。 | 有効 |
r.PSOPrecache.ProxyCreationDelayStrategy |
コンポーネントのプロキシを作成中にまだ PSO をコンパイルしている場合、マテリアルをデフォルトのマテリアルに置き換えるオプションが追加されます。これは、r.PSOPrecache.ProxyCreationWhenPSOReady に依存します。詳細は、下記の「プロキシ作成の遅延戦略」を参照してください。 |
0 (下記参照) |
r.PSOPrecaching.WaitForHighPriorityRequestsOnly |
ロード中、優先度の高い PSO のみを待機します。必須でない PSO はすべて、ゲームプレイ中にコンパイルされます。PSO がプロキシで必要になり、まだコンパイルされていない場合は、優先度が高くマークされます。 | 無効 |
プロキシ作成の遅延戦略
r.PSOPrecache.ProxyCreationDelayStrategy
CVar は r.PSOPrecache.ProxyCreationWhenPSOReady
CVar に依存します。ProxyCreationWhenPSOReady
を「1」(有効) に設定すると、ProxyCreationDelayStrategy
は値に応じて次の動作を実行します。
設定値 | 動作 |
---|---|
0 | PSO の準備が整うまで描画がスキップされます。 |
1 | PSO の準備が整うまで、エンジンのデフォルト マテリアルにフォールバックします。 |
システム リソースを管理する
PSO 事前キャッシュはバックグラウンド スレッドを使用する非同期コンパイルに依存し、システム メモリに影響を与えます。このセクションでは、プロジェクトに合わせてこれらのリソースの使用を調整および最適化するために利用可能なオプションについて説明します。
メモリ
ランタイム システム メモリを節約するため、UE ではコンパイル後に事前キャッシュ用にコンパイルした PSO を削除します。アプリケーションによって事前キャッシュした PSO の量が非常に多い場合は、クリーンアップしない限り、アプリケーションのメモリ使用量が非常に増加する (数百 MB) 可能性があります。
PSO 事前キャッシュは、基盤となる圧縮されたドライバ キャッシュの存在に依存します。ランタイム時に PSO が必要となる場合は、グラフィック ドライバによって圧縮されたドライバ キャッシュからロードされます。ただし、これはリソースを大量に消費する可能性もあり、これらのキャッシュからの最初の取得には、数ミリ秒かかる場合があります。D3D12.PSOPrecache.KeepLowLevel
を使用して、D3D12 で事前キャッシュ済みの PSO の削除を無効にすることができます。
処理
デフォルトでは、UE のバックグラウンド スレッドは PSO の非同期コンパイルに使用されます。ただし、別個の PSO 事前キャッシュ スレッド プールを使用して、フォアグラウンド スレッドとの競合を減らすことができます。これらは、次の CVars のいずれかを使用して設定することができます。
CVar | 説明 |
---|---|
r.pso.PrecompileThreadPoolSize |
プールで使用するスレッドの数を指定します。 |
r.pso.PrecompileThreadPoolPercentOfHardwareThreads |
スレッド プールのサイズを利用可能なハードウェア スレッドに対する割合で設定し、そのサイズのスレッド プールを作成します。 |
コマンドライン引数の -clearPSODriverCache
を使用して、強制的にドライバ キャッシュをクリアできます。これは、初めてゲームを起動するエクスペリエンスをテストする場合にお勧めします。
コマンドライン引数の -corelimit=n
を使用して (n
はコアの数)、コア数の多い PC でテストを行う場合はコア数を「8」に、一般的なグレードの PC の場合は別の一般的なコア数に制限することをお勧めします。これにより、最終的なユーザー エクスペリエンスをより正確にレプリケートすることができます。
スムーズなゲームが行えることを評価するテストの実行ではすべて、一貫して -clearPSODriverCache
スイッチを使用してください。使用しない場合、グラフィック ドライバによってビルドされ、以前の実行で残った PSO キャッシュによってヒッチがマスクされることがあります。
検証とトラッキング
PSO 事前キャッシュ システムのパフォーマンスを検証およびトラッキングするためのオプションがいくつかあります。
次の値で r.PSOPrecache.Validation
を使用すると、検証を有効にすることができます。
設定値 | 説明 |
---|---|
0 | 無効になります。 |
1 | 高レベルの数値のみを使用する軽量トラッキング (パフォーマンスへの影響は最小限)。 |
2 | 詳細なトラッキングです。 |
PSO 事前キャッシュ検証がアクティブな場合、stat PSOPrecache
コンソール コマンドを使用して収集された統計を検証することができます。

PSO 事前キャッシュ検証システムによって収集された統計情報です。stat PSOPrecache コンソール コマンドを使用して表示することができます。
統計データは 3 つのグループに分割することができます。
グループ | 説明 |
---|---|
シェーダーのみの PSO | これらの統計は、使用されている RHI シェーダーのみをトラックし、PSO 内の他の情報はすべて無視します。これは、少なくともすべてのシェーダーが事前キャッシュされているかどうか、他のレンダリング状態で何かが欠如していたり誤りがあったりしないかを確認する場合に便利です。r.PSOPrecache.Validation.TrackMinimalPSOs が必要です。 |
最小 PSO | シェーダーと、レンダー ターゲット情報を除くすべてのレンダリング統計および頂点要素情報を含みます。レンダー ターゲット情報は描画時の検証でのみ利用可能ですが、最小 PSO 統計は MeshDrawCommand ビルド中に更新およびチェックすることができます。r.PSOPrecache.Validation.TrackMinimalPSOs が必要です。 |
完全な PSO | グラフィック API によって使用されるランタイムに必要な完全な PSO 状態。これは最初 PSO と同じですが、追加のレンダー ターゲット情報が含まれています。 |
各グループで、次のパラメータがトラックされます。
パラメータ | 説明 |
---|---|
Missed | 事前キャッシュされていませんが、描画またはディスパッチの際に必要になるため事前キャッシュされるはずだった PSO の数。原因として、誤りがあるシェーダー、レンダー ターゲットの状態、頂点属性、レンダー ターゲット情報が考えられます。 |
Untracked | 事前キャッシュが有効になっていない PSO の数。原因として、無効にされた検証、グローバル マテリアル、サポートされていない頂点ファクトリ、サポートされていないメッシュ パス プロセッサ タイプが考えられます。シッピング ビルドでは、特定のデバッグ情報が利用できない場合に、未追跡の PSO が代わりに欠落しているものとして表示されます。 |
Hit | 正常に事前キャッシュされ、ランタイム時に使用される PSO の数。 |
Too late | 事前キャッシュ用にキューに入っていたが、必要なタイミングまでにコンパイルされなかった PSO の数。 |
Used | ランタイム時に使用される PSO の数 (上記すべての合計)。 |
Precached | 事前キャッシュされた (ただし、必ずしも使用されるとは限らない) PSO の数。 |
シェーダー パイプライン キャッシュ も、PSO のコンパイル自体によって発生した実際のランタイム ヒッチの検出数に関する情報を提供します。PSO コンパイルは、ランタイム PSO のコンパイルがある一定のミリ秒数よりも長くかかった場合にヒッチとしてマークされます。デフォルトのしきい値は 20 ミリ秒です。r.PSO.RuntimeCreationHitchThreshold
を使用してこれを変更することができますが、できる限り小さくする必要があります。
最初のドライバ キャッシュの実行は時間が長くなる可能性があるため、デフォルト値は 20 ミリ秒と高くなっています。
PSO 事前キャッシュの情報を収集する
Visual Studio デバッガと Unreal Insights を使用して、PSO 事前キャッシュの詳細情報を取得し、特定の PSO がランタイム時にヒッチを発生させる理由を調査することができます。正しい PSO 事前キャッシュの状態は、PSO の検証が有効な場合にのみ Insights に表示されます (上記の「検証とトラッキング」を参照)。
次のスクリーンショットには、ランタイム ヒッチを発生させている PSO 事前キャッシュのミスが表示されています。
画像をクリックすると拡大表示されます。
次のスクリーンショットには、未追跡の PSO によるヒッチが表示されています。これらはおそらく、レベルのロード直後に初めて使用されるグローバル シェーダーです。
画像をクリックすると拡大表示されます。
Unreal Insights では、PSO のコンパイルによるヒッチについての高レベルな情報が提供されます。上記の PSO 事前キャッシュのミスの原因をデバッグするには、Visual Studio で手動のデバッグを使用する必要があります。
詳細情報は、グローバル PSO 検証ヘルパー オブジェクトを参照してください。検証を完全なトラッキング (r.PSOPrecache.Validation=2
) に設定している場合、メッシュ パス プロセッサと頂点ファクトリ タイプで数値がグループ化されます。これは、特定のミスの原因をトラッキングするのに役立ちます。また、これによってすべての事前キャッシュされた PSO がどこから発生しているのかをより明確に把握できるようにもなり、それほど多くのシェーダーを事前キャッシュすべきではない外れ値を見つけることもできます。
これらのパスごと、および頂点ごとのファクトリ統計情報は直接公開されませんが、それらを収集するデータ構造体を移動することによってデバッグ中に検査することができます。それらは PSOPrecache.cpp
にあります。
-
FullPSOPrecacheStatsCollector
-
ShadersOnlyPSOPrecacheStatsCollector
-
MinimalPSOPrecacheStatsCollector
.
次のスクリーンショットに例を示します。
Click to enlarge image.
新しいエンジン機能を使用して PSO 事前キャッシュを拡張する
このセクションでは、PSO 事前キャッシュ用のサポート オブジェクトを拡張する方法について説明します。
新規 UPrimitiveComponent
UPrimitiveComponent
では、PSO 初期化子を設定するために必要なすべての情報を収集します。これにはマテリアル インスタンス、頂点ファクトリ (可能な頂点要素セットを含む)、および FMeshPassProcessor
で使用される最終的なシェーダーまたはレンダリング状態に影響することがあるパラメータのセットが必要です。
パラメータは FPSOPrecacheParams
に格納され、正確なデフォルト値は UPrimitiveComponent::SetupPrecachePSOParams
に設定されます。
PSO 事前キャッシュ用のベース エントリ関数は次のとおりです。
/** プリミティブ コンポーネントによって使用できるすべての PSO を事前キャッシュします*/
ENGINE_API virtual void PrecachePSOs();
多くの場合、派生コンポーネントにはこの関数を実装する必要がなく、シンプルに事前キャッシュ パラメータ収集関数をオーバーライドすることができます。
/**
* PSO 事前キャッシュに必要なデータをすべて収集します
*/
struct FComponentPSOPrecacheParams
{
EPSOPrecachePriority Priority = EPSOPrecachePriority::Medium;
UMaterialInterface* MaterialInterface = nullptr;
FPSOPrecacheVertexFactoryDataList VertexFactoryDataList;
FPSOPrecacheParams PSOPrecacheParams;
};
typedef TArray<FComponentPSOPrecacheParams, TInlineAllocator<2> > FComponentPSOPrecacheParamsList;
virtual void CollectPSOPrecacheData(const FPSOPrecacheParams& BasePrecachePSOParams, FComponentPSOPrecacheParamsList& OutParams) {}
完全な機能の例は UStaticMeshComponent::CollectPSOPrecacheData
で、よりシンプルなユース ケースは WaterMeshComponent::CollectPSOPrecacheData
で見つけることができます。
新規 FVertexFactory
新しい頂点ファクトリには、頂点ファクトリ宣言マクロ IMPLEMENT_VERTEX_FACTORY_TYPE
を使用して提供することができるフラグ `EVertexFactoryFlags::SupportsPSOPrecaching を使用して PSO 事前キャッシュをサポートすることをフラグ付けする必要があります。
次に、頂点ファクトリには次の関数を実装する必要があります。
static void GetPSOPrecacheVertexFetchElements(EVertexInputStreamType VertexInputStreamType, FVertexDeclarationElementList& Elements);
FVertexFactory::GetPSOPrecacheVertexFetchElements
は、明示的な頂点要素セットが指定されていない場合、PSO 事前キャッシュ中に使用されます。
固定されている頂点要素セットは、EVertexFactoryFlags::SupportsManualVertexFetch
フラグが頂点ファクトリに設定されている場合や、固定されている頂点要素セットがシェーダーで使用されている場合に有効です。
頂点要素リストがメッシュの頂点バッファ データに依存している場合、正しいセットを FPSOPrecacheVertexFactoryData
で提供する必要があります。これは、UPrimitiveComponent::CollectPSOPrecacheData
中に発生します。例については、UStaticMeshComponent::CollectPSOPrecacheData
および FLocalVertexFactory::GetVertexElements
を参照してください。
新規 FMeshPassProcessor
メッシュ パス プロセッサでは、次の関数を実装して、指定されている FPSOPrecacheParams
を使用して特定のマテリアルを描画する場合に使用できる PSO をすべて収集することができます。
virtual void CollectPSOInitializers(const FSceneTexturesConfig& SceneTexturesConfig, const FMaterial& Material, const FPSOPrecacheVertexFactoryData& VertexFactoryData, const FPSOPrecacheParams& PreCacheParams, TArray<FPSOPrecacheData>& PSOInitializers) override {}
ロジックは、AddMeshBatch
とほぼ同じです (理想としては部分的に共有できます)。ただし、AddMeshBatch
は MeshDrawCommand のビルド時に呼び出されますが、PSO 事前キャッシュ システムでは、より迅速に情報を収集しようと試みます (コンポーネントの PostLoad)。
シンプルな例は FDistortionMeshProcessor::CollectPSOInitializers
を参照してください。より包括的な例は FBasePassMeshProcessor::CollectPSOInitializers
を参照してください。
PSO 事前キャッシュのミスをデバッグする
最小限の PSO 状態でのミスは描画時ではなく MeshDrawCommand ビルド中にトリガーされるため、デバッグすることは簡単です。最終的なレンダー ターゲット情報 (完全な PSO のコンピューティングに必要です) は描画中にのみ利用可能なため、これによりデバッグが難しくなります。
関数 PSOCollectorStats::FPrecacheStatsCollector::UpdatePrecacheStats
はトラックされている内部状態を更新し、ランタイム時にミスが発生した場合にデバッガを中断するのに適した場所になります。コール スタックとウォッチ ウィンドウでは、使用されているマテリアル、レンダリング パス、頂点ファクトリ、および FPrimitiveSceneProxy
に関する詳細情報を提供します。また、ComponentForDebuggingOnly
メンバーを使用する UPrimitiveComponent
についての情報も取得することができます。
ただし、UpdatePrecacheStats
が実行されるまでに、通常はそのコンポーネントで PSO 事前キャッシュがすでに行われています。そのコンポーネントの PSO の事前キャッシュ中に不正なシェーダーやレンダリング状態が使用されている理由を探ろうとする場合、そのコンポーネントや指定されたパスのマテリアルの PSO 事前キャッシュ中にブレークポイントを追加する必要があります。
事前キャッシュ中にブレークポイントを設定する最も簡単な方法は、MeshDrawCommand のビルド中に FMaterial
のアセット名を探し、その名前を使用して同じ MeshPassProcessor の PSO コレクション中にブレークポイントを追加することです。次に、MeshDrawCommand のビルド中に使用されている PSO 状態と PSO 事前キャッシュ中の状態セットアップを比較します。
また、FPSOPrecacheParams
の値は PSO で使用されるシェーダーやレンダリング状態に影響する可能性があるため、それらをチェックする必要もあります。
UE_DISABLE_OPTIMIZATION
void FBasePassMeshProcessor::CollectPSOInitializers(const FSceneTexturesConfig& SceneTexturesConfig, const FMaterial& Material, const FPSOPrecacheVertexFactoryData& VertexFactoryData, const FPSOPrecacheParams& PreCacheParams, TArray<FPSOPrecacheData>& PSOInitializers)
{
FString MaterialName = Material.GetAssetName();
if (MaterialName == TEXT("TEST_MATERIAL_NAME"))
{
UE_DEBUG_BREAK();
}
// … 残りの関数…
}