Apple tech-talks - Demystify and eliminate hitches in the render phase

247 阅读6分钟

Apple tech-talks 本文来自,Apple tech-talks. developer.apple.com/videos/play…

iOS 使用渲染循环来显示视图 在渲染循环没能及时完成一个帧 以进行显示时 就会出现卡顿

今天聊聊渲染阶段的卡顿

在提交阶段 app 修改其用户界面, 并提交更新的用户界面图层树以进行处理, 我们将其称为“提交”, 渲染服务器负责渲染所有前台进程的提交的内容.

渲染服务器有两个阶段: 渲染准备和渲染执行

举🌰 我们来看下,一帧的渲染过程 我们可以留意到,这个长方形和圆形组成的图案,有一圈阴影.

我们从 app 提交到左侧渲染服务器的图层树开始,渲染服务器会逐层编译一系列绘图命令,使图形处理器能从后向前绘制用户界面 使图形处理器能从后向前绘制用户界面 从根节点开始,渲染服务器从同级到次级,从父级到子级,直到涵盖层级中的每个图层 最终 它有了图形处理器可以 在下一个执行阶段执行的整个管线

一次渲染的步骤

1. 第一个蓝色的图层开始 它在指定的边界内绘制颜色

2.较深的蓝色被绘制在其边界内

3.图形处理器绘制阴影(这里会解释离屏渲染的发生流程)

阴影的形状由下面两个层(图1,和图2)定义 但是由于绘制过程是一层一层的,目前这两个图片都还没有绘制完成, 因此图形处理器不知道,用什么形状来绘制阴影,但如果我们先绘制圆形和长条 那么阴影会用黑色遮挡它们 看起来会不正确 这意味着图形处理器遇到了障碍 要继续 它必须切换到不同的纹理 以确定阴影的形状,我们称之为“离屏渲染” 因为我们在最终纹理以外绘制 从这里它可以绘制圆形和长条 然后它将阴影形状隔离在离屏纹理内 通过先将图层变成黑色 然后将其模糊,具备了绘制阴影形状所需的所有条件 然后将那个离屏纹理,复制到最终纹理中,阴影图层就完成了

4.绘制圆形和长方形

离屏渲染 Offscreen Pass

我们现在完成了两个渲染阶段,并且帧准备好进行显示了, 这个过程中,我们不得不用一个特殊的技巧来渲染阴影 这导致渲染需要更长的时间,这称为离屏渲染 就阴影而言 它必须绘制图层,以确定最终形状,离屏通道可能会积少成多,导致渲染出现卡顿

离屏渲染类型

阴影 shadowing 示例渲染中 看到了一个阴影离屏的例子 在本例中 不先绘制附加到它上面的图层 渲染器就没有足够的信息来绘制阴影 蒙版 Masking 当图层或图层树需要遮蔽时 渲染器需要渲染被遮蔽的子树 但它也需要避免覆盖 被遮蔽形状之外的像素 因此它只会在被屏蔽形状内的像素 复制回最终纹理之前 渲染整个子树 这种离屏可能会导致渲染 许多用户永远不会看到的像素, 浪费性能. 圆角矩形 类型与蒙版有关 将图层的角圆化有时可能需要离屏 如果没有提供足够的信息 渲染器可能必须离屏绘制整个视图 然后将圆化形状内的像素复制回来 视觉效果 户界面套件提供两种视觉效果类型: 鲜亮化和模糊 要应用这些效果 渲染器必须用离屏通道 将视觉效果视图下面的内容 复制到另一个纹理 然后将视觉效果类型应用到结果 并将其复制回来 你会在用户界面导航栏 用户界面标签栏 和许多其它标准控件中看到这个 因为这在 iOS、tvOS 和 macOS 中非常常见

这四种离屏类型可能会降低渲染速度 并导致渲染卡顿

如何用 Instruments和Xcode视图调试器在app中捕获并鉴别渲染卡顿?

提交轨迹显示在该帧期间 发送到渲染服务器的所有提交阶段

渲染器和图像处理器轨迹 显示渲染服务器执行的工作

帧生命周期轨迹显示 构成帧所需的整个持续时间 从活动到显示

缓冲区计数是发生卡顿时 渲染服务器使用的缓冲区数量 默认值为 2 但在渲染帧被延迟 且渲染服务器在试图追赶时可以为 3 在双缓冲模式下 我们有两帧 或者在 iPhone 上为 33.34 毫秒 这是我们在延迟列中看到的 始终记住遵循上面的卡顿持续时间轨迹 这会始终突出显示关注的区域 无论缓冲区计数是多少

卡顿类型 卡顿类型显示卡顿的类型 并帮助你了解 app 中要深入研究的东西 在这里 我们看到代价过高的 提交和图形处理器时间 这是我们在上面的轨迹中看到的

如何在Xcode 上看离屏渲染的数目

我们在视图调试器中暂停了膳食计划 app 在左侧 我们可以看到视图控制器 窗口、约束和视图 但从 Xcode 11.2 开始 我们也可以显示图层 如果我们点击编辑器 让我们点击新的显示图层项目

在选择一个图层时 我们会看到这个全新的图层 inspector 它提供我们的图层的有用属性

我们可以看到 标签视图支持图层 我们可以看到背景颜色、不透明度 是否启用了 masksToBounds 和许多其它属性

重要的是 我们可以看到离屏计数 这是渲染该图层所需的离屏数量 下面是我们所说的离屏标志 这些说明离屏的原因 我们在 inspector 中看到, 它有5个离屏

在xcode12中添加了一个runtime分析的layer,简单但有价值的更改 不会影响你的图层的整体外观 在导航器中 我们可以在一些图层上 看到紫色的运行时问题指示器

针对渲染卡顿的优化建议

使用新的API

从 iOS 13 开始 我们可以使用 cornerCurve 属性 将 cornerRadius 效果变成 椭圆形矩形形状

// Setup shadow properties
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOpacity = 0.5

// Set a shadow path on a basic layer
view.layer.shadowPath = UIBezierPath(roundedRect: view.layer.bounds, 
                                     cornerRadius: view.layer.cornerRadius).cgPath

对于大多数图层 只需将图层边界舍入到其 cornerRadius 以创建 UIBezierPath 即可创建一个很棒的 shadowPath

优化整个app的遮蔽,使用masksToBounds遮蔽为矩形,圆角矩形或椭圆形

它的性能比自定义蒙版图层好得多 总之 确保遮蔽是必需的 如果子树中的内容不会超出边界 则完全禁用 masksToBounds