iOS的离屏渲染
图像显示原理(Off-Screen Rendering)
- CPU(Central Processing Unit,中央处理器)和GPU(Graphics Processing Unit,图形处理器)通过总线连接起来
- CPU往往输出结果的是位图。它负责对象的创建和销毁、对象属性的调整、布局计算、文本的计算和排版、图片的格式转换和解码、图像的绘制(Core Graphics等。
- CPU输出的位图信息在合适的时机上传到GPU,GPU拿到位图后进行图层的渲染,包括纹理的合成。之后会将处理结果放在帧缓冲区。
- 由视屏控制器根据垂直同步信号(VSync)在指定的时间之前,在帧缓冲区提取屏幕显示内容,然后将其显示到手机屏幕上。
卡顿产生等原因
- CPU和GPU工作时所花时间过长,垂直同步信号过来的时候,计算和渲染还没有完成,导致掉帧
- 卡顿解决的主要思路:尽可能减少CPU、GPU资源消耗
什么是离屏渲染
当我们在UIView上设置某些图层属性,比如设置视图的圆角属性、蒙层遮罩,标记未预合成之前,不能用于直接显示的时候,就触发了离屏渲染。离屏渲染的概念起源于GPU,指的是在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。
离屏渲染导致卡顿的原因:
- 需要创建新的缓冲区
- 离屏渲染的整个过程,需要多次切换上下文环境,先是从当前屏幕(On-Screen)切换到离屏(Off-Screen);等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上,又需要将上下文环境从离屏切换到当前屏幕
- 增加了GPU的工作量,可能导致GPU + CPU的耗时过多,在16.7毫秒(1秒60帧)的时候没有完成任务,出现卡顿掉帧。
如何检测是否触发离屏渲染?
WechatIMG422.png打开模拟器运行,找到设置选项debug
,勾选Color Off-screen Rendered
,如果触发离屏渲染就会将控件标记为黄色。
什么操作会触发离屏渲染
圆角
- 在iOS9之前,
UIImageView
同时设置layer.masksToBounds = YES
、layer.cornerRadius
大于0时就会触发离屏渲染,iOS 9之后系统做了优化,不会触发了。虽然系统做了优化,但是用这种方式给UIImageView
圆角切割又设置了背景色,那么还是会触发离屏渲染的(普通UIView
设置纯背景色不会)。 -
UIButton
的masksToBounds = YES
下发生离屏渲染与背景图存不存在有关系, 如果没有给按钮设置btn.image = [UIImage imageName:@"xxxxx"];
是不会产生离屏渲染的。
如何解决呢?
- 让美工切图
- 通过CoreGraphics绘制裁剪圆角
不透明(group opacity)
GroupOpacity 是指 CALayer
的allowsGroupOpacity
属性,UIView 的alpha
属性等同于 CALayer opacity
属性。开启 GroupOpacity 后,子 layer在视觉上的透明度的上限是其父layer 的opacity
。
GroupOpacity 开启离屏渲染的条件是:layer.opacity != 1.0
并且有子 layer 或者背景图。
遮罩(mask)
- mask生来会触发离屏渲染的。
- mask是
CALayer
的一个属性,它也是一个CALayer
对象,默认为nil。当给一个layer
设置mask时,mask的alpha
值决定了多少层背景跟内容通过并显示。当mask的alpha
为0时,整个mask与底层CALayer都不会显示。当alpha
为1时,只有mask区域以内非透明区域,会显示该区域对应底层CALayer的像素值。
阴影(shadow)
阴影直接合成在视图的下面,视图结构里并没有多出一个视图。在没有指定阴影路径时,阴影是沿着视图的非透明部分扩展的,而且 CALayer 的三个视觉元素至少有一个存在时才会有阴影。
使用阴影必须保证 layer 的masksToBounds = false
,因此阴影与系统圆角不兼容。但是注意,只是在视觉上看不到,对性能的影响依然。通常这样实现一个阴影:
let imageViewLayer = avatorView.layer
imageViewLayer.shadowColor = UIColor.blackColor().CGColor
imageViewLayer.shadowOpacity = 1.0 //此参数默认为0,即阴影不显示
imageViewLayer.shadowRadius = 2.0 //给阴影加上圆角,对性能无明显影响
imageViewLayer.shadowOffset = CGSize(width: 5, height: 5)
//设定路径:与视图的边界相同
let path = UIBezierPath(rect: cell.imageView.bounds)
imageViewLayer.shadowPath = path.CGPath//路径默认为 nil
也就是说在设置阴影同时给layer设置相同的shadowPath,可避免离屏渲染。
光栅化(EdgeAntialiasing)
shouldRasterize(光栅化): 将图转化为一个个栅格组成的图象。 光栅化特点:每个元素对应帧缓冲区中的一像素。
shouldRasterize = YES
在其它属性触发离屏渲染的同时,会将光栅化后的内容缓存起来,如果对应的layer或者 sublayers没有发生改变,在下一帧的时候可以直接复用,从而减少渲染的频率。
当使用光栅化是, 可以开启 "Color Hits Green and Misses Red"来检查该场景下是否适合选择光栅化,绿色表示缓存被复用,红色表示缓存在被重复创建.对于经常变动的内容,不要开启,否则会造成性能的浪费。
如果cell里面的内容不断变化(cell的复用),如果设置了cell.layer.shouldRaseterize = YES
则会降低图形性能,造成离屏渲染.