DirectX 12 コマンドキューの完全チューニングガイド:GPU性能を最大限引き出す実装戦略
DirectX 12のコマンドキュー最適化手法を徹底解説。非同期実行、マルチエンジン同期、動的優先度制御を実装例とともに紹介し、GPU利用率を最大化する方法を学ぶ。
約10分で読めますDirectX 12の登場により、開発者はGPUを低レベルで制御できるようになりました。その中核機能がコマンドキューです。しかし、この強力な機能を正しく活用しなければ、DirectX 11以下のパフォーマンスしか得られません。本記事では、コマンドキューのチューニング手法を実装レベルで解説し、GPU性能を最大限引き出す方法を示します。
コマンドキューとは何か:DirectX 12の設計哲学
DirectX 12のコマンドキューは、GPUに対する命令の送り先となるハードウェア抽象化レイヤーです。従来のDirectX 11が単一の暗黙的なキューで動作していたのに対し、DirectX 12では複数の明示的なキューを同時に使用できます。
3種類のコマンドキュー
DirectX 12は用途に応じて3種類のキューを提供します:
- Direct Queue:グラフィックス、コンピュート、コピーすべての操作が可能
- Compute Queue:コンピュートシェーダーとコピー操作のみ
- Copy Queue:データ転送(DMA)専用
最新のGPUは、これらのキューに対応する複数のハードウェアエンジンを搭載しており、並列実行が可能です。たとえば、3Dレンダリング中に別エンジンでテクスチャをアップロードし、さらに別エンジンでコンピュートシェーダーを実行できます。
// 各種キューの作成例
D3D12_COMMAND_QUEUE_DESC directQueueDesc = {};
directQueueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
directQueueDesc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL;
directQueueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
directQueueDesc.NodeMask = 0;
device->CreateCommandQueue(&directQueueDesc, IID_PPV_ARGS(&directQueue));
D3D12_COMMAND_QUEUE_DESC computeQueueDesc = {};
computeQueueDesc.Type = D3D12_COMMAND_LIST_TYPE_COMPUTE;
device->CreateCommandQueue(&computeQueueDesc, IID_PPV_ARGS(&computeQueue));
D3D12_COMMAND_QUEUE_DESC copyQueueDesc = {};
copyQueueDesc.Type = D3D12_COMMAND_LIST_TYPE_COPY;
device->CreateCommandQueue(©QueueDesc, IID_PPV_ARGS(©Queue));
非同期実行によるGPU利用率の最大化
複数のコマンドキューを活用することで、GPU内の異なるハードウェアエンジンを同時稼働させ、全体的な利用率を向上させることができます。
実装例:レンダリングとテクスチャロードの並列化
// メインレンダリング(Direct Queue)
ID3D12GraphicsCommandList* renderCmdList;
device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT,
renderAllocator, nullptr, IID_PPV_ARGS(&renderCmdList));
renderCmdList->SetGraphicsRootSignature(rootSignature);
renderCmdList->DrawInstanced(vertexCount, 1, 0, 0);
renderCmdList->Close();
// テクスチャアップロード(Copy Queue)
ID3D12GraphicsCommandList* copyCmdList;
device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_COPY,
copyAllocator, nullptr, IID_PPV_ARGS(©CmdList));
copyCmdList->CopyTextureRegion(&dstTexture, 0, 0, 0, 0, &srcBuffer, nullptr);
copyCmdList->Close();
// 並列実行
ID3D12CommandList* directLists[] = { renderCmdList };
directQueue->ExecuteCommandLists(1, directLists);
ID3D12CommandList* copyLists[] = { copyCmdList };
copyQueue->ExecuteCommandLists(1, copyLists);
この構成により、3Dレンダリングと並行してテクスチャデータがVRAMへ転送され、全体のフレーム時間が短縮されます。AMD GPUOpenの資料によれば、適切な非同期実行により最大30%のGPU利用率向上が観測されています。
フェンスによる同期制御:デッドロックを避ける実装パターン
複数キューを使う際の最大の課題が同期制御です。キュー間で依存関係がある場合、フェンスを用いた明示的な同期が必要です。
基本的なフェンスパターン
// Compute Queueでの処理完了を待つ
UINT64 computeFenceValue = 0;
ID3D12Fence* computeFence;
device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&computeFence));
// Compute処理実行
computeQueue->ExecuteCommandLists(1, computeLists);
computeQueue->Signal(computeFence, ++computeFenceValue);
// Direct Queueで待機
directQueue->Wait(computeFence, computeFenceValue);
directQueue->ExecuteCommandLists(1, renderLists);
注意すべきハードウェア依存性
Microsoftの公式ドキュメントとGameDev.netのディスカッションによれば、特にNVIDIA Maxwell世代GPUでは非同期コンピュートに大きなペナルティが発生することが報告されています。ハードウェア特性を理解し、プロファイリングツール(後述のPIX)で実測することが不可欠です。
動的優先度制御:GDC 2026の新機能
従来、コマンドキューの優先度は作成時に固定されていましたが、DirectX 12の最新機能であるCommand Queue Dynamic Priorityにより、実行時に優先度を変更できるようになりました。
Scheduling Groupを用いた優先度制御
// Scheduling Group作成
D3D12_COMMAND_QUEUE_SCHEDULING_GROUP_DESC groupDesc = {};
groupDesc.NumQueues = 2;
groupDesc.ppQueues = new ID3D12CommandQueue*[2] { directQueue, computeQueue };
groupDesc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_HIGH;
ID3D12SchedulingGroup* schedulingGroup;
device->CreateSchedulingGroup(&groupDesc, IID_PPV_ARGS(&schedulingGroup));
// 実行時に優先度変更
schedulingGroup->SetPriority(D3D12_COMMAND_QUEUE_PRIORITY_GLOBAL_REALTIME);
この機能により、ゲームプレイ中に重要なフレームのレンダリング優先度を一時的に引き上げたり、バックグラウンドの動画デコードを低優先度に設定したりできます。
コマンドリストのバッチング:オーバーヘッドの削減
ExecuteCommandLists()の呼び出しにはCPU/GPUオーバーヘッドが伴います。Microsoftの最適化ガイドによれば、1回の呼び出しで複数のコマンドリストを投入することで、GPU側でインターリーブ実行が行われ、効率が向上します。
効率的なバッチング戦略
// 悪い例:複数回の細かい投入
for (auto& cmdList : commandLists) {
ID3D12CommandList* lists[] = { cmdList };
queue->ExecuteCommandLists(1, lists); // 毎回オーバーヘッド発生
}
// 良い例:一括投入
std::vector<ID3D12CommandList*> batchedLists;
for (auto& cmdList : commandLists) {
batchedLists.push_back(cmdList);
}
queue->ExecuteCommandLists(batchedLists.size(), batchedLists.data());
ただし、コマンドリスト数が増えすぎるとスケジューリングオーバーヘッドが増大するため、10〜20個程度でバッチ化するのが最適とされています。
PIXによるパフォーマンスプロファイリング
マイクロソフトが提供する**PIX(Performance Investigator for Xbox)**は、DirectX 12専用のプロファイリングツールです。2026年現在、最新版のPIX 2201.24では、コマンドキュー間の依存関係を視覚化する機能が強化されています。
PIXでのキュー分析手順
-
GPUキャプチャの実行
PIXをアプリケーションにアタッチし、フレームキャプチャを実行 -
タイムラインビューでキューを確認
各キュー(Direct/Compute/Copy)の実行時間と並列度を視覚的に確認 -
同期待機のボトルネック特定
Fenceによる待機時間が長い箇所を特定し、依存関係を再設計 -
ハードウェアカウンタの確認
GPU利用率、メモリ帯域幅、シェーダーユニット稼働率を確認
PIXはMicrosoft Learnで詳細なドキュメントが公開されています。
まとめ:最適化のチェックリスト
DirectX 12のコマンドキューを最適化する際は、以下の点を確認してください:
- キューの種類を適切に分離:レンダリング、コンピュート、コピーを別キューに分散
- 非同期実行を活用:依存関係のない処理は並列化し、GPU利用率を向上
- フェンスで明示的に同期:デッドロックを避けつつ、必要最小限の待機に抑える
- ハードウェア特性を考慮:特にMaxwell世代GPUでは非同期コンピュートのペナルティに注意
- 動的優先度制御を検討:重要なフレームの優先度を実行時に調整
- コマンドリストをバッチ化:
ExecuteCommandLists()の呼び出し回数を減らしてオーバーヘッド削減 - PIXで実測:理論値ではなく、実際のGPU利用率とボトルネックを確認
DirectX 12のコマンドキューは、正しく使えばDirectX 11比で20〜40%のパフォーマンス向上が期待できます。しかし、誤った実装では逆にオーバーヘッドが増大します。本記事の実装例とプロファイリング手法を活用し、自身のプロジェクトに最適なキュー構成を見つけてください。
Sources:
- Visual Studio での DirectX 12 のサポート - Microsoft Learn
- ゲーム性能向上が見込めるDirectX 12のアップデート - ニッチなPCゲーマーの環境構築Z
- Right on Queue: Advanced DirectX 12 Programming - AMD GPUOpen
- DirectX 12 News from GDC 2026 - My Comments
- Leveraging Asynchronous Queues for Concurrent Execution - AMD GPUOpen
- ID3D12CommandQueue Dynamic Priority - DirectX-Specs
- Executing and Synchronizing Command Lists - Microsoft Learn
- Design Philosophy of Command Queues and Command Lists - Microsoft Learn