Direct3D11初期化&画面クリア 続き
間が空いてしまいましたが、前回の続きを書いていきたいと思います。
画面クリア
画面クリアの手順を見ていきましょう。
1.バックバッファのリサイズ処理
IDXGISwapChain::ResizeBuffers 関数でクライアント領域に合わせて、
バックバッファのリサイズを行っています。
// SampleApp::CreateBackBuffer(const Size2D& newSize) // バッファのサイズを変更 HRESULT hr = m_SwapChain->ResizeBuffers( m_BufferCount, static_cast<UINT>(newSize.width), static_cast<UINT>(newSize.height), m_BufferFormat, DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH ); if (FAILED(hr)) { return false; }
IDXGISwapChain::ResizeBuffers 関数
HRESULT ResizeBuffers(
UINT BufferCount,
UINT Width,
UINT Height,
DXGI_FORMAT NewFormat,
UINT SwapChainFlags
);
BufferCount
スワップチェインのバッファ数です。
作成した時と同じ値を指定しておけば問題ないでしょう。
Width
バック バッファーの新しい幅です。
0 が指定された場合、ターゲット ウィンドウのクライアント領域の幅が使用されます。
Height
バック バッファーの新しい高さです。
0 が指定された場合、ターゲット ウィンドウのクライアント領域の高さが使用されます。
NewFormat
バック バッファーの新しいフォーマットです。
DXGI_FORMAT の値を指定できます。
変更しない場合は作成時と同じ値を指定します。
SwapChainFlags
スワップチェインの動作オプションです。
DXGI_SWAP_CHAIN_FLAG の値を指定できます。
変更しない場合は作成時と同じ値を指定します。
2.バックバッファを取得
IDXGISwapChain::GetBuffer 関数でバックバッファを取得します。
// SampleApp::CreateBackBuffer(const Size2D& newSize) ComPtr<ID3D11Texture2D> backBuffer; hr= m_SwapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer)); if (FAILED(hr)) { return false; }
IDXGISwapChain::GetBuffer 関数
HRESULT GetBuffer(
UINT Buffer,
REFIID riid,
void **ppSurface
);
Buffer
取得したいバッファーのインデックスです。
レンダーターゲットを作成する場合は 0 で問題ないでしょう。
riid
バッファーの操作に使用するインターフェイスの種類です。
ppSurface
バック バッファー インターフェイスへのポインターです。
※引数の指定の仕方について
IID_PPV_ARGS マクロを使用すると簡潔に記述できます。
m_SwapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer));
3.レンダーターゲットを作成
ID3D11Device::CreateRenderTargetView 関数でレンダーターゲットを作成します。
// SampleApp::CreateBackBuffer(const Size2D& newSize) hr = m_Device->CreateRenderTargetView( backBuffer.Get(), nullptr, &m_RenderTargetView ); if (FAILED(hr)) { return false; }
ID3D11Device::CreateRenderTargetView 関数
HRESULT CreateRenderTargetView(
ID3D11Resource *pResource,
const D3D11_RENDER_TARGET_VIEW_DESC *pDesc,
ID3D11RenderTargetView **ppRTView
);
pResource
レンダー ターゲットを表す ID3D11Resource へのポインターです。
このリソースは、D3D11_BIND_RENDER_TARGET フラグを使用してあらかじめ作成しておく必要があります。
ここでは、IDXGISwapChain::GetBuffer 関数で取得した値を指定しています。
pDesc
レンダー ターゲット ビューの記述を表す D3D11_RENDER_TARGET_VIEW_DESC へのポインターです。
バックバッファのレンダーターゲットの場合 NULL を指定できます。
ppRTView
作成されたレンダーターゲットを表す ID3D11RenderTargetView へのポインターのアドレスです。
4.レンダーターゲットを設定
ID3D11DeviceContext::OMSetRenderTargets 関数を使用して設定します。
// SampleApp::CreateBackBuffer(const Size2D& newSize) ID3D11RenderTargetView* pRenderTargetViews[] = { m_RenderTargetView.Get() }; m_Context->OMSetRenderTargets( _countof(pRenderTargetViews), pRenderTargetViews, nullptr );
ID3D11DeviceContext::OMSetRenderTargets 関数
void OMSetRenderTargets(
UINT NumViews,
ID3D11RenderTargetView *const *ppRenderTargetViews,
ID3D11DepthStencilView *pDepthStencilView
);
NumViews
バインドするレンダー ターゲットの数です (範囲は 0 ~ D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT)。
ppRenderTargetViews の要素数です。
ppRenderTargetViews
デバイスにバインドするレンダー ターゲットの配列へのポインターです。
このパラメーターが NULL の場合、レンダー ターゲットはバインドされません。
pDepthStencilView
デバイスにバインドする深度ステンシル ビューへのポインターです。
このパラメーターが NULL の場合、深度ステンシル ステートはバインドされません。
5.ビューポートの設定
D3D11_VIEWPORT 構造体で設定を記述し、
ID3D11DeviceContext::RSSetViewports 関数で設定します。
// SampleApp::CreateBackBuffer(const Size2D& newSize) D3D11_VIEWPORT viewport; viewport.Width = static_cast<FLOAT>(newSize.width); viewport.Height = static_cast<FLOAT>(newSize.height); viewport.MinDepth = 0.0f; viewport.MaxDepth = 1.0f; viewport.TopLeftX = 0.0f; viewport.TopLeftY = 0.0f; m_Context->RSSetViewports(1, &viewport);
6.画面クリア
ID3D11DeviceContext::ClearRenderTargetView 関数を使用して画面をクリアします。
// SampleApp::Render() // 指定色でクリア FLOAT clearColor[4] = { 0.0f, 0.125f, 0.3f, 1.0f }; // red, green, blue, alpha m_Context->ClearRenderTargetView(m_RenderTargetView.Get(), clearColor);
7.バックバッファを画面に反映
IDXGISwapChain::Present 関数でバックバッファを画面に反映します。
// SampleApp::Render() // 結果をウインドウに反映 HRESULT hr = m_SwapChain->Present(0, 0); if (FAILED(hr)) // Error! }
最後の方が説明不足な気がしますが、これで画面クリアができました。
長かったです。
Direct3D11初期化&画面クリア
Direct3D11 を使用して初期化&画面クリアするアプリケーションを作成しました。
サンプルアプリケーションクラス
初期化&画面クリアに必要な最小クラスです。
// SampleApp.hpp #pragma once #include "IApp.hpp" #include <dxgi.h> #include <d3d11.h> #include <wrl.h> template<class T> using ComPtr = Microsoft::WRL::ComPtr<T>; class SampleApp { public: SampleApp(IApp* pApp); ~SampleApp(); // 初期化 bool Init(); // 描画処理 void Render(); // リサイズ void OnResize(const Size2D& newSize); private: // バックバッファを作成 bool CreateBackBuffer(const Size2D& newSize); private: IApp * m_pApp; UINT m_BufferCount; DXGI_FORMAT m_BufferFormat; D3D_FEATURE_LEVEL m_FeatureLevel; ComPtr<IDXGIFactory> m_Factory; ComPtr<IDXGISwapChain> m_SwapChain; ComPtr<ID3D11Device> m_Device; ComPtr<ID3D11DeviceContext> m_Context; ComPtr<ID3D11RenderTargetView> m_RenderTargetView; };
Direct3D11 初期化
初期化の手順を見ていきましょう。
1.ライブラリのリンク
Direct3D11 を利用するのに必要なライブラリをリンクしています。
// SampleApp.cpp // DXGI & D3D11 のライブラリをリンク #pragma comment(lib, "dxgi.lib") #pragma comment(lib, "d3d11.lib")
2.IDXGIFactory の作成
IDXGISwapChain を作成するのに必要なので、あらかじめ作成しておきます。
CreateDXGIFactory 関数で作成します。
// SampleApp::Init() HRESULT hr = CreateDXGIFactory(IID_PPV_ARGS(&m_Factory)); if(FAILED(hr)) { return false; }
3.ID3D11Device & ID3D11DeviceContext の作成
D3D11CreateDevice 関数で作成しています。
IDXGISwapChain と同時に作成も可能なのですが、今回は別々に作成しています。
// SampleApp::Init() #if defined(DEBUG) || defined(_DEBUG) createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG; #endif D3D_FEATURE_LEVEL featureLevels[] = { D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, }; // デバイス&コンテキストを生成 hr = D3D11CreateDevice( nullptr, D3D_DRIVER_TYPE_HARDWARE, // ハードウェア ドライバー を使用 nullptr, createDeviceFlags, featureLevels, _countof(featureLevels), D3D11_SDK_VERSION, &m_Device, &m_FeatureLevel, &m_Context ); if (FAILED(hr)) { return false; }
D3D11CreateDevice 関数
HRESULT D3D11CreateDevice(
IDXGIAdapter *pAdapter,
D3D_DRIVER_TYPE DriverType,
HMODULE Software,
UINT Flags,
CONST D3D_FEATURE_LEVEL *pFeatureLevels,
UINT FeatureLevels,
UINT SDKVersion,
ID3D11Device **ppDevice,
D3D_FEATURE_LEVEL *pFeatureLevel,
ID3D11DeviceContext **ppImmediateContext
);
pAdapter
デバイスの作成時に使用するビデオ アダプターへのポインターです。
通常は既定のアダプターで良いので、NULL で問題ないです。
DriverType
作成するデバイスの種類です。
D3D_DRIVER_TYPE のいずれかを指定します。
pAdapter に NULL 以外の値を指定する場合は、 D3D_DRIVER_TYPE_UNKNOWN を指定する必要があります。
通常は D3D_DRIVER_TYPE_HARDWARE 問題ないでしょう。
Software
ソフトウェア ラスタライザーを実装する DLL のハンドルです。
DriverType に D3D_DRIVER_TYPE_SOFTWARE を指定した場合、NULL に設定することはできません。
D3D_DRIVER_TYPE_SOFTWARE を指定することはほぼないので、NULL で問題ないでしょう。
Flags
有効にするランタイム レイヤーです。
D3D11_CREATE_DEVICE_FLAG の値を OR 演算で指定できます。
デバッグレイヤーを有効にするには、D3D11_CREATE_DEVICE_DEBUG を指定します。
デフォルトで良い場合は 0 で構いません。
pFeatureLevels
作成を試みる機能レベルの順序を指定する配列へのポインターです。
機能レベルが高いものから順に並べておくと、最大の機能レベルを取得できます。
FeatureLevels
pFeatureLevels の要素数です。
SDKVersion
SDK のバージョンです。D3D11_SDK_VERSION を指定します。
ppDevice
作成されたデバイスを表す ID3D11Device オブジェクトへのポインターのアドレスを返します。
pFeatureLevel
成功した場合は、成功した pFeatureLevels 配列の最初の D3D_FEATURE_LEVEL を返します。
失敗した場合は 0 を返します。
ppImmediateContext
デバイス コンテキストを表す ID3D11DeviceContext オブジェクトへのポインターのアドレスを返します。
4.MSAA (Multi-Sample Anti-Aliasing) の設定
今回は画面クリアまでなので必要ないかもしれませんが、一応設定だけしておきます。
MSAA については、ここでは説明を省きます。
スワップチェインの作成時に MSAA を有効化できるのですが、その設定に利用する値を調べます。
ID3D11::CheckMultisampleQualityLevels 関数を使って、利用可能なサンプリングカウントと品質の最大値を調べます。
// SampleApp::Init() // 使用可能なMSAAを取得 DXGI_SAMPLE_DESC sampleDesc; ZeroMemory(&sampleDesc, sizeof(sampleDesc)); for (int i = 1; i <= D3D11_MAX_MULTISAMPLE_SAMPLE_COUNT; i <<= 1) { UINT Quality; if (SUCCEEDED(m_Device->CheckMultisampleQualityLevels(DXGI_FORMAT_D24_UNORM_S8_UINT, i, &Quality))) { if (0 < Quality) { sampleDesc.Count = i; sampleDesc.Quality = Quality - 1; } } }
5.DXGI_SWAP_CHAIN_DESC の設定
スワップチェイン作成に必要な設定をします。
下のような感じです。
// SampleApp::Init() // DXGI_SWAP_CHAIN_DESC の設定 DXGI_SWAP_CHAIN_DESC swapChainDesc; ZeroMemory(&swapChainDesc, sizeof(swapChainDesc)); swapChainDesc.BufferDesc.Width = static_cast<UINT>(clientSize.width); swapChainDesc.BufferDesc.Height = static_cast<UINT>(clientSize.height); swapChainDesc.BufferDesc.RefreshRate.Numerator = 60; swapChainDesc.BufferDesc.RefreshRate.Denominator = 1; swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_CENTERED; swapChainDesc.BufferDesc.Format = m_BufferFormat; swapChainDesc.SampleDesc = sampleDesc; swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT; swapChainDesc.BufferCount = m_BufferCount; swapChainDesc.Windowed = TRUE; swapChainDesc.OutputWindow = hWnd; swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
DXGI_SWAP_CHAIN_DESC 構造体
typedef struct DXGI_SWAP_CHAIN_DESC {
DXGI_MODE_DESC BufferDesc;
DXGI_SAMPLE_DESC SampleDesc;
DXGI_USAGE BufferUsage;
UINT BufferCount;
HWND OutputWindow;
BOOL Windowed;
DXGI_SWAP_EFFECT SwapEffect;
UINT Flags;
} DXGI_SWAP_CHAIN_DESC;
BufferDesc.Width
バッファの横幅です。
BufferDesc.Height
バッファの縦幅です。
BufferDesc.RefreshRate.Numerator
リフレッシュレートの分子です。
BufferDesc.RefreshRate.Denominator
リフレッシュレートの分母です。
BufferDesc.ScanlineOrdering
スキャンラインの方法です。
バックバッファをフリップした時にハードウェアがパソコンのモニターに点をどう描くかを指定します。
プログレッシブやインターレスなどが選択可能ですが、
特段理由が無ければデフォルト値(DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED)でOKです。
BufferDesc.Scaling
バッファのスケーリングの設定です。
ウィンドウのサイズに応じてスケーリングするかどうかの設定を行えます。
スケーリングする場合は DXGI_MODE_SCALING_STRETCHED 、
スケーリングしない場合は DXGI_MODE_SCALING_CENTERED を指定します。
BufferDesc.Format
バッファのフォーマットです。
フォーマットの設定についてはかなりの種類があるので MSDN を参考にしてください。
SampleDesc
MSAA の設定です。
「4.MSAA (Multi-Sample Anti-Aliasing) の設定」で取得した値を設定します。
何も指定しなかった場合は MSAA が無効になります。
BufferUsage
バッファの使用方法です。
レンダーターゲットとして使用する場合は DXGI_USAGE_RENDER_TARGET_OUTPUT 、
シェーダー入力用(レンダリングテクスチャ)として使用する場合は DXGI_USAGE_SHADER_INPUT を指定します。
BufferCount
バッファの数です。
2ならダブルバッファ、3ならトリプルバッファとなります。
特別こだわりがなければ2で問題ないと思います。
Windowed
ウィンドウモードで作成するかどうかです。
TRUE ならウィンドウモード、FALSE ならフルスクリーンモードです。
OutputWindow
出力ウィンドウです。ウィンドウハンドルを指定します。
Flags
スワップチェインの動作オプションです。
DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH を指定した場合、
フルスクリーンモードとウィンドウモードの切り替えが可能になります。
6.IDXGISwapChain の作成
やっとスワップチェインの作成です。
IDXGIFactory::CreateSwapChain 関数で作成します。
// SampleApp::Init() // スワップチェインの生成 hr = m_Factory->CreateSwapChain( m_Device.Get(), &swapChainDesc, &m_SwapChain ); if (FAILED(hr)) { return false; }
IDXGIFactory::CreateSwapChain 関数
HRESULT CreateSwapChain(
IUnknown *pDevice,
DXGI_SWAP_CHAIN_DESC *pDesc,
IDXGISwapChain **ppSwapChain
);
pDevice
スワップ チェーンに 2D イメージを書き込むデバイスへのポインター。
D3D11CreateDevice で作成したデバイスを指定します。
pDesc
DXGI_SWAP_CHAIN_DESC へのポインターです。
NULL は指定できません。
ppSwapChain
作成されたスワップ チェーンへのポインターです。
ここまで記事を書いてきたのですが、
長くなったので続きは次回にしようと思います。
次回はレンダーターゲットの作成と画面クリアの処理についてです。
テストアプリケーション用テンプレートを作った
テスト用アプリケーションを作成しやすいように、テンプレート的なものを作りました。
アプリケーションクラスの作成
ウィンドウ作成などのWin32APIの面倒な処理をこのクラスが担当します。
一応、他プラットフォームでも対応できるようにインターフェイスクラスとして宣言しています。
KEY_CODE は列挙型で、別のヘッダーファイルで宣言されています。
AppMain関数がメインエントリポイントになります。
App.hpp
#pragma once struct Position2D { s32 x; s32 y; }; struct Size2D { u32 width; u32 height; }; // コールバック登録用 struct AppCallbacks { // リサイズ void(*pOnResize)(const Size2D& newSize, void* pUser); void* pOnResizeUser; // キーダウン void(*pOnKeyDown)(KEY_CODE key, void* pUser); void* pOnKeyDownUser; // キーアップ void(*pOnKeyUp)(KEY_CODE key, void* pUser); void* pOnKeyUpUser; // マウスキーダウン void(*pOnMouseKeyDown)(const Position2D& position, MOUSE_BUTTON button, void* pUser); void* pOnMouseKeyDownUser; // マウスキーアップ void(*pOnMouseKeyUp)(const Position2D& position, MOUSE_BUTTON button, void* pUser); void* pOnMouseKeyUpUser; // マウスホイール void(*pOnMouseWheel)(const Position2D& position, s32 delta, void* pUser); void* pOnMouseWheelUser; }; // アプリケーションインターフェイス class IApp { public: virtual ~IApp() = default; // コールバックを登録 virtual void SetAppCallbacks(const AppCallbacks& callbacks) = 0; // true の間、メインループを実行 virtual bool IsLoop() const = 0; // アプリケーションを終了 virtual void PostQuit() = 0; // クライアント領域のサイズを取得 virtual Size2D GetClientSize() const = 0; // ウィンドウハンドルを取得 virtual void* GetWindowHandle() const = 0; // メッセージボックスを表示 virtual void ShowMessageBox(const std::string& message, const std::string& caption) = 0; }; // IApp ファクトリーメソッド bool CreateApp(const Size2D& clientSize, const std::string& title, IApp ** ppOut); // メインエントリポイント void AppMain();
実装例
AppMain.cppではAppMain関数の定義と各種コールバックの登録を行っています。
AppMain関数内でコールバックの登録後、IApp::IsLoop()がtrueの間メインループを実行します。
AppMain.cpp
#define UNUSED(x) (x) // リサイズ void OnResize(const Size2D& newSize, void* pUser) { UNUSED(newSize); UNUSED(pUser); } // キーダウン void OnKeyDown(KEY_CODE key, void* pUser) { IApp* pApp = reinterpret_cast<IApp*>(pUser); if (pApp == nullptr) { return; } if (key == KEY_CODE_ESCAPE) { // Escapeキーで終了 pApp->PostQuit(); } } // キーアップ void OnKeyUp(KEY_CODE key, void* pUser) { UNUSED(key); UNUSED(pUser); } // マウスキーダウン void OnMouseKeyDown(const Position2D& position, MOUSE_BUTTON button, void* pUser) { UNUSED(position); UNUSED(button); UNUSED(pUser); } // マウスキーアップ void OnMouseKeyUp(const Position2D& position, MOUSE_BUTTON button, void* pUser) { UNUSED(position); UNUSED(button); UNUSED(pUser); } // マウスホイール void OnMouseWheel(const Position2D& position, s32 delta, void* pUser) { UNUSED(position); UNUSED(delta); UNUSED(pUser); } // シーン更新処理 void UpdateScene() { } // シーン描画処理 void RenderScene() { } // エントリポイント void AppMain() { IApp* pApp = nullptr; Size2D clientSize = { 640, 480 }; if (!CreateApp(clientSize, "Sample", &pApp)) { return; } // コールバックの設定 AppCallbacks callbacks = { 0 }; // リサイズ callbacks.pOnResize = OnResize; callbacks.pOnResizeUser = pApp; // キーダウン callbacks.pOnKeyDown = OnKeyDown; callbacks.pOnKeyDownUser = pApp; // キーアップ callbacks.pOnKeyUp = OnKeyUp; callbacks.pOnKeyUpUser = pApp; // マウスキーダウン callbacks.pOnMouseKeyDown = OnMouseKeyDown; callbacks.pOnMouseKeyDownUser = pApp; // マウスキーアップ callbacks.pOnMouseKeyUp = OnMouseKeyUp; callbacks.pOnMouseKeyUpUser = pApp; // マウスホイール callbacks.pOnMouseWheel = OnMouseWheel; callbacks.pOnMouseWheelUser = pApp; pApp->SetAppCallbacks(callbacks); // メインループ while (pApp->IsLoop()) { UpdateScene(); RenderScene(); } delete pApp; }
できるだけ使いやすく作ったつもりですが、もう少しシンプルにできたかもしれません。
コードはGitHubにて公開していますので、ご自由にお使いください。
Visual Studioのビルドログファイルを出力されないようにする
通常、Visual Studioでビルドすると「.tlog」というフォルダが自動で作成されるのですが、
できるだけビルド環境はきれいにしておきたいので出力されないように設定しました。
メニューバーの
ツール(T) > オプション(O)...
でオプションウィンドウを開きます。
開いたら、検索窓に「VC++ プロジェクトの設定」と入力し、
出てきた設定を選びます。
その設定の一番上の「ビルドのログ」を「いいえ」変更し、「OK」ボタンを押すと完了です。