チュートリアル: サンプルアプリケーションの最適化
更新日時: 2024/07/30
LINK PC-VR DOCUMENTATION
このトピックでは、PC SDKを利用した開発について説明します。Meta Questアプリのパフォーマンスの最適化については、使用する開発プラットフォームに応じて、次のトピックのいずれかをご覧ください。 このセクションは、VRパフォーマンスの最適化に関する詳細な実践的ガイドを提供するチュートリアルです。
このチュートリアルは、次のものを使用したサンプルアプリ最適化のプロセスを説明します。
- Oculusデバッグツール
- OculusパフォーマンスHUD
- ロストフレームキャプチャツール
- Windowsイベントトレーシング(ETW)
- ovrlog/ovrlog_win10とxperf
- Windowsパフォーマンスアナライザー(WPA)
- GPUView
1.Oculus PC-SDKをインストールします。
2.Visual Studio 2015をインストールします。
注: このチュートリアルで使うOculus ORTデモアプリとWindowsドライバーキット(WDK)は、Visual Studio 2017との互換性がまだありません。
3.GPUViewをインストールします。
GPUViewは、Windows8.1 SDK内のWindowsパフォーマンスツールキット(WPT)に含まれています。こちらから入手できます。
4.Windowsイベントトレーシング(ETW)をインストールします。
5.以下をいずれもインストールします。
- Windows 10版Windows SDK、バージョン1703 (またはそれ以降)
- Windows 10版WDK、バージョン1703(またはそれ以降)
6.Windowsパフォーマンスアナライザー(WPA)をインストールします。
WPAは、次のWindowsアセスメント&デプロイメントキット(Windows ADK)に含まれています。
ここでは、次のORTバッファ化ハプティクスのサンプルプロジェクトを使います。
{SDK Folder}\Samples\OculusRoomTiny_Advanced\ORT (Buffered Haptics)\Projects\Win\VS2015
1.アプリを実行し、Riftが適切に設定され正しく動作していることを確認します。
2.Visual Studio 2015でORT(バッファ化ハプティクス)ソリューションを開きます。
3.このアプリをデバッガーで実行し(F5を押す)、Riftヘッドセットでシーンを表示します。
頭を動かすと、シーンは通常どおり動きます。Oculusミラーでは、アイディスプレイは中心に残り、通常の形状を保ちます。
4.デバッガーを停止します。
5.GPUパフォーマンスの問題を発生させます。
このアプリで使われているデフォルトのテクスチャシェーダールーティンを編集し、Forループを追加して過度の負荷をかけることで人工的にGPUをオーバーロードさせ、負荷の多いシェーダールーティンを模倣します。
このサンプルではシェーダールーティンは次のものに含まれています。
{Oculus SDK Folder}\Samples\OculusRoomTiny_Advanced\Common\Win32_DirectXAppUtil.h
このヘッダーファイルをVisual Studioで開き、defaultPixelShaderSrcに次のコードを追加します。
// Artificially overload the GPU with too much work, mimicking an expensive shader routine
" if (TexCol.a!=0) for (int i = 1; i < 10000; i++) { Color = Color * i * 2; Color = Color/i; Color = Color/2; } "
以下では、このコードの場所がハイライトされています。

注: ループ変数の値をいくつか試してみるとよいでしょう。上記の10,000という値は、ハードウェアの性質によっては極端過ぎるかもしれません。このチュートリアルでは、1500前後の値にしておいた方がよいかもしれません。チュートリアルの後半に、ループ変数の複数の値による結果を載せています。これにより、さまざまなレベルのシェーダーの複雑性が模倣されます。
6.アプリを再度実行します。
アイレンダリングに遅延が起こっています(頭を動かすと確認できます)。頭が動いている間、ヘッドセット内のディスプレイの横に黒い垂直の長方形のエリアが表示されます。Oculusミラーでは、頭を動かしている間はアイディスプレイが平たくなり、中心から外れているように見えます。
原因は我々が意図的に作り出したGPU問題です。しかし一般的に、この症状はCPUまたはGPUオーバーロードなどさまざまな問題が原因である可能性もあります。では、Oculusデバッグツールを使用して問題を絞り込んでいきます。
1.Oculusデバッグツールを実行します。
%PROGRAMFILES%\Oculus\Support\oculus-diagnostics\OculusDebugTool
2.Oculusデバッグツールのウィンドウが表示されます。

次の点を確認します。
- 可視HUDがパフォーマンスに設定されている
- パフォーマンスHUDのモードがパフォーマンスサマリーに設定されている
3.[ファイル] > [アプリを起動...]を選択して簡単にアプリを起動することもできます。
4.アプリのバイナリ(次の場所にあります)を起動します。
{Oculus_SDK}\Samples\OculusRoomTiny_Advanced\ORT (Buffered Haptics)\Bin\Windows\Win32\Debug\VS2015\z.Buffered Haptics.exe
5.ヘッドセットにOculusパフォーマンスHUDが表示されるはずです。

HUDが、4つのメトリックを示すパフォーマンスサマリーを表示します。
- App Motion-to-Photon Latency (アプリのMotion-to-Photon遅延) - アプリがコントロールを与えられてからフレームが完了するまでの時間を示しています。この時間のほとんどがアプリレンダリングの時間です。上記の値は約284 msで、とても高い値であり、明らかに問題を引き起こしていることが分かります。これを基に、HUDはアプリがCPU使用率のレベルをどの程度変更するべきか提案します。今回のケースでは約15倍の減少となっています。
- パフォーマンスヘッドルーム — これはアプリがパフォーマンス問題を露呈し始めるまでに使用可能なレンダリング時間の割合です。(この値は、
1 - (FrameRenderTime / FrameVSyncToNextVsync)として計算されます。例えば、フレームのレンダリングに5 msかかった場合、ヘッドルームは1 - (5/11.11) = 0.54 = 54%になります)。この例では、パフォーマンスヘッドルームは実際は大きな負数で、アプリのフレームがドロップしていることを示します。 - ドロップしたアプリフレーム — これはアプリがドロップしたフレーム数です。この例では、大量のコマ落ちが発生しており、そのため行が赤色になっています。
- Compositor Frames Dropped (コンポジターのコマ落ち) — これはコンポジターで発生したコマ落ちの数です。
このアプリが[App Motion-to-Photon Latency (アプリのMotion-to-Photon遅延)]カテゴリで時間を消費し過ぎていることは明らかです。この例では問題が極端に誇張されています。1フレームに18 ms消費されていれば、それは典型的なパフォーマンス問題の一例と言えます。
6.パフォーマンスHUD下で、[Mode (モード)]を[App Render Timing (アプリレンダリングタイミング)]に設定します。

これでOculusパフォーマンスHUDのコンテンツは、アプリのレンダリングタイミングデータを表示します。ここでCPUとGPUの計算負荷を簡単に比較することができます。

問題がGPU負荷にあることは明らかです。この例も、これらの分析ツールを使うことで得られる情報の解説のために、極端に誇張されています。
次に、ロストフレームキャプチャツールを使って、アプリがドロップしたフレームを確認します。
1.Oculusデバッグツールで[Lost Frame Capture (ロストフレームキャプチャ)]ボタンをクリックします。

2.ロストフレームキャプチャツールが表示されます。

3.アプリがOculus Riftヘッドセットに表示されていることを確認します。
(ロストフレームキャプチャツールはOculus Riftに特有のもので、現在実行しているアプリのフレームのキャプチャが可能です。)
4.ロストフレームキャプチャツールで[Record (記録)]をクリックします。
5.ヘッドセットをいろいろな方向に動かすなど、アプリで若干のアクションを実行します。
6.ヘッドセットを外し、ロストフレームキャプチャツールウィンドウ内のストップボタンをクリックします。
7. (任意)[Save(保存)]をクリックします。キャプチャしたデータがOculusデバッグアーカイブ(ODA)ファイルに保存されます。そのデータに、app_1000.oda(シェーダールーティンのループ変数が1000に設定されている場合)など、適切な名前を設定します。
これでロストフレームを視覚化することができます。さらに、カーソルを動かしてフレームリストを確認することもできます。

フレームがロストしたときに特定のオブジェクトが現れる場合、そのオブジェクトが持つポリゴン数を減らす、使用しているシェーダーを簡素化する、アルファブレンディングルーティンを簡素化する等、レンダリングの最適化を行う必要があるかもしれません。
今度は、Windowsイベントトレーシング(ETW)を使用して、下層イベントのキャプチャを行います。これによりアプリの挙動をさらに詳しく分析することができます。ここではこれを、ovrlogユーティリティを実行して行います(ETWセッションを簡単に開始/停止できます)。イベントはイベントトレースログ(.etl)ファイルにキャプチャされます。Windowsフォーマンスアナライザー(WPA)とGPUViewの2つのツールを使用してイベントストリームを分析します。WPAは、イベントストリームのより上層のビューを提供するものだと思っておいても構いません。基本的に、WPAはイベントストリームのパフォーマンス関連の特性を要約したチャートとグラフを提供します。一方GPUViewは、イベントそのものの粒度の非常に細かいビューを表示します。そのため、GPUViewを使うことによってCPUとGPU負荷の間のインタラクションを詳細に調べることが可能になり、アプリを微調整してこれらの負荷のタイミングとコンテンツを最適化することができます。
1.管理者権限によりWindows CMDウィンドウを開始します。

これ以降のチュートリアルでは、Windows 10上ではovrlog_win10スクリプト、それ以前のバージョンのWindowsではovrlogスクリプトを実行してください。このスクリプトはxperfユーティリティを呼び出し、VRアプリの実行中に起こるイベントをキャプチャするETWトレースセッションを開始します。
ovrlog_win10(またはovrlog)スクリプトを管理者権限で実行する必要があります。カーネルレベルのイベントトレーシングには高い権限が必要です。これはOculusアプリに関連するすべてのイベントをキャプチャするのに必要です。
初めてovrlog_win10またはovrlogスクリプトを実行する場合、次のアクションが発生します。
xperfユーティリティの場所を特定する- アプリのパフォーマンスに関するインサイトを示すイベント定義のマニフェストをインストールする
- ETWセッションを開始するために、
xperfユーティリティを実行する - イベントのキャプチャを開始する
2度目にovrlog_win10またはovrlogスクリプトを実行すると、次のアクションが行われます。
- ETWセッションを停止する
- マニフェストをアンインストールする
- 発生したイベントのフローを分析するために、後でWPAまたはGPUViewへロードできるトレースファイル(
{xperf_folder}\trace\merged.etl)に結果を集約する
2.Xperfフォルダーに、許可ユーザーに対して次のアクセス許可が提供されていることを確認します。
Xperfフォルダーは次の場所にあります。
%PROGRAMFILES%\Oculus\Support\oculus-diagnostics\ETW
すなわち、Xperfフォルダーへの許可は次のように表示されます。

3.以下の4つの手順を妥当な範囲で間隔を空けないようにしつつ問題なく実行できるように、準備をします。
これらの4つの手順を完了した後に、コンソールアウトプットにエラーメッセージが見られる場合(赤色)、%PROGRAMFILES%\Oculus\Support\oculus-diagnostics\ETW\trace\フォルダーを削除してみてください。
これはovrlog_win10またはovrlogを実行する際に、中間トレースファイルと競合しないようにするために必要です。フォルダーを削除した後、もう一度次の4つの手順を実行します。
4.バッファ化ハプティクスアプリを実行し、ヘッドセット内で目視できることを確認します。(必要であればこれをVisual Studioデバッガー内で直接実行することもできます。)
5.管理者権限で、コマンドプロンプトから以下のスクリプトを実行します。
%PROGRAMFILES%\Oculus\Support\oculus-diagnostics\ETW\overlog_win10.cmd
または、Windows 10より前のバージョンのWindowsを使っている場合は、次のようになります。
%PROGRAMFILES%\Oculus\Support\oculus-diagnostics\ETW\overlog.cmd
6.ヘッドセットで確認しながら、VRアプリ内で希望のアクションを実行します。
7. もう一度ovrlog_win10またはovrlogを実行して、キャプチャイベントを停止します。
代わりに、「stop」の引数でovrlog_win10またはovrlogコマンドを実行して、トレーシングを停止することもできます。同じコマンドプロンプトから引数無しでコマンドを再度実行すると、トレーシングが停止します。たいていは、コマンドはこのように使用されます。しかし、この方法は環境変数の設定に依存しているため、失敗する傾向がより強くなっています。
次のファイルが生成されます。
%PROGRAMFILES%\Oculus\Support\oculus-diagnostics\ETW\trace\merged.etl
1.Windowsパフォーマンスアナライザー(WPA)を起動します。これは次の場所にあります。
C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit
2.WPAウィンドウが表示されます。

3.[File(ファイル)] > [Open(開く)]を選択し、上記で生成されたmerged.etlファイルを開きます。
%PROGRAMFILES%\Oculus\Support\oculus-diagnostics\ETW\trace\merged.etl
4.表示は次のようになるはずです。

5.CPU使用量を確認するため、左側のナビゲーションの小さな[コンピュテーション]ウィンドウをダブルクリックします。作業域にCPU使用量グラフ全体が表示されます。

6.z.Buffered.Haptics.exeプロセスが表示されるまで下にスクロールします。CPUリソースの使用割合がとても小さいことを確認できます。

これにより、このアプリのCPUリソースの使用率がパフォーマンス問題の原因ではないことが明確になりました。
7. GPU使用量を表示するため、[Video (動画)]タブを展開します。

小さな動画ウィンドウを確認すれば、GPUがオーバーロードを起こしていることがすぐに分かります。(実は、これはとても極端な例です。一般的なGPU使用量の問題は、ここで見られるほど顕著なものではありません。)
8. 作業域からコンピュテーショングラフを削除し、動画ウィンドウをダブルクリックします。作業域にGPU使用量グラフが表示されます。

この例において、ovrlog_win10またはovrlogが初めて実行された際には、アプリは正常に動作していました。イベントキャプチャ期間の途中で、ヘッドセットが置かれました。その時点で、VRアプリはフリーズし、ひとつのフレームを再レンダリングしました。その期間にコンポジターは呼び出されません。そのため、OVRServer_v64.exeのGPU使用量はゼロになります。その数秒後、ovrlog_win10またはovrlogが再び実行され、イベントキャプチャ期間が終了しました。
9. グラフのうち、アプリとコンポジターの両方が実行されている小さな部分を選択します。その部分でマウスを水平にドラッグします。その後、Ctrlを押しながらマウスのスクロールホイールでズームインして、100分の1秒が見えるようにします。
注: このタイムスケールはフレームレートに近く、毎秒90フレームです。
シェーダールーティン内に作成したループで、ピクセル毎に100、500、1000、またはそれ以上のサイクルをループするループ変数を設定して実験できます。さまざまなループ変数の値が与えられたアプリが見せる挙動は、正確には、使用中のコンピューターの特性と、イベントキャプチャ期間中にどの程度の負荷がかけられていたかによって異なります。
ループ変数が比較的低い値に設定されている場合に100分の1秒にズームインすると、次のような結果になるはずです。

ここではフレームサイクルをはっきりと確認できます。アプリ(緑色)は、フレーム毎にGPUリソースをかなり大量に消費しており、コンポジター(明るい茶色)は同じフレームで少量のGPUリソースを消費しています。
シェーダールーティン内のループのピクセルあたりの実行回数がさらに多い場合、状況はより厳しくなります。

ここでは、アプリがフレーム処理を終了してからコンポジターがフレームをアウトプットするまでの時間は、ゼロではないものの、非常に短くなっています。さらにコンポジターは、アプリと同時にGPU処理を使っています(2つの使用量グラフが積み重なっていることから分かります)。この例において、シェーダールーティンはユーザー体験が快適に感じられる複雑さの限界以上の状態にあります。
シェーダールーティンのループ変数を増加させると、状況はさらに悪化します。

ここでは、フレームサイクルをはっきりと確認することすらできなくなっています。
本質的に、GPUは処理を完了できません。GPUの使用量の多くはz.Buffered Haptics.exeプロセスによるものです。それはそのプロセスをハイライトし、グラフ内の対応するアクティビティを見れば確認できます。ほかのプロセスはGPUをほとんど使っていません。
次にWPA内のトレースデータをもう少し詳しく見ていきましょう。
10. さらにズームインし、アイコンをクリックしてグラフだけを表示させます。

表が消え、グラフのみが表示されます。

11. チャートの最下部を下へドラッグし、ビューを拡大します。

左の[シリーズ]ボックスで、z.Buffered.Haptics.exeとOVRServer_x64.exeの階層を開き、z.Buffered.Haptics.exeの[レンダリング]を選択します。

z.Buffered Haptics.exeプロセスの上のレンダリングに関係するスペースが埋まっています。ご覧のように、z.Buffered Haptics.exe内でのレンダリングに圧倒的に多く時間が費やされています。(これはもちろん、予想どおりの結果です。シェーダールーティンに差し込んだループによるものです。)

WPAでは、アプリが実際にGPUをどう扱っているのかが詳しく表示されませんが、GPUのアクティビティには主要なフェーズとしてレンダリングフェーズとBLTフェーズ(サイクルの最後にあるバッファの複製)の2つがあります。BLTを選択すると、そのフェーズにほとんど時間が割かれていないことが分かります。
12. 左の「シリーズ」ボックスで、OVRServer_x64.exeの「レンダリング」を選択してください。サーバーが使うGPUの時間は、すべてレンダリングに適用されていることが分かります。

マウスを押し続け、サイクルのある時点から次のサイクルの同じ時点までをドラッグすると、継続時間は約11.1マイクロ秒、すなわち90分の1秒であることが分かります。これはRiftのフレームレートです。よって、この例ではフレームレートの条件が満たされていることが分かります。

WPAでVSyncイベントを実際に表示させることはできません。しかし通常は、上の例のようにフレームサイクルを特定することは可能です。
次の手順では、GPUView内のETWトレースファイルを見ていきます。WPAは各ハードウェアリソースのパケット(コンピュテーション作業の不可分な集まり)の特定のキューを表示しません。GPUViewが提供するのはそのレベルの分析です。
1.GPUViewを起動します。
c:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\gpuview\GPUView.exe
2.GPUViewを起動すると、まずファイルマネージャダイアログが表示されます。分析するmerged.etlファイルを見つけて、それをGPUViewに読み込みます。
%PROGRAMFILES%\Oculus\Support\oculus-diagnostics\ETW\trace\merged.etl
GPUViewウィンドウが現れ、アプリ動作中にmerged.etlファイルにキャプチャされた、ETWトレースの高層のビューを表示します。

この例において、merged.etlファイルはループ変数が1500に設定された時点でアプリの挙動を示しています。しかしながら、この挙動はあなたのハードウェアの特性によって異なることがあります。
3.トレースには、カーネルレベルのプロセスやほかのアプリに関連したプロセスを含む、多くのプロセスがキャプチャされています。このチュートリアルはこれらのプロセスのことは扱いません。そこで、シンプルな表示にするため、次の操作によりすべてのプロセスを折り畳み表示にします。
[ビュー] > [プロセス] > [すべてのプロセスを非表示にする]

4.ここで表示するのはz.Buffered Haptics.exeプロセスだけです。それでそのプロセスを右クリックし、[プロセスを非表示にする/表示する]を選択します。

スクリーンの最上部には、ハードウェアリソースが表示されています。スクリーンの最下部には、アプリ内の対応するアクティビティが表示されています。

5.イベントフローのうち関係する部分を選択することによりズームインして、Ctrl-Zを押します。

徐々にズームアウトするには、Zを押してください。元のビューまでズームアウトするには、Ctrl+Hを押してください。
拡大したビューが表示されています(下)。この例では、トレースが始まった時点でヘッドセットは静止していて、それからシーン内を移動しました。ディスプレイの右側では、フレームがスペースで分けられていることを確認できます。それがフレームがドロップしていることを示していることもあれば、そうでないこともあります。これはタイミングによって異なります。

6.タイミングをより慎重に調べるため、セクションを選択してCtrl-Zを押し、さらにズームインします。

7. これでパケットの詳細を確認できるようになりました。

フレームを選択し、スクリーンの最上部に表示されているタイムスケールを確認します。タイムスケールのインクリメントは10 msです。必要なフレームレートである毎秒90フレームを満たすには、フレームサイクルを約11.1 ms以内で完了させる必要があります。フレームには、それよりも長い時間が消費されていることが確認できます。実際、フレーム間の空白が示すとおり、その後に続くフレームも遅延しています。よってこの例では、タイムラインの右側のフレームがひとつ置きにドロップしているということになります。

このズームレベルでは、パケットと呼ばれる色の付いた四角形のスタックが見られます。パケットとは、アプリがCPUまたはGPUで実行する予定の作業の、不可分な最小単位です。フェンスとは、ひとつの演算が次の演算までに完了することを保証する同期プリミティブです。
フェンスは上でピンク色のボックスで表示されています。
紺色のパケットはGPUのワークとなるグラフィックコマンドの集まりです。
8. パケットをクリックします。これで、そのパケットの正確なライフサイクルや詳細情報を確認できます。

GPUViewでは、パケットがスタックになって並べられていて、時間進行は水平軸で示されています。最下部のパケットは現在作業対象になっている項目です。スタックの上部であるほど、パケットが列の後方にいることを示しています。スタックの高さはワークが終了する度に低くなり、ワークがキューに追加される度に高くなります。パケットをクリックすることで、ライフサイクルとパケット同士の従属関係が確認できます。
WPAでは、ズームインして、調べたいフレームの正確なフレームインデックスを特定することができました。それから、そのフレームインデックスを用いて、GPUView内の同じフレームを見つけることができました。これが便利なのは、GPUViewが文脈的なトップダウンビューを提供していないためです。
上の例では、アプリはDirectXドライバーを呼び出し、プリミティブをレンダリングするようリクエストしました。そのドライバーの呼び出しがGPUのワークを作り出しました。つまり、「3D」の部分がハイライトされた選択範囲でそのワークを処理しているということになります。
一般的には、初期化、CPUワークの送信、関連するグラフィックワークの送信、ワークが正しい順で処理されることを保証するフェンスのインストール、CPUのワーク(したがってフェンスも)が終了するまで待機、という流れになります。その後、次のフレームに進みます。
上記において同じGPUパケットは時間軸に沿ってハイライトされています。しかし時間が経つと、キューの先頭(最下部)に来て、処理中になります。パケットの幅は、上部にあるタイムスケールを単位とする処理時間を示しています。
パケットのトレースを取得することも可能です。アプリがシステムに負荷をかけていて、原因が分からない場合は、問題を引き起こした呼び出しの正確なシーケンスを確認することができます。
GPUView内でVSyncを確認できるのは非常に便利です。しかし、[オプション] > [VSyncをトグル]のメニュー項目を選んでも、Oculus Vsyncは表示されません。このメニュー項目で表示されるのはWindowsモニターのVSyncです。
ovrlog_win10とovrlogは、システムが使用しているハードウェアに応じて、NVIDIAまたはAMDのvsyncイベントを含むトレースを生成します。GPUView内でvsyncを表示させるには、ETWトレースを生成する前に次のバッチファイルを実行する必要があります。
%PROGRAMFILES%\Oculus\Support\oculus-diagnostics\ETW\IHVETW\setup.bat
マニフェストのインストール後、NVIDIAのVsyncをキャプチャするイベントは次のとおりです。
NVIDIA-VR-DirectMode VsyncDPC
AMDにも同様のイベントがあります。
GPUViewでトレースを開き、[ツール] > [イベントビューア]をクリックしてから、NVIDIA-VR-DirectModeを有効にすると、トレース図表に赤い垂直の線が現れるはずです。