大多数浏览器和
Developer App 均支持流媒体播放。
-
认识 RealityKit Trace
了解如何使用 RealityKit Trace 来提高空间计算 App 的性能。探索该平台的性能分析指南,并了解 RealityKit Trace 模板如何帮助你优化 App 的渲染。我们还将提供有关分析 App 中各种类型内容的指南,以帮助查明性能问题。
章节
- 0:44 - Rendering
- 2:17 - Profiling spatial apps
- 3:34 - Introduction to RealityKit Trace
- 7:38 - Optimizing offscreen passes
- 11:30 - Optimizing asset rendering
- 14:02 - Optimizing system power impact
- 17:55 - Overview of optimized World app
- 19:01 - Recommendations
资源
相关视频
WWDC23
WWDC21
Tech Talks
-
下载
♪ 悦耳的器乐嘻哈 ♪ ♪ Sarina Wu:大家好 我是 Sarina 是 RealityKit Tools 团队的 一名软件工程师 Harjas Monga:我是 Harjas 一名性能分析工具工程师 Sarina:我和 Harjas 今天会介绍 Instruments 中的 RealityKit Trace 模板 我们会为你展示此模板如何帮你优化 空间体验的性能 在空间计算中 性能是用户体验的关键所在 了解如何优化空间体验 我们会简要介绍 此平台上的渲染工作原理 展示如何在 Instruments 中使用 RealityKit Trace 模板做性能分析 并简要介绍 其他可用来优化内容的有力工具 此平台有独特的性能约束 要想理解这些内容 你首先需要理解 渲染的工作原理 渲染包括 App 进程、 渲染服务器 以及合成器 App 与这些组件的交互方式 由你创建的体验类型决定 我们来看一下你可以创建的空间 App 体验类型以及对应的渲染方式 平台上的 App 可以进入共享空间 或全空间 要考虑到不同的渲染方式 会带来不同的性能影响 多个 App 并行运行时 都在同一空间进行渲染 因此我们称之为共享空间 也就是说你的 App 性能 会因为渲染服务器同时渲染其他 App 而受影响 渲染服务器与合成器合作 产生最后画面帧 当你的 App 进入全空间 所有其他可见 App 都会隐藏 也就是说你的 App 性能 不再受当前隐藏 App 的 渲染进程影响 想要进一步了解 关于如何进入全空间的信息 请查看讲座 “SwiftUI 助你超越窗口的限制” 基于刚才的介绍 我们推荐两种 渲染 App 的方式 无论何时 在检查性能问题 或分析系统功耗影响时 都应单独分析 App 性能 从而了解 App 对于系统性能的影响 如果你希望与其他 App 并行运行 就应该将你的 App 与其他 App 一起进行分析 这对于了解 你的 App 的用户体验至关重要 下面我们分析 一个空间 App 的性能来为你展示 如何使用 RealityKit Trace 模板 单独优化你的 App 性能 我们一直在分析 Hello World 希望确保它没有性能问题 Sarina:这是 App 的开始界面 也是一个 SwiftUI 视图 此视图有个 Objects in Orbit 按钮 可以轻点此按钮进一步了解 关于绕地球运行物体的内容 点击按钮可以打开一个新视图 其中列举了正在绕地球运行的物体 此视图包含这些物体的 3D 模型 如卫星、 月球 以及望远镜 在这个视图中 还有一个 View Orbits 按钮 若要进一步查看可以轻点此按钮 这会打开一个视图 展示地球 和环行的卫星 带来沉浸式体验 我们为这些模型 使用了细节丰富的资源 我想可能会影响 此 App 的性能 在沉浸式体验中 我们可以看到卫星 绕地球运行的轨迹动画 我们还可以放大地球 看到更多细节 此交互场景卡顿严重 我觉得可能有些性能问题 我和 Harjas 使用了 RealityKit Trace 模板 对此空间体验进行性能分析 Harjas 你能给我们介绍一下吗? Harjas:当然可以 我们来看一下 RealityKit Trace 的各种功能 RealityKit Trace 是 Instruments 15 的 新模板 可以用来分析真实设备或模拟器 为得到最准确可操作的信息 最好对真实设备进行性能分析 如果对模拟器进行性能分析 时序信息不一定完全准确 因为 Mac 上和真实设备上的 软硬件有差异 但你仍然可以用它来快速迭代 和改进一些 不基于时间的数据 RealityKit Trace 模板包含几个工具 第一个要看的工具 是 RealityKit Frames 工具 此工具追踪 设备渲染的每一帧 你可以放大这些帧的视图来查看 渲染每一帧所需的时长 通过此工具 可以检查渲染帧的每个阶段 所花费的时间 至此你可大致了解渲染管线的哪个部分 可能导致性能问题 为了获得流畅的用户体验 你的 App 应该能够达到每秒 90 帧 但操作系统并非总以 90 fps 为目标 它会以最适合所显示内容 和设备所处环境的帧率进行渲染 因为帧率可以改变 所以每一帧都有 完成渲染的截止时限 这样设备才能达到当前的目标帧率 帧分为三类: 早在截止时限前完成渲染的帧 在截止时限内刚好完成渲染的帧 以及超截止时限完成渲染 导致丢帧的帧 这些分类分别用 绿色、橙色和红色进行标示 超过截止时限的帧 会对用户体验产生负面影响 如果缩小帧的视图并以全局视角查看帧 颜色标示可让你快速找到 追踪中有问题的部分 你可以将性能检查范围缩小到 丢帧较多的区域 除了查看单个帧 此工具还可显示系统在 CPU 或 GPU 渲染每帧花费的平均时间 下一个要看的工具 是 RealityKit Metrics 工具 从全局视图来看 此工具绘制出 检测到的所有瓶颈 这些瓶颈是通过查看 整个渲染管线的 综合时序信息而生成的 优先列出与超时限完成渲染的帧 同时出现的瓶颈 在下面的细节视图中 可以看到 这些 RealityKit 瓶颈 按严重程度和类型进行了总结 你可以进一步了解 此工具发现了哪种瓶颈 以及对整体性能的影响有多大 在扩展的细节视图中 该工具提供了 进一步 诊断瓶颈的建议 以及缓解瓶颈的步骤 展开 RealityKit Metrics 轨道 可以看到渲染管线不同组件的 几类指标 这些数据有助于你理解 App 呈现场景的全部复杂度 一些关键指标有相关阈值 可帮你建立对这些指标的合理期望 使用这些指标 可帮你进一步诊断 瓶颈或帧超截止时限的原因 RealityKit Metrics 可显示 运行 App 的 RealityKit 系统 每帧所花费的时长 包括所有内置系统 和你的 App 可实现的所有自定义系统 此信息最好 与 Time Profiler 结合使用 这样可以优化 RealityKit 系统代码 最后 查看 RealityKit Metrics 中显示的 System Power Impact 通道 来了解你的 App 需要 在什么功耗范围内运行 才能提供良好稳定的用户体验 现在我们来看一下 在体验 Hello World 过程中 进行的追踪 App 的第一个场景是 在 SwiftUI 中实现的开始界面 在 Frames 工具中 整个追踪过程中有很多丢帧 这些丢帧可能看起来并不重要 却会严重影响用户体验 可以选择拖拽操作放大 有问题的区域 通过调整时间范围 可以查看 RealityKit Metrics 工具 找出这些持续时间长的帧中的瓶颈 此工具发现在这段时间内 最大的瓶颈是 Core Animation 编码 因此 我要查看 Core Animation 数据 可以点击 RealityKit Metrics 工具旁的三角按钮 然后选择 标有 Core Animation 的轨道 这些 Core Animation 指标 可帮我们了解 丢帧的原因 研究这些指标时 会发现其中一些指标的上下文 可以体现其严重程度 在时间轴上通过颜色编码体现出来 这可以让你了解 这些关键指标的合理阈值 根据时间轴上显示的信息 可以明显看出 App 的 离屏准备次数 超出了推荐阈值 底部总结信息表示这里的平均 离屏准备次数是 180 是一个相当高的平均值 分析 Core Animation 数据时 需要考虑 3 种工作类型 首先 透明和模糊效果 对于系统来说是功耗很高的操作 除非这些效果可有效提升用户体验 否则需谨慎使用 渲染通道的数量与 Core Animation 为整个图像 渲染的层数有关 最后 还要考虑离屏通道 顾名思义 离屏通道 是在屏幕外 而非显示屏上进行渲染的通道 离屏通道需要当前进行渲染的通道 暂停当前工作 进行一些不会显示给用户的工作 但继续正常的渲染通道时 需要离屏通道的输出 离屏渲染对于空间 App 至关重要 与其他 App 平台不同 此平台持续渲染空间 App 因为每一帧都要考虑环境因素 如用户的头文件移动 因此 你的静态 UI 需要足够高效 才能以系统目标帧率进行渲染 共有 4 种工作类型 会触发离屏通道: 阴影、 遮罩、 圆角矩形 和视觉效果 想要进一步了解关于离屏通道的信息 可以观看我们的科技讲座 “揭秘和消除渲染阶段的卡顿” 由于存在大量的离屏通道 所以我要检查此视图的 SwiftUI 代码 找出触发离屏渲染的原因 在 SwiftUI 代码中 此视图没有使用 任何遮罩或视觉效果 但是有些实例应用了阴影 例如 在 SwiftUI 视图项目中 多个按钮应用了阴影 阴影功耗很高 与透明结合使用时功耗尤其高 虽然阴影是种有用的 UI 元素 但对于空间 App 来说 应该在 为用户带来显著效果的情况下使用 我会禁用这些阴影 然后看一下新的追踪 禁用阴影后 在 RealityKit Frames 工具中 几乎没有丢帧问题 而且离屏准备次数降低了 4 倍 我们在 World App 中看到的下一个场景 是物体在轨道中运行的视图 我会打开从该场景中生成的追踪 看是否有可优化的地方 在 Frames 工具中 整个追踪中 到处都是丢帧并且有很多瓶颈 RealityKit Metrics 的细节视图 提供了瓶颈的总结信息
总结中显示大部分瓶颈 与 GPU 工作暂停有关 因为报告最频繁的瓶颈类型 是 GPU 工作暂停 我要再次扩展 RealityKit Metrics 但我这次会使用 3D Render 轨道进行检查
我会选择追踪中 丢帧较多的区域 在选择的时间段内 3D Render 指标报告显示 三角和顶点数量 远超推荐阈值 接下来 我会突出显示追踪中 没有太多丢帧的区域
渲染指标显示 三角和顶点数量 在推荐阈值范围内 也就是说应该评估 App 在场景中使用资源的数量与质量 优化资源渲染时 首先在 RealityKit Metrics 中的 3D Rendering 组中检查三角、 顶点和绘制调用数量 尽量使用简单的形状网格 从而优化渲染指标 使用有相同网格的资源时 可以利用实例化 使用 Reality Composer Pro 中的数据 检查资源的复杂度 这是个新的开发者工具 可以用来组装、编辑和预览 3D 内容 稍后 可以在 Xcode 项目中使用代码 直接访问这些内容 想进一步了解此工具或如何创建良好资源 可以查看 “认识 Reality Composer Pro”讲座 我把之前使用的资源替换为 使用较少多边形的资源 并重新进行追踪 在追踪中 Frames 工具报告显示 所有帧都满足截止时限 再次检查 3D Render 数据
报告显示三角和顶点数量 大大减少 尽管这些资源使用的多边形较少 但体验感受并未降低 我们接下来会追踪 地球模型交互场景 在此场景中 缩放地球模型 会造成严重卡顿 RealityKit Metrics 报告显示 系统功耗影响通道 大部分时间都很高 这表明 App 某部分 效率低下 用户体验可能也会受影响 要尽量保证 App 运行良好 同时尽量让设备的系统功耗影响 长时间处于正常状态 为降低系统功耗影响 进行性能分析时 确保 App 单独运行 才能获得最可操作的信息 可以使用多种方法降低系统功耗影响 首先 确保 RealityKit Metrics 的数据符合预期 如果数据超出预期 设备可能是在高功耗状态下 长时间运行 才实现了流畅的体验 接着 查看 CPU 和 GPU 在执行的工作 对于 CPU 检查 Time Profiler 是否在高功耗绘制区域期间 报告高 CPU 使用率 如果有的话 使用 Time Profiler 对 CPU 密集型代码进行优化 对于 GPU 有性能状态作参考 当 GPU 处于最大阶段时 功耗非常高 如此 应该用 Instruments 的 Metal System Trace 模板 查看 GPU 在执行的工作 这样就能了解哪里需要优化 回到追踪中 Time Profiler 显示在该区域内 达到了平均 100% 的 CPU 使用率 而 GPU 的性能状态 大部分时间都处于最低水平 使用 Time Profiler 可看出 CPU 高使用率的原因 扩展细节视图中 展示了最复杂的堆栈追踪信息 这是 Time Profiler 非常有用的功能 可以让你快速定位 调用树中最耗资源的代码 从这些帧看来 Entity.makeModel 在占用大量的 CPU 时间 下一个帧调用了 Entity.generateCollisionShapes 因此 性能问题似乎是由于 不断生成模型和碰撞形状引起的 这一操作功耗很高 我要打开 Xcode 看看如何解决 这是 Entity.makeModel 函数调用 调用树中显示它占用了大量 CPU 时间 此调用在 makeGlobe 函数内部进行 我可以右键单击 makeGlobe 函数 看看是谁在调用 它是在 Orbit SwiftUI 视图主体中被调用的 这是反模式 应避免使用 因为视图主体需要快速计算 在 SwiftUI 视图主体中 应该避免进行模型加载 或其他高功耗操作 因为每当视图的状态更改时 所有这些高功耗操作都要重新计算 因此 我要把此调用 从视图主体中移除 接下来 在 ViewModel 中 我会添加一个 可复用的 EarthEntity 最后 我会在 Orbit 视图中 使用这个可复用的 EarthEntity 现在 当视图主体重新计算时 App 不再浪费时间重新加载相同的模型 看一下修复后的追踪信息 功耗影响回降到了正常状态 Time Profiler 报告显示 CPU 从 100% 降到了 10% 的使用率 经过优化 只剩少数的瓶颈报告 基本上每帧都能满足截止时限 并且功耗也符合预期 World App 现在对于平台来说 是个充分优化的 App 我们减少了离屏通道数量 用低多边形资源替换了高多边形资源 并且降低了 CPU 使用率和功耗 我们会逐步完成此 App 的优化版本 开始界面看起来很棒 由于阴影对用户体验没有很大的提升 所以这一优化很合理 我们接下来打开 Objects in Orbit 尽管我们没怎么用多边形资源 但这些模型看着还不错 所以说额外的细节只是浪费资源 最后 我们再次打开地球模型 并进行缩放
此交互体验现在非常顺畅 以上就是关于使用 RealityKit Trace 为这个新平台优化 App 的简要介绍 Sarina 还有其他开发人员可用的工具吗? Sarina:有几种工具可用来 为空间计算优化 App 对于优化 SwiftUI 内容 Instruments App 提供了针对特定领域 分析 SwiftUI、 Core Animation 和 Hangs 的工具 可以在 “使用 Instruments 分析 Hangs”讲座中 进一步了解关于 Hangs 工具的信息 还有几种工具可用来 优化基于 3D 资源的内容 Time Profiler 工具可帮你找到 App 花费时间最多的地方 如花费大量时间加载资源的情况 RealityKit Metrics 工具 可帮你诊断哪些场景资源过多 或过于复杂 最后 你可以通过 使用 Reality Composer Pro 组装场景 来检查资源复杂度 要进一步了解 关于 Reality Composer Pro 的信息 请观看讲座 “认识 Reality Composer Pro” 如果 App 中使用了 Metal 最有用的工具就是 Instruments 中的 Metal System Trace 模板 此模版中包含关键指标 如 GPU 时间线、 GPU 计数器和 GPU 性能状态 要进一步了解有关此模板 和其他分析 Metal 内容的工具信息 请观看讲座“发现 Metal 调试、 分析和资产创建工具” 总结一下 性能对于此平台至关重要 App 需要充分优化 才能提供最佳用户体验 可以使用 RealityKit Trace 模板 找到 App 中的性能瓶颈 积极使用其他工具进行性能分析 并在 Reality Composer Pro 中检查内容 也可以帮你发现并解决性能问题 要进一步了解如何使用 RealityKit Trace 模板 优化 App 的信息 请查阅开发人员文档 要更好地了解 此平台的性能 请观看“为空间计算 优化 App 效率和性能”讲座 无论追踪结果如何 请尽情享受优化空间计算 App Harjas:感谢你的观看 ♪
-
-
10:50 - SwiftUI View with High Offscreens
private struct Item: View { var module: Module // The corner radius of the item's hightlight when selected or hovering. 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) } }
-
16:33 - EarthEntity Factory
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) } }
-
16:53 - Orbit SwiftUI View Body
struct Orbit: View { @EnvironmentObject private var model: ViewModel var body: some View { Earth( world: EarthEntity.makeGlobe(), earthConfiguration: model.orbitEarth, satelliteConfiguration: [model.orbitSatellite], moonConfiguration: model.orbitMoon, showSun: true, sunAngle: model.orbitSunAngle, animateUpdates: true ) .place( initialPosition: Point3D([475, -1200.0, -1200.0]), useCustomGesture: model.useCustomGesture, handOffset: model.customGestureHandOffset, isCustomGestureAnimated: model.isCustomGestureAnimated, debugCustomGesture: model.debugCustomGesture, scale: $model.orbitEarth.scale) } }
-
17:26 - SwiftUI ViewModel
class ViewModel: ObservableObject { // MARK: - Navigation @Published var navigationPath: [Module] = [] @Published var titleText: String = "" @Published var isTitleFinished: Bool = false var finalTitle: String = "Hello World" // MARK: - Globe @Published var globeEarthEntity: EarthEntity = .makeGlobe() @Published var isShowingGlobe: Bool = false @Published var globeEarth: EarthEntity.Configuration = .globeEarthDefault @Published var globeEarthOffset: SIMD3<Double> = [0, 0, 0] @Published var globePanelOffset: SIMD3<Double> = [0, -50, 30] @Published var showSatelliteButton: Bool = false @Published var isShowingSatellite: Bool = false // MARK: - Orbit @Published var orbitEarthEntity: EarthEntity = .makeGlobe() @Published var useCustomGesture: Bool = true @Published var customGestureHandOffset: SIMD3<Float> = [0, 0.21, -0.07] @Published var isCustomGestureAnimated: Bool = false @Published var debugCustomGesture: Bool = false @Published var orbitSatelliteScale: Float = 0.9 @Published var orbitMoonScale: Float = 0.9 @Published var orbitTelescopeScale: Float = 0.8 @Published var orbitSatelliteZOffset: Double = 100 @Published var orbitMoonZOffset: Double = 100 @Published var orbitTelescopeZOffset: Double = 100 @Published var isShowingOrbit: Bool = false @Published var orbitImmersionStyle: ImmersionStyle = .mixed @Published var orbitEarth: EarthEntity.Configuration = .orbitEarthDefault @Published var orbitSatellite: SatelliteEntity.Configuration = .orbitSatelliteDefault @Published var orbitMoon: SatelliteEntity.Configuration = .orbitMoonDefault @Published var orbitSunAngle: Angle = .degrees(150) var orbitSunAngleBinding: Binding<Float> { Binding<Float>( get: { Float(self.orbitSunAngle.degrees) }, set: { self.orbitSunAngle = .degrees(Double($0)) } ) } // MARK: - Solar System @Published var solarEarthEntity: EarthEntity = .makeCloudyEarth() @Published var isShowingSolar: Bool = false @Published var solarImmersionStyle: ImmersionStyle = .full @Published var solarEarth: EarthEntity.Configuration = .solarEarthDefault @Published var solarSatellite: SatelliteEntity.Configuration = .solarTelescopeDefault @Published var solarMoon: SatelliteEntity.Configuration = .solarMoonDefault @Published var solarSunDistance: Double = 700 @Published var solarSunAngle: Angle = .degrees(280) @Published var solarSunSpotIntensity: Float = 10.5 @Published var solarSunEmissionIntensity: Float = 10.5 var solarSunPosition: SIMD3<Float> { [Float(solarSunDistance * sin(solarSunAngle.radians)), 0, Float(solarSunDistance * cos(solarSunAngle.radians))] } }
-
17:33 - SwiftUI Orbit View Body
struct Orbit: View { @EnvironmentObject private var model: ViewModel var body: some View { Earth( world: model.globeEarthEntity, earthConfiguration: model.orbitEarth, satelliteConfiguration: [model.orbitSatellite], moonConfiguration: model.orbitMoon, showSun: true, sunAngle: model.orbitSunAngle, animateUpdates: true ) .place( initialPosition: Point3D([475, -1200.0, -1200.0]), useCustomGesture: model.useCustomGesture, handOffset: model.customGestureHandOffset, isCustomGestureAnimated: model.isCustomGestureAnimated, debugCustomGesture: model.debugCustomGesture, scale: $model.orbitEarth.scale) } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。