【Unity】VisionPro向けUnityアプリのパフォーマンスチューニング方法【AppleVisionPro】

ARアプリのスタートアップ会社のGraffity でエンジニアをしてますcovaです。
UnityのVisionPro 向けSDKであるPolySpatialを利用してゲームを開発するにあたり
避けては通れない道として `パフォーマンスチューニング` があります。

ここの部分で四苦八苦したことを備忘録として残しておこうと思います

# TL;DR
- VisionPro の仕様を知っておく必要がある
- 実際のパフォーマンスを測定するときはXcode の RealityKit Trace を使いましょう
- Unity 側のProfiler はEditor上でのパフォーマンスチェック程度にとどめましょう
- 基本的にはUnityのパフォーマンスチューニングの方式が利用可能。ただし、VisionOS特有のチューニングはなさそう。


# 経緯
Graffityで開発しているハンドトラッキングを活用したゲームで、途中から極端にパフォーマンスが下がる問題がありました。
基本的に知見が世に出回ってないため、ひたすらTry&Error を繰り返してひたすら実験しました


# おさえておきたいAVPの仕様

まずは以下の公式ドキュメントを読みましょう.

:::note warn
※長いですが頑張って全てに目を通しましょう
※長いですが頑張って全てに目を通しましょう
※長いですが頑張って全てに目を通しましょう
:::

https://developer.apple.com/documentation/visionOS/analyzing-the-performance-of-your-visionOS-app#Inspect-frame-rendering-performance

ざっくりとした要約としては

- AVP は `９０FPS`
    - `９０FPS` なので1frameは 11ms以内に収めないといけないです
        - Quest3 が72FPSなのでさらにパフォーマンス維持の条件は厳しくなります
    - :::note warn 
      ・パフォーマンスが低下するとフレームレートが半分になります
      ・フレームレートは徐々には下がりません
      ・パフォーマンスが厳しいとなった閾値タイミングで急に半減します
      :::
- `Reality Kit Trace` というProfiler ツールを使いましょう
- レンダリングパイプラインは現在のフレームのGPU処理中に次フレームのCPU処理を行うような形です
    - 90FPSキープのためには CPU/GPU ともに１１ms以内に収める必要があります

## ドキュメントの解説
### `Reality Kit Trace` 導入方法
![スクリーンショット 2024-05-24 17.41.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65257/59ad343b-128a-aeb2-8116-3e04bc049bed.png)

1. VisionPro 実機向けビルド用のXcodeProjectを開きます
    - :::note warn 
      シミュレーター向けにUnity側でビルドしてると動かないです
      :::
1. ビルドに必要な各種エラーを解消します
1. ビルド先がVisionPro 実機になっていることを確認します
    - :::note warn 
      Any VisionOS Device(arm64)だと動かないです
      :::
    ![スクリーンショット 2024-05-24 17.44.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65257/faddf81b-74b6-1bee-28f6-6ac798b956bf.png)
1. ビルドの代わりに ⌘I or Xcodeのメニューバーから `Product` → `Profile` を選択してProfile用のビルドを開始います
1. ビルドが完了してアプリが実機に転送されるとApple公式Docのスクショのように `RealityKit Trace` を選択します
1. 選択するとInstrument Window が開いてProfile用の画面が開きます
    ![Trace.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65257/70425f8e-0749-1435-5e43-044e09a9fa3b.png)
    - 画像のモザイクかけている部分に `接続中の実機の名称` , `ビルドしたアプリの名称Bundle名` が表示されていれば準備OKです
1. ６の画像の赤枠で囲っている部分を選択してProfile&アプリ開始


## RealityKit Trace のMetrics について
### RealityKit Frames
- 各種フレーム情報が入ります
    - CPUとGPUの各処理時間がどれくらいかが表示されます
- 各フレームのGPUでRenderingしている間に次のパイプラインのCPUが走るため1行目の１F目→2行目の１F目→3行目の１F目→1行目の２F目という形のループで処理が進んでいきます
- ![RealityKitFrames.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65257/afe62b92-db26-11e4-f95e-03943aacfde7.png)
    - 緑色: パフォーマンス問題なし
    - 黄色： ギリギリフレームに収まっている
    - 赤色： フレームにも収まらないためドロップされたフレーム
- Average CPU/GPU 
    - 各フレーム処理時間を表記
    - 基本的に22ms超えるような場合はコンテンツの内容や構造を見直す必要あり

### RealityKit Metrics
![RealityKitMetrics](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65257/141304aa-a790-daad-adb1-102a7e0a08bc.png)
- 3Dコンテンツについての情報が可視化されています
- 3D Render
    - 選択すると画面下部に詳細が表示されます
    - Mode が `Summary` だと全計測区間におけるGPUに送信した頂点数やポリゴン数、DrawCall数などを確認できます
        - ![スクリーンショット 2024-06-23 17.28.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65257/c32ff6f4-dd52-c8de-ae7a-ef3120f8485f.png)
    - Summray のついてない `Reality Module Metrics` の方を選択すると、そのフレーム `付近` の実測値が表示されます
    - ![スクリーンショット 2024-06-23 17.28.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65257/97510fe3-9135-e9bd-1428-0253953f2780.png)
        - 注意点
            - そのフレーム周辺の情報のログを切り取った形で表示されるので、Start にあるタイムスタンプをよく見て、どこまでが同一フレームなのかを確認する必要があります
            - Countでソートしてしまうと時系列とかの情報がごちゃごちゃになるため基本は非推奨です
            - Countの隣の Importance で `Low` `Moderate` であれば基本的に問題ないです
    - CoreAnimation Renderer
        - UI に関するRendering が表示されます
        - SwiftUI やOpenGL 系での描画を使わない限りは使用しません
    - Entity Commits
        - 全アプリで共有しているReality Kit のEntityの数などを表示する場所です
        - ≒ Object 数 と認識しても問題ないかもしれないです <font color=red>（要検証） </font>
    - RealityKit Animations
        - Skeletal Animation (BoneAnimation) の数です
        - VisionOS 1.x系は結構Skeltal Animation の処理が苦手で40~50体ほどローポリモデルでもSkeltal Animation を行っているとパフォーマンスが半減したりします。それくらい結構苦手分野のようです
    - Reality Kit Physics
        - 物理シミュレーションに関する結果表示用です
        - Collider/Rigidbody の増減の監視等
        - よっぽどえげつない物理シミュレーションを行わない限りは気にしなくても良いです
    - Spatial System
        - 実際はCustomAnchor 数を監視しているだけです
### Run Loops
![RunLoops.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65257/bd8416d6-78cd-6ede-963e-eb70eb416443.png)

 - スレッドの idle/busy 状態を確認できます
 - idle が多すぎる場合はもう少し負荷を加えることが可能です
 - busy が多すぎる場合は過負荷状態なので、処理内容の最適化が必須になります

### Time Profiler
- 各スレッドのCPU使用率と実行時間が表示されます
- `Unity のProfiler に一番近いのはコレ`
- BackTrace(StackTrace) も見ることができるのでここを通じてどこが重いのかを探っていくことが可能です
### Hangs
- メインスレッドが応答しない期間を表示
- いわゆるフリーズ状態かどうかの監視
- Hangしている部分は絶対にほぼ修正必須になるので、基本は発生回数を極力抑えられるようにしましょう

### その他
- Metal Application
    - Command Buffer へ送った回数やタイミングを確認可能
    - 現状Metal をゴリゴリに操作できないため不要
- M2
    - デバイスのチップの動きチェック用
    - ハードウェア側への操作はUnityからはできないので基本無視
- Metal Shader Compiler
    - Shader コンパイルタイミングを可視化
    - 現状Unityからは干渉できないので基本無視
- backboardd
    - 用途不明 <font color=red>（要検証） </font>
- アプリ名
    - 各スレッドの実行時間などを閲覧可能
    - こちらもGPGPU(ComputeShader) は未サポート状態なので現状は無視してOK

# パフォーマンスチューニングで見ておくべきMetrics の項目
UnityのProfilerに対応している部分を特に重視！

1. RealityKit Frams
    - まずはざっくりどの辺りで処理落ちし始めるのかを掴むのが大事
    - 黄色・赤をとにかく探す
    - あとは11ms秒で処理が終わらなくなり始めたエリアを探して原因のあたりをつける
1. RealityKit Metrics
    - 描画周りはだいたいここ見ればOK
    - Battery に関してはAR/MR系だとパススルー映像の処理や各種センサー類がフル稼働なので９０を下回るようになるのは結構無理難題
    - ３DRenderでDrawCall などUnity でいうところの Profiler のRendering の項目を確認可能
        - めっちゃ大事
        - だいたいDrawCall や頂点数/ポリゴン数でHigh で怒られている場合は対応必要
    - RealityKit Animations の個数推移は監視必須
        - 50~100はまずパフォーマンスが半減すると思っていてOK
        - パフォーマンスを維持しつつの目安は30前後
1. Time Profiler
    - CPU Profiler に一番近いがIL2CPPを通した後のコードの処理なので、Unityの開発者が触れるコードに辿り着くのは至難の業
    - クラッシュやスレッドでの処理の実行順を追うのはここしかない
        - トラブルが起きた場合はここを気合いで見ていくしかない

# パフォーマンスチューニング
## コードのパフォーマンスチューニング

### 情報更新の最小化
- Entity の作成/破棄の量を抑える必要あり
    - ObjectPoolの利用
    - Instantiate/Destroy の代わりにSetActive(VisionOS でいうところの isEnabled) で制御
- イベントドリブンな設計
    - Update での毎フレームの更新を必要最小限にする
    - イベントドリブンで必要なタイミングでのみ処理を発火させるようにする
    - `UniRx, R3` などが非常に相性が良い
    - 不必要なタイミングでSwift UIの再描画依頼をしない

### Asset について
- AssetLoad は基本的に非同期読み込みAPIを利用してメインスレッドのブロックを回避しましょう

### Physics
Unity と全く同じ
- Collider はシンプルな形でいけるならそちらを利用する（MeshCollider は可能な限り避ける)
    - ARMesh などやむを得ない場合しか使わないようにする
- そもそも物理演算の対象数を減らす
- Collider の数は必要最小限にとどめる


## 描画パフォーマンスチューニング

:::note warn
まずは公式ドキュメントに目を通しましょう
:::
https://developer.apple.com/documentation/visionos/reducing-the-rendering-cost-of-realitykit-content-on-visionos

あとWWDC24 の記事も同じ内容+一部テクニックを解説してたりもします。

https://developer.apple.com/videos/play/wwdc2024/10186/


### 推奨環境
- SharedSpace (アプリのホーム画面とかに並ぶようなコンテンツ)
    - 250 Drawcall 以下
    - 頂点数/ポリゴン数: 25万以下
- Full Space (Unityで作る XR系コンテンツ　. 他のアプリが同時に映らないようなコンテンツ)
    - 500 Drawcall 以下
    - 頂点数/ポリゴン数: 50万以下

### 半透明の描画について
- PBR(物理ベースレンダリング) を行う場合はOpaque(不透明Object)必須
    - なるべくObject の大きさは小さい方が良い

### Asset について
- Texture/Mesh/Audio/Video コンテンツのアセットサイズは言わずもがなですが小さくしましょう

### SkeltalAnimation について
- Animation させるMesh の頂点数が多い場合は可能な限り削減しましょう
- GPU側で 頂点を動かしてAnimation させるのでとにかく不要なWeight 減らしたり、Animationさせる領域を最小限にしましょう


### Particle
- 少ない数のParticle を同期的にたくさん出すより、たくさんのParticle を数個のObjectから出す方が良い
    - Particle コンポーネントの数は少なければ少ないほど良い
- オーバードローが多くならないように particle のMeshの形状を最適化したり、半透明の使用を避ける
- 


# 最後に
- 現在もほとんど情報が出回っていないため、各種フォーラム等でいろんな開発者が人柱になった事案をヒアリングする等の情報収集がとにかく必要なフェーズです
- 基本的にはUnityの最適化の知見は有用ですが、そのまま適応できない場合も多いため要注意です


# 参考

- 公式のRealityKit Traace の説明動画

https://developer.apple.com/jp/videos/play/wwdc2023/10099/

- WWDC24 の描画最適化周りの動画

https://developer.apple.com/videos/play/wwdc2024/10186/