魔界の前に


しりあらいぜーしょん

どうやらBoostのserialization以外にもC++でシリアライズ目的のライブラリがある模様。

その名も s11n.net という。
こちらも変わらずtemplateを駆使しているみたい。
そして日本語の解説サイトも見あたらないな。

これもちょっと覗いてみるか。Boostに比べれば可愛いくらいのtemplate使用な感じだったし。


いざ魔界へ


Boost魔界へ

以前のリフレクションシステムは、会社でのソースを見た後だったので、
恐ろしく構造が似ていました。いやー、見てもらったらパクリx2 言われてしまった。
実装はtemplateメインにしたのでちょっとは違うのですが。
使うユーザー側にとっては定義の仕方一緒だし…。

そんな中最近のBoostにはserializationが入ったらしいです。
これを使えばシリアライズできるじゃん!ということです。
ただシリアライズはできるもののフィールドに対するメタ情報を扱うわけではないです。

そういう背景もあって自分でなんとか作ってやろうじゃないかと勉強中です。

そのためにはBoostの該当コード理解しないとと思ってますが、魔界です…

正直何やってるかわからない。template地獄。実行させながら追ってみるもののやはりさっぱり。

出直してきます・・・・・ orz

とりあえず自分に足りない要素として、

  • メタプログラミングに対する理解が不十分
  • templateによるジェネリックプログラミングの理解

とくに後者は多段のtemplateが使えるようにならなきゃ話にならないですね。


DirectX10 (D3D10)


Tutorial04

今回はTutorial04を見てました。このサンプルは立方体(キューブ)の表示サンプルでした。
前回のTutorial02の部分に比べて増えたところと言えば、IndexBufferを作っているところです。
ほかには、シェーダーで透視変換させているところ。大きく変わった部分はないです。

IndexBufferもVertexBufferと同じくBufferDescriptionとSubresourceDescriptionの2つを設定して、デバイスのメソッドで作成するだけです。

変更になってる部分はデータをどう使うか指示するところ、
「D3D10_BIND_INDEX_BUFFER」「D3D10_BIND_VERTEX_BUFFER」の違いくらいです

描画する前には以下のように

各バッファをデバイスに設定し、プリミティブタイプを指定しておきます。この辺が若干以前と変わっています。
前はDrawPrimitive系でプリミティブタイプを指定していました。

また、インデックス用のフォーマット指定(D3DFMT_INDEX16/32)がありましたが、
どうやら今回からは統一されたようです。

気になったのはこのサンプル、インデックスに32ビット指定してます。
従来は16ビット(WORD型)を基本的に使っていたのに、このように変更となるとデバイスの最適化もこれからは32ビットインデックスが主流となっていくんですかねぇ…。(※ 現状のデバイス(当時)は16ビットインデックスに最適化されています)

シェーダーに対して、各マトリックスをセットして、パスを回しています。
見てわかるように描画のDrawIndexedの引数が減っています。すっきりしましたね〜。あとDX9世代でBegin/BeginPassで行っていたパスを回す処理が変更になっています。テクニックのDescから値を取得して、回数と使用指示を設定しているようです。

この辺は使う側としてはあまり変更がないといってもいいでしょう。
あまり大きな変更ではないように思います。

気になったのは今までシェーダー定数を書き換えたときには、CommitChangesなどのメソッドを呼んで確定させていた気がしますが、それが消えています。

シェーダーコード

HLSLの中身も若干変わっています。

今までfloat4x4とかやっていた部分がmatrixになっています!

Passの部分も

となっており、ぱっとみてわかる程度の変更です。

ここにジオメトリシェーダーの記述があって、そのうちいじってみたいなとわくわくです。


DirectX10 (D3D10)


Tutorial02解説

今回は、DX10での三角形(ポリゴン)描画までのサンプルを読み解いてみました。
前回までの初期化にプラスして、頂点データの作成、シェーダーの作成と要素が詰まってます。

シェーダーの作成は以下のようになってました。若干自分なりのコメントをつけてみました。

間違っている場合もあると思いますが、気づいた人はコメントでご指摘ください。(^^;

[code lang=”cpp”]
// Create the effect
// このフラグは新しいHLSLのセマンティクスを有効にするため?
// 従来のHLSLコードを通過させないためのフラグである模様
DWORD dwShaderFlags = D3D10_SHADER_ENABLE_STRICTNESS;
#if defined( DEBUG ) || defined( _DEBUG )
dwShaderFlags |= D3D10_SHADER_DEBUG;
#endif
hr = D3DX10CreateEffectFromFile( L"Tutorial02.fx", NULL, NULL, "fx_4_0", dwShaderFlags, 0,
g_pd3dDevice, NULL, NULL, &g_pEffect, NULL, NULL );
[/code]

どうやらDX9時代と変更がない感じです。プロファイルが fx_4_0になったくらいでしょうか。
シェーダーのフラグでDX10用のHLSLを要求しているように見えます。

fxファイルを覗くとわかるのですが、technique10や今までに見慣れないセマンティクスが使われています。
これらを要求するためのフラグみたいです。

[code lang=”cpp”]
// Define the input layout
D3D10_INPUT_ELEMENT_DESC layout[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },
};
UINT numElements = sizeof(layout)/sizeof(layout[0]);
// Create the input layout
D3D10_PASS_DESC PassDesc;
g_pTechnique->GetPassByIndex( 0 )->GetDesc( &PassDesc );
// 上記で設定したinput elementと、シェーダーのバイトコードからinputレイアウトの作成.
hr = g_pd3dDevice->CreateInputLayout( layout, numElements, PassDesc.pIAInputSignature, PassDesc.IAInputSignatureSize, &g_pVertexLayout );
if( FAILED( hr ) ) return hr;
// Set the input layout
g_pd3dDevice->IASetInputLayout( g_pVertexLayout );
// Create vertex buffer
SimpleVertex vertices[] =
{
D3DXVECTOR3( 0.0f, 0.8f, 0.5f ),
D3DXVECTOR3( 0.5f, -0.5f, 0.5f ),
D3DXVECTOR3( -0.5f, -0.5f, 0.5f ),
};
D3D10_BUFFER_DESC bd;
bd.Usage = D3D10_USAGE_DEFAULT;
bd.ByteWidth = sizeof( SimpleVertex ) * 3;
bd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
bd.CPUAccessFlags = 0;
bd.MiscFlags = 0;
D3D10_SUBRESOURCE_DATA InitData;
InitData.pSysMem = vertices;
hr = g_pd3dDevice->CreateBuffer( &bd, &InitData, &g_pVertexBuffer );
if( FAILED( hr ) ) return hr;
// Set vertex buffer
UINT stride = sizeof( SimpleVertex );
UINT offset = 0;
g_pd3dDevice->IASetVertexBuffers( 0, 1, &g_pVertexBuffer, &stride, &offset );
// Set primitive topology
g_pd3dDevice->IASetPrimitiveTopology( D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST );
[/code]

シェーダーのバイトコードと、頂点宣言二つからinputlayoutを作成します。
そしてこのレイアウトはIA(InputAssembler)にセット。
この次に、ようやく頂点バッファの作成に取りかかります。

DX9時代とかなり変更になっているのがこの部分。
BufferDescriptionとSubresourceDescriptionの二つから頂点バッファを作成しています。

各DESCに値をセットして、初期化時にデータを転送してしまうっぽいです。

このD3D10_USAGE_DEFAULTフラグでは、GPUからのアクセスはできて、CPUからのアクセスは禁止という意味だそうです。
DX10からはすべてのバッファがMap(従来のLock)ができるわけではなくなりました。

つまり上記の例ではMapすら禁止ということで、初期化時にシステムメモリ上のデータを設定することで、初回1度だけデータ転送しているということなんでしょう。

SubResourceとはなんぞや?

よくわからなかったので調べてみました。

上記でもSubResourceDescription を設定してます。
このdescではバッファの初期データを与えるためにセットしています。

SubresourceDescriptionは実際のリソースデータを示し、データのサイズとレイアウトに関する情報を保持します。

どこかにも記述があったのですが、Subresourceっていわゆる実データなんでしょうか。
Surfaceに相当とか書いてあった気が…。


DirectX10 (D3D10)


基本からさわってみよう。D3D10

まずは2007Apr版のDXSDKをダウンロードして、サンプルを実行してみてました。
DirectX10がどう動いてくれるか、わくわくしながらサンプルを実行したのですが、

・・・意外と安定動作しない(><

ものによっては放置しておくだけで、GPUが停止し復帰しましたとのメッセージが出まくるし、プログラムの終了時にはエラーメッセージのダイアログが出るし。まだまだきちんと整備されていないということでしょうか。

あとこれは8600GTという点だからかもしれませんが、
ジオメトリシェーダー使ったサンプルが遅い!

極端に速度低下してました。CubeMapGSのサンプルなんか車モデルにしたら 5fpsくらいになってましたし。

それでも基本的にDX9と切り替えられるタイプのサンプルでは、
DX10のほうが高速動作している感じでした。FPSの向上もみられました。

Tutorial01解説もどき

自分のメモも含めて少し解説。DX9の頃と違って、かなり初期化周りも変更になっている。
Tutorial01.cppのソースを抜き出して以下に書いてみます。

※値のセット部分は省いてあるのでコピペしても動きません。

[code lang=”cpp”]
DXGI_SWAP_CHAIN_DESC sd; ZeroMemory( &sd, sizeof(sd) ); // この後、値を詰めていく
for( UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++ ) {
g_driverType = driverTypes[driverTypeIndex]; // デバイスとスワップチェインを作成.
hr = D3D10CreateDeviceAndSwapChain( NULL, g_driverType, NULL, createDeviceFlags, D3D10_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice );
if( SUCCEEDED( hr ) ) break;
}

// 次にRenderTargetViewを作成。リソースの種類は2DTextureってことですね
ID3D10Texture2D *pBackBuffer;
hr = g_pSwapChain->GetBuffer( 0, __uuidof( ID3D10Texture2D ), (LPVOID*)&pBackBuffer );
if( FAILED(hr) ) return hr;
hr = g_pd3dDevice->CreateRenderTargetView( pBackBuffer, NULL, &g_pRenderTargetView );
pBackBuffer->Release();
if( FAILED(hr) ) return hr;

// 作成したビューをレンダーターゲットとしてセットする
g_pd3dDevice->OMSetRenderTargets( 1, &g_pRenderTargetView, NULL );[/code]

どうやら、スワップチェインを作成を作成した後、そのスワップチェインの中にあるバッファデータを取得。

そのデータを2Dのテクスチャデータとしてバインドし直してレンダーターゲット設定している、といったところでしょうか。

Render()処理

[code lang=”cpp”]
float ClearColor[4] = { 0.0f, 0.125f, 0.3f, 1.0f }; //red,green,blue,alpha
g_pd3dDevice->ClearRenderTargetView( g_pRenderTargetView, ClearColor );
g_pSwapChain->Present( 0, 0 );
[/code]

これをみても明らかなように、レンダーターゲットに対してクリアしてます。
今まではPresentもpd3ddeviceでしたが、今回からはPresentはスワップチェインに対して操作しています。
より明確に分かれた感じがしますね。とりあえずTutorial01では、このくらいしか従来と大きく変わった部分は無さそうです。

あ、フォーマットの指定はかなり変わってました。よりデータ型を意識するものになっていました。


PC新調しました


新しいマシンを組みました

価格改定を待てず、Core2Duoのマシンを組み立てました。
どこへ行ってもE6400が無かったため、E6300で組み立て。

元々、新PCは二の次で、やりたかったことのメインは、
『GeForce8600GTを買うこと!そして、VistaでDirectX10!』
が本当の目的です。

新PCを組むなら P965のチップセットで〜も条件だったため、適当にパーツ選んできました。

CPU Core2Duo E6300
メモリ 2GB (DDR2 800)
M/B Gigabyte 965P-DS3P
VGA Albatron 8600GT (256MB)

こんな感じです。
これからVistaなど色々と入れてゴールデンウィークにはD3D10いじってみようと計画中です。


メモリリーク追跡


DirectXのメモリリーク

DirectXを使ったプログラムを作っていて、VRAMに確保したデータを解放せずに終了させてしまうこと、よくあることです。
特に適当に作ったプログラムや、実験を重ねて複雑になってしまったものによくあります。

そういうとき、DirectXのデバッグランタイムを使用しているのであれば、
以下のようなメッセージがプログラム終了時に表示されます

こういうときに、実はデバッグランタイムを使用していればどこのタイミングで確保したモノなのかがチェックできるらしいです。

上記の、AllocIDの部分でデバッガのブレークを貼ることが出来ますが、
手元でチェックしてみたところ、一つの目安にしかならない感じがします。

定番なのかもしれませんが、このIDの数が大きい方から処理していった方が良さそうです。
たとえば、上記AllocID=1ですと、CreateDeviceでデバイス作成したところで引っかかります。

考察としては、COMが参照カウンタ方式なため、参照された瞬間にカウンタがインクリメントされ、のちのちまで残ってしまったために、上記で表示されているのだと考えられます。

よって、数が大きいモノ、最後に参照カウンタをいじって生き残っているモノからデバッグ(解放処理?)を入れていくのは正しいのではないかと思います。


SkinnedMesh


スキンメッシュの表示

前回からチャレンジしていて、助言を元に色々と試行錯誤してました。

で、最終的にはコンバータから出力した頂点データが 固定機能のインデックス付き頂点データで、
表示させようとしている側は、シェーダーによる表示方法を選択。という不一致が原因だった模様。

コンバータから出力するデータも若干おかしいと思える箇所を SkinnedMeshサンプルを参考にして修正。
表示側も固定機能による表示方法(D3DINDEXED)を適用することでやっと表示できました。

次はシェーダーによる表示方法にチャレンジしたいと思います。ConvertToIndexedBlendedMeshによる変換作業が具体的に何をやっているかが謎ですね。固定機能にしろシェーダーにしろ特に中身のデータが変わるとは思えないのですが。

追加

シェーダー定数の個数制限のためにパレット制限がかかるということに気付きました。

基本的に26個のマトリックスしかシェーダーに転送できず、
そのためにConvertToIndexedBlendedMeshはデータを変換して、
ボーンコンビネーションを複数回に分割するということをしている模様。

tiny.xの例で言えば、固定機能時はパレット36個は1回のdrawコールでOKだったものが、シェーダーを使うと2回の描画に分かれる。コンビネーション数が2回になってました。

この分け方のアルゴリズムとかは興味ありつつも、調査はしないことにします。
とりあえずもっと先へ進みたいので。

その前にコンバーターのソースを整形しないと不味そうですが…。リークもあるし。


Windowsキーの復活


英語キーボードでもWinキー使いたい!

家でも会社でも、英語キーボードの101配列なのですが、
Vistaとか使い始めると、フリップ3Dとか目玉機能があるのに使えないという状況に。

そして、噂で聞いていたWindowsキーを別のキーに代替させるという方法をとってみることにしました。

参照先 :http://journal.mycom.co.jp/column/winxp/181/index.html

手順は以下の通り

  1. regeditを起動
  2. HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Keyboard Layout を開く
  3. この場所に新規のキーを追加して、キーの代替を設定します。
  4. キー名は”Scancode Map”, キータイプは”バイナリ値”.

うちでは、以下のようにキーの中身を設定しました。

代表的なスキャンコードを以下に記述しておきます。

キー名
Alt(右) 0xE038
Alt(左) 0x38
Caps Lock 0x3A
Ctrl(右) 0xE01D
Ctrl(左) 0x1D
Esc 0x01
F11〜F12 0x57〜0x58
F1〜F10 0x3B〜0x44
Num Lock 0x45
Scroll Lock 0x46
Windows(右) 0xE05C
Windows(左) 0xE05B
ひらがな 0x70
スペース 0x39
半角/全角 0x29
変換 0x79
無変換 0x7B

設定時のポイントは、レジストリに設定するときに2バイトのコードは逆転して記述すること。

(0xE038 → 38 E0 と設定)


ライブラリ進捗


ようやくモデル出ました

ようやく独自モデルファイルを読み込んで、表示することが出来ました。

現在では単なるポリゴンメッシュのみで、スキニング適用したものや親子構造を持っているモノについては正常に表示されません。

次のステップとしては親子構造、そしてスキニングメッシュを表示ですかね。

さらにその後、テクスチャを貼るところまでやらないとモデル読み込み完成とは言えないですね。

さらにこのモデルライブラリで試験的に表示できるクラス含めていますが、

表示オブジェクト事にバッファ作っちゃうので、使用には耐えません。
実際に使うときには一つのバッファを共用するようにしなくては。

もっともそれはさらに上位ライブラリがやるべきことなんですがね。