こんにちは!
ロジカルビート情熱開発部プログラム課プログラマの柄本です。
最近、お仕事でもDirectX12(以下、DX12)に触れる機会がありました。今回はその中でもPipeline State Object(以下、PSO)のCachedPSOを用いた生成について調べてみました。
雑に検証した部分もあるため、こちらの検証が必ずしも合っているというわけではないです。
今回の検証は以下の環境で行いました。
- Windows 10
- Visual Studio 2019
- Windows SDK 10.0.19041.0
PSOについて
DX12にはパイプラインの状態を管理するのに使われるPSOというものがあります。
このPSOはGraphicとComputeのものがあり、シェーダーのバイナリデータやルートシグネチャ、入力アセンブラの情報などが含まれています。
こちらのオブジェクトは一度作ってしまえば使いまわすことができるのですが、生成に多少負荷がかかります。そのため、ある程度は初期化段階やローディング中に作ってしまうなどの工夫が必要になります。
しかし、場合によってはゲームをプレイしつつ生成する必要が出てくることもあります。そこでPSOの生成負荷自体を下げることはできないかと模索したのが今回の検証となります。
CachedPSO とは
PSOの生成に必要な情報を記述するための構造体 “D3D12_GRAPHICS_PIPELINE_STATE_DESC” と “D3D12_COMPUTE_PIPELINE_STATE_DESC” を以下に引用しています。
typedef struct D3D12_GRAPHICS_PIPELINE_STATE_DESC
{
ID3D12RootSignature *pRootSignature;
D3D12_SHADER_BYTECODE VS;
D3D12_SHADER_BYTECODE PS;
D3D12_SHADER_BYTECODE DS;
D3D12_SHADER_BYTECODE HS;
D3D12_SHADER_BYTECODE GS;
D3D12_STREAM_OUTPUT_DESC StreamOutput;
D3D12_BLEND_DESC BlendState;
UINT SampleMask;
D3D12_RASTERIZER_DESC RasterizerState;
D3D12_DEPTH_STENCIL_DESC DepthStencilState;
D3D12_INPUT_LAYOUT_DESC InputLayout;
D3D12_INDEX_BUFFER_STRIP_CUT_VALUE IBStripCutValue;
D3D12_PRIMITIVE_TOPOLOGY_TYPE PrimitiveTopologyType;
UINT NumRenderTargets;
DXGI_FORMAT RTVFormats[ 8 ];
DXGI_FORMAT DSVFormat;
DXGI_SAMPLE_DESC SampleDesc;
UINT NodeMask;
D3D12_CACHED_PIPELINE_STATE CachedPSO;
D3D12_PIPELINE_STATE_FLAGS Flags;
} D3D12_GRAPHICS_PIPELINE_STATE_DESC;
typedef struct D3D12_COMPUTE_PIPELINE_STATE_DESC
{
ID3D12RootSignature *pRootSignature;
D3D12_SHADER_BYTECODE CS;
UINT NodeMask;
D3D12_CACHED_PIPELINE_STATE CachedPSO;
D3D12_PIPELINE_STATE_FLAGS Flags;
} D3D12_COMPUTE_PIPELINE_STATE_DESC;
こちらの中にあるCachedPSOという変数はPSOの生成に必須ではないのですが、設定することで生成の速度アップが図れるようです。
こちらの型である “D3D12_CACHED_PIPELINE_STATE” についてのMicrosoft公式による説明が以下の通りです。
This structure is intended to be filled with the data retrieved from ID3D12PipelineState::GetCachedBlob. This cached PSO contains data specific to the hardware, driver, and machine that it was retrieved from. Compilation using this data should be faster than compilation without. The rest of the data in the PSO needs to still be valid, and needs to match the cached PSO, otherwise E_INVALIDARG might be returned.
https://docs.microsoft.com/en-us/windows/win32/api/d3d12/ns-d3d12-d3d12_cached_pipeline_state
CachedPSOにはコンピュータのハードウェアやドライバーなどの情報が含まれており、こちらを再利用することでPSOの生成が早くなるということみたいです。CachedPSOはPSOの生成に必須ではありませんが、利用することで効率化を図れるというわけですね。
CachedPSO試してみた
Microsoft公式のサンプルにCachedPSOのサンプルもあるのですが、それ以外の要素も含まれているというのと、CachedPSOの使用条件なども調査したかったのでまずはシンプルなもので確かめてみることにしました。
公式のサンプルの “D3D12HelloTriangle” を元に作成しました。以下のコードはD3D12HelloTriangle.cppのOnUpdate関数内に書いたものです。
// PSO生成
if( m_createPSO )
{
// CachedPSOの取得
ComPtr<ID3DBlob> cachedPSO;
m_pipelineState->GetCachedBlob(&cachedPSO);
OutputDebugString( L"====== Create PSO ======\n" );
LARGE_INTEGER freq;
QueryPerformanceFrequency( &freq );
LARGE_INTEGER start, end;
// PSO生成の計測開始
OutputDebugString( L"Start Create PSO\n" );
QueryPerformanceCounter( &start );
// create pipeline state
{
// PSOの基本情報格納
D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
psoDesc.InputLayout = { m_inputElementDescs, _countof(m_inputElementDescs) };
psoDesc.pRootSignature = m_rootSignature.Get();
psoDesc.VS = CD3DX12_SHADER_BYTECODE(m_vertexShader.Get());
psoDesc.PS = CD3DX12_SHADER_BYTECODE(m_pixelShader.Get());
psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
psoDesc.DepthStencilState.DepthEnable = FALSE;
psoDesc.DepthStencilState.StencilEnable = FALSE;
psoDesc.SampleMask = UINT_MAX;
psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
psoDesc.NumRenderTargets = 1;
psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
psoDesc.SampleDesc.Count = 1;
// CachedPSOの情報を格納
psoDesc.CachedPSO.pCachedBlob = cachedPSO->GetBufferPointer();
psoDesc.CachedPSO.CachedBlobSizeInBytes = cachedPSO->GetBufferSize();
// 速度判定のため生成を繰り返す(今回は1000回)
for( int i = 0; i < 1000; i++ )
{
m_pipelineState = nullptr;
ThrowIfFailed( m_device->CreateGraphicsPipelineState( &psoDesc, IID_PPV_ARGS( &m_pipelineState ) ) );
}
}
// 計測終了
QueryPerformanceCounter( &end );
OutputDebugString( L"Finish Create PSO\n" );
double time = static_cast<double>( end.QuadPart - start.QuadPart ) * 1000.0 / freq.QuadPart;
wchar_t timeLog[64];
swprintf_s( timeLog, 64, L"time : %lf[ms]\n", time );
OutputDebugString( timeLog );
m_createPSO = false;
}
CachedPSOには元となるPSOが必要になります。ここでいう元となるPSOとはPipelineStateDesc構造体の中の”CachedPSO”変数以外の変数が全て一致しているPSOです。
公式の説明の中でCashedPSOにはコンピュータのドライバーやハードウェアの情報が含まれているという一文があったので、他のシェーダーやパラメータの違うステートなどにも流用できるのかという淡い希望があったのですが、元々のPSOの情報もしっかり含まれているようなのでできませんでした。
CachedPSOはID3D12PipelineState::GetCachedBlob関数によって元々のPSOからID3DBlob型の変数に格納されます。検証コード内の “cachedPSO” が該当の変数です。
取得した情報のうちCachedPSO::pCachedBlobにデータを、CachedPSO::CachedBlobSizeInBytesにデータサイズを入れます。そして、他の構造体メンバの中身がキャッシュ元と同じであればID3D12Device::CreateGraphicsPipelineState(またはCreateComputePipelineState)関数でCachedPSOを利用したPSOの生成ができます。
キャッシュ元のPSOと生成するPSOでパラメータが違う、または、ドライバーが更新されたなどの環境の変更がある場合エラーにより生成が失敗します。
以下が今回検証したPSOの生成時間の計測結果です。(1000回の生成を5回したものの平均[ms])
CachedPSO無し | CachedPSO有り |
561.5759[ms] | 542.667[ms] |
そこまで早くなっていないという印象ですね。
もしかしたら、私のやり方が悪いのかもしれないと思い公式のD3D12PipelineStateCacheサンプルを試してみました。Build関数の前後に計測用のコードを入れてみたところ以下のような結果になりました。
CachedPSO無し | CachedPSO有り |
約7[ms] | 約12[ms] |
…遅くなった?
こちらのサンプルではメモリではなくディスクにファイルとしてキャッシュしているのですが、そちらが関係しているのでしょうか。PCのスペックも関わってくるのでしょうけど詳細は調査しきれませんでした。
最後に
ここまで検証してきた結果を踏まえてCachedPSOに対する印象ですが、実装には色々と気をつかう必要があると思われます。
スペックにもよるとは思いますが、導入したからといって格段にPSOの生成スピードが速くなるというわけではないようです。また、同じPSOの再生成の場合にのみ使えることから、PSOが増えれば増えるほどキャッシュするデータ量が多くなっていきます。ドライバーの更新などにより再利用が不可になってしまうという点も気を付けなければいけません。
ここまで気をつかうよりも、PSOの生成タイミングをどうにか調整してゲーム中に悪影響が出ないようにするといったことを先に考えるべきではあると感じました。
また同じくPSOをキャッシュする仕組みにID3D12PipelineLibraryというものがあるようです。CachedPSOとは違い、PSOそのものをキャッシュしていくものです。うまく実装できれば、最初の生成以降は全くPSOを生成することなく読み込みだけでやりくりすることができるかもしれません。CachedPSOと同じくMicrosoft公式のサンプル”D3D12PipelineStateCache”に実装されています。
こちらはCachedPSOよりお手軽にできそうではあるので、機会があれば調査をしてみたいですね。
参考サイト
- D3D12_CHACHED_PIPELINE_STATEの説明
D3D12_CACHED_PIPELINE_STATE (d3d12.h) – Win32 apps | Microsoft Docs - DirectX12のサンプル
GitHub – microsoft/DirectX-Graphics-Samples: This repo contains the DirectX Graphics samples that demonstrate how to build graphics intensive applications on Windows.
【免責事項】
本サイトでの情報を利用することによる損害等に対し、
株式会社ロジカルビートは一切の責任を負いません。