阅读 478

iOS的渲染循环、离屏渲染原理、卡顿原理

在iOS开发中,卡顿的问题是一个绕不开的问题,在这里我们从iOS的渲染循环(Render Loop)的角度来分析在渲染过程中可能会出现卡顿的原因。

渲染循环

VSYNC

截屏2021-10-10 下午9.30.43.png 渲染循环是一个连续性的过程。通过触碰事件传送给app,然后转化到用户界面,向操作系统传送。最终呈现给用户,这就是循环,随着设备的刷新率发生。

截屏2021-10-10 下午9.35.38.pngiphone和ipad中,VSYNC信号的频率为60HZ,在 ipad pro中为 120HZ。我们以iphone为例,这意味着每 16.67毫秒,就可以显示一个新帧

整个渲染循环五个阶段组成 :事件阶段(Event)提交阶段渲染准备渲染执行展示阶段

在提交或渲染或展示阶段,如果花费的时间超过一帧,就会造成卡顿

事件阶段(Event)

截屏2021-10-10 下午9.43.10.png 在这个阶段,App处理触碰事件或者Timer等其他事件,决定用户界面是否需要变化。

提交阶段(Commit)

截屏2021-10-10 下午9.45.44.png 在提交阶段,app向渲染服务器提交渲染命令。

渲染准备

在下一个VSYNC中,渲染服务器处理命令,在渲染准备阶段,为在GPU上绘制做好准备。

渲染执行

截屏2021-10-10 下午9.49.38.png 在渲染执行阶段,GPU将用户界面的最后图像绘制出来。

展示阶段

截屏2021-10-10 下午10.04.56.png 在下一个VSYNC,这一帧将会呈现给用户。

要想有丝滑的用户体验,每一个阶段都至关重要。如果某一阶段的时间超过了VSYNC的时间,则会造成卡顿

截屏2021-10-10 下午8.56.57.png

提交阶段的卡顿

提交事务

渲染循环Event阶段,处理触摸事件及其他事件,收到事件后,需要改变ViewbackgroundColorframe等属性

截屏2021-10-10 下午10.22.40.png 下一次事务提交时,系统记录这些子视图将需要某个布局或显示

截屏2021-10-10 下午10.24.19.png 在提交事务时,这些需要某个显示或布局的视图,会通过调用 drawRectlayoutSubviews 来进行相应的更新。

提交事务的4个步骤:

提交事务有 Layout(布局)Display(展示)Prepare(筹备)Commit(提交)4个步骤。

Layout(布局阶段)

layoutSubviewsview需要布局的时候会调用,以下情况需要重新布局:

  • Positioning views(位置改变),例如,frame,bounds,transform属性改变,会重新布局。
  • 添加或者删除view。
  • 显示调用 setNeedsLayout()

Display(展示阶段)

需要更新内容的视图,都会调用draw(rect:)方法。以下情况会调用draw(rect:)方法:

  • 添加了重写draw(rect:)方法的视图。
  • 直接调用了 setNeedsDisplay()方法,以表明需要展示。

Prepare(筹备阶段)

  • 对未解码的图片,进行解码。
  • 若某个图像的颜色格式图形处理器无法直接使用,将会进行转换,这样会消耗很多的内存。

Commit (提交阶段)

视图层次结构将被递归打包,并发送到渲染服务器。

避免提交卡顿的建议

  • 1,保持视图的轻量:

      1.1:尽量使用`CALayer`的可用属性。
      1.2: 避免出现空的 drawRect 实现。
      1.3: 复用视图,避免使用代价过高的视图层级结构操作,比如添加和移除。如果一定要移除,可以考虑使用 hidden 属性。
    复制代码
  • 2,在需要更新布局是,尽量只使用setNeedsLayoutlayoutIfNeeded会消耗当前事务的生命周期,会造成卡顿。大多数时候,可以等到下一次循环执行时,在更新布局。

  • 3,加载图片时,对图片进行解码。

渲染阶段的卡顿

渲染阶段里面包含两个阶段:渲染准备阶段(Render prepare)渲染执行阶段(Render execute)

渲染准备阶段

将图形树分解为一系列简单的操作,供GPU执行

渲染执行阶段

GPUApp图层绘制成最终图像。

这两个阶段都可能造成帧延迟。

渲染过程

通过绘制一个有阴影的示例,来讲解下绘制过程

截屏2021-10-10 下午11.24.38.png

渲染准备阶段,渲染服务器会逐层编译一系列绘图命令,使GPU能从后向前绘制用户界面。

截屏2021-10-10 下午4.12.22.png根节点开始,渲染服务器从同级到同级,从父级到子级,直到涵盖层级中的每个图层,这样就得到了渲染的整个管道

截屏2021-10-10 下午11.30.46.png 在渲染执行阶段,按照这个管道的顺序进行绘制。

1,先绘制蓝色 2,绘制深蓝色

截屏2021-10-10 下午11.33.11.png 3,绘制阴影

阴影的形状由它下面的两个层定义,因此GPU不知道用什么形状来绘制阴影,但如果先绘制圆形和长条,那么阴影会用黑色遮挡它们,看起来会不正确,此时,GPU必须切换到不同的纹理,以确定阴影的形状,这种情况,我们称之为 离屏渲染,在新开辟的纹理中,加下面的层复制过来,确定阴影的形状,阴影渲染完成后,将那个离屏纹理复制到最终纹理中

截屏2021-10-10 下午11.35.33.png

截屏2021-10-10 下午11.36.43.png

4,绘制圆形和长方形和文字

截屏2021-10-10 下午11.39.05.png

在这个过程中,我们绘制阴影的时候,使用了一个特殊的技巧来绘制:GPU先在其他地方渲染一个图层,然后再将其复制过来,我们称之为 离屏通道(Offscreen Pass)。就阴影而言,它必须绘制图层,以确定最终形状,离屏渲染会积少成多,对性能造成影响。

对于阴影,我们可以通过UIBerthPath给阴影指定形状,来避免离屏渲染。

为了方便离屏渲染的检测和给出相对应的解决方法,在Xcode 12中添加了一个新的运行时问题类型,称之为优化机会。在LLDB状态下,在Editor选项下

截屏2021-10-11 上午12.01.24.png

如何消除卡顿

关于如何消除卡顿,有兴趣的可以查看这篇文章iOS 列表滑动的卡顿检测和优化

参考

Explore UI animation hitches and the render loop

Find and fix hitches in the commit phase

Demystify and eliminate hitches in the render phase

文章分类
iOS
文章标签