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コマンドごとの実行計測では正しい値が得られない
    • 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単位で計測している)ため信用できる