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 触发显示

  1. 上面有一点值得注意的是,正常渲染过程是在GPU中进行的,CPU只是在第2步提供好了图元信息(也就是每个CALayer的位置、大小、父子Layer的关系等);而如果重写drawRect等代理,渲染会在CPU进行,同时还会另外申请一块backing strore同等大小的内存
  2. 注意:重写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渲染的博客看的挺多了,但是内容都比较同质化,下面的链接是我认为有自己想法的博客

    1. https://joakimliu.github.io/2019/03/02/wwdc-2014-419/
    2. https://zhuanlan.zhihu.com/p/72653360
       
posted @ 2021-02-18 12:55  zzfx  阅读(277)  评论(0编辑  收藏  举报