Top / D3D9使い浦島太郎のD3D11入門 / 第1話 戻ってきた浦島太郎

第1話 戻ってきた浦島太郎

Windows Vistaとかつかわねーよとかいっていたプログラマの皆様そろそろOS以降の時期ですよ。
Windows7はインストールされたでしょうか?え?まだ?そろそろXPを卒業しないと、いつまでも
古い権力にしがみついているどこかの老人と同じです。
WinXPになるときもWin2Kにしがみついていた人たちを見ましたね。乗り換えていない方は今すぐ!
今ならまだ間に合います。 さて、XPはずっとD3D9でしたが、 Win7ではD3D11が標準となります。
えっ?D3D10はどこへいった!? D3D10をやらなかったD3D9使いの人たちはすでに浦島太郎状態です。
さぁD3D9を勉強したあの頃を思い出して今一度D3D11を勉強しようじゃありませんか。

注:
 * D3D11はWindows Vista SP2とWindows 7で利用可能です。
 * 使用したSDKのバージョンは DirectX SDK February 2010 です。
 * 2010/5/1現在 D3D11 RTM(Release to manufacturing) ですが、まぁほとんど本番仕様でしょう
 * 浦島太郎は竜宮城から戻ってきたら700年たっていたそうですが、
 D3D9使いの浦島太郎はせいぜい7年くらいしかたってませんがまぁいいでしょう。

玉手箱をあける

必要なものはWindows7かVista SP2 とDirectX SDK

かくいう私も浦島太郎です。何を思ったかD3D11の玉手箱を開けてしまいました(笑
というわけで、D3D9浦島太郎がD3D11世界になじんでいく記録です。 D3D9は知っている前提で
話を進めていきます。というか、まだD3D11に関する記事が少ないですねぇ。しかも各サイトで
結構表記がずれていてわかりづらい・・。まぁこのサイトの表記が正しいとも限りませんが・・。
まぁいくつかのサイトを踏まえて、浦島太郎の観点からD3D11を解説していきます。
浦島太郎なので間違っていることを書いている可能性もあるのでもし間違っていたら教えてください。
(てか情報すくないよ・・・)

 あ、D3D11はD3D11対応ハードウェアを持っていなくても動作します。
Windows7かVista SP2がインストールされていればOKです。あとはSDKね。
D3D9世代のハードウェアを持っていても動作します。
もはやオンボードのintelグラフィックチップでさえもD3D10対応している時代なのでほとんどのPCで
動作すると思っていて問題ないでしょう。

DirectX SDK (Feb 2010)をダウンロード

一番簡単なサンプルからはじめる

まぁ一番簡単なサンプルは画面をクリアしたものを表示させるものでしょう。
D3Dの初期化が主な中心の話題ですね。


サンプルのダウンロード

まずは初期化

さて、まずは初期からです。かなり変わっています。どこかのイベントのセミナーではD3D9より簡単
とかいってましたが、まぁある意味簡単だとは思いますが、浦島太郎にとっては 難しい気がします。
まぁ慣れの問題ですかね?

capsの廃止、FeatureLevelsの導入

D3D9のころの初期化を思い出してみます。

bool InitD3D9(HWND hWnd, int width, int height)
{
	IDirect3D9* pD3D = Direct3DCreate9(D3D_SDK_VERSION);
	if (pD3D == NULL)
	    return false;

	D3DPRESENT_PARAMETERS d3dpp;
	・・・d3ppにいろいろ代入・・・

	pD3D->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps);
	・・・capsを使ってVertex/PixelShaderのバージョンとか調べる・・・

	IDirect3DDevice9* pDevice;
	pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
	    hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &pDevice);
}

ここからはpDeviceを使っていろいろやればOKなのがD3D9でした。
たいていの場合はIDirect3DDevice9* pDevice;を最初の初期化で取得してしまえば ここまでの
初期化なんて忘れてもほとんど問題ないのがD3D9ですが、D3D11ではそうもいかないようです。

D3D11だと初期化は以下のようになります。

bool InitD3D11(HWND hWnd, int width, int height)
{
	D3D_DRIVER_TYPE	dtype = D3D_DRIVER_TYPE_HARDWARE;
	UINT            flags = 0;
	D3D_FEATURE_LEVEL featureLevels[] = // featureLevel(新)
	{
		D3D_FEATURE_LEVEL_11_0,
		D3D_FEATURE_LEVEL_10_1,
		D3D_FEATURE_LEVEL_10_0,
		D3D_FEATURE_LEVEL_9_3,
		D3D_FEATURE_LEVEL_9_2,
		D3D_FEATURE_LEVEL_9_1,
	};
	UINT numFeatureLevels = sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL);
	UINT sdkVersion = D3D11_SDK_VERSION;
	D3D_FEATURE_LEVEL validFeatureLevel;

	// スワップチェーンの設定(D3D9でいうD3DPRESENT_PARAMETERS相当の設定)
	DXGI_SWAP_CHAIN_DESC scDesc;
	ZeroMemory( &scDesc, sizeof(scDesc) );
	scDesc.BufferCount                        = 1;
	scDesc.BufferDesc.Width                   = width;
	scDesc.BufferDesc.Height                  = height;
	scDesc.BufferDesc.Format                  = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
	scDesc.BufferDesc.RefreshRate.Numerator   = 60;
	scDesc.BufferDesc.RefreshRate.Denominator = 1;
	scDesc.BufferUsage                        = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	scDesc.OutputWindow                       = m_hWnd;
	scDesc.SampleDesc.Count                   = 1;
	scDesc.SampleDesc.Quality                 = 0;
	scDesc.Windowed = TRUE;

	// デバイスとデバイスコンテキストとスワップチェーンの作成
	IDXGISwapChain*         pSwapChain;
	ID3D11Device*           pDevice;
	ID3D11DeviceContext*    pDeviceContext;
	IDXGIAdapter*           adapter;
	HRESULT	hr = D3D11CreateDeviceAndSwapChain(
		adapter,
		dtype,
		NULL,
		flags,
		featureLevels,
		numFeatureLevels,
		sdkVersion,
		&scDesc,
		&pSwapChain,
		&pDevice,
		&validFeatureLevel,
		&pDeviceContext );
	if ( FAILED(hr) )
		return false;
		
	return true;
}

という感じだそうです。

 いきなりデバイスポインタ相当のものができてしまうようです。またcapsとか取得しなくても、
featureLevelsというものが取得できます。 validFeatureLevelに現在のハードウェアのレベルが入っています。
caspだとサポートする機能を細かく判定していましたが、FeatureLevelは大雑把に

    D3D_FEATURE_LEVEL_11_0 = D3D11 レベル
    D3D_FEATURE_LEVEL_10_1 = D3D10.1レベル
    D3D_FEATURE_LEVEL_10_0 = D3D10 レベル
    D3D_FEATURE_LEVEL_9_3 = D3D9 ShaderModel2 レベル <-これはSM2でいいらしい
    D3D_FEATURE_LEVEL_9_2 = D3D9 ShaderModel2 レベル
    D3D_FEATURE_LEVEL_9_1 = D3D9 ShaderModel1 レベル

としているようです。 まぁ初期化の作業は簡単になった気がしますが、ガラッと変えてきたので
最初に面くらいます。 スワップチェーンは複数のディスプレイや複数のグラフィックスカードがあるときに
利用しますが、まぁたいていの場合は1ディスプレイ1グラフィックスカードを前提としたアプリの場合が
多いのでここではあまり触れないことにします。

デバイスポインタはどこへいった

D3D9では IDirect3DDevice9* pDevice;を取得していろいろやってました。
D3D11の初期化では以下の3つのインターフェースが取得できました。

IDXGISwapChain* pSwapChain;
ID3D11Device* pDevice;
ID3D11DeviceContext* pDeviceContext;

早い話が、今までIDirect3DDevice9*の機能が3つに分かれています。
もし、あなたがD3D9のアプリをD3D11に移植しようとしているのであれば、 IDirect3DDevice9* pDevice;
のところに上の3つのインターフェースを置き換えてやればOKです。 なぜ分かれているかというと、
おそらくマルチスレッド対応が主な目的でしょう。 D3D9もマルチスレッド対応は一応してましたが、
なんか申し訳程度のもので関数を呼ぶときにロックするようなものでした。
マニュアルにもパフォーマンスが落ちるのでできるだけ使わないようにと書いてありました。結局D3D9を
操作するのはシングルスレッドのアプリがほとんどでした。しかし、最近のメニイコアの流れを活用しない
手はありません。特に多くのオブジェクトを描画しようと思うと、GPUパワーよりも描画コマンドを発行する
CPUのボトルネックのほうが問題になってきました。そこで描画コマンドを並列に実行できるように
したのがこの仕組みです。ただし、いきなりは使えません。いろいろ手順を踏む必要があるようです。
後ほど解説します。

 さて、D3D11のインターフェースの解説に戻ります。

IDXGISwapChain* pSwapChain; // バックバッファの切り替え

D3D9ではバックバッファの切り替えをpDevice->Preset(NULL,NULL,NULL,NULL) とかやってましたが、
D3D11ではこのインターフェースで 行います。
pSwapChain->Present(NULL,NULL);

ID3D11Device* pDevice; // リソース作成

リソースの作成に利用します。CreateXXXなやつは全部このインターフェースです。
CreateTextureとかCreateRenderTargetとか。 このインターフェースはスレッドセーフです
スレッドで共有して呼び出すことができます。 マルチスレッドでリソースの作成とかができます。
これを利用するのは簡単ですね。

ID3D11DeviceContext* pDeviceContext; // 描画、ステートの切り替え

描画やステートの変更はこのインターフェースを利用します。 ID3D11Device*では描画はできません。
このインターフェースはスレッドセーフではありません
D3D11CreateDeviceAndSwapChainで作られた、このコンテキストは ImmediateContextとも呼ばれています。
これはマルチスレッドでの利用時に 必要となってくる知識です。まぁとりあえず放置で。後ほど解説。
まぁD3D9時代にIDirect3DDevice9* でCreateXXX"ではない"命令は このインターフェースに実装されている
と思ってください。 スワップチェーンはデバイスとデバイスコンテキストと別に作ることができます。
しかし、結局必要となるのでD3D11では D3D11CreateDeviceAndSwapChain()で一緒に作ることができます。
デバイスポインタ相当が何かわかってしまえば、D3D9使いの浦島太郎さんには怖いものなしですよね!

だがまだ絵はでない・・。

D3D9ならIDirect3DDevice9 を取得して適当にAPIをたたいてやれば絵が出ましたが、
D3D11はこれだけでは絵が出ません。なぜなら、D3D9時代はスワップチェーンとデバイスが
一体型だったので特に何もしなくてもよかったのですが、D3D11は分かれているのでそれらを
結び付けてやる必要があります。(D3D9使いからすると非常に意味不明で面倒な部分ですね・・。)
D3D9ではバックバッファとデプスバッファをCreateDeviceで作成してました。
以下のような感じでした。

D3DPRESENT_PARAMETERS d3dpp;
d3dpp.BackBufferFormat = D3DFMT_R8G8B8;
d3dpp.AutoDepthStencilFormat = D3DFMT_D24X8;

とかやりましたよね D3D11では

DXGI_SWAP_CHAIN_DESC scDesc;
scDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;

としかしていません。
つまりバックバッファしか作られていません。またバックバッファはスワップチェーンにしかないので、
デバイスコンテキストは何も知らないわけです。なのでD3D9と同じ状態にするにはデプスバッファの作成
とバックバッファとデプスバッファをデバイスコンテキストに結び付けてやる必要があります。

void setDefaultBackBufferAndDepthBuffer(int width, int height)
{
	ID3D11RenderTargetView* pRenderTargetView; // バックバッファ用
	ID3D11Texture2D*        pDepthStencil;     // デプスバッファの元
	ID3D11DepthStencilView* pDepthStencilView; // デプスバッファ用

	// バックバッファの取得
	ID3D11Texture2D* pBackBuffer;
	pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D),
		reinterpret_cast<void**>(&pBackBuffer) );
	
	// RenderTargetViewの作成
	pDevice->CreateRenderTargetView( pBackBuffer, NULL, &pRenderTargetView);
	pBackBuffer->Release();


	// デプステクスチャの作成
	D3D11_TEXTURE2D_DESC depthDesc;
	ZeroMemory( &depthDesc, sizeof(depthDesc) );
	depthDesc.Width              = width;
	depthDesc.Height             = height;
	depthDesc.MipLevels          = 1;
	depthDesc.ArraySize          = 1;
	depthDesc.Format             = DXGI_FORMAT_D24_UNORM_S8_UINT;
	depthDesc.SampleDesc.Count   = 1;
	depthDesc.SampleDesc.Quality = 0;
	depthDesc.Usage              = D3D11_USAGE_DEFAULT;
	depthDesc.BindFlags          = D3D11_BIND_DEPTH_STENCIL;
	depthDesc.CPUAccessFlags     = 0;
	depthDesc.MiscFlags          = 0;
	pDevice->CreateTexture2D( &depthDesc, NULL, &m_pDepthStencil );
	
	// DepthStencilViewの作成
	D3D11_DEPTH_STENCIL_VIEW_DESC dsvDesc;
	ZeroMemory( &dsvDesc, sizeof(dsvDesc) );	
	dsvDesc.Format = depthDesc.Format;
	dsvDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DMS;
	dsvDesc.Texture2D.MipSlice = 0;
	pDevice->CreateDepthStencilView(pDepthStencil, &dsvDesc, &pDepthStencilView);
	
	
	// レンダリングターゲットを設定する
	pDeviceContext->OMSetRenderTargets(1, &pRenderTargetView, pDepthStencilView);

	// ついでにビューポートの設定
	D3D11_VIEWPORT	vp;
	vp.Width    = static_cast<float>(m_width);
	vp.Height   = static_cast<float>(m_height);
	vp.MinDepth = 0.0f;
	vp.MaxDepth = 1.0f;
	vp.TopLeftX = 0;
	vp.TopLeftY = 0;
	pDeviceContext->RSSetViewports(1, &vp);
}
(わかりやすさのためエラー処理は省いているので適宜つけてね)

ID3D11RenderTargetView* と ID3D11DepthStencilView* という 見慣れないインターフェースが出てきました。
D3D11ではインターフェースに渡すオブジェクトは ID3D11XxxView*という インターフェースになるようです。
ここではRenderTargetとDepthStencilだったので ID3D11RenderTargetView* と ID3D11DepthStencilView*
という名前になっていました。 これは仕様なので覚えるしかないですね。
いろいろやって最後に
pDeviceContext->OMSetRenderTargets(1, &pRenderTargetView, pDepthStencilView );
としてデバイスコンテキストにバックバッファとデプスバッファを関連付けています。
これで、デバイスコンテキストで描画命令などを設定し、バックバッファに絵を書く準備ができました。
ここまでを初期化処理としたらD3D9時代よりも手数ふえとるやんけ・・・と思いますが、
まぁ汎用的にするためっぽいのでしょうがないかなぁ。

バッファをクリア

と、とりあえず画面をクリアだ!

D3D9でのデバイスの描画系相当のコマンドはD3D11ではデバイスコンテキストが持っているのでした。
 pContext->Clear() 関数が・・・ないねぇ・・・。 先ほどがんばってバックバッファとデプスバッファを
結びつけましたが、 どうやら結びつけたバッファをクリアする関数は内容です。 というわけで、
個別にバッファをクリアすることで内容をクリアできるようです。

void Clear()
{
	float clearColor[] = { 0.0f, 0.0f, 1.0f, 0.0f };
	pDeviceContext->ClearRenderTargetView( pRenderTargetView, clearColor );
	pDeviceContext->ClearDepthStencilView( pDepthStencilView,
	    D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0 );
}

デバイスコンテキストにクリア関数があってもよさそうですが、なぜ分かれているかは謎です。
まぁこうしろというので仕方ないですね。

スワップバッファ

すわっぷばふぁぁぁぁぁああ

画面をクリアしたらスワップバッファですね

void SwapBuffer()
{
	pSwapChain->Present(NULL, NULL);
}

これは簡単ですね。スワップチェーンが元から持っているバックバッファが入れ替えられます。

終了処理

お片づけ

お片づけはいつもどおりです。

#define SAFE_RELEASE(p)	  if(p) { p->Release(); p = NULL; }
void Destroy()
{
	SAFE_RELEASE( pDepthStencilView );
	SAFE_RELEASE( pDepthStencil );
	SAFE_RELEASE( pRenderTargetView );

	SAFE_RELEASE( pSwapChain );
	SAFE_RELEASE( pDevice );
	SAFE_RELEASE( pDeviceContext );
}

あとがき

浦島太郎 大地(D3D11)に立つ!

いかがだったでしょうか。 D3D9時代からすると知らないことだらけです。
ほとんどのものはD3D10から実装されていたようですが、浦島太郎には知ったこっちゃないですね・・。
まぁとりあえず、見返してみるとたいしたことないような気もします。
これを書いている私も最初は「はぁぁぁあああ!?」とか思っていましたが、 解説を書いてみて、
たいしたことないかもと思い始めました。 しかしまぁトータルの手数とわかりにくさはD3D9よりも上だと
思いますね。 ここから3Dプログラミングを始める人たちは苦労されるのではないでしょうか・・。

次回は描画系をやろうと思います。 これもまた、D3D9と違って見慣れないステージやらシェーダを
書かないとまず絵が出ないという めんどくさそうなもの盛りだくさんですね。

サンプルのダウンロード