1.iOS界面渲染过程
WWDC14 4191.在Application这一层中主要是CPU在操作,而到了Render Server这一层,CoreAnimation会将具体操作转换成发送给GPU的draw calls(以前是call OpenGL ES,现在慢慢转到了Metal),显然CPU和GPU双方同处于一个流水线中,协作完成整个渲染工作。
2.Runloop有一个60fps的回调,即每16.7ms绘制一次屏幕,所以view的绘制必须在这个时间内完成,view内容的绘制是cpu的工作,然后把绘制的内容交给GPU渲染,包括多个view的拼接,纹理的渲染等,最后显示在屏幕上。
屏幕渲染有如下三种:
CPU计算好显示内容提交到GPU,GPU渲染完成后将渲染结果放到帧缓存区
,随后视频控制器会按照VSync信号
逐行读取帧缓存区
的数据。
1、GPU中的当前屏幕渲染 (On-Screen Renderin)
指的是GPU的渲染操作是在当前用于显示的帧缓冲区
中进行
2、GPU中的离屏渲染 (Off-Screen Rendering)
指的是GPU在当前帧缓冲区
以外新开辟一个离屏缓冲区
进行渲染操作
3、CPU中的渲染(特殊离屏渲染,即不在GPU中的渲染)
如果我们重写了drawRect方法,并且使用任何Core Graphics
的技术进行了绘制操作,系统也会为这个view申请一块内存区域,等待CoreGraphics
可能的绘画操作,对于类似这种“新开一块CGContext来画图“的操作,有很多文章和视频也称之为“离屏渲染”。
注:通过CPU渲染就是俗称的“软件渲染”,而真正的离屏渲染发生在GPU,打开Xcode调试的“Color offscreen rendered yellow”开关,你会发现这片区域不会被标记为黄色,说明Xcode并不认为这属于离屏渲染。
2.什么情况会出现离屏渲染
1.离屏渲染:如果要在显示屏上显示内容,我们至少需要一块与屏幕像素数据量一样大的frame buffer,作为像素数据存储区域,而这也是GPU存储渲染结果的地方。如果有时因为面临一些限制,无法把渲染结果直接写入frame buffer,而是先暂存在另外的内存区域,之后再写入frame buffer,那么这个过程被称之为离屏渲染。
2.UIView与CALyer
1.UIView继承UIResponder,可以处理系统传递过来的事件;遵守CALayerDelegate。每个UIView内部都有一个CALayer提供内容的绘制和显示
2.CALayer继承NSObject类,负责UIView提供内容的contents。CALayer有三个视图元素:背景色,内容,边框。其中内容的本质就是一个CGImage
3.设置layer.cornerRadius只会设置backgroundColor和border的圆角,不会设置content的圆角,除非同时设置了layer。masksToBounds为true(对应view中的clipsToBounds属性)
3.常⻅见触发离屏渲染的⼏几种情况:
- cornerRadius+clipsToBounds,原因就如同上面提到的,不得已只能另开一块内存来操作。而如果只是设置cornerRadius(如不需要剪切内容,只需要一个带圆角的边框),或者只是需要裁掉矩形区域以外的内容(虽然也是剪切,但是稍微想一下就可以发现,对于纯矩形而言,实现这个算法似乎并不需要另开内存),并不会触发离屏渲染。
- shadow,其原因在于,虽然layer本身是一块矩形区域,但是阴影默认是作用在其中”非透明区域“的,而且需要显示在所有layer内容的下方,因此根据画家算法必须被渲染在先。但矛盾在于此时阴影的本体(layer和其子layer)都还没有被组合到一起,怎么可能在第一步就画出只有完成最后一步之后才能知道的形状呢?这样一来又只能另外申请一块内存,把本体内容都先画好,再根据渲染结果的形状,添加阴影到frame buffer,最后把内容画上去(这只是我的猜测,实际情况可能更复杂)。不过如果我们能够预先告诉CoreAnimation(通过shadowPath属性)阴影的几何形状,那么阴影当然可以先被独立渲染出来,不需要依赖layer本体,也就不再需要离屏渲染了。
- group opacity,其实从名字就可以猜到,alpha并不是分别应用在每一层之上,而是只有到整个layer树画完之后,再统一加上alpha,最后和底下其他layer的像素进行组合。显然也无法通过一次遍历就得到最终结果。将一对蓝色和红色layer叠在一起,然后在父layer上设置opacity=0.5,并复制一份在旁边作对比。左边关闭group opacity,右边保持默认(从iOS7开始,如果没有显式指定,group opacity会默认打开),然后打开offscreen rendering的调试,我们会发现右边的那一组确实是离屏渲染了。
-
mask,我们知道mask是应用在layer和其所有子layer的组合之上的,而且可能带有透明度,那么其实和group opacity的原理类似,不得不在离屏渲染中完成。
mask 渲染过程 -
UIBlurEffect,同样无法通过一次遍历完成,其原理在WWDC中提到:
UIBlurEffect 渲染过程 - 光栅化(shouldRasterize)
光栅化使用建议
1.如果layer没有被复用,则没有必要开启光栅化
2.如果layer不是静态的,需要被频繁修改,比如处于动画之中,那么开启离屏缓存反而影响效率了
3.离屏换粗内容有时间限制,缓存内容100ms内如果没有被使用,那么他就会丢弃,无法复用了
- 离屏缓存空间有限,超过
2.5倍屏幕像素
大小的话,也会失效,且无法进行复用
3.离屏渲染对性能的影响
GPU的操作是高度流水线化的。本来所有计算工作都在有条不紊地正在向frame buffer输出,此时突然收到指令,需要输出到另一块内存,那么流水线中正在进行的一切都不得不被丢弃,切换到只能服务于我们当前的“切圆角”操作。等到完成以后再次清空,再回到向frame buffer输出的正常流程。
4.性能检测和离屏渲染分析
- iOS的性能调试有许多方法,而官方提供的Instruments是一个强大的性能调试工具,可以分析如下功能:内存、核心动画、自动化、布局、网络等等,本文主要介绍一下利用Core Animation来分析应用的性能问题。
- Color Offscreen-Rendered Yellow
开启后会把那些需要离屏渲染的图层高亮成黄色,这就意味着黄色图层可能存在性能问题
当然Debug还有其它的选项,来分析不同的性能问题,如有需求,请参考其它资料。
5.离屏渲染的优化方案
- 大量应用AsyncDisplayKit(Texture)作为主要渲染框架,对于文字和图片的异步渲染操作交由框架来处理。
- 对于图片的圆角,统一采用“precomposite”的策略,也就是不经由容器来做剪切,而是预先使用CoreGraphics为图片裁剪圆角
- 对于视频的圆角,由于实时剪切非常消耗性能,我们会创建四个白色弧形的layer盖住四个角,从视觉上制造圆角的效果
- 对于view的圆形边框,如果没有backgroundColor,可以放心使用cornerRadius来做
- 对于所有的阴影,使用shadowPath来规避离屏渲染
- 对于特殊形状的view,使用layer mask并打开shouldRasterize来对渲染结果进行缓存
- 对于模糊效果,不采用系统提供的UIVisualEffect,而是另外实现模糊效果(CIGaussianBlur),并手动管理渲染结果
网友评论