WWDC Quick Look 💓 By SwiftGGTeam
Optimize app power and performance for spatial computing

Optimize app power and performance for spatial computing

元の動画を見る

ハイライト

Apple は visionOS に常時レンダリングのディスプレイパイプラインを導入し、システムが 90Hz で毎フレームを継続レンダリングします。開発者はレンダリング、入力、ARKit、オーディオ/ビデオ、メモリ使用を最適化する必要があり、さもないとレンダリング締切を逃したアプリは終了されます。

主要内容

空間コンピューティングのパフォーマンス課題

iPhone や iPad では、アプリが更新しないとき画面コンテンツは静止し、GPU は休めます。visionOS では、ユーザーが頭を回す、手を動かす、まばたきするたびに画面を更新する必要があります。アプリがコンテンツを更新しているかどうかに関わらず、システムは常に 90Hz で毎フレームをレンダリングします。

これは次を意味します。

  • レンダリングコストが「動的コンテンツ」から「静的コンテンツ」まで拡張
  • システムが複数アプリを同時に処理する空間アルゴリズムを実行
  • 熱管理がバッテリー寿命ではなくパフォーマンスのボトルネックになる

00:39

パフォーマンス計画の構築

開発段階で Instruments と Xcode Gauges を使用します。RealityKit Trace は空間コンピューティングアプリのパフォーマンスと消費電力分析専用の Instruments 新テンプレートです。

重要な原則:

  • 実機で分析必須 — シミュレーターは実際のワークロードを再現できない
  • 複数のインタラクションシナリオをテスト:オーディオ/ビデオ再生、FaceTime、Personas
  • 複数アプリ同時実行時のリソース競合をテスト
  • MetricKit と Xcode Organizer で本番データを収集

03:34

詳細

レンダリングパイプライン最適化

visionOS のレンダリングパイプラインは 3 段階で構成されます。

  1. アプリ層:コンテンツを更新。メインスレッドは期限内に完了する必要がある
  2. レンダーサーバー:アプリ更新、ユーザー入力、空間データを継続処理
  3. コンポジター:表示リフレッシュレート(通常 90Hz)で画面にフレームを出力

アプリのレンダリングに時間がかかりすぎると、レンダーサーバーが最適レンダリング遅延の締切を逃し、視覚更新が 1 フレーム遅れ、ユーザーはカクつきを感じます。深刻なレンダリング停滞はアプリ終了を引き起こします。

06:28

SwiftUI / UIKit 最適化

// @StateObject の代わりに @Observable を使い、不要なレイアウト更新を減らす
import SwiftUI

@Observable
class TaskStore {
    var tasks: [Task] = []
}

struct TaskListView: View {
    let store: TaskStore
    
    var body: some View {
        List(store.tasks) { task in
            TaskRow(task: task)
        }
    }
}

キーポイント:

  • @Observable はより細かい変更追跡を提供し、不要なレイアウト更新を削減
  • 重なる UI ビューに半透明効果を使わない — 半透明は overdraw を引き起こす
  • シャドウ、ぼかし、マスクなどオフスクリーンレンダリングを引き起こす視覚効果を削減
  • ウィンドウのデフォルトサイズを小さくすることを検討 — ピクセルが多いほどレンダリング作業量が増える

08:33

RealityKit 3D レンダリング最適化

import RealityKit
import SwiftUI

struct OptimizedSceneView: View {
    var body: some View {
        RealityView { content in
            // 複雑なリソースを非同期に読み込み、メインスレッドのブロックを避ける
            if let entity = try? await Entity(named: "optimized_scene", in: realityKitContentBundle) {
                content.add(entity)
            }
        }
    }
}

キーポイント:

  • Reality Composer Pro から最適化ファイルをエクスポートし、テクスチャ圧縮を自動取得
  • 共有マテリアルのメッシュ部分をマージし、独立メッシュ数を削減
  • 三角形と頂点数を小さくする
  • 大型または半透明コンテンツには Custom material + unlit surface を使用し、高コストなライティング計算を回避
  • エンティティを事前作成し、isEnabled やシーン階層で表示/非表示を制御。頻繁な作成/破棄を避ける

10:58

Metal 没入型体験最適化

import Metal
import CompositorServices

// 各フレームで最新の foveation map と pose prediction を問い合わせる
func renderFrame(layerRenderer: LayerRenderer) {
    guard let frame = layerRenderer.queryNextFrame() else { return }
    
    // 最後の瞬間に入力データを問い合わせる
    let timing = frame.predictTiming()
    let pose = frame.posePrediction(at: timing.presentationTime)
    let foveation = frame.queryFoveationMap()
    
    // GPU 作業をエンコード
    encodeRenderWork(pose: pose, foveation: foveation)
    
    // コンポジタが毎フレーム新しい画像を受け取れるようにする
    frame.submit()
}

キーポイント:

  • CompositorServices でレンダーサーバーをバイパスし、レンダリングサーフェスをコンポジターに直接送信
  • 毎フレーム新しい foveation map と pose prediction をクエリ
  • GPU 作業をエンコードする直前の最後の瞬間に入力データをクエリ
  • 長時間のフレーム停滞を避ける — さもないとシステムがアプリを終了

16:56

入力パフォーマンス

ユーザーは目、手、音声、ハードウェア入力でアプリとインタラクションします。入力更新はメインスレッドで処理され、8 ミリ秒以内に完了する必要があります(90Hz リフレッシュレートでの最適遅延)。

最適化の提案:

  • RealityKit コンテンツに物理コライダーを追加する際は static collider を優先
  • 重なるインタラクティブコンテンツを削減し、ヒットテスト作業量を低減

18:57

ARKit 最適化

import RealityKit

// "once" トラッキングモードを使い、継続的なトラッキングコストを避ける
let anchor = AnchorEntity(.head, trackingMode: .once)
entity.components.set(AnchorComponent(.head, trackingMode: .once))

キーポイント:

  • AnchorComponent"once" 追跡モードで継続追跡コストを回避
  • 永続アンカーと一時アンカーの総数を最小化
  • 使用前の最後の瞬間に ARKit データをクエリ
  • シーン理解メッシュの衝突データが不要なときは速やかに無効化

20:17

オーディオ/ビデオ最適化

Spatial Audio はデフォルトでオン。システムはユーザー位置、環境、音源情報をリアルタイム処理します。Spatial Audio 作業量を減らす 3 つの方向:

  • 同時再生音源数を削減
  • 移動音源数を削減
  • soundstage サイズを縮小

ビデオ再生の推奨:

  • 最高のパフォーマンスと消費電力のため 24 または 30fps ビデオを使用
  • 同時再生・レンダリングするビデオ数を削減
  • ビデオ再生中は UI や 3D コンテンツ更新を最小化

22:16

熱管理とメモリプレッシャー

import Foundation

// 熱状態の変化を監視し、コンテンツを動的に調整
NotificationCenter.default.addObserver(
    forName: ProcessInfo.thermalStateDidChangeNotification,
    object: nil,
    queue: .main
) { _ in
    let state = ProcessInfo.processInfo.thermalState
    switch state {
    case .serious, .critical:
        reduceVisualQuality()
        lowerUpdateRate()
    default:
        break
    }
}

キーポイント:

  • thermalStateDidChangeNotification を購読し、熱プレッシャーに応じてコンテンツを動的調整
  • Xcode の thermal inducers で高熱状態をシミュレート
  • オフスクリーンレンダリング、ウィンドウ数、メディアコンテンツを削減してレンダリングメモリ割り当てを低減
  • テクスチャ解像度、メッシュジオメトリサイズ、パーティクル数を下げて RealityKit メモリを削減

25:19

重要ポイント

  • やること:visionOS アプリ向けパフォーマンスベンチマークテストスイートを構築

    • 価値がある理由:空間コンピューティングの常時レンダリングFeatureは、パフォーマンスリグレッションがユーザー体験に直接影響し、アプリ終了を引き起こす
    • 始め方:Instruments の RealityKit Trace テンプレートで主要シーンのフレーム時間、GPU 使用率、システム消費電力を記録し、CI フローに組み込む
  • やること:動的品質ダウングレードシステムを実装

    • 価値がある理由:熱プレッシャーはいつでも発生する — 能動的なダウングレードはシステム終了より良い
    • 始め方thermalStateDidChangeNotification を監視し、.serious 状態でマテリアル複雑度、パーティクル数、レンダリング解像度を下げる
  • やること:3D アセットの読み込み戦略を最適化

    • 価値がある理由:複雑なアセットはメインスレッドをブロックし、メモリプレッシャーを増加
    • 始め方Entity(named:in:) で Reality Composer Pro エクスポートの最適化ファイルを非同期読み込み。同一アセットのエンティティをインスタンス化
  • やること:没入型体験向け Metal レンダリングパイプラインを実装

    • 価値がある理由:CompositorServices でレンダーサーバーをバイパスし、フレーム送信タイミングを直接制御
    • 始め方LayerRenderer でフレームタイミング、pose prediction、foveation map をクエリ。毎フレームコンポジターに新画面を送信
  • やること:マルチアプリ共存に優しいリソース使用戦略を設計

    • 価値がある理由:visionOS ユーザーは複数アプリを同時使用 — リソース競合は常態
    • 始め方:テスト時に他アプリを積極的に実行。Shared Space でのレンダリング遅延とシステム消費電力を監視。リソース制約下でも 90Hz を維持

関連セッション

コメント

GitHub Issues · utterances