WWDC Quick Look 💓 By SwiftGGTeam
Meet RealityKit Trace

Meet RealityKit Trace

观看原视频

Highlight

Instruments 15 introduces the RealityKit Trace template, providing frame-by-frame rendering analysis, bottleneck detection, and system power impact measurement to help developers optimize spatial computing apps on visionOS.

核心内容

你的 visionOS 应用在模拟器上运行流畅,但到了真机上却卡顿。问题可能出在 GPU、CPU、离屏渲染,或者只是 SwiftUI 视图 body 里做了太多工作。在普通平台上,偶尔掉几帧用户可能不会注意。但在 visionOS 上,渲染延迟直接对应视觉不连贯,轻则体验下降,重则引发不适。

RealityKit Trace 是 Instruments 15 新增的模板,专门为 visionOS 的渲染管线设计。它覆盖应用进程、渲染服务器和合成器三个组件,是专用工具组。

渲染管线架构

00:49)一帧画面从代码到屏幕,经过三个环节:

  1. 应用进程:你的 SwiftUI/RealityKit 代码运行在这里,提交绘制命令
  2. 渲染服务器:系统守护进程,统一管理 GPU 资源,执行实际渲染
  3. 合成器:将渲染结果合成到显示缓冲区,应用时间扭曲补偿头部运动

在 Shared Space 中,多个应用共享渲染服务器和合成器。其他应用的渲染工作会影响你的帧率。在 Full Space 中,只有你的应用在渲染,性能不再受其他应用干扰。

01:52)测试策略:调查性能问题或分析功耗时,先在隔离环境中测试(Full Space);验证与其他应用共存的体验时,在 Shared Space 中测试。

RealityKit Frames Instrument

04:13)这个 instrument 逐帧追踪渲染时间。每帧用颜色标记:绿色表示远在截止时间内完成,橙色表示刚好赶上,红色表示超时掉帧。目标帧率是 90fps,但系统会根据内容和环境动态调整。

05:49)RealityKit Metrics instrument 在顶层直接标出检测到的瓶颈。这些瓶颈由整个渲染管线的综合计时信息生成。优先关注与红色帧同时出现的瓶颈。

实战案例:Hello World 应用优化

演讲者用一个 Hello World 应用演示了完整的优化流程。应用有启动屏幕(SwiftUI)、轨道物体列表(3D 模型)和沉浸式地球轨道体验。

案例一:SwiftUI 视图的离屏渲染

07:48)启动屏幕有很多掉帧。RealityKit Metrics 发现瓶颈是 Core Animation Encoding。展开 Core Animation 轨道,发现 offscreen prepares 数量远超推荐阈值——平均 180 次。

10:50)检查 SwiftUI 代码,发现按钮上使用了 .shadow(radius: 10)。阴影是特别昂贵的操作,尤其是与透明度结合时。禁用阴影后,离屏渲染次数减少了 4 倍,掉帧基本消失。

四种主要操作会触发离屏渲染:shadows、masking、rounded rectangles、visual effects。

案例二:3D 模型面数过高

11:41)轨道物体视图有很多 GPU Work Stalls 瓶颈。展开 3D Render 轨道,发现三角形和顶点数量远超推荐阈值。换用低面数模型后,所有帧都按时完成,3D 渲染统计大幅下降,且视觉质量没有明显损失。

案例三:CPU 过载导致功耗飙升

14:06)缩放地球时交互很卡顿。System Power Impact 指标长时间处于高位。Time Profiler 显示 CPU 平均占用 100%,最重调用栈是 Entity.makeModelEntity.generateCollisionShapes

16:34)问题根源:makeGlobe() 在 SwiftUI 视图 body 中被调用。每次视图状态变化都会重新加载模型和生成碰撞形状——这是昂贵的操作。修复方式:在 ViewModel 中创建可复用的 Earth entity,视图 body 中直接使用缓存的实体。

修复后,功耗回到 nominal 状态,CPU 从 100% 降到 10%。

详细内容

高离屏渲染的 SwiftUI 视图

private struct Item: View {
    var module: Module
    let cornerRadius = 20.0

    var body: some View {
        NavigationLink(value: module) {
            VStack(alignment: .leading, spacing: 3) {
                Text(module.eyebrow)
                    .font(.titleHeading)
                    .foregroundStyle(.secondary)
                VStack(alignment: .leading, spacing: 7) {
                    Text(module.heading)
                        .font(.largeTitle)
                    Text(module.abstract)
                }
            }
            .padding(.horizontal, 5)
            .padding(.vertical, 20)
        }
        .buttonStyle(.bordered)
        .shadow(radius: 10)  // 触发离屏渲染
        .buttonBorderShape(.roundedRectangle(radius: cornerRadius))
        .frame(minWidth: 150, maxWidth: 280)
    }
}

关键点:

  • .shadow(radius: 10).buttonStyle(.bordered) 组合触发离屏渲染
  • 在 visionOS 中,空间应用持续渲染,离屏渲染每帧都会发生
  • 静态 UI 必须足够高效以维持系统目标帧率
  • 优化:移除不必要的阴影,或使用预渲染纹理替代实时阴影

实体工厂模式

class EarthEntity: Entity {
    static func makeGlobe() -> EarthEntity {
        EarthEntity(earthModel: Entity.makeModel(
            name: "Earth",
            filename: "Globe",
            radius: 0.35,
            color: .blue)
        )
    }

    static func makeCloudyEarth() -> EarthEntity {
        let earthModel = Entity()
        earthModel.name = "Earth"

        Task {
            if let scene = await loadFromRealityComposerPro(
                named: WorldAssets.rootNodeName,
                fromSceneNamed: WorldAssets.sceneName
            ) {
                earthModel.addChild(scene)
            } else {
                fatalError("Unable to load earth model")
            }
        }

        return EarthEntity(earthModel: earthModel)
    }
}

关键点:

  • makeGlobe() 使用程序化模型,创建成本低,无异步加载
  • makeCloudyEarth() 从 Reality Composer Pro 异步加载,可能有数帧延迟
  • 在视图 body 中调用 makeGlobe() 会导致每次状态变化都重新创建模型
  • 性能建议:在 ViewModel 中缓存实体,避免在 body 中执行昂贵操作

优化后的 ViewModel

class ViewModel: ObservableObject {
    // 缓存可复用的地球实体,避免在视图 body 中重复创建
    @Published var globeEarthEntity: EarthEntity = .makeGlobe()
    @Published var orbitEarthEntity: EarthEntity = .makeGlobe()

    @Published var isShowingGlobe: Bool = false
    @Published var isShowingOrbit: Bool = false
    @Published var orbitImmersionStyle: ImmersionStyle = .mixed

    // Orbit 配置
    @Published var orbitEarth: EarthEntity.Configuration = .orbitEarthDefault
    @Published var orbitSatellite: SatelliteEntity.Configuration = .orbitSatelliteDefault
    @Published var orbitMoon: SatelliteEntity.Configuration = .orbitMoonDefault
    @Published var orbitSunAngle: Angle = .degrees(150)
}

关键点:

  • 实体在 ViewModel 初始化时创建一次,之后复用
  • @Published 属性变化触发视图更新,但实体本身不再重新加载
  • 配置对象(EarthEntity.Configuration)轻量,适合频繁更新
  • 分离”实体”(重)和”配置”(轻)是 RealityKit 应用的关键架构模式

Orbit 视图

struct Orbit: View {
    @EnvironmentObject private var model: ViewModel

    var body: some View {
        Earth(
            world: model.orbitEarthEntity,  // 使用缓存的实体
            earthConfiguration: model.orbitEarth,
            satelliteConfiguration: [model.orbitSatellite],
            moonConfiguration: model.orbitMoon,
            showSun: true,
            sunAngle: model.orbitSunAngle,
            animateUpdates: true
        )
        .place(
            initialPosition: Point3D([475, -1200.0, -1200.0]),
            scale: $model.orbitEarth.scale)
    }
}

关键点:

  • world: model.orbitEarthEntity 使用 ViewModel 中缓存的实体
  • animateUpdates: true 启用状态变化的动画过渡
  • .place() 设置 3D 空间位置(单位:米)
  • 视图 body 中只做轻量配置传递,不做模型加载

核心启发

1. 建立性能基准测试

在应用的关键场景(启动、主交互、复杂场景加载)中,使用 RealityKit Trace 录制性能 trace,建立帧率、GPU 时间和功耗的基准值。后续每次代码变更后对比基准,及早发现性能回归。入口:Instruments 15 的 RealityKit Trace 模板 + 真机测试。

2. 优化 SwiftUI 视图的离屏渲染

检查应用中所有使用 shadow、mask、rounded rectangle 和 visual effect 的地方。在 visionOS 上,这些效果每帧都会触发离屏渲染,成本远高于传统平台。入口:RealityKit Metrics 的 Core Animation 轨道中的 “Offscreens” 指标。

3. 将昂贵操作移出 SwiftUI 视图 body

模型加载、碰撞形状生成、纹理解码等操作绝对不能放在视图 body 或 @Published 属性的 getter 中。这些操作应该在 ViewModel 初始化时完成,或通过异步任务在后台完成。入口:Time Profiler 检查视图 body 的调用栈,寻找 Entity.makeModelgenerateCollisionShapes 等调用。

4. 简化 3D 资产

使用 Reality Composer Pro 检查模型的三角形数、顶点数和绘制调用。如果超过推荐阈值,简化模型几何或使用实例化(instancing)复用相同网格。入口:Reality Composer Pro 的统计面板 + RealityKit Metrics 的 3D Render 轨道。

关联 Session

评论

GitHub Issues · utterances