スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

【コード】Window から FullScreen(デバイスロスト)に対応

ここ三日ほどデバイスロストについて物凄く悩んでおりました。
出来た!と思ってたらその矢先にバグは見つかるし
そのバグを直そうとすると今度は予期せぬエラーで落ちたりしてました。
トライアンドエラーを繰り返し、今回追加した機能が二点。

・フルスクリーン時等に割り込みが発生した場合のデバイスロストへの対応
・ウインドウモードからフルスクリーンへの切り替え時に発生するデバイスロストへの対応

この二点になります。

デバイスロストとはその名の通り「デバイスが消失する状態」を意味しており、
消失するとデバイスの状態を復帰させる、ないしアプリケーションを終了させる等の選択をしなければいけません。


【フルスクリーン時等に割り込みが発生した場合のデバイスロストへの対応】
フルスクリーン時はそのアプリケーションが画面を占有している状態にあるため
割り込みが発生するとロスト状態に陥ります。

割り込みとは例えば、フルスクリーン時に Alt+Tab キーを押してデスクトップ画面に移行したり
更新プログラムが起動しました…等、裏で何かしらのアクションがあった時に発生するものです。
PCのゲームなので常に起こりうる可能性がありますので対応しなければいけません。

if(m_pDev->Present(NULL, NULL, NULL, NULL) == D3DERR_DEVICELOST)
{
DeviceLost();
}


m_pDev は LPDIRECT3DDEVICE9(IDirect3DDevice9*)型のデバイスオブジェクトです。
デバイスロストが発生すると IDirect3DDevice9::Present の戻り値に
D3DERR_DEVICELOST が返ってきますので
この戻り値の場合はロスト対策をしなければいけません。


bool DeviceLost()
{
// デバイスの現在の状態を取得
HRESULT whr = m_pDev->TestCooperativeLevel();

// デバイスの状態は正常である
if(whr == D3D_OK)
{
// デバイスロストが起こったからこの関数が呼ばれているので
// 正直この戻り値のチェックはいらないと思います。
// 成功すると D3D_OK が返るので書いておきました。
return TRUE;
}

// D3DERR_DEVICENOTRESET の場合は描画デバイスを復帰(リセット)できる状態
if(whr != D3DERR_DEVICENOTRESET)
{
return FALSE; // D3DERR_DEVICELOST の状態
}

ResetDevice(); // デバイスのリセット

return TRUE; // デバイスロストの対応が終わりました!
}


デバイスロストが発生しましたのでデバイスを復旧させるのですが
復旧できるかどうかを調べる必要があり、そのメソッドに
IDirect3DDevice9::TestCooperativeLevel を使います。

このメソッドは失敗すると次のエラーを返します。
D3DERR_DEVICELOST 又は D3DERR_DEVICENOTRESET

D3DERR_DEVICELOST の場合はデバイスがロストしていてリセットが出来ない状態(後述)
D3DERR_DEVICENOTRESET の場合はデバイスはロストしているがリセット可能な状態(後述)。

IDirect3DDevice9::TestCooperativeLevel を定期的に(ゲームループ等で)呼び出し
D3DERR_DEVICENOTRESET が返ってくる(復旧可能になる)まで待たないといけません。

その場で待機させるだけだと効果はありませんので
間違っても Sleep() なんかを使ってその場で待機させてはいけません。
要するにロスト中もメインループはそのまま動かし続けないといけないという事です。
(Windows のメッセージポンプを行う)



void ResetDevice()
{
// リソースの解放
// ここで特定のリソースを解放しなければいけません!
// それについては後述


// デバイスのリセット(解放しなければいけないリソースを解放しなければ成功しない)
// 戻り値が D3D_OK 以外の場合、復元出来ないのでアプリケーションを終了する
if(m_pDev->Reset(&m_D3Dpp) != D3D_OK)
{
PostQuitMessage(0);
return;
}

// リソースの復元
// 解放したリソースを再作成する必要があります!!


}

D3DERR_DEVICENOTRESET が返ってくるとデバイスをリセットできる状態になりますが、
その前に注意しなければいけない所が水色文字の部分です。
一部リソースを解放しなければいけません。
MSDN には以下のような記述があります。

IDirect3DDevice9::Reset を呼び出すと、すべてのテクスチャ メモリ サーフェイスが失われて、
管理下のテクスチャがビデオ メモリからフラッシュされ、すべてのステート情報がクリアされる。
デバイスに対して IDirect3DDevice9::Reset メソッドを呼び出す前に、
アプリケーションでは明示的なレンダリング ターゲット、深度ステンシル サーフェイス、
追加スワップ チェーン、ステート ブロック、およびデバイスに関連付けられている
D3DPOOL_DEFAULT リソースを解放する必要がある。


これらのリソースを Reset の前に解放しなくてはいけません。

・IDirect3DDevice9::CreateRenderTarget メソッドで作成したもの
・IDirect3DDevice9::CreateDepthStencilSurface メソッドで作成したもの
・IDirect3DDevice9::CreateAdditionalSwapChain メソッドで作成したもの
・テクスチャ等を D3DPOOL_DEFAULT で作成している場合これも解放
 (D3DPOOL_MANAGED や D3DPOOL_SYSTEMMEM は解放の必要はありません)
・D3DXCreateFontIndirect で作成したフォントオブジェクト等々

この解放が躓いた部分でした。
D3DXCreateFontIndirect で作成したフォントオブジェクトも解放して
作り直さないといけないようで、見落としているだけだと思いたいのですが
MSDN にはそのような記述を見つける事が出来ませんでした。


デバイスのリセットには IDirect3DDevice9::Reset を使います。

第一引数に &m_D3Dpp とい引数がありますが、
これはプレゼンテーションパラメータ構造体(D3DPRESENT_PARAMETERS)のアドレスです。
この構造体はデバイスを作成する時にも使っているはずですので割愛させて頂きますが、
その時の情報を保持しておかなくてはいけないのでクラス内のメンバ変数等で保持してあげて下さい。
そうするとその情報でデバイスがリセットされ復帰が完了します。

その後は先程解放したリソースを作成し直します。
こういう時のためにリソースを初期化するためのメソッドを用意しておくといいと思います。

気をつけなければいけないのが、解放して再作成するのでポインタが保持するアドレスが
違う場所を指していると言う部分です。
面倒な場合はテクスチャを D3DPOOL_MANAGED で作成してしまうのも手だと思います。
(解放しなくていいので考えなくて良い)

ここまでくればデバイスロストから復帰できる状態になります。



【ウインドウモード ⇔ フルスクリーンへの切り替え】
これもデバイスロストが発生するので処理は同じです。
違う部分はウインドウのサイズを変更する点と、フラグを切り替えると言う事ですが
そこまで難しいものではありませんのでソースを載せときます。

bool CDevice::ChangeDispMode()
{
// ディスプレイモードの切り替え
// m_D3Dpp.Windoed が TRUE ならウィンドウモード
// FALSE ならフルスクリーンになります。
(m_D3Dpp.Windowed == DISPMODE_FULLSCREEN) ? m_D3Dpp = m_D3DppFull : m_D3Dpp = m_D3DppWnd;

ResetDevice(); // デバイスのリセット

// フルスクリーン時のウインドウサイズ
if(m_D3Dpp.Windowed == DISPMODE_FULLSCREEN)
{
SetWindowLong(hWnd, GWL_STYLE, WS_POPUP);
SetWindowPos( hWnd, HWND_TOPMOST, // 最前面に表示
0,
0,
フルスクリーン時の幅,
フルスクリーン時の高さ,
SWP_SHOWWINDOW);

ShowCursor(FALSE); // マウスカーソルを表示しない
}
// ウインドウモード時のウインドウサイズ
else
{
// ここでは RECT 型への参照を返すメソッドから
// ウインドウのサイズと位置を取得してます
const RECT &wRect = pCWindow->GetWindowRect();

//ウィンドウモードへ変更する。
SetWindowLong(hWnd, GWL_STYLE, ウインドウ作成時のウインドウスタイル(WS_POPUPとか));

//ウィンドウモードへ変更したとき、ウィンドウの位置を変更する。
SetWindowPos( hWnd,
HWND_NOTOPMOST, // 最前面の表示を解除
wRect.left,
wRect.top,
wRect.right-wRect.left,
wRect.bottom-wRect.top,
SWP_SHOWWINDOW);
ShowCursor(TRUE); // マウスカーソルを表示する
}

return TRUE;
}


切り替える時にこのような記述をしてます。
(m_D3Dpp.Windowed == DISPMODE_FULLSCREEN) ? m_D3Dpp = m_D3DppFull : m_D3Dpp = m_D3DppWnd;

m_D3Dpp は今のパラメータが入っており、
m_D3DppWnd, m_D3DppFull に関してはデバイスの初期化の時点で
ウインドウモードの情報とフルスクリーンの情報が入っております。

ウインドウを切り替える時にウインドウの情報を m_D3Dpp に代入し、
それを Reset(&m_D3Dpp) で渡してあげる形です。

D3DPRESENT_PARAMETERS::Windowed のフラグを切り替えるだけで
Window ⇔ FullScreen の対応ができるので非常に便利であります。
ここまでくれば後はウインドウの情報を元にサイズの変更をかけるだけです。


長くなりましたがここ数日にかけて悩んでいた部分が解決できて嬉しいです。
気になるのが他の PC でどの様な挙動になるのか全くわからない点です。
これも後々テストしないといけませんね・・・。

この記事へのコメント

- はぐれステンレス - 2012年05月24日 07:37:53

やばいww
呪文にしか見えない_| ̄|○

- DVDM♪管理人♪ - 2012年05月24日 08:30:07

>> はぐれステンレスさん
プログラムは解らなければ本当に呪文だと思いますよw
呪文みたいに決まった事を書きますし、あながち間違ってはいないかと・・・。

トラックバック

URL :

検索フォーム
プロフィール

DVDM

Author:DVDM
自作ゲームの開発過程ブログ。
赤髪愛なら誰にも負けない。

 
Pixiv バナー


ブロとも申請フォーム
最新記事
カテゴリ
最新コメント
最新トラックバック
RSSリンクの表示
リンク
ブロとも一覧
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。