DirectMusicのキャッシュな話お命頂戴仕る>MS
Write: 01/10/08 UpDate:--/--/--

時間がある今のうちにゲーム用のライブラリをバージョンアップしようとぴこぴこキーボードを叩いてます。
少しずつライブラリを育てるのは楽しいですね〜
今使ってるのはとびちよん用に急造した奴なんで、結構妖しいコードが混ざってます。
昨日まで3D周りをいじってたんですがビュー行列の作成で大ボケをかましてました。
Y軸周り以外の回転がまともに動作してね〜、ぐすん。
ってな感じでやってるんですが、今回の目玉はライブラリ全体をアーカイブファイルからの読み込みに対応させること。
アーカイバはlhaをありがたく流用させてもらってさくさく作業してたんですが、 サウンド周りの挙動がおかしくなってしまいました。
DirectMusic使ってるんですが、データをファイルから読み込むLoadObjectFromFileを使ったときには大丈夫なのに、
メモリから読み込むGetObjectを使うとうまく動きません(アーカイブされたファイルはメモリ上に展開される)。
なぜか複数のサウンドを再生しようとすると、前に再生したサウンドが後で再生しようとしているサウンドに影響をあたえるんですね。
たとえばWaveを再生した後にMIDIを再生しようとすると、MIDIの代わりに前に再生したWaveが再生されるといった次第。
半日原因を調べてやっとこさ理由が分かりました(多分)。
どんなコードを書いていたかというと

char *pData = new char[SIZE_A];
データ読み込み
再生
delete [] pData;
pData = new char[SIZE_B];
データ読み込み
再生
delete [] pData;

とまぁこんな感じです。
それぞれ単体ではちゃんと鳴るのに、続けて鳴らそうとすると前のサウンドがでしゃばってくる。
で、それじゃあ前のデータを跡形もなく消し去ってくれようと

char *pData = new char[SIZE_A];
データ読み込み
再生
for( int i = 0; i < SIZE_A; i++ ) pData[i] = 0;
delete [] pData;
pData = NULL; // 念のため
pData = new char[SIZE_B];
データ読み込み
再生
delete [] pData;

こんな感じでゼロクリアしても変化ありません。
元のデータがこの世に存在してないのに、そのデータが鳴るというのは恐怖です。
ところが、

char *pData_A = new char[SIZE_A];
データ読み込み
再生
char *pData_B = new char[SIZE_B];
データ読み込み
再生
delete [] pData_B;
delete [] pData_A;

としてやるとちゃんと動くんですね〜
大体読めてきました。
先ほどデータを消したのに鳴ったということは、どこかにキャッシュされてるからでしょう。
そもそも

char *pData = new char[SIZE_A];
データ読み込み
delete [] pData;
再生

としてもちゃんとサウンドが鳴るということは、どっかにメモリが確保されているはずです。
で、そのメモリをキャッシュするのはDirectMusic君しかいません。
確かに奴はキャッシュ機構を備えてます。
そのキャッシュの検索方法は『データの先頭アドレス』に違いありません。
ば、馬鹿だ・・・
つまり前のデータをdeleteすると次のデータは前のデータの先頭位置からnewするんで(もちろん次のデータを読み込む前に別のメモリ確保が行われていなければの話)
せっかく読み込んだ次のデータは実際には読み込まれずにキャッシュから全然違う奴が読み込まれているんでしょう。
静的な data[MAX_SIZE] みたいな配列を読み込み用のバッファに使った日には、永遠に新しいサウンドを鳴らすことができないと思われます。
一つの手としてEnableCache( GUID_DirectMusicAllTypes, FALSE ); でキャッシュを切ってしまうというのも ありですが、
こちとらシューティングで爆発音やら発射音やら同じ音をバンバン鳴らしてるんで、
毎回新しく読み込みにいってパフォーマンスが落ちるのも面白くありません。
とすると別のサウンドを鳴らすときは確実に先頭アドレスの異なるデータを食わせなければいけません。
ぐぁ〜、めんどくせー。
てなわけでMSさん反省して修正してください。
いや、ほんとマジで。

 

DirectX8のテンプレートな話大した事ではないですが
Write: 01/10/06 UpDate:--/--/--

いままで大変お世話になったLightWave5.6->lws2xでのXファイル作成からLightWave6.5->DirecX Exportに乗り換えの最中です。
lws2xはすんげーソフトですがバージョンアップが止まってしまっているので、そろそろかな〜といった所。
で、ちょこちょこいじって見たところテキスト形式でExportすると動くのにバイナリ形式だと動かないんで困ってしまったわけです。
調べてみるとなにやらX8バイナリ形式で出力するとだめで、X7バイナリ形式ならOKな模様。
じゃあ何が違うんですかいなと比べてみるとテンプレートが増えているようです。
こちらを参照
てなわけで
pxofapi->RegisterTemplates( (LPVOID)D3DRM_XTEMPLATES, D3DRM_XTEMPLATE_BYTES );
だけだとX7まで(RM?)のテンプレートしか登録されないので

char *szTemplates = "xof 0303txt 0032\
template XSkinMeshHeader {\
<3cf169ce-ff7c-44ab-93c0-f78f62d172e2>\
WORD nMaxSkinWeightsPerVertex;\
WORD nMaxSkinWeightsPerFace;\
WORD nBones;\
}\
template VertexDuplicationIndices {\
<b8d65549-d7c9-4995-89cf-53a9a8b031e3>\
DWORD nIndices;\
DWORD nOriginalVertices;\
array DWORD indices[nIndices];\
}\
template SkinWeights {\
<6f0d123b-bad2-4167-a0d0-80224f25fabb>\
STRING transformNodeName;\
DWORD nWeights;\
array DWORD vertexIndices[nWeights];\
array FLOAT weights[nWeights];\
Matrix4x4 matrixOffset;\
}";
pxofapi->RegisterTemplates(szTemplates, strlen(szTemplates));


こんな感じでX8で増えた3つのテンプレートを登録してやるとOKです。

 

ブラーな話目にもとまらぬ早業〜
Write: 01/08/29 UpDate:01/09/13

えー、ブラーです。ぶら〜、ぶら〜、ぶらぶら、んじゃそういうことで、以上。
いや、話が終ってしまっちゃよろしくありません。
さてさて、ここの所DirectX8上で2Dエフェクトをどうやってやろうかと考えてます。
確かに便利なX8君なのではありますが、いかんせん3D寄り過ぎる設計思想がちょっといただけません。
どうやらサーフェースを直接操作するということにあまり向いていないようです。
Lock関係のコマンドやCopyRects関係のコマンドが妙に遅くて困りものです。
レンダリングパイプラインのフラッシュとVRAM->SYSTEMMEMORY間での転送速度が足を引っ張っているようですが、
リアルタイムにピクセル単位の操作をするのがかなり
困難です。
まぁ、そこを何とかしようと実験中な訳ですが、いまいちうまくいかないのが実情です。
てなわけで、その過程でブラーを作ったので紹介しとこうというのが今回の主旨。
世間ではモーションブラーと残像とごっちゃになってるきらいがあります。
そこらへんの説明はこちらに譲ります。
適当を旨とする、当サイトでは細かいことは気にしません。
なんとなく移動物体の後ろに移動軌跡が残っていればOKということにしときます。
しかし、本物のモーションブラーというのは計算コストが非常にかかります。
LightWaveで試してみると少しずつ時間を進めて複数回レンダリングしている様が良く分かるのですが、
そりゃ時間がかかって仕方ないわけです。
3DCGの世界ではAfterEffectなんかを使って後付けで2D的にブラー効果をつけることもやってます。
そうでもしないと延々とレンダリングしつづけて終らないシーンがあるからなんですね。
そこで、いかに手抜きしてブラー効果を出せばいいのかということなんですが、種を明かせば簡単。
『前フレームで描画した画像を今回描画したフレームに半透明で重ねる』だけです。
楽勝ですね。
例えばフレームを半透明で重ねていくと時間がたつにつれそのフレームはどんどん色が薄くなっていきます。
これがブラーに見えるわけです。
で、実際にはどうやるかというと、通常Clear命令をD3DCLEAR_TARGET | D3DCLEAR_ZBUFFERフラグを指定して
サーフェースをきれいにしてから描画するところをD3DCLEAR_ZBUFFERだけで実行します。
次に背景色のポリゴンを半透明で画面いっぱいに描画します。
あとはいつも通りポリゴンオブジェクトなり何なり描いてあげるだけです。
つまり半透明ポリゴンで前フレームを上書きすることで薄くしてから、今回のフレームを上書きするわけです。
Zバッファーだけはクリアしておかないと今回のフレームがちゃんと描画できないのでClearだけは気をつけなければなりません。
非常に低コストな処理なんで、余裕でリアルタイムに動かすことができます。
ではどきどきの、サンプルソースです。以下すべて32Bitカラーモード用プログラムです。
それっぽく見えるんじゃないでしょうか。
しかし、この方法は重大な欠点があります。
一目瞭然なので、サンプルソース(NG版)をご覧下さい。
先ほどの操作は言い換えると、半透明の前フレームを最も奥(Z=1.0)の位置に描画して、その手前に今回のフレームの
オブジェクトを描画していることになります。
そうすると今回の描画で複数のオブジェクトがあって、それらが重なっているとき、
後ろ側にあるオブジェクトが手前側のオブジェクトのブラーとして見えるはずの部分を上書きして消してしまうんですね。
つまり背景などで画面いっぱいにオブジェクトが描画されるとまったくブラーの効果が得られないことになります。
これは、ちょっと困ります。
宇宙空間に浮かぶ戦闘機とかならともかく、普通背景に多数のオブジェクトが存在します。
そんな場合でも野村XX式似非ブラーなら大丈夫!!
この通り、頑固な汚れもあっという間に落ちてしまいます(違う)
さっきの問題は前フレームが最も奥(Z=1.0)の位置に描画されているのが原因だったので、
最も手前(Z=0.0)の位置に描画することができればよいのです。
これは前フレームの画像をテクスチャとして手前にポリゴン描画すれば可能です。
しかし、サーフェースをテクスチャにコピーするにはLockしてmemcpyするとかCopyRectsをつかうとかしなければならず、
これが前述の通りえらい時間を食います。
ここで一工夫。
実はIDirect3DTexture8は内部的にIDirect3DSurface8を持っているらしくCreateTextureを使ってテクスチャを作るときに
D3DUSAGE_RENDERTARGETフラグを指定しておくと、レンダリングサーフェースとして使うことができます。
さらに複数のテクスチャをレンダリングターゲットとしてSetRenderTargetで切り替えて使うことができます。
そこで、2枚のテクスチャを用意して交互にレンダリングサーフェースとして使用します。
そうすることで一つ前のフレームがどちらか一方のテクスチャ上に常に残ることになります。
テクスチャAに現在のフレームを描画->その上にテクスチャBの内容を半透明で上書き->レンダリングターゲットをプライマリサーフェースに変更->
テクスチャAの内容をプライマリサーフェースに描画->テクスチャBに現在のフレームを描画->その上にテクスチャAの内容を半透明で上書き->
レンダリングターゲットをプライマリサーフェースに変更->テクスチャBの内容をプライマリサーフェースに描画->...(繰り返し)
こんな感じですね。
ちょっとトリッキーですがたいしたことはしていません。
まぁ、プライマリサーフェースと同じサイズのテクスチャが2枚必要になるので結構VRAMを食いますが…。
概算してみると4バイト(32bitcolor) x 640ドット x 480ドット x 2枚 = 2.5Mバイト位になります。
さらに色々ワークエリアを取られるとしても、せいぜい4Mもあれば十分じゃないでしょうか。
一昔前なら論外なこのサイズですが、すでに3世代前になるTNT2あたりのグラフィックカードですら16MのVRAMをつんでることを
考えれば問題ナッシング。
てなわけで、サンプルソースです。
さっきのサンプルに比べ少し見え方が異なります。
前フレームを今フレームの上に半透明描画しているので、オブジェクトの端の方が透けて見えます。
気になると言えば気にになりますが、どちらかと言えばこちらの方が正しい見え方ですし
適当に流してしまうのが大人の態度ってもんでしょう。

いつも通りですが、エラーチェックは最低限であり、
このサンプルの使用によるいかなる不幸も当方では責任を負いかねます(^^;
今回のはいつものに輪をかけて適当です。

01/09/13 追記 間抜けな記述に気がついたので修正

 

DirectX8のアニメーションな話(補)MSさんは相変わらずです
Write: 01/07/01 UpDate:01/07/02

うだうだやっていてオブジェクト全体に対する操作と衝突判定用のバウンディングボックス生成部分が出来ました。
あいかわらず親切なDirectX8君にはD3DXComputeBoundingSphere,D3DXComputeBoundingBoxなる関数があって
一発でバウンディングボックスを生成できます。
で、問題は当たり判定を球でやるか直方体でやるかですが、こっちのニーズとしては直方体の方が精度が良いんですが、
なんせ直方体同士の判定なんてめんどくさいものやってられません。
ちょっと調べてみましたが、解答ずばりというものも見当たりませんでした。自分の頭で考えるのは論外です(笑)
そんなわけで球の半径を小さめにして利用することに決定。計算量的にもお得ですしね。
DirectX9で直方体同士の衝突判定関数が出来ることを祈ってます。
そこらへんは余談で、今回はプロファイラの挙動について覚書を書いておこうというのが本題です。
プロファイラ使ってプログラムの解析するのは常套手段ですが、VC++の奴はボケてます。
[プロジェクト]->[設定]->[リンク]の中の[プロファイルを行う]チェックボックスにうまくチェックを入れられないという
訳のわからんバグがあります。回避策は[プロジェクトオプションに]手作業で/profileを書いてあげること。
あとは[ビルド]->[プロファイル]でプロファイリングが出来ます。
あと、実行時に使用するデータファイルなんかをプログラム中で相対パスで書いてあるとヤバイです。
通常プログラムをVC++上で実行する場合プロジェクトのあるディレクトリがルートになってますが、
プロファイル時には実行ファイルのある場所(Debugディレクトリ等)がルートになっているようです。
うまく動かないんで悩んでしまいました、むぅ。

01/07/02追記 >これって「プロジェクト」->「設定」の部分でルートを指定できるはずっすよ。
というナイス突込みをいただきました。プロファイル時にはちゃんと指定しましょうね、うふ。

 

DirectX8のアニメーションな話もぞもぞ
Write: 01/06/30 UpDate:01/10/06

世の中、しちゃいけない発言というものがいくつかあります。
先日某社の飲み会でうっかりその会社の製品と思い込んで別のところの製品を誉めてしまいました。
いや場が凍ること凍ること、つらかったですね。
この手のことを年に何回かやって、さらに年に何回か思い出しては夜中に布団の中でもだえています。
さてDirectXに関して言えば、このしちゃいけない質問のひとつに
『IMでアニメーションしたいんですけど』
って奴があります。
DirectXBBSなんかでしょっちゅう質問が出ては
『うぜぇ、ボケ、自分で何とかしろ(意訳)』
ってな回答が繰り広げられています。
そうはいっても、分からんもんは分からんし、めんどくさいもんはめんどくさい。
ご多分に漏れず、私も自分でやるのがめんどくさかったんで、広大なネットに適当なソースを探す旅に出たんですよ。
どうやらDirectX7以前で使えそうなのではここのソースが最高(モーションブレンドまでOK)ではないかと思うんですが、
DirectX8用が見当たらないんですね。
仕方ないんでMSのSkinnedMeshとかいうサンプルを読んでみたものの、複雑怪奇、魑魅魍魎。
大体ワンスキンなモデルを使う気も無いし、作る技量もありません。
結局、親子付けモーションの再生ルーチンを書くことにしました。
まず、Xファイルについて適当な解析。

モデル
Frameってのが一番大きな入れ物で、その中にTransformMatrix,Mesh,MeshMaterialList,MeshNormalsなんかが
入っている。で、Frameの中にFrameを入れ子にできて、これが親子関係を表している。
TransformMatrixはアニメーションの無いモデルのときは各フレームの位置関係を指定するために使用。
アニメーションするときはPosition行列があるので使う必要がないっぽい。

アニメ
AnimationSetってのが一番大きな入れ物で、その中にAnimationが入っている。
AnimationSetを複数持てるのかは不明だが、ニーズはありそうなんできっとできるんでしょう。
今回は1個のAnimationSetだけを考える。
Animationは一つのフレームに対して一つずつあるようで、その中にフレーム名とAnimationKeyが入っている。
AnimationKeyは頭2つのデータがキーの種類(回転、移動、スケール、行列キー(前3つの行列の合成))と
キーの数を表している。

ざっと手持ちのXファイルを眺めたところ、こんな感じではないかと思われます。
アニメーションをループをさせる時には最終キーから最初のキーにつなげなきゃならないんですが、
LightWave->lws2xでXファイルを作ると最終キーと最初のキーの間に1フレーム存在するようです。
いや、そんな気がするだけで単に私のモーションの作り方が悪いだけかもしれません。
他の3Dツールでは違うかもしれないですし・・・。
ここらへんを踏まえてコーディング開始です。
最低単位がメッシュなのでこいつをクラスにします。次にメッシュを幾つか集めたものがフレームなのでこいつもクラスにします。
フレームは親子付けされているので親フレームへのポインタを保持します。
最後にフレームを幾つか集めたものがアニメーションオブジェクトになるのでこいつもクラスにしましょう。
使えそうなソースはSkinnedMeshサンプルからかっぱらってきて使用します。
Xファイルによってメッシュクラスやらフレームクラスやらがいくつ必要になるか分からないので、
STLを使ってお手軽に動的配列を実装します。いや、いい時代だ。
こんな感じであっという間に出来てしまいます。

作ってるときに引っかかったことは GetNameを使ってフレームやメッシュの名前を取得するときに
一度GetName( NULL,&dwNameLen )を読んであげないと駄目らしいこと。
あと、アニメーションを読み込むときにそのアニメーションが適用されるフレーム名が
Animation Animation {
 {FrameName}
 AnimationKey {
 }
}
のようにキーの前に書いてある場合(LightWave->lws2x)と

Animation Animation {
 AnimationKey {
 }
 {FrameName}
}
のようにキーの後に書いてある場合(SkinnedMeshサンプルで使っているTiny.x)があるみたいです。
必ずキーの前に書いてあれば楽ちんなんですが、そうも言ってられないようなので、
一度アニメーションを仮のフレームに読み込んでおいてから改めてそれぞれのフレームにコピーすることにしました。
ちょっとださいなぁ。
アニメーションの拡大、回転、移動の各行列の掛け順は拡大はともかく回転、移動はこの順で。
もちろん親子付けされているので、子供のフレームには親のフレームの行列も掛けてあげる必要があります。

以上、一応動くらしいのでサンプルソース。外部依存ファイルに対するパスは自分で直してください。
昔作ったロボットに適当なテクスチャ張っときました。
テキスト形式、バイナリ形式どちらのXファイルでも読めるはずです。
LightWave->lws2x以外の方法で作ったXファイルの挙動は謎です。
暇な方は動作報告してくれるとうれしかったり。
叩き台のソースなのでオブジェクト全体に対する操作(移動や回転、マテリアル変更)なんかは
全然実装してません。まぁ、数日ででっち上げるソースなんてそんなもんです。

くどいようですが、エラーチェックは最低限であり、
このサンプルの使用によるいかなる不幸も当方では責任を負いかねます(^^;

01/07/05追記 アニメーションしないオブジェクトの表示でTransformMatrixの扱いをミスっていました。
親フレームのマトリクスかけ忘れてました、馬鹿ですね。ついでに変数名のミスも直しときました。

01/07/22追記 このプログラムはメッシュがフレームに乗っかってることが前提になっています。
よってLightWaveのlwoファイルに対してLws2Xをかけて出来るフレームの無いメッシュだけのXファイルを読み込むことは出来ません。
一度lwsでセーブしてからLws2Xして下さい。まぁ、暇なときに直しておきます。

01/10/06追記 気が向いたので01/07/22追記分の修正とDirectX8用のテンプレートが含まれたXファイルに対応しときました。
これでメッシュ単体のXファイルも読み込めるはずです。直す気ありませんがTransformMatrixは拡大回転、移動行列にばらして、
もしアニメーションオブジェクトに対応するキーがない場合はかけてやらなきゃ駄目ですね。

 


戻る