ここぷろ!

私がプログラミングで学んだこと、行き詰った事などを書いていきます。たまに個人的なことも載せるかも

【DirectX12】コマンドキューとスワップチェインの作成【初期化】

Library & Includes

#include <d3dx12.h>
#pragma comment(lib, "d3d12.lib")
#include <dxgi1_4.h>
#pragma comment(lib, "dxgi.lib")
#include <D3Dcompiler.h>
#pragma comment(lib, "d3dcompiler.lib")
#include <DirectXMath.h>

using Microsoft::WRL::ComPtr;
using namespace DirectX;

コマンドキュ―の作成

コマンドキューとは

 コマンドキューとは、コマンドリストというGPUが実行するべき命令のリストを、指定した順序で GPUに転送するための仕組みです。

 コマンドリストやコマンドキューなどの関係については、違う機会に解説しようと思います。

 コマンドキューを操作するには、ID3D12CommandQueueインタフェースを使います。

CreateCommandQueue関数

 コマンドキューを作成する為には、ID3D12Device::CreateCommandQueue関数を使用します。
 ID3D12Device::CreateCommandQueue method | Microsoft Docs

  • CreateCommandQueue関数
    • 第1引数:D3D12_COMMAND_QUEUE_DESC型の変数のポインタ
    • 第2引数:COMオブジェクト各インタフェース固有のGUID
    • 第3引数:ID3D12CommandQueueインタフェースポインタを取得した変数へのポインタ

D3D12_COMMAND_QUEUE_DESC構造体

 CreateCommandQueue関数の、第1引数として渡す、D3D12_COMMAND_QUEUE_DESC型には、以下の4つのメンバが存在します。
 今回は全てをデフォルトの値で初期化して使用しています。
 D3D12_COMMAND_QUEUE_DESC | Microsoft Docs

  • D3D12_COMMAND_QUEUE_DESC
    • Type:コマンドキューの種類を示すD3D12_COMMAND_LIST_TYPE列挙型の値。コマンドキューの初期化に使用できるのはBUNDLEを除いた以下の3種類
      • Direct汎用全てのコマンドリストを実行可能
      • COMPUTEコンピュートシェーダーを使用する為のものCOMPUTEとCOPYコマンドリストが実行可能
      • COPYデータのコピー専用COPYコマンドリストのみ実行可能
    • Priority:複数キューを作成した場合の実行優先度を設定。デフォルトはD3D12_COMMAND_QUEUE_PRIORITY_NORMAL。ひとつしか作成しない場合は使用しない
    • Flags:キューの特別な挙動がある場合に設定。デフォルトはD3D12_COMMAND_QUEUE_FLAG_NONE
    • NodeMask:複数のGPUを使用する場合、どのGPUに向けたコマンドキューなのか指定。ひとつのGPUのみ使用する場合0を指定


 今回はFlagsに、D3D12_COMMAND_QUEUE_FLAG_DISABLE_GPU_TIMEOUTを定義しています。
 これはGPUが長時間応答を返さない場合でも実行完了を待ち続ける必要がある場合に設定するものです。

事前定義
ComPtr<ID3D12Device> g_device;    // デバイス
ComPtr<ID3D12CommandQueue> g_commandQueue; // コマンドキュー
実装
const D3D12_COMMAND_QUEUE_DESC commandQueueDesc = {};
if (FAILED(g_device->CreateCommandQueue(&commandQueueDesc , IID_PPV_ARGS(&commandQueue)))) {
    return false;
}

スワップチェインの作成

スワップチェインとは

 スワップチェインフレームバッファの表示側と描画側を切り替えるための仕組みです。
 スワップチェインを操作するには、IDXGISwapChain3インタフェースを使用します。

CreateSwapChainForHwnd関数

 スワップチェインの作成には、IDXGIFactory2:: CreateSwapChainForHwnd 関数を使います。
 IDXGIFactory2::CreateSwapChainForHwnd method | Microsoft Docs

  • CreateSwapChainForHwnd関数
    • 第1引数:作成したコマンドキューのポインタ
    • 第2引数:ウィンドウハンドル
    • 第3引数:DXGI_SWAP_CHAIN_DESC1構造体のポインタ
    • 第4引数:DXGI_SWAP_CHAIN_FULLSCREEN_DESC構造体のアドレス
    • 第5引数:IDXGIOutputインタフェースのポインタ
    • 第6引数:DXGISwapChain1インタフェースのポインタを格納する変数のアドレス
  • 補足説明
    • 第1引数DIRECTコマンドキューのみ指定可能
    • 第2引数:ここで指定されたウィンドウに、描画されたフレームバッファを表示
    • 第3引数スワップチェインの詳細を示す構造体のポインタ
    • 第4引数:特に指定しない場合nullptrを渡すことが出来ます
    • 第5引数:マルチモニターを使用する場合に、出力先のモニターを指定するために使用。マルチモニターを扱わない場合nullptrを渡します
    • 第6引数スワップチェインの作成に成功した場合、この変数にインタフェースのポインタが格納されます

DXGI_SWAP_CHAIN_DESC1構造体

 上記で解説した、CreateSwapChainForHwnd関数の第三引数として渡す、DXGI_SWAP_CHAIN_DESC1構造体は以下のメンバが存在します。
 DXGI_SWAP_CHAIN_DESC1 | Microsoft Docs

  • DXGI_SWAP_CHAIN_DESC1構造体
    • Width,Heightフレームバッファの幅と高さ。通常はウィンドウの描画領域と同じ大きさを設定
    • Formatフレームバッファピクセルフォーマット
    • Stereo立体視を使用するか有無を指定
    • SampleDesc.Count:AAの為のサンプリング回数を指定。AAを使用しない場合、1を指定。
    • SampleDesc.Quality:サンプリングの品質を指定。AAを使用しない場合、0を指定。
    • BufferUsageフレームバッファの使用方法を指定。スワップチェインの作成で使用できるのはDXGI_USAGE_RENDER_TARGET_OUTPUTか、DXGI_USAGE_SHADER_INPUTのどちらか。ウィンドウにスワップチェインを出力する場合、DXGI_USAGE_RENDER_TARGET_OUTPUTを指定。
    • Scalingフレームバッファとウィンドウのサイズが異なる場合の表示方法を指定
      • DXGI_SCALING_STRETCH:ウィンドウのサイズに引き伸ばす(デフォルト)
      • DXGI_SCALING_NONE:引き伸ばしを一切行わない
      • DXGI_SCALING_ASPECT_RATIO_STRETCH:縦横の比率を維持して引き伸ばす
    • SwapEffect:画面を切り替えた後、描画画面になったフレームバッファの扱い方を指定
      • DXGI_SWAP_EFFECT_DISCARDGPUに扱い方を任せる(デフォルト)
      • DXGI_SWAP_EFFECT_SEQUENTIAL フレームバッファの内容を維持
      • DXGI_SWAP_EFFECT_FLIP_SEQUENTIALGPUに扱い方を任せる(フルスクリーン用)
      • DXGI_SWAP_EFFECT_FLIP_DISCARDフレームバッファの内容を維持(フルスクリーン用)
    • AlphaModeフレームバッファの内容をウィンドウに表示する際のアルファ値の扱い方を指定
      • DXGI_ALPHA_MODE_UNSPECIFIEDGPUに扱い方を任せる(デフォルト)
      • DXGI_ALPHA_MODE_PREMULTIPLIED:事前乗算済みアルファとして扱う
      • DXGI_ALPHA_MODE_STRAIGHT:通常のアルファ合成を行う
      • DXGI_ALPHA_MODE_IGNORE:アルファ値を無視
    • Flags:DXGI_SWAP_CHAIN_FLAG列挙型の値の論理和を指定。通常は使わないので0を指定。
    • IDXGISwapChain1:下記に詳細を記載

 ※AA = アンチエイリアス
 アンチエイリアス - Wikipedia


 最後のIDXGISwapChain1についてですが、インタフェースを取得した後、ComPtr::As関数を使うことでIDXGISwapChain3インタフェースを取得します。
 なぜIDXGISwapChain1に対して、わざとIDXGISwapChain3を取得しています。
 これには理由があり、最後に使用するIDXGISwapChian3::GetCurrentBackBufferIndex関数IDXGISwapChain3からしか呼び出せない為です。
 ComPtr::As メソッド


 そして、最後にIDXGISwapChian3::GetCurrentBackBufferIndex関数を使い、描画側フレームバッファのインデックスを取得しています。
 IDXGISwapChain3::GetCurrentBackBufferIndex method (Windows)

 関数名にあるバックバッファですが、これはどのフレームバッファなのかを差す用語になります。

用語 解説
フロントバッファ 表示側のフレームバッファ
バックバッファ 描画側のフレームバッファ

 実際にユーザーの目に触れる(舞台上)からフロント(前面)、触れさせない(舞台裏)からバック(背面)という認識で良いと思われます。

事前定義
const UINT WINDOW_WIDTH = 1920;    // ウィンドウの幅
const UINT WINDOW_HEIGHT = 1080;   // ウィンドウの高さ

HWND hwnd;    // ハンドルウィンドウ

ComPtr<IDXGISwapChain3> g_swapChain;    // スワップチェイン
ComPtr<ID3D12CommandQueue> g_commandQueue; // コマンドキュー

UINT g_frameIndex = 0; // 描画側フレームバッファのインデックス

ComPtr<IDXGIFactory4> dxgiFactory // ファクトリ
実装
// DXGI_SWAP_CHAIN_DESC1構造体の設定
DXGI_SWAP_CHAIN_DESC1 swapChainDesc= {};
scDesc.Width = WINDOW_WIDTH ;
scDesc.Height = WINDOW_HEIGHT ;
scDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
scDesc.SampleDesc.Count = 1;
scDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
scDesc.BufferCount = frameBufferCount;
scDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;

// スワップチェインの作成
ComPtr<IDXGISwapChain1> tmpSwapChain;
if (FAILED(dxgiFactory->CreateSwapChainForHwnd(commandQueue.Get(), hwnd, &swapChainDesc, nullptr, nullptr, &tmpSwapChain))) {
    return false;
}

// スワップチェインをキャスト
swapChain.As(&g_swapChain);
// バックバッファのインデックスを格納
g_frameIndex = g_swapChain->GetCurrentBackBufferIndex();

終わりに

 お疲れさまでした。今回はかなり情報密度の濃い回となりました。
 DirectXの関数や構造体などは項目が多いので、説明するにも見やすさとの兼ね合いが難しいですね。
 読みにくかった場合は、申し訳ありません。

 今回の実装内容はGitHubに上げてあります。
 解説が間違っていたり、より良い方法などがあった場合は教えて頂けるとありがたいです。

 今回でフレームバッファを制御する準備が出来たので、次回はフレームバッファを作成していきたいのですが、その前にデスクリプタヒープと呼ばれるものを作成しなければなりません。
 なので、次回はデスクリプタヒープの作成について解説していきたいと思います。

 それでは、今回はこの辺りで。

github.com