メインコンテンツへスキップ
Tech Playground
ゲーム開発

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(&copyQueueDesc, IID_PPV_ARGS(&copyQueue));

非同期実行による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(&copyCmdList));

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でのキュー分析手順

  1. GPUキャプチャの実行
    PIXをアプリケーションにアタッチし、フレームキャプチャを実行

  2. タイムラインビューでキューを確認
    各キュー(Direct/Compute/Copy)の実行時間と並列度を視覚的に確認

  3. 同期待機のボトルネック特定
    Fenceによる待機時間が長い箇所を特定し、依存関係を再設計

  4. ハードウェアカウンタの確認
    GPU利用率、メモリ帯域幅、シェーダーユニット稼働率を確認

PIXはMicrosoft Learnで詳細なドキュメントが公開されています。

まとめ:最適化のチェックリスト

DirectX 12のコマンドキューを最適化する際は、以下の点を確認してください:

  • キューの種類を適切に分離:レンダリング、コンピュート、コピーを別キューに分散
  • 非同期実行を活用:依存関係のない処理は並列化し、GPU利用率を向上
  • フェンスで明示的に同期:デッドロックを避けつつ、必要最小限の待機に抑える
  • ハードウェア特性を考慮:特にMaxwell世代GPUでは非同期コンピュートのペナルティに注意
  • 動的優先度制御を検討:重要なフレームの優先度を実行時に調整
  • コマンドリストをバッチ化ExecuteCommandLists()の呼び出し回数を減らしてオーバーヘッド削減
  • PIXで実測:理論値ではなく、実際のGPU利用率とボトルネックを確認

DirectX 12のコマンドキューは、正しく使えばDirectX 11比で20〜40%のパフォーマンス向上が期待できます。しかし、誤った実装では逆にオーバーヘッドが増大します。本記事の実装例とプロファイリング手法を活用し、自身のプロジェクトに最適なキュー構成を見つけてください。

Sources:

#DirectX12 #GPU最適化 #パフォーマンスチューニング #グラフィックスAPI
シェア: