Open3
パフォーマンスチューニング(Mobile GPU)


AndroidのGPU時間、正しく測れている?
- RenderDocの計測は、TB(D)Rにおいて有用な値にならない
- Tile-Basedであることが今回の問題
- UnityのFrameTiming.gpuFrameTime
- フレーム全体のGPU時間計測で、URP/HDRP標準でUI(Display Stats)が存在する
- ただ、OpenGL ES 3.xにおいて不正な値を示す
- UnityのProfiling.Recorder API: Profiling.Sampler
- 黒川さんの記事(Reflectionを使用してProfilingSamplerを収集する方法)
- 値の計測のされ方によっては、有用でないものがある(RenderDocと理由は同じ)
- UE5.3の"stat unit", "stat gpu"による計測
- Androidでは標準で動かない
- ソースコードを調べると、有効にする方法はある
- 値の計測のされ方によっては、有用でないものがある(RenderDocと理由は同じ)
- そもそもこれらのGPU計測はどのようにおこなっているか?
- 通常のGraphics APIの仕様にある機能を使用している(GPUベンダ固有のカウンタは使用していない)
- 機能:GPUタイムスタンプ
- GPUコマンド列の中に開始と終了を書き込み、差分をとる
- Vulkan:vkCmdWriteTimestamp
- GLES3:GL_EXT_disjoint_timer_query
- Androidでは動作にクセがある(Unityで正しく測れないことに繋がる)
- queryPoolにコマンドを定義する
- RenderDocの計測手法も基本的にはこれ
-
根本原因はIMRとTB(D)Rの動作の違いにある
- IMRとMRとTB(D)Rでは、論理的なRenderingPipeline構築手法が異なっている
- IMRのRenderingPipeline実行のながれ
- 破棄されないフラグメントは、VRAMのRenderTargetに読み書きが実行される
- DepthTest, Blend, Store...
- IMRのdrawコマンド実行はひとかたまりで実行されるため、drawコマンドのGPU時間の計測に意味がある
-
TB(D)Rのdrawコマンドはタイルごと実行されるため、
- チップ外へのメモリアクセスを最小限に抑えるためにも、タイルベースが採用されている
- RenderTargetごとに大きく2つのパスに分かれる
- Binnig Pass
- drawコマンドたちのジオメトリを、タイルに振り分ける
- Adreno: Binning Pass, Mali: Geometry Processingと呼ぶ
- 前提として、RenderTargetのTile分割は端末に依存する
- TB(D)Rでは、頂点シェーダはそのままBinning Passが呼ばれるわけではなく、ドライバが加工したものを使用している
- Binning Passの動作実行手順
- drawコマンドの実行は上記頂点シェーダ+プリミティブアセンブリのみを対象としているため、drawコマンドごとの実行単位で計測すると、Rendering Pass分の計測ができない
- Rendering Pass
- Adreno: Rendering Pass, Mali: Fragment Processingと呼ぶ
- Rendering Pass全体フロー
- タイルごとにループする
- タイル中のdrawコマンドの描画
- 終われば結果をRenderTargetへStore
- 次のタイルへ
- ラスタライゼーション~フレームバッファへのStoreの処理は、複数のタイルで細切れで実行される(他のdrawやタイルストアと混ざって散在する)
- そのため、drawコマンドごとの実行計測では正しい値が得られない
- Binnig Pass
- Maliドライバ開発者の発言では
- 「draw callはひとかたまりの実態として存在しない」
- 「timer queryでdraw callを計測しようとするな」
- 「drawのフラグメントシェーディングはタイルごと」
- 「どのタイルかはトライアングルたちの位置による」
- RenderDoc開発者の発言では
- 「RenderDocはデバッガであってプロファイラではない」
- 「プロファイラであるとは宣伝されていない」
- でもタイムスタンプで計測できているのでは?
- => Adrenoでは「最後のタイル」での処理時間が結果に書き込まれている
- => ただ、実装依存で結果が変わるため、厳密な計測には使えない
- 最終、GPU負荷計測はどうすればよい?
- => RenderTarget単位での計測をおこなえば良い(TB(D)R用語で言えばRenderPass単位での計測)
- RenderPass:RenderTarget 1つの実行単位(VulkanのVkRenderPassとは関係の深い別の概念)
- RenderTarget 1つの実行単位:Binning ~ 最後のタイルストア
- RenderPassを囲うようにタイムスタンプを設定することで、計測が可能
- Unityの場合
- FrameTimingManager...Vulkanのみ使用可能とするしかない
- Profiling.Recorder APIから値を取得する方法
- RenderPass単位とわかる計測値だけ信用すること
- URPの場合、DrawOpaqueObjects, DrawtzransparentObjectsは同一RenderTargetの処理で、かつタイムスタンプが同一RenderPass内になるため、正しい値は得られない
- UEの場合
- "stat unit", "stat gpu":コマンドによるリアルタイム表示は、(場合によるが)少し改造すれば活用可能
- DeviceProfiles.iniで設定される、GPUタイムスタンプ関連のコードパスの有効/無効を決める変数"CVarVulkanSupportsTimestampQueries"を設定すると計測可能
- "stat unit"...RenderPass外であり、有用
- "stat gpu"...調べる必要がある
- 通常オブジェクト描画 SCOPED_GPU_STAT(..., Basepass)はRenderPass内にタイムスタンプが存在するため、信用できず
- RDG_RHI_GPU_STAT_SCORE(..., MobileSceneRender)やRDG_GPU_STAT_SCORE(..., PostProcessing)はRenderPass外にタイムスタンプが存在する(=RenderPass単位で計測している)ため信用できる
- "stat unit", "stat gpu":コマンドによるリアルタイム表示は、(場合によるが)少し改造すれば活用可能

資料が公開されました。