【C++】DirectXにおける3Dアニメーションプログラミングを分析する~その6

過去の勉強の振り返り、ということで、以前、もう3ヶ月以上前の記事になるけれども、C++でMMDのビューワのソースコードを解析していた。

過去の記事は以下となる。

【C++】DirectXにおける3Dアニメーションプログラミングを分析する~その5
【C++】DirectXにおける3Dアニメーションプログラミングを分析する~その4
【C++】DirectXにおける3Dアニメーションプログラミングを分析する~その3
【C++】DirectXにおける3Dアニメーションプログラミングを分析する~その2
【C++】DirectXにおける3Dアニメーションプログラミングを分析する~その1

ソースコードの元ネタは、下記となる。
うp主のmはげさんのサイト
うp主のmはげさんはニコニコ動画でひじょうにわかりやすい解説も公開しておられる。
mはげ さんの公開マイリスト

さて、ソースコードの解析の方は、目標のFK実装やIK実装の箇所の解読まで終わって、満足して終わってしまった。

けれども、このソースコードにはメモリリークという大きな問題が残っていた。

一般公開するようなプログラムを作るわけじゃないから、ま、いいか、と放置したままだったのだ。

先日、このサイトに訪問して下さった方からメモリリークの件で質問をいただいたんだけれども、当然答えられなかった、わはは。

大変、お恥ずかしい。

そこで、リベンジということで、今回、このメモリリークの問題に取り組んでみた。

【注意】使用したソースコードはビルドや実行するに当りカスタマイズを施してあります。なので、メモリリークの原因や対処方法が当てはまるかどうかはわかりません。そもそも他の環境ではメモリリークが起きない場合もあるかもしれません。参考程度に読んでいただければ幸いです。

さて、まずはメモリリーク箇所の特定から。

これにはひじょうに泥臭いやり方を行った。

地道にソースコードから、全ての処理をコメントアウトし、少しずつコメントをはずしてやりながら、ビルド・実行を繰り返して、メモリリークを起こしている処理ブロックを特定するというもの。

・まずは空っぽの状態(ウィンドウだけが表示)→当然メモリリークは起きない。
・次に背景となる網の目の表示。→メモリリークは起きない。
・さらにモデルのメッシュを表示(アニメーションなし)→メモリリークは起きない。
・そして、アニメーションデータ(VMD)のロード→メモリリーク発生!

ちなみに、アニメーションデータを読込んでアプリケーション終了後にDELETEしただけで、アニメーション自体は適用していない。

どうやら読込んだアニメーションデータの解放がうまくできていないようだ。

ソースコードでいうと、VmdMotionController.cpp内でVMDファイルからキーフレーム情報を読込んでアニメーションデータをメモリ上に構築している箇所だ。

デストラクタでは、ちゃんとメモリ解放のコードが書いてある。

【コード①】

VmdMotionController::~VmdMotionController() {
	for (unsigned int i = 0; i < keyFrames.size(); ++i) {
		for (list<KeyFrame>::iterator ite = keyFrames[i].begin(); ite != keyFrames[i].end(); ++ite) {
			SAFE_DELETE((*ite).bezie_x);
			SAFE_DELETE((*ite).bezie_y);
			SAFE_DELETE((*ite).bezie_z);
			SAFE_DELETE((*ite).bezie_r);
		}
	}
}

結論から言うと、キーフレーム情報を表現する構造体 KeyFrame が定義されているんだけれども、この中のメンバの bezie_x, bezie_y, bezie_z, bezie_r がポインタになっている。

そして、VMDファイルの読込時にBezieクラスを new して突っ込んでいるんだけれども、この解放が【コード①】のやり方ではうまく解放されていないようだ。

【Bezieをnewして突っ込んでいるところ(VmdMotionController.cpp内)】

keyFrame.bezie_x = new Bezie(vmdMotions[i].interpolation[0], vmdMotions[i].interpolation[4], vmdMotions[i].interpolation[8], vmdMotions[i].interpolation[12]);
keyFrame.bezie_y = new Bezie(vmdMotions[i].interpolation[1], vmdMotions[i].interpolation[5], vmdMotions[i].interpolation[9], vmdMotions[i].interpolation[13]);
keyFrame.bezie_z = new Bezie(vmdMotions[i].interpolation[2], vmdMotions[i].interpolation[6], vmdMotions[i].interpolation[10], vmdMotions[i].interpolation[14]);
keyFrame.bezie_r = new Bezie(vmdMotions[i].interpolation[3], vmdMotions[i].interpolation[7], vmdMotions[i].interpolation[11], vmdMotions[i].interpolation[15]);

対処法としては、そもそもKeyFrameの構造体のメンバである bezie_x, bezie_y, bezie_z, bezie_r をポインタで持つ必要はないと判断し、値を保持するように構造体を修正した。

【KeyFrame構造体を定義しているところ(SkinMeshStruct.h内)】


// キーフレームアニメーションデータ
struct KeyFrame {
string boneName;
unsigned long frameNo;
D3DXVECTOR3 position;
D3DXQUATERNION rotation;
Bezie bezie_x;//ポインタから値に変更
Bezie bezie_y;//ポインタから値に変更
Bezie bezie_z;//ポインタから値に変更
Bezie bezie_r;//ポインタから値に変更
bool operator < (const KeyFrame &k) const { return frameNo < k.frameNo; } }; [/cpp] 後は、Bezieクラスに引数なしのコンストラクタを追加してやって、ポインタ型から値型への変更に伴う修正(->を.に、など)を数箇所で行えば、見事にメモリリークの警告が出なくなった。

なぜ、KeyFrameの当該のメンバがポインタになっているのかは作者に聞いてみないとわからない。

また SAFE_DELETE((*ite).bezie_x) がなぜうまく解放されないのかも、俺にはわからない。

なので、ベストの解決ではないかもしれない。誰かわかる人いたらご教示願いたい。

最後に、『進め!キノピオ隊長』の箱庭感がくっそ楽しいです。

さらにマリオカート8のダウンロードコンテンツ(8コース+キャラ+マシン)も来てた。

4年前

4 コメント

  1. あけましておめでとうございます。
    遅れてしまい申し訳ございません

    参考にさせていただきます!!
    わざわざありがとうございました!

    1. あけましておめでとうございます。

      いえいえ~こちらこそ、いい機会になりました。
      記事の内容がお役に立てればいいんですけどねー。
      ゲーム制作や学業、がんばってくださいー。

  2. 私の環境ではメモリリークにはなりませんね
    このソースコードを元に自分も勉強していますがそれと違って別のところでメモリリーク起きるようになっちゃいました(^-^;)

    1. そうですかぁ、お役に立てなくて残念です。

      スキンメッシュからIKや物理演算までカバーしているmはげさんのソースコードは本当にありがたいですよね。

      今回はソースコード解析がメインでしたが、次回は自習用にリファクタリングして一から作り直してみたいなと考えています。

コメントを残す

メールアドレスが公開されることはありません。