探究iOS离屏渲染原理
一、什么是离屏渲染?
在我前面的博客中对图像是如何显示到屏幕上有了详细的解读 传送门,这里在简单回顾下:
显示原理主要有以下三步:
- CPU计算需要显示的内容,然后通过数据总线传给GPU
- GPU拿到数据之后开始渲染数据并保存在帧缓存区中
- 随后视频控制器会按照
VSync
信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器显示。
屏幕渲染的两种方式:
-
On-Screen Rendering
: 即当前屏幕渲染,指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行。当前屏幕渲染显示都是直接从帧缓存区中读取数据然后直接显示。 -
Off-Screen Rendering
意为离屏渲染,指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区(离屏缓存区)进行渲染操作。等所有数据都在离屏渲染区完成渲染后才会提交到帧缓存区,然后再被显示。 -
除此之外,如果将非当前屏幕渲染都称之为离屏渲染的话,那么还有一种特殊的离屏渲染:
CPU渲染:如果我们重写了drawRec
t方法,并且使用任何Core Graphics
的技术进行了绘制操作,就涉及到了CPU渲染。整个渲染过程由CPU在App内 同步地完成,渲染得到的bitmap
最后再交由GPU用于显示。
CoreGraphic
通常是线程安全的,所以可以进行异步绘制,显示的时候再放回主线程,一个简单的异步绘制过程大致如下:
-(void)display {
dispatch_async(backgroundQueue, ^{
CGContextRef ctx = CGBitmapContextCreate(...); // draw in context...
CGImageRef img = CGBitmapContextCreateImage(ctx);
CFRelease(ctx);
dispatch_async(mainQueue, ^{
layer.contents = img;
});
});
}
二、离屏渲染触发的原因
-
1.使用了
mask
的layer
(layer.mask
);
在使用 mask
的时候,会分别绘制mask
的内容和layer
的内容并放入离屏缓存区,然后将二者混合给帧缓存区,然后再进行显示。
-
2.需要进行裁剪的
layer
(layer.masksToBounds / view.clipsToBounds)
(使用圆角时候搭配使用layer.masksToBounds / view.clipsToBounds
):
下图为CALayer
的结构图:
如果你只是使用layer.cornerRadius
:
layer.cornerRadius = 4;
只会设置backguroundColo
r和border
的圆角,不会设置content
的圆角,除非同时设置layer.masksToBounds
或者view.clipsToBounds
:
//二者效果相同,使用任意一个即可
layer.masksToBounds = YES;
view.clipsToBounds = YES;
这个时候才会触发离屏渲染,所以并不是多个层级就一定会触发离屏渲染。
-
3.设置了组透明度为
YES
,并且透明度不为 1 的layer(layer.allowsGroupOpacity/ layer.opacity)
(注意是组透明度); -
4.添加了投影的
layer (layer.shadow)
; -
5.采用了光栅化的
layer (layer.shouldRasterize)
:
在苹果官方文档中对layer.shouldRasterize
有如下解释:
When the value of this property is
YES
, the layer is rendered as a bitmap in its local coordinate space and then composited to the destination with any other content.
意思简单理解就是说,当开启这个属性后, 这个layer
会渲染成位图存起来,需要等所有的要显示的内容全部渲染完毕后,再混合才能够显示到屏幕上。那layer
的位图和其它渲染完成的内容放在哪里呢?只能放在离屏缓存区中了,所以光栅化也会触发离屏渲染。
layer.shouldRasterize
使用建议如下:
-
如果layer不能被复用,则没有必要打开光栅化;
-
如果layer不是静态的,需要被频繁修改,比如处于动画之中。这样的情况下开启光栅化反而影响效率。在比如:我们日程经常打交道的
TableViewCell
,因为TableViewCell
的重绘是很频繁的(因为Cell
的复用),如果Cell的内容不断变化,则Cell
需要不断重绘,如果此时设置了cell.layer
可光栅化。则会造成大量的离屏渲染,降低图形性能。 -
离屏渲染缓存的内容是有时间限制的,缓存的内容
100ms
内如果没有使用,那么它就会被丢弃,无法进行复用了; -
离屏渲染的缓存空间有限,超过屏幕像素大小
2.5
倍的话,也会失效,且无法进行复用了。 -
6.绘制了文字的
layer
(UILabel, CATextLayer, Core Text
等)
三、为什么要使用离屏渲染?
帧缓存区只是暂存显示内容,内容显示到屏幕上后,就会被丢弃了。但是当使用圆角,阴影,遮罩的时候,图层属性的混合体被指定为在未预合成之前是不能直接在屏幕中绘制,也就是说没办法一次渲染完成直接显示的,所以就需要建立一个新的屏幕外渲染,使用离屏缓存区将内容分批保存,等待所有的内容渲染完成后再传到帧缓存区,以便显示。
屏幕外渲染并不意味着软件绘制,但是它意味着图层必须在被显示之前在一个屏幕外上下文中被渲染(不论CPU还是GPU)。
相比于当前屏幕渲染,离屏渲染的代价是很高的,主要体现在两个方面:
- 创建新缓冲区:要想进行离屏渲染,首先要创建一个新的缓冲区。
- 上下文切换:离屏渲染的整个过程,需要多次切换上下文环境:先是从当前屏幕(On-Screen)切换到离屏(Off-Screen);等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上有需要将上下文环境从离屏切换到当前屏幕。而上下文环境的切换是要付出很大代价的。
觉得不错记得点赞哦!听说看完点赞的人逢考必过,逢奖必中。ღ( ´・ᴗ・` )比心