ページ 11

[DirectX]メッシュやテクスチャを描画するだけでメモリ使用量が増えてしまう

Posted: 2012年4月04日(水) 03:45
by がるか
表題の事を疑問に感じています。
.xや.bmpを読み込んでゲーム画面に表示する際、タスクマネージャでメモリ使用量を確認しています。
その際、メモリ確保処理を記述していないのにも関わらず、ゲーム時間経過と共にメモリ使用量は増加し、
開放できない領域が最大で17MB程度たまってしまいます。

具体的なメモリ使用量を追った所、次のような状況でした。


・ ウインドウ作成時点で約16MB、画像とメッシュ合わせて約30MBの計46MB使用

・ 一定時間ゲームを実行(実行中はメモリの動的確保処理を記述していない)

・ メモリ使用量を確認(この時点で約60MB)

・ 画像とメッシュを開放

・ メモリ使用量を確認(この時点で約30MB) ← 約16MBであって欲しい


描画処理を繰り返すと、追加でメモリを確保するよう、DirectXが設計されているのでしょうか?

Re: [DirectX]メッシュやテクスチャを描画するだけでメモリ使用量が増えてしまう

Posted: 2012年4月04日(水) 05:24
by fulls
自分のDirectXを使用したプログラムを調べた所、ウィンドウを作成した所で2MB程、DirectXを初期化した所で9MB程でした。
なのでウィンドウ作成時(DirectX初期化まで?)で16MBというのは多いような気がします。
また、DirectXがそのように描画を繰り返すたびにメモリをガツガツ食うように設計されているということはないと思います。
そう考えるとメモリの喰い過ぎはそのプログラムの書き方だと思うのですが、よろしければソースコードを貼っていただけませんか?
また、解放されないメモリは_CrtSetDbgFlagを使って調べてみるといいかもしれません。

Re: [DirectX]メッシュやテクスチャを描画するだけでメモリ使用量が増えてしまう

Posted: 2012年4月05日(木) 09:26
by がるか
pefs3d さん
>また、解放されないメモリは_CrtSetDbgFlagを使って調べてみるといいかもしれません。
なるほど、これは知りませんでした。早速使ってみようと思います。

ソースコードを添付します。長くなるので、一部抜粋して貼ります。

ウインドウ:グラフィックの初期化呼び出し

コード:


//==============================================================
// <CGame>コンストラクタ
CGame::CGame(const char* app_name, bool zbuffer)
:	/*メンバ変数初期化処理(記述省略)*/
{
	/*アイコン処理、アクセラレータキー等の処理(記述省略)*/

	// ウィンドウの作成
	long style=WS_CAPTION|WS_VISIBLE|WS_SYSMENU|WS_MINIMIZEBOX|WS_MAXIMIZEBOX;
	RECT r={0, 0, WindowWidth, WindowHeight};
	AdjustWindowRect(&r, style, false);
	HWnd=CreateWindow(
		app_name, app_name, WS_OVERLAPPEDWINDOW, 
		100, 100, r.right-r.left, r.bottom-r.top, 
		GetDesktopWindow(), NULL, wc.hInstance, NULL);
	HWndCGameMap.insert(
		THWndCGameMap::value_type(HWnd, this));

	// グラフィックスの作成
	Graphics=new CGraphics(HWnd);
	if (!Graphics) {
		string s=
			"このプログラムにはDirectX 9.0c以上が必要です";
		MessageBox(HWnd, s.c_str(), app_name, MB_OK);
		exit( EXIT_FAILURE );
	}
	Graphics->Clear();
	Graphics->Present();
	if (FullScreen) ResetScreen();
}

グラフィッククラス:DirectXの初期化

コード:

//==============================================================
// コンストラクタ
CGraphics::CGraphics(HWND hwnd)
:	/*メンバ変数の初期化*/
{
	// ウィンドウスタイルの保存
	WindowStyle=GetWindowLong(hwnd, GWL_STYLE);

	// クライアント領域のサイズを取得
	RECT r;
	GetClientRect(hwnd, &r);
	Width=r.right-r.left;
	Height=r.bottom-r.top;

	// Direct3Dインタフェースの作成
	D3D=Direct3DCreate9(D3D_SDK_VERSION);

	// Direct3Dデバイスの作成
	ResetDevice();
}
//==============================================================
// デバイス

// デバイスのリセット
bool CGraphics::ResetDevice() {

	// ウィンドウスタイルとサイズの設定:
	if (FullScreen) {
		SetWindowLong(HWnd, GWL_STYLE, WS_VISIBLE);
	} else {
		SetWindowLong(HWnd, GWL_STYLE, WindowStyle);
		RECT r={0, 0, Width, Height};
		AdjustWindowRect(&r, WindowStyle, GetMenu(HWnd)!=NULL);
		SetWindowPos(HWnd, HWND_NOTOPMOST, 
			100, 100, r.right-r.left, r.bottom-r.top, SWP_SHOWWINDOW);
	}

	// デバイスのパラメータ設定
	D3DPRESENT_PARAMETERS d3dpp;
	ZeroMemory(&d3dpp, sizeof(d3dpp));
	d3dpp.hDeviceWindow=HWnd;
	d3dpp.BackBufferWidth=Width;
	d3dpp.BackBufferHeight=Height;
	d3dpp.Windowed=!FullScreen;
	d3dpp.FullScreen_RefreshRateInHz=FullScreen?RefreshRate:0;
	d3dpp.SwapEffect=D3DSWAPEFFECT_DISCARD;
	d3dpp.BackBufferFormat=D3DFMT_A8R8G8B8;
	d3dpp.PresentationInterval=D3DPRESENT_INTERVAL_ONE;
	d3dpp.EnableAutoDepthStencil=TRUE;
	d3dpp.AutoDepthStencilFormat=DepthStencilFormat;

	// デバイスの作成・リセット
	if (!Device) {
		if (FAILED(D3D->CreateDevice(
			D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, HWnd,
			D3DCREATE_MIXED_VERTEXPROCESSING,
			&d3dpp, &Device))
		) 
		if (FAILED(D3D->CreateDevice(
			D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, HWnd,
			D3DCREATE_HARDWARE_VERTEXPROCESSING,
			&d3dpp, &Device))
		)
		if (FAILED(D3D->CreateDevice(
			D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, HWnd,
			D3DCREATE_SOFTWARE_VERTEXPROCESSING,
			&d3dpp, &Device))
		)
		if (FAILED(D3D->CreateDevice(
			D3DADAPTER_DEFAULT, D3DDEVTYPE_REF, HWnd,
			D3DCREATE_SOFTWARE_VERTEXPROCESSING,
			&d3dpp, &Device))
		) return false;
	} else {
		if (FAILED(Device->Reset(&d3dpp))) return false;
	}

	// 幅と高さの取得
	LPDIRECT3DSURFACE9 back_buffer;
	if (SUCCEEDED(Device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &back_buffer))) {
		D3DSURFACE_DESC desc;
		back_buffer->GetDesc(&desc);
		Width=desc.Width;
		Height=desc.Height;
		back_buffer->Release();
	} else {
		Width=Height=0;
	}

	// リフレッシュレートの取得
	D3DDISPLAYMODE mode;
	if (SUCCEEDED(Device->GetDisplayMode(0, &mode))) {
		RefreshRate=mode.RefreshRate;
	} else {
		RefreshRate=0;
	}

	IsGDISurface=true;
	return true;
}

メッセージループ

コード:

//==============================================================
// <CGame>ゲームの実行
void CGame::Run() {
	
	// ダミーのメッセージハンドラを,
	// 正式なメッセージハンドラに置き換える
	SetWindowLong(HWnd, GWL_WNDPROC, (long)::WndProc);
	
	// メッセージループ
	MSG msg;
	ZeroMemory(&msg, sizeof(msg));
	ResetTime();
	while (msg.message!=WM_QUIT) {
		
		
		// バックグラウンド処理
		if (PauseInTheBackground && HWnd!=GetForegroundWindow()) {
			GetMessage(&msg, NULL, 0U, 0U);
			if (!TranslateAccelerator(HWnd, HAccel, &msg)) {
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
			ResetTime();
			continue;
		}
		
		// フォアグラウンド処理
		if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) {
			if (!TranslateAccelerator(HWnd, HAccel, &msg)) {
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
			continue;
		}

		// 一時停止
		if (Pause || MenuPause) continue;

		// 時間調整
		LARGE_INTEGER freq, count;
		DWORD tick;
		if (QueryPerformanceFrequency(&freq) && 
			QueryPerformanceCounter(&count)) {
			Elapsed+=FPS*(count.QuadPart-LastPerfCounter.QuadPart)/freq.QuadPart;
			LastPerfCounter=count;
		} else {
			tick=GetTickCount();
			Elapsed+=FPS*(tick-LastTickCount)/1000;
			LastTickCount=tick;
		}

		// 移動と描画
		if (Graphics->GetRefreshRate()==FPS || Elapsed>=1.0) {
			if (DropFrames) {
				for (; Elapsed>=1.0; Elapsed-=1.0) Move();
			} else {
				Elapsed=0;
				Move();
			}
			CGame::DrawScene();
		}
	}

	// メッセージハンドラをダミーに戻す
	SetWindowLong(HWnd, GWL_WNDPROC, (long)::DummyWndProc);
	
	// 終了処理
	UnregisterClass(AppName.c_str(), GetModuleHandle(NULL));
}

//==============================================================
// <CGame>描画処理
void CGame::DrawScene() {

	// デバイスが消失していたら再構築する
	switch (Graphics->GetDevice()->TestCooperativeLevel()) {
		case D3DERR_DEVICELOST:
			Sleep(50);
			return;
		case D3DERR_DEVICENOTRESET:
			OnLostDevice();
			if (Graphics->ResetDevice()) OnResetDevice();
			return;
	}

	// 描画開始
	LPDIRECT3DDEVICE9 device=Graphics->GetDevice();
	Graphics->BeginScene();

	Draw();

	// 描画完了::
	Graphics->EndScene();
	Graphics->Present();
}

3Dの描画

コード:

//==============================================================
// 描画

// メッシュの描画
void CMesh::Draw() {

	// メッシュの描画
	D3DMATERIAL9 mat;
	for (DWORD i=0; i<NumMaterials; i++) {
		mat=Materials[i];
		D3DXCOLOR* col;
		
		#define COLOR_OPERATION(TARGET) \
			col=(D3DXCOLOR*)&mat.TARGET;\
			D3DXColorModulate(col, col, &ColorMultiplier);\
			D3DXColorAdd(col, col, &ColorAddition);
		COLOR_OPERATION(Diffuse);
		COLOR_OPERATION(Ambient);
		COLOR_OPERATION(Specular);
		COLOR_OPERATION(Emissive);
		Device->SetMaterial(&mat);
		Device->SetTexture(0, Textures[i]);
		Mesh->DrawSubset(i);
	}	
}
// 変換行列を指定して描画
void CMesh::Draw(const D3DXMATRIX& mat) {
	Device->SetTransform(D3DTS_WORLD, &mat);
	CMesh::Draw();
}
以上です。実際には、luaをゲームに組み込んでおり、luabindを通してlua側から
毎フレームCMesh::Draw(mat)を呼ぶという実装になっています。
よろしくお願いします。

Re: [DirectX]メッシュやテクスチャを描画するだけでメモリ使用量が増えてしまう

Posted: 2012年4月06日(金) 01:22
by fulls
ソースコードの方はぱっと見問題なさそうですが、見逃しているところがあるかもしれません。
あと、ソースコードをみていて思ったのですが、(間違っていたらすいません)シューティングゲームのコードですか?
「弾幕」という本とソースコードの書き方が似ているなーと思ったのですが...
あと、メモリがだんだん増えていくというのは、例えばstdのvector等と使っていて、これらから削除する時にvectorのリストからはeraseしたがメモリからはdeleteしていないとか...
見当違いだったらすいません。

Re: [DirectX]メッシュやテクスチャを描画するだけでメモリ使用量が増えてしまう

Posted: 2012年4月06日(金) 08:55
by がるか
pefs3d さん
>あと、ソースコードをみていて思ったのですが、(間違っていたらすいません)シューティングゲームのコードですか?
>「弾幕」という本とソースコードの書き方が似ているなーと思ったのですが...

はい、その本を参考にコードを書きました。
と言っても、アクションゲームを作成するために、CGameCMeshCTexture以外のクラスはほぼ全部書き換えてしまいましたが。。

>あと、メモリがだんだん増えていくというのは、例えばstdのvector等と使っていて、
>これらから削除する時にvectorのリストからはeraseしたがメモリからはdeleteしていないとか...

確かにありますが、局所変数として使用しており、スコープを抜けると解放される・・・と思っていたので開放処理は記述してませんでした。
重ねて質問ですが、vectorやmapでpushした領域は、popしなければスコープを抜けてもメモリに残るのでしょうか?

Re: [DirectX]メッシュやテクスチャを描画するだけでメモリ使用量が増えてしまう

Posted: 2012年4月06日(金) 09:02
by beatle
がるか さんが書きました:確かにありますが、局所変数として使用しており、スコープを抜けると解放される・・・と思っていたので開放処理は記述してませんでした。
重ねて質問ですが、vectorやmapでpushした領域は、popしなければスコープを抜けてもメモリに残るのでしょうか?
そんなことはありません。push自体で自動的に確保されたメモリ領域は、そのvectorやmapの寿命が終わるときに自動的に開放されます。

pefs3dさんが言っているのはおそらく、自前でnewしたポインタをpushした場合でしょう。

コード:

vector<int*> a;
a.push_back(new int(10));
こんな風に書いたとすると、push_backで確保されたポインタ変数用のメモリは開放されますが、newで明示的に確保されたint型の領域は自動では開放されません。

Re: [DirectX]メッシュやテクスチャを描画するだけでメモリ使用量が増えてしまう

Posted: 2012年4月06日(金) 18:09
by ISLe
がるか さんが書きました:以上です。実際には、luaをゲームに組み込んでおり、luabindを通してlua側から
毎フレームCMesh::Draw(mat)を呼ぶという実装になっています。
Luaがメモリ食ってる可能性はないのですか?

Re: [DirectX]メッシュやテクスチャを描画するだけでメモリ使用量が増えてしまう

Posted: 2012年4月06日(金) 23:37
by がるか
beatle さんが書きました: こんな風に書いたとすると、push_backで確保されたポインタ変数用のメモリは開放されますが、newで明示的に確保されたint型の領域は自動では開放されません。
なるほど、ありがとうございます。使うときには気を付けようと思います。
ISLe さんが書きました: Luaがメモリ食ってる可能性はないのですか?
最初は自分でもそれを疑っていましたが、lua_close(L)しても数kbしか変化しなかったので、恐らく違うんじゃないかと思われます。

色々試した結果、気になる挙動がありました。

コード:

// luaでDraw3Dを呼ぶためのバインド関数
void Draw3D(
	std::string name , // 表示するメッシュ:ここでは"001.x"が入る
	int x,int y,int z, // 座標
	int sx,int sy,int sz, // 拡大率
	TURN_ORDER order,float alpha // 回転方向とアルファブレンド
) {
	// ① 
	// CGameCoreクラスへのポインタ->CResourceクラスへのポインタ->boost::bimapと介して表示
	// 変数Mはboost::bimaps::bimap<string,CMesh*>型
	// Drawは与えられた引数を座標変換して上述のCMesh::Draw(mat)を呼ぶ
	// for文は描画回数が増えた時(=時間経過後の挙動を即座に見るため)の振舞いを見る
	for(int i=0; i<100; i++)
		Core->Resource->M.left.at(name.c_str())->Draw(x,y,z,sx,sy,sz,order,alpha);
}
// luaでNowLoadingを表示するためのバインド関数
void NowLoading() {
	// ②
	// CGameCoreクラスへのポインタ->CMeshへのポインタと介して表示
	// Mesh_001には"001.x"が格納されている
	for(int i=0; i<100; i++)
		Core->Mesh_001->Draw(0,0,0,10,10,10,TO_XYZ,1);
}
上のコードで、ゲーム中にDraw3D() or NowLoading()だけを呼び、1分後に終了というコードを試しました。
①だと数十秒後には30MB消費するのに対し、②は25MBでした。
どちらも呼ぶ描画関数以外は同条件で、最終的にはvoid型の同じ関数を呼ぶのに、なぜメモリ使用量に差がでたのか、自分ではわかりません。
どなたかわからないでしょうか。。

Re: [DirectX]メッシュやテクスチャを描画するだけでメモリ使用量が増えてしまう

Posted: 2012年4月08日(日) 22:02
by ISLe
いったん拡張されたCRTヒープはメモリブロックを解放しただけでは縮小されません。

たぶんDirectX内部においてWin32 APIで確保されたメモリはワーキングセットから消えて、
標準ライブラリによって確保されたメモリだと同時に確保された最大値がワーキングセットに残ると思います。

_heapminを呼び出すとCRTヒープを縮小できます。
メモリブロックのフラグメント等で完全に未使用領域が解放されるとは限らないと思いますが。