DX12 Command QueueとFence
CPUとGPUを正確なタイミングで同期する
本ブログ作成文は、講義を目指して製作されました。 また、作成された文章の情報も間違っている可能性がありますので、ご注意ください。
DirectX 12では、CPUがCommand Listを記録し、GPUが後で実行する。 両方のデバイスが同時に移動するため、まだGPUが使用しているリソースをCPUが上書きしないように同期する必要があります。
Command Queue と Command List
Command Listは描画コマンドを記録するリストであり、Command Queueは完成したリストをGPUに提出する。
CPU
CommandAllocatorの初期化
CommandList 履歴
CommandList Close
↓
CommandQueue 送信
↓
GPUの実行
CommandAllocatorは書き込みに必要なメモリを管理します。 GPUが前のフレームの命令をまだ読んでいるが、同じAllocatorをリセットしないでください。
Fenceで完了時点を確認する
void WaitForGPU()
{
const UINT64 SignalValue = ++FenceValue;
CommandQueue->Signal(Fence, SignalValue);
if (Fence->GetCompletedValue() < SignalValue)
{
Fence->SetEventOnCompletion(SignalValue, FenceEvent);
WaitForSingleObject(FenceEvent, INFINITE);
}
}
QueueにSignal値を入れると、GPUが前の命令をすべて処理した後、Fence値を更新する。 CPUは、完了値が目標より小さい場合にのみイベントを待ちます。
全フレーム待つと遅い
三角形の例では、わかりやすく Present の後にWaitForGPU()を呼び出すことができます。 しかし、これを行うと、CPUとGPUが重なって動作することができず、毎フレームシリアルで実行される。
実戦ではBackBuffer数だけFrame Resourceを置き、各フレームのFence値を別々に記録する。
struct FrameContext
{
ComPtr<ID3D12CommandAllocator> Allocator;
UINT64 FenceValue = 0;
};
現在使用しているフレームリソースのFenceだけを待ち、他のフレームはGPUが処理を続けます。
Debug Layerをオンにする
DX12は、間違った状態遷移やDescriptorの使用を開発者が直接担当します。 初期化前にDebug Layerをオンにすると、どのResource Stateとコマンドの順序が間違っているかをはるかに早く見つけることができます。
DX12同期の目的は無条件に待つことではありません。 CPUとGPUが同時にできるだけ働かせながら、同じリソースを再利用するときだけ正確に待つのだ。