iOS 离屏渲染
一、概念
屏幕显示图像的原理:
显示器如何显示图像,需要显示的图像经过 CRT 电子枪以极快的速度一行一行的扫描,扫描出来就呈现出了一帧画面,随后电子枪又回到初始位置循环扫描,形成了我们看到的图片或视频。
为了让显示器的显示与视频控制器同步,显示器会用硬件时钟产生一系列的定时信号。当电子枪换到新的一行时,准备进行扫描时,显示器会发出一个水平同步信号,简称 HSync,而当一帧画面绘制完成后,电子枪回复到原位,准备画下一帧前,显示器会发出一个垂直同步信号,简称 VSync。显示器通常以固定频率进行刷新,这个刷新频率就是 VSync 信号产生的频率。
GPU 屏幕渲染有两种方式:
(1)On-Screen Rendering(当期屏幕渲染)
指的是 GPU 的渲染操作是在当前用于显示的屏幕缓冲区进行。
(2)Off-Screen Rendering(离屏渲染)
指的是 GPU 在当前屏幕缓冲区以外开辟一个缓冲区进行渲染操作。
当前屏幕渲染不需要额外创建新的缓存,也不需要开启新的上下文,相对于离屏渲染性能更好。但是受当前屏幕渲染的局限因素限制(只有自身上下文、屏幕缓存有限等),当前屏幕渲染有些情况下的渲染解决不了的,就使用离屏渲染。
相比于当前屏幕渲染,离屏渲染的代价是很高的,主要体现在两个方面:
(1)创建新缓冲区
要想进行离屏渲染,首先要创建一个新的缓冲区。
(2)上下文切换
离屏渲染的整个过程,需要多次切换上下文环境:先是从当前屏幕(On-Screen)切换到离屏(Off-Screen),等离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上又需要将上下文环境从离屏切换到当前屏幕。而上下文环境的切换时要付出很大代价的。
由于垂直同步的机制,如果在一个 VSync 时间内,CPU 或者 GPU 没有完成内容提交,则那一帧就会被丢弃,等待下一次机会再显示,而这时显示屏会保留之前的内容不变。这就是界面卡顿的原因。
既然离屏渲染这么耗性能,为什么有这套机制呢?
有些效果被认为不能直接呈现于屏幕,而需要在别的地方做额外的处理预合成。图层属性的混合体没有预合成之前不能直接在屏幕中绘制,所以就需要屏幕外渲染。屏幕外渲染并不意味着软件绘制,但是它意味着图层必须在被显示之前在一个屏幕外上下文中被渲染(不论是 CPU 还是 GPU )。
二、UIView 与 CALayer 关系
UIView 继承自 UIResponder ,可以处理系统传递过来的事件,如:UIApplication、UIViewcontroller、UIView 以及所有从 UIView 派生出来的 UIKit 类。每个 UIView 内部都有一个 CALayer 提供内容的绘制和显示,并且作为内部 RootLayer 的代理试图。
CALayer 继承自 NSObject 类,负责显示 UIView 提供的内容 contents。CALayer 有三个视觉元素:背景色、内容和边框,其中,内容的本质是一个 CGImage。
下图为 CALayer 的结构图:
界面渲染过程:
RunLoop 有一个60fps的回调,即每16.7ms绘制一次屏幕,所以 view 的绘制必须在这个时间内完成,view 内容的绘制是 CPU 工作,然后把绘制的内容交给 GPU 渲染,包括多个 View 的拼接(compositing)、纹理的渲染(Textture)等,最后显示在屏幕上。但是,如果无法是16.7ms内完成绘制,就会出现丢帧的问题,一般情况下,如果帧率保证在30fps以上,界面卡顿不明显,那么就需要在33.4ms内完成 View 的绘制,而低于这个帧率,就会产生卡顿的效果,影响体验。
渲染的过程如下:
-UIView 的 Layer 层有一个 Contents,指向一块缓存,即 backing store
-UIView 绘制时,会调用 drawRect 方法,通过 context 将数据写入 backing store
-在 backing store 写完后,通过 render server 交给 GPU 去渲染,将 backing store 中的 bitmap 数据显示在屏幕上
下面的情况会引发离屏渲染:
-为图层设置遮罩(layer.mask)
-将图层的 layer.masksToBounds/views.clipToBounds 属性设置为 true
-将图层 layer.allowsGroupOpacity 属性设置为 YES 和 layer.opacity 小于1.0
-为图层设置阴影 (layer.shadow *)
-为图层设置 layer.shouldRasterize=true
-具有 layer.cornerRadius, layer.edgeAntialiasingMask, layer,allowsEdgeAntialiasing的图层
-文本(任何种类,包括 UILabel, CATextLayer, Core Text 等)
-使用 CGContext 在 drawRect:方法中绘制大部分情况下会导致离屏渲染,甚至仅仅是一个空的实现
参考文章:
https://www.jianshu.com/p/cff0d1b3c915
https://www.cnblogs.com/fishbay/p/7576176.html
https://www.jianshu.com/p/15b8b1844e9c