いまさらDirect3D11入門

初めてグラフィックスAPIを触る人に向けて

初期化

前回までの内容でDX11の機能はあらかた見終えました。 残すパートもあとわずかとなり、今パートではようやくDX11の初期化について見ていきます。

概要

このパートでは今まで使ってきたID3D11DeviceID3D11DeviceContextの作成方法とDXGIについて見ていきます。 対応するプロジェクトはPart15_Initializeになります。

ID3D11DeviceとID3D11DeviceContextの作成

ID3D11DeviceID3D11DeviceContextの作成にはD3D11CreateDeviceを使用します。

ドキュメント:D3D11CreateDevice (日本語) (英語)

// Scene::onInit関数の一部
//ID3D11DeviceとID3D11DeviceContextの作成
std::array<D3D_FEATURE_LEVEL, 3> featureLevels = { {
    D3D_FEATURE_LEVEL_11_0,
    D3D_FEATURE_LEVEL_10_1,
    D3D_FEATURE_LEVEL_10_0
  } };
UINT flags = 0;
D3D_FEATURE_LEVEL usedLevel;
hr = D3D11CreateDevice(
  this->mpAdapter.Get(),
  D3D_DRIVER_TYPE_UNKNOWN,
  nullptr,
  flags,
  featureLevels.data(),
  static_cast<UINT>(featureLevels.size()),
  D3D11_SDK_VERSION,
  this->mpDevice.GetAddressOf(),
  &usedLevel,
  this->mpImmediateContext.GetAddressOf()
  );
if (FAILED(hr)) {
  throw std::runtime_error("ID3D11Deviceの作成に失敗。");
}

D3D11CreateDevice

  • 第1引数:pAdapter

    使用するGPUを表すアダプタを渡します。型はIDXGIAdapterになり、この後詳しく見ていきます。 この引数にはnullptrを渡すことができ、その時は使用できるIDXGIAdapterの中から1つ自動的に選択されます。

  • 第2引数:DriverType

    作成するID3D11Deviceのドライバタイプを指定します。 GPUを使用するときはD3D_DRIVER_TYPE_HARDWAREを指定することになります。 注意点としてpAdapterを指定して作成するときは必ずD3D_DRIVER_TYPE_UNKNOWNを指定する必要があります。
    ドキュメント:D3D_DRIVER_TYPE (日本語) (英語)

  • 第3引数:Software

    ソフトウェアによるラスタライザを実装したDLLを渡します。 DriverTypeD3D_DRIVER_TYPE_SOFTWAREを指定した際に使用します。 詳細はドキュメントを参照してください。

  • 第4引数:Flags

    ランタイムレイヤーを表すフラグを渡します。 フラグはD3D11_CREATE_DEVICE_FLAGで定義されています。
    ドキュメント:
    D3D11_CREATE_DEVICE_FLAG (日本語) (英語)
    ソフトウェア レイヤー (日本語) (英語)

  • 第4引数:pFeatureLevels

    使用したい機能レベルを配列で渡します。 優先順位は要素の若いものとなります。

  • 第5引数:FeatureLevels

    pFeatureLevelsの要素数を渡します。

  • 第6引数:SDKVersion

    D3D11_SDK_VERSIONを渡してください。

  • 第7,8,9引数:ppDevice, pFeatureLevel, ppImmediateContext

    作成したID3D11DeviceID3D11DeviceContextおよび、機能レベルを受け取る変数を渡します。

アダプタ

次にアダプタについて見ていきます。アダプタはIDXGIAdapterと表されます。

IDXGIAdapterDirectX Graphics Infrastructure(略称:DXGI)に属するクラスになります。 DXGIはDirect3D10から導入され、Direct3Dに依存しない部分を管理し、Direct3Dの各バージョンの共通したフレームワークを提供しています。 全画面表示や現在の使用できるディスプレイを列挙したりするときに利用します。

IDXGIAdapterについての詳細はドキュメントを参照にしていただきたいのですが、簡単に言うとGPUのことになります。 DXGIを使用することで現在使用できるGPUを調べることが出来ます。

また、IDXGIAdapterにはDXGIのバージョンに合わせていくつか種類があり、ここではDXGI1.1に対応したIDXGIAdapter1を使っています。 DXGI1.1はDX11で追加されたフォーマットに対応しているバージョンになっています。

ドキュメント:
DXGI の概要 (日本語) (英語)
DirectX Graphics Infrastructure (DXGI):ベスト プラクティス
IDXGIAdapter1 (日本語) (英語)

// Scene::onInit関数の一部
//DXGIを使う上で必要となるIDXGIFactory1を作成
HRESULT hr;
Microsoft::WRL::ComPtr<IDXGIFactory1> pFactory;
hr = CreateDXGIFactory1(IID_PPV_ARGS(pFactory.GetAddressOf()));
if (FAILED(hr)) {
  throw std::runtime_error("IDXGIFactoryクラスの作成に失敗しました。");
}
//GPUアダプターを列挙して一番最初に見つかった使えるものを選ぶ
Microsoft::WRL::ComPtr<IDXGIAdapter1> pAdapterIt;
for (UINT adapterIndex = 0; S_OK == pFactory->EnumAdapters1(adapterIndex, pAdapterIt.GetAddressOf());  ++adapterIndex) {
  DXGI_ADAPTER_DESC1 desc;
  pAdapterIt->GetDesc1(&desc);

  // ...アダプタ情報のログ出力は省略

  if (nullptr == this->mpAdapter) {
    if (desc.Flags ^= DXGI_ADAPTER_FLAG_SOFTWARE) {
      this->mpAdapter = pAdapterIt;
      OutputDebugStringA(std::string("このアダプターを使用します。 adapterIndex = " + std::to_string(adapterIndex) + "\n").c_str());
    }
  }
  //使い終わったら必ずReleaseすること
  pAdapterIt.Reset();
}


IDXGIFactoryの作成

DXGIを利用するときははじめにIDXGIFactoryを生成します。 DXGIではIDXGIFactoryを使って必要なものを作成していきますので、ID3D11Deviceと似た役割になります。

IDXGIFactoryの作成にはCreateDXGIFactoryを使用します。 また、IDXGIFactoryIDXGIAdapterと同じでいくつかの種類が存在しますので、DXGIのバージョンを確認して使用する種類を選択してください。

ドキュメント:
IDXGIFactory1 (日本語) (英語)
CreateDXGIFactory1 (日本語) (英語)

// Scene::onInit関数の一部
//DXGIを使う上で必要となるIDXGIFactory1を作成
HRESULT hr;
Microsoft::WRL::ComPtr<IDXGIFactory1> pFactory;
hr = CreateDXGIFactory1(IID_PPV_ARGS(pFactory.GetAddressOf()));
if (FAILED(hr)) {
  throw std::runtime_error("IDXGIFactoryクラスの作成に失敗しました。");
}


使用するアダプタの選択

IDXGIFactoryを作成したらEnumAdaptersを利用して、使用するIDXGIAdaptorを選択します。

EnumAdaptersの引数には0から始まるアダプタの添字とアダプタを受け取る変数を渡します。 使用できるアダプタ全てを見ていくにはサンプルコードのようにEnumAdaptersDXGI_ERROR_NOT_FOUNDを返すまでループします。

アダプタの情報はIDXGIAdaptor::GetDescで取得できるDXGI_ADAPTER_DESCにあります。 製品情報やメモリ量など見れますが、アダプタの選択の際にはFlagsの内容を使用しています。 サンプルコードではGPUを使用したいのでFlagsDXGI_ADAPTER_FLAG_SOFTWAREでないものを選択しています。 DXGI_ADAPTER_FLAG_SOFTWAREはWindows 8から導入されたものなので注意してください。

ドキュメント:
IDXGIFactory1::EnumAdapters1 (日本語) (英語)
DXGI_ADAPTER_DESC1 (日本語) (英語)

// Scene::onInit関数の一部
//GPUアダプターを列挙して一番最初に見つかった使えるものを選ぶ
Microsoft::WRL::ComPtr<IDXGIAdapter1> pAdapterIt;
for (UINT adapterIndex = 0; S_OK == pFactory->EnumAdapters1(adapterIndex, pAdapterIt.GetAddressOf());  ++adapterIndex) {
  DXGI_ADAPTER_DESC1 desc;
  pAdapterIt->GetDesc1(&desc);

  // ...アダプタ情報のログ出力は省略

  if (nullptr == this->mpAdapter) {
    if (desc.Flags ^= DXGI_ADAPTER_FLAG_SOFTWARE) {
      this->mpAdapter = pAdapterIt;
      OutputDebugStringA(std::string("このアダプターを使用します。 adapterIndex = " + std::to_string(adapterIndex) + "\n").c_str());
    }
  }
  //使い終わったら必ずReleaseすること
  pAdapterIt.Reset();
}

このように選択したアダプタを先に説明したD3D11CreateDeviceに渡すことで好きなGPUを使うことが出来ます。

IDXGISwapChainの作成

次は描画結果をディスプレイに表示するために必要なIDXGISwapChainについて見ていきます。

// Scene::onInitの一部
//IDXGISwapChainの作成
DXGI_SWAP_CHAIN_DESC swapChainDesc = { 0 };
swapChainDesc.OutputWindow = Win32Application::hwnd();
swapChainDesc.BufferCount = 2;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_SEQUENTIAL;

swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
//swapChainDesc.Flags = 0;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
//フルスクリーンとウィンドモードの切り替えがしたい場合は、まずウィンドウモードとして生成することを推奨しているみたい
//https://msdn.microsoft.com/en-us/library/bb174579(v=vs.85).aspx
swapChainDesc.Windowed = true;
//希望する画面設定
swapChainDesc.BufferDesc.Width = this->width();
swapChainDesc.BufferDesc.Height = this->height();
swapChainDesc.BufferDesc.RefreshRate.Numerator = 60;
swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
//上の画面設定に一番近いものを調べる
Microsoft::WRL::ComPtr<IDXGIOutput> pOutput;
if (DXGI_ERROR_NOT_FOUND == this->mpAdapter->EnumOutputs(0, pOutput.GetAddressOf())) {
  throw std::runtime_error("アダプターの出力先が見つかりません。");
}
DXGI_MODE_DESC modeDesc;
hr = pOutput->FindClosestMatchingMode(&swapChainDesc.BufferDesc, &modeDesc, this->mpDevice.Get());
if (FAILED(hr)) {
  throw std::runtime_error("表示モードの取得に失敗");
}
//IDXGISwapChainの作成
swapChainDesc.BufferDesc = modeDesc;
hr = pFactory->CreateSwapChain(this->mpDevice.Get(), &swapChainDesc, this->mpSwapChain.GetAddressOf());
if (FAILED(hr)) {
  throw std::runtime_error("IDXGISwapChainの作成に失敗");
}

IDXGISwapChainは画面に表示するバッファを管理するものになり、描画中に起きる画面のちらつきを防ぐための手法であるバックバッファを簡単に扱うことが出来ます。

バックバッファを簡単に説明すると画面に表示するバッファと描画を行うバッファを用意して、描画が終わったらその2つを入れ替える手法になります。 画面に表示するイメージをフロントバッファと呼ばれています。 イメージの枚数は2枚でなくてもよく実装しているものに合わせて枚数を増やせます。

ドキュメント:IDXGISwapChain (日本語) (英語)

IDXGISwapChainを作成している部分は下のものになります。

ドキュメント:IDXGIFactory::CreateSwapChain (日本語) (英語)

// Scene::onInitの一部
//IDXGISwapChainの作成
hr = pFactory->CreateSwapChain(this->mpDevice.Get(), &swapChainDesc, this->mpSwapChain.GetAddressOf());
if (FAILED(hr)) {
  throw std::runtime_error("IDXGISwapChainの作成に失敗");
}

IDXGIFactory::CreateSwapChainの第1引数にはID3D11Deviceを渡してください。 第2引数のDXGI_SWAP_CHAIN_DESCIDXGISwapChainの設定を行います。

DXGI_SWAP_CHAIN_DESC

  1. BufferDesc

    バッファの設定を行うためのものです。 画面サイズやリフレッシュレートなど自由に設定できますが、ディスプレイによって最適な設定が存在しますのでそうなるように設定してください。 最適な設定にはサンプルコードのようにIDXGIOutput::FindClosestMatchingModeを利用することができますので、活用していきましょう。 また、全画面モードを使用する際は必ず最適な設定にする必要があります。 IDXGIOutputはディスプレイなどを表すものになり、使い方はドキュメントとサンプルを参考にしてください。
    ドキュメント:
    DXGI_SWAP_CHAIN_DESC (日本語) (英語)
    IDXGIOutput (日本語) (英語)
    IDXGIOutput::FindClosestMatchingMode (日本語) (英語)

  2. SampleDesc

    マルチサンプリングの設定を行うためのものです。
    ドキュメント:DXGI_SAMPLE_DESC (日本語) (英語)

  3. BufferUsage

    バッファの使用法を指定します。 バッファはシェーダリソースやレンダーターゲット、アンオーダードアクセスとして設定することができます。
    ドキュメント:DXGI_USAGE (日本語) (英語)

  4. BufferCount

    内部に持つバッファの数を指定します

  5. OutputWindow

    出力先のウィンドウをあわわすHWNDを渡します。HWNDについてはWINPAIについて調べてください。

  6. Windowed

    全画面モードかウィンドウモードかを表すフラグです。trueでウィンドウモードになります。

  7. SwapEffect

    フロントバッファの切り替えを行った時のバッファの内容をどうするかを指定するものになります。 指定にはDXGI_SWAP_EFFECTを使用します。
    ドキュメント:DXGI_SWAP_EFFECT (日本語) (英語)

  8. Flags

    IDXGISwapChainの動作を指定するフラグになります。 指定できるものはDXGI_SWAP_CHAIN_FLAGで定義されています。
    ドキュメント:DXGI_SWAP_CHAIN_FLAG (日本語) (英語)

IDXGISwapChainの作成は以上になります。

バックバッファのリサイズ

IDXGISwapChainを作成した後に画面サイズを変更するにはIDXGISwapChain::ResizeBuffersを使用します。 使用する際はIDXGISwapChainが生成したバッファのビューなどを全て開放する必要がありますので、忘れないようにしてください。

IDXGISwapChain::ResizeBuffersには変更したい画面サイズとフォーマットやバッファの数、フラグも一緒に変更できます。 サンプルでは作成した時のものと同じものを設定しています。

ドキュメント:IDXGISwapChain::ResizeBuffers (日本語) (英語)

// Scene::onResizeWindowの一部
DXGI_SWAP_CHAIN_DESC swapChainDesc;
this->mpSwapChain->GetDesc(&swapChainDesc);
if (!swapChainDesc.Windowed) {
  return;
}
this->mpBackBuffer.Reset();
this->mpBackBufferRTV.Reset();
auto hr = this->mpSwapChain->ResizeBuffers(swapChainDesc.BufferCount, width, height, swapChainDesc.BufferDesc.Format, swapChainDesc.Flags);
if (FAILED(hr)) {
  throw std::runtime_error("バックバッファのサイズ変更に失敗");
}
this->initRenderTargetAndDepthStencil(width, height);

全画面モードとウィンドウモードの切替

続いて全画面モードとウィンドウモードの切り替えも行えます。 その際はDXGI_SWAP_CHAIN_DESCFlagsDXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCHを指定する必要があります。

切り替えにはIDXGISwapChain::SetFullscreenStateを使用します。 第1引数に全画面かウィンドウのどちらにするかを指定し、trueで全画面モードに、falseでウィンドウモードに切り替えます。 また全画面モードに切り替える際は第2引数に渡すIDXGIOutputでどのディスプレイを全画面にするかを決めることができます。

全画面モードにするときはディスプレイが対応できるサイズを指定しないと、パフォーマンスが落ちますので注意してください。

また、全画面モードとウィンドウモードの切り替えを行う際は、IDXGISwapChain作成時には必ずウィンドウモードで作成してください。

ドキュメント:IDXGISwapChain::SetFullscreenState (日本語) (英語)

// Scene::onKeyUpの一部
DXGI_SWAP_CHAIN_DESC desc;
this->mpSwapChain->GetDesc(&desc);
if (desc.Windowed) {
  //全画面モードに切り替える際はどのディスプレイを対象にするか決めれる
  if (DXGI_ERROR_NOT_FOUND == this->mpAdapter->EnumOutputs(0, pOutput.GetAddressOf())) {
    throw std::runtime_error("アダプターの出力先が見つかりません。");
  }
}
auto hr = this->mpSwapChain->SetFullscreenState(desc.Windowed, nullptr);
if (FAILED(hr)) {
  throw std::runtime_error("フルスリーンモードとウィンドウモードの切り替えに失敗。");
}

まとめ

今回のパートではDX11の初期化について見てきました。 これでフルスクラッチでDX11のアプリケーションを作成することもできるようになると思います。 (WINAPIについての知識も必要となりますが、まぁ大丈夫でしょう。)

ここまでくればもうDX11について説明することはほとんどありません。 あとは興味のある分野について調べてDX11を利用して実装していくだけになります。

補足

D3D11CreateDeviceAndSwapChain

DX11ではID3D11DeviceIDXGISwapChainを一緒に作成できる関数が用意されています。 使い方は今回説明した内容とほぼ一緒ですので、紹介だけにとどめておきます。

ドキュメント: D3D11CreateDeviceAndSwapChain (日本語) (英語)

IDXGISwapChain::Present

バックバッファに描画をし終えた後、IDXGISwapChain::Presentを使ってその内容をディスプレイにおくる必要があります。 呼び出すのを忘れると画面になにも表示されないのでかなり重要な事なのですが、説明するタイミングがなかったのでここで紹介しました。

//DXSample.cpp DXSample::render関数の一部

//     ...
// 何かを描画する
//     ...

//画面に表示したいものを描画し終えたらフロントバッファを切り替える。
this->mpSwapChain->Present(1, 0);
    

ドキュメント: IDXGISwapChain::Present (日本語) (英語)

<前 トップ 次>