iOS 渲染
https://xiaozhuanlan.com/topic/9273604158
先来张渲染的流程图:
这张图其实有很多误导,我的更改如下:
可以看到整个流程是一个pipeline(一次pipeline要跨越三帧)形式的,下面对每个步骤都做一个解释:
1.Handle Events: 处理事件,比如点击事件,这个过程中有可能会需要改变页面的布局和界面层次。(在runloop层面看,就是唤醒runloop,比如source1/0、定时事件等,然后经过一轮或多轮runloop后,就到了beforeWaiting的observers监听事件了,也就下面的2部分执行的内容
2.Commit Transaction: 此时 app 会通过CPU 处理显示内容的前置计算,比如布局计算、图片解码等任务,接下来会进行详细的讲解。之后将计算好的图层进行打包(这里的打包其实就是Encode编码,所以对应了下面的Decode解码)发给 Render Server (这一步对应到代码就是CA::Transaction:commit()
,里面会调用UIView的layoutSubviews
和CALayer的displayLayer
等方法,具体可见我的另一篇博客为什么说重写了drawRect:后会增加内存开销
3.Decode: 打包好的图层被传输到 Render Server 之后,首先会进行解码。注意完成解码完并没有直接 Draw Calls(这里的Calls是指call openGL/Metal),这是有目的的下面会讲
4.Draw Calls: 渲染服务器必须等待下一次重新同步,以便等待缓冲区从 它们实现渲染的显示器 返回,然后最终开始为 GPU 绘制,这里调用的是 OpenGL or metal接口。这里和iOS的双缓冲机制相辅相成,Draw Calls 并不会在 Decode结束就马上进行,而是等上一帧显示完成了、buffer空出来了再调用openGL/Metal来在空出的buffer里渲染,此时显示器显示另一个buffer里的内容
5.Render:这一阶段主要由 GPU 进行渲染(渲染成bitmap,存放在render buffer中
6.Display:显示阶段,需要等 render 结束的下一个 RunLoop 触发显示
- 上面有一点值得注意的是,正常渲染过程是在GPU中进行的,CPU只是在第2步提供好了图元信息(也就是每个CALayer的位置、大小、父子Layer的关系等);而如果重写
drawRect
等代理,渲染会在CPU进行,同时还会另外申请一块backing strore同等大小的内存- 注意:重写
drawRect
在CPU中进行渲染在一些博客中也称为离屏渲染,这个我认为是不准确的,行业普遍认为的离屏渲染是在GPU中渲染的,注意这个术语问题。打开Xcode模拟器的“Color Offscreen rendered Yellow”,可以看到即使是重写了drawRect:
方法的View也不会标黄
上面3、4、5步骤的详细流程在下面这个图:
这里顺便提下用户卡顿感知的原因:
首先人眼能够感觉到流畅的最低要求是24帧,当然帧率越高人眼感知的流畅读越高(这里有兴趣可以看下这篇文章的介绍),理论上iOS刷新率是60Hz,完全能保持流畅。但当Display显示屏显示一个render buffer 16.67ms后,由于双缓冲机制会转而去显示另一个render buffer,但可能由于其他步骤(比如Handle Events等)卡了一段时间,导致这个render buffer还没有被GPU更新,所以这一帧的内容没有变化,用户的感知就是卡住了。
动画渲染
动画渲染流程:
关于动画有个优化,应用程序一次性把动画参数打包给Render进程,然后Render进程一帧一帧渲染,这里优化了应用程序和Render Server之间的多次进程间通信的消耗
参考文章
我网上关于iOS渲染的博客看的挺多了,但是内容都比较同质化,下面的链接是我认为有自己想法的博客