タイムルーラー

注:今回紹介するコンポーネントはデバッグサンプルに入っています。 時を測る 前回紹介したFPSカウンターではゲーム全体のパフォーマンスを測定するには使えますが、どの処理がどれだけ時間が掛かっているかを判定するには不向きです。 例えば、ねこが大勢の敵をなぎ倒す「ねこ無双」というゲームを作っていて、敵が沢山出てきた時に処理落ちになった場合に最適化をする必要があったとします。 最適化ルールその3:「最も負荷の高い部分(ボトルネック)の処理を最適化する」 これは当然の話で、1フレーム内で10%しか消費していない処理よりも、50%消費している部分を最適化する方が効果的です。では、この高負荷の処理を発見するにはどうしたら良いでしょうか?ねこ無双の場合だと、敵が増えると処理落ちがするんだから真っ先に思いつくのは敵AI、敵のアニメーション、敵のコリジョン判定、敵の描画処理部分です。 しかし、必ずしもそうとは限りません。もしかしたら、敵の数ではなく戦っている場面の描画部分がもともと負荷が多かったのかもしれないし、敵を表示するレーダー部分かもしれません。 このボトルネックを発見するのに有用なのがリアルタイムプロファイラー、TimeRuler(タイムルーラー)です。TimeRulerでは、複数の処理時間を測定することができ、その結果をグラフでリアルタイムに表示してくれます。 上図の例では、青い部分が更新処理、黄色い部分が描画処理になっています。この状態で最適化する場合は更新処理部分を最適化するのが効果的だと言うことが一目で判ります。 アルゴリズムのチェック 理想的なアルゴリズムは計算量のオーダーがO(n)、もしくはO(n log n)になることです。例えば、敵を10体出現させたときの処理時間が1msだった場合、敵が100体になった時の処理時間が10ms~20ms以内に収まっていれば理想的なアルゴリズムを使っているということになります。逆に敵100体の処理時間が1,000msになってしまうのは、計算量オーダーがO(n^2)になっているアルゴリズムを使っている可能性があります。良くあるケースでは、コリジョン判定を単純な多重ループにしている時などです。 特に最近のゲームでは大量のオブジェクト数を処理するので、アルゴリズムの選択を間違えると、あっという間に処理が間に合わなくなってしまうので注意が必要です。 TimeRulerを使ってオブジェクトの数を10,20,…100と順々に増やしていき、それぞれの処理時間を測定してグラフにすることで問題のあるアルゴリズム部分を発見することができます。 瞬間最大負荷を測る TimeRulerはリアルタイムプロファイラーなので、瞬間的な負荷も視覚的に見ることができます。例えば同じフレーム内で大量の敵を一気に発生させると、敵の初期化処理に時間が掛かりすぎて、そのフレームだけ処理落ちするということがあります。こういった瞬間的な負荷はFPSカウンターで発見することは不可能ですが、TimeRulerを使うとグラフが一瞬大きくブレるので判ります。 また、TimeRulerを表示した状態でビデオに録画したり、動画をキャプチャーしておけば、こういった瞬間的な負荷を細かく調べることもできます。 TimeRulerを使う為の準備 TimeRulerコンポーネントはDebugManagerにあるフォントを使用するので、TimeRulerを追加する前にDebugManagerコンポーネントを追加する必要があります。// デバッグマネージャーの初期化と追 debugManager = new DebugManager( this ); Components.Add( debugManager ); また、必須ではありませんが、TimeRulerはインスタンス生成時にGame.ServicesにIDebugCommandHostインターフェースが追加されていると、trデバッグコマンドを登録します。DeubgCommandUIはIDebugCommandHostインターフェースを実装しているので、このコンポーネントを追加した後にTimeRulerを追加することでtrコマンドを使うことができます。// デバッグマコマンドUIの初期化と追 debugCommandUI = new DebugCommandUI( this ); // デバッグコマンドUIを最上面に表示させる為にDrawOrderを変更する debugCommandUI.DrawOrder = 100; Components.Add( debugCommandUI ); デバッグコマンドUIをTabキーを押して表示させた後に、trとタイプするとTimerRulerの表示/非表示の切り替え、”tr on”で表示、”tr off”で非表示にすることができます。 また、trコマンドには以下のオプションがあります。 log ログの表示/非表示の切り替え。 例) tr log…

7

FPSカウンター

注:今回紹介するコンポーネントはデバッグサンプルに入っています。 正確な測定には注意が必要 FPSカウンターは、一定時間内に(数秒程度)何フレーム更新できたかを計測した結果から、1秒間のフレーム数、FPS(Frame Per Second)を表示するという、非常に簡単な機能です。 ですが、正確なFPSを測定するには幾つかの注意が必要です。Game.IsFixedTimeStepの既定値はtrueになっているので、ゲームの更新と描画に掛かる時間が1msだとしても、見かけ上は16.6ms、つまり60FPSになってしまいます。また、描画時にV-Syncに同期するようになっているので、IsFixedTimeStepだけをfalseにしてもやはり60FPSになってしまいます。 FPSを正しく測定するにはゲームクラスのコンストラクタ内で以下の様なコードを実行する必要があります。graphics = new GraphicsDeviceManager( this ); graphics.SynchronizeWithVerticalRetrace = false; IsFixedTimeStep = false; これでFPS自体は正しく測定することができますが、IsFixedTimeStepをfalseに変更するということは、ゲームの更新の仕方が固定更新から、可変更新に変えるということです。もし、キャラクターを移動するコードが以下の様になっている場合に可変更新に変更すると、キャラクターの移動が速すぎてゲームがプレイ不可能になる場合があります。pos += speed; この問題を解決するには以下の様に、経過時間によって移動するようなコードになっている必要があります。float dt = (float)gameTime.ElapsedGameTime.TotalSeconds; pos += speed * dt; 固定更新、可変更新の振る舞いの違いについては、この投稿が参考になると思います。もし、既に固定更新でゲームを作っている場合にはFPSカウンターを使う意味はあまりありません。 GPUとCPUバランスに注意 また、CPUとGPUの負荷バランスが違う場合には、FPS値は参考になりません。例えば60フレームのゲームを作っている時にFPS値が丁度60フレームになっているからといって、必ずしもこれ以上の処理を追加できないと言うわけではありません。例えば、1フレームあたりの処理時間が、CPUが50%、GPUが100%近く消費している場合、CPU側にはまだ50%の余力があるにも関わらずFPSの値は60になるので、余力が無いように見えてしまいます。逆にCPU100%、GPU50%の状態でも同じです。 CPU、GPUバランスを調べる簡単な方法は、GPUの負荷を測定するにはUpdate内では何も処理をせずにDrawだけ実行し、CPUの負荷を測定するには逆にDrawの処理をしないようにする方法があります。ただし、Draw内にもCPU部分の処理があるので、もっと正確にCPUの負荷を測定するにはDrawPrimitiveのメソッドのみをコメントアウトする必要があります。 60FPSにならないんだけど IsFixedTimeStepとSynchronizeWithVerticalRetraceをtrueにした状態で、明らかにゲーム全体の処理が16.6ms以内で済んでいるのにも関わらず、FPSにはいつも59.xxしか表示されない。というのはおかしいと思う人もいるかもしれません。 これには3つの理由があります。ひとつめはTVの60フレームというのは実際には59.94(厳密に言えば60/ 100.1%)になるので、理論的に60と言う数値にはならないこと。ふたつめは、測定する場合に1フレーム以下の余りの時間分で誤差がでるということ。そして、ハードウェア的には59.94でフレームを更新していても、ソフトウェア的に測定ポイントまで戻ってくるまでの時間が微妙に変わってくるということです。 ですから、59.xxと言う数値を見たときには約60FPSと考えても問題ありません。 FpsCounterコンポーネントの使い方 FpsCounterコンポーネントはDebugManagerにあるフォントを使用するので、FpsCounterを追加する前にDebugManagerコンポーネントを追加する必要があります。// デバッグマネージャーの初期化と追 debugManager = new DebugManager( this ); Components.Add( debugManager ); また、必須ではありませんが、FpsCounterはインスタンス生成時にGame.ServicesにIDebugCommandHostインターフェースが追加されていると、fpsデバッグコマンドを登録します。DeubgCommandUIはIDebugCommandHostインターフェースを実装しているので、このコンポーネントを追加した後にFpsCounterを追加することでfpsコマンドを使うことができます。 デバッグコマンドUIをTabキーを押して表示させた後に、fpsとタイプするとFPSの表示/非表示の切り替え、”fps on”で表示、”fps off”で非表示にすることができます。//…

1

デバッグサンプル

2010/09/17 追記: XNA Game Studio 4.0用のサンプルをhttp://higeneko.net/hinikeni/sample/xna40/DebugSample.zipにアップしました。詳細は「サンプルコードをXNA 4.0向けに更新」を見てください。 2009/06/25 追記: XNA GS 3.1用のサンプルを http://higeneko.net/hinikeni/sample/xna31/DebugSample.zipにアップしました 2009年1月24日追記: サンプルプログラムのキーボード処理部分でテンキーの0を押すと1が入力されるバグを修正、アップデートしました。バグがあった場所はGameDebug/KeyboardUtils.csファイル内のInitializeKeyMapメソッド内の初期化コード部分です。AddKeyMap( Keys.NumPad0, "10" )となっているのをAddKeyMap( Keys.NumPad0, "0" )にする事で修正できます。連絡してくださったDERNAさんに感謝します。   デバッグ3種の神器 ゲーム開発現場で最も使われているゲーム内で動作するデバッグ用の機能といえば以下の3つがあります。 FPSカウンター リアルタイムプロファイラー デバッグコマンド 市販されているPCゲームの中にはオプション画面でFPSカウンターを表示できるようになっているものもあるので、パフォーマンスの指標として有名です。後のふたつについては一般にはあまり知られていませんが、ゲーム開発現場、特に北米のゲーム開発現では良く目にする機能です。 前回紹介したGamefest Japan 2008のデモでも、これらの機能を使っています。そこで、今回は前回のデモからデバッグ機能だけを抜き取ったサンプルを作りました。  http://higeneko.net/hinikeni/sample/DebugSample.zip 前回と同じく、フォントはConsolasを使用しています。Consolasフォントが無い場合はビルドエラーになるので、以下のURLからConsolasフォントをダウンロードするか、フォント名を変更するようにしてください。 https://www.microsoft.com/downloads/details.aspx?familyid=22e69ae4-7e40-4807-8a86-b3d36fab68d3&displaylang=en 使い方 サンプルを起動すると、画面右上に使い方が表示されます。 FPSカウンターの表示 Aボタン、またはAキー TimeRulerの表示 Bボタン、またはBキー TimeRulerログ表示 Xボタン、またはXキー デバッグコマンドUIの表示 Tabキー サンプルプログラムの終了 Backボタン、またはEscapeキー デバッグコマンドUIはキーボード入力からデバッグ用コマンドを実行します。Xbox 360でもUSBキーボードを本体に繋げることで使うことができます。デバッグコマンドUIには元々使えるコマンドとして画面をクリアするclsコマンド、登録されているコマンドを表示するhelpコマンドがあります。基本的にWindowsにあるコマンドシェルと使い方は一緒で、上下キーで最近使ったコマンドのヒストリを表示する機能もあります。 このデモでは、DebugSampleGame.cs内には独自のデバッグコマンドを追加するサンプルコードが含まれていて、ここではposコマンドを追加して、画面上に表示される「+」マークの位置を表示したり、変更できるようになっています。下の動画では見づらいですが、最初は左上にあった白い点がposコマンドによって画面下中央に移動しているのが判ると思います。 サンプルに含まれるファイル このサンプルに含まれるファイルには、デバッグ機能の使い方のサンプルコードが入ったDebugSampleGame.csファイルの他に、デバッグ機能を提供する以下の8つのファイルがあります。 DebugSample/GameDebug/DebugCommandUI.cs デバッグコマンドUIクラス DebugSample/GameDebug/DebugManager.cs デバッグ用のコンテントを格納する為のゲームコンポーネント…

7

Gamefest Japan 2008 デモプログラム

2010/9/17 追記: XNA Game Studio 4.0のサンプルをhttp://higeneko.net/hinikeni/sample/xna40/GamefestJapan2008Demo.zipにアップしました。変更点は「サンプルコードをXNA 4.0向けに更新」に書いてあります。 2010/05/16 追記: マテリアルバッチ描画時のDrawIndexedPrimitiveメソッドへの引数設定が間違っていたのを修正しました。 2009/06/25 追記: XNA GS 3.1用のサンプルを http://higeneko.net/hinikeni/sample/xna31/GamefestJapan2008Demo.zipにアップしました 2008/12/26 追記: デモプログラムの更新、キーボード入力で操作できるようになりました。 やっと終わりました 今年の9月に行われたGamefest Japan 2008のプレゼンテーション資料が先月公開されました。 http://msdn.microsoft.com/ja-jp/xna/cc723908.aspx 私も僭越ながら2つのセッションを担当させて頂き、そのプレゼンテーション資料も公開されています。XNA Game Studioでのゲーム開発においてのパフォーマンス改善について戦略的手法と実践的な手法、主にシステムレベルでの最適化の効果の程を紹介させていただきました。 http://download.microsoft.com/download/0/4/2/04291420-033b-4f46-b87d-de64a2dc6bac/X5.zip 本当はプレゼン時にサンプルプログラムの配布したかったのですが、本業の方のXNA Framework作業の方が佳境の最中で、その合間に書いたコードだったので非常に汚いコードであったのと、先月のXNA Game Studio 3.0のリリースまでその忙しさが続いたので、なかなか公開することができませんでした。っていうか、コメントを追加するのは当然として、他にも3.0対応にしたり、再利用可能なコンポーネントを使いやすく書き換えたりして、発表時のコードよりも4,000行近く増えてしまったのも、今まで時間が掛かった原因でした。 ともかく、なんとか人に見せられる程度の体裁は整えたので、デモプログラムを公開します。 http://higeneko.net/hinikeni/sample/GamefestJapan2008Demo.zip 解凍したフォルダの中には以下の3つのソリューションファイルがあるので、好きなソリューションファイルを使ってください。いずれのプロジェクトを開くのにもXNA Game Studio 3.0が必要です。また、Windowsで実行するにはシェーダーモデル2.0以上のビデオカードが必須になります。 DemoWin.sln Windows用のソリューションファイル DemoXbox.sln Xbox 360用のソリューションファイル Demo.sln Windows/Xbox 360の両方のプロジェクトが入ったソリューションファイル フォントは私がプログラムする時に使っている等幅フォントのConsolasを使用しています。Consolasフォントが無い場合はビルドエラーになるので、以下のURLからConsolasフォントをダウンロードするか、フォント名を変更するようにしてください。 https://www.microsoft.com/downloads/details.aspx?familyid=22e69ae4-7e40-4807-8a86-b3d36fab68d3&displaylang=en このサンプルプロジェクトのコード、アセットは非商用、商用に関わらず、自由に使ってかまいません。ただし、一部のコードはクリエータークラブオンラインのMesh Instancingのコードを流用、または変更しているので、それらのコードのライセンスについては付属されるMicrosoft Permissive License.rtfに沿います。 http://creators.xna.com/en-US/sample/meshinstancing で、お約束ですが、このサンプルコードやアセットを使用したことで発生したいかなる問題にも当方は責任を負いません。…

9