On-Screen Rendering:当前屏幕渲染,CPU、GPU 不停地将内容渲染完成放入frame buffer
帧缓冲区中,显示屏幕从 frame buffer
中获取内容显示。
Off-Screen Rendering:离屏渲染,先创建离屏渲染帧缓冲区offscreen frame buffer
,然后逐一将内容渲染放入其中,完成后对离屏渲染缓冲区做阴影叠加、裁剪等操作,最后将结果拷贝或切换到帧缓冲区frame buffer
中,显示屏幕从 frame buffer
中获取内容显示。
那么为什么需要离屏渲染帧缓冲区offscreen frame buffer
呢?我们先来了解下“画家算法”。
画家算法,也叫作优先填充,它是三维计算机图形学中处理可见性问题的一种解决方法(三维场景投影到二维平面)。如下图,画家算法首先将场景中的多边形根据深度进行排序,然后按照由远到近的顺序进行描绘,这种方法通常会将不可见的部分覆盖,这样就可以解决可见性问题。
对于有前后依赖的图层(如阴影叠加、裁剪等),通过由远到近的图层叠加算法是无法实现的,我们需要先申请一个临时缓冲区,所有图层按照画家算法,由远到近在临时缓冲区渲染,渲染完成后,再对这个临时缓冲区做最后的全局操作(如阴影叠加、裁剪等),最后再把临时缓冲区拷贝或切换到当前的缓冲区上,交给显示器显示。
总结一下,使用离屏渲染大概是因为以下原因:
需要实现特殊的效果,比如说全局叠加、裁剪等等,需要用额外的帧缓冲区offscreen frame buffer
保存中间状态。
出于效率目的,针对不会经常变更的图层,可以缓存到offscreen frame buffer
,供下次刷新使用。
最常见的情形就是使用了Masking
蒙版,我们看下官方提供的Masking
渲染流程:
如上图,渲染Masking
蒙版分为3步:
layer
的 mask
纹理,流程同 Tile Based Rendering
。layer
的 content
纹理,流程同 Tile Based Rendering
。mask
、content
纹理。由于需要叠加两层渲染结果,所以在叠加前,需要额外的缓冲区保存渲染结果,也就是触发了离屏渲染。
iOS 8 提供的blur
模糊特效也会引起离屏渲染,我们看下官方提供的blur
渲染流程:
如上图,渲染模糊过程分为4步:
layer
的 content
。layer
的 content
,进行缩放。blur
模糊效果也会触发离屏渲染,而且需要更多的缓冲区存储渲染结果,更浪费性能。
光栅化,开启光栅化后,会触发离屏渲染,GPU 会强制把图层的渲染结果保存下来,方便下次复用。我们看下官方的描述:
光珊化的本意是为了避免静态内容重绘、复杂view层级重绘带来的影响。使用需要注意以下几点:
这里有一个iOS交流圈:891 488 181 有兴趣的都可以来了解,分享BAT,阿里面试题、面试经验,讨论技术,裙里资料直接下载就行, 大家一起交流学习成长!
shouldRasterize
可开启光珊化。组不透明度,某些情况也会触发离屏渲染,可以通过CALayer
的allowsGroupOpacity
控制。我们看官方的描述:
总结一下,有以下两点:
建议关闭CALayer
的 allowsGroupOpacity
属性。
如果开启了 allowsGroupOpacity
,当 layer 的 opacity
小于1.0,且有子 layer 或者背景图,则会触发离屏渲染。
而在iOS 7.0 SDK以上,allowsGroupOpacity
默认 true,所以不需要的时候,建议关闭。
投影,和Masking
一样,涉及到多个渲染结果合并,也会启用离屏渲染。来看下官方的解释:
如果单纯设置shadowOffset
,会触发离屏渲染,但是我们可以设置shadowPath
,告诉Core Animation
投影路径,那么系统就知道如何绘制投影了,就不会触发离屏渲染了。
通常我们通过layer.cornerRadius
设置圆角,只会默认设置layer backgroundColor
和 border
的圆角,同时结合layer.masksToBounds
对 content
设置圆角。
单纯的cornerRadius+masksToBounds
不一定会产生离屏渲染,如果这个圆角裁剪操作需要作用多个图层,也就是layer
上有其他图层,那么肯定会发生离屏渲染。比如以下两种情况:
UIImageView
,同时设置了 backgroundColor
和 image
,会触发离屏渲染。比如以下代码,运行后,打开Xcode
-> Debug
-> View Debuging
-> Rendering
-> Color Offscreen Rendered Yellow
开关,查看离屏渲染情况:
let view = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
view.layer.cornerRadius = 2
view.layer.masksToBounds = true
self.view.addSubview(view)
// 1.设置 layer.contents ,触发离屏渲染
view.layer.contents = UIImage(named: "bc_delete")?.cgImage
// 2.addSubview ,触发离屏渲染
let textLab = UILabel(frame: CGRect(x: 20, y: 20, width: 40, height: 40))
textLab.text = "test"
view.addSubview(textLab)
// 3\. UIImageView同时设置image、backgroundColor,触发离屏渲染
let view = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
view.image = UIImage(named: "bc_delete")
view.backgroundColor = UIColor.gray
view.layer.cornerRadius = 20
view.layer.masksToBounds = true
self.view.addSubview(view)
那么如何避免圆角裁剪产生的离屏渲染呢?我们先来看官方描述:
总结一下:
不要使用不必要的 mask,可以预处理图片为圆形,通过 Core Graphics
手动绘制圆角。
使用中间为圆形透明的白色背景视图覆盖。但这种方式不能解决背景色为图片或渐变色的情况。
用 UIBezierPath
贝塞尔曲线绘制闭合带圆角的矩形,再将不带圆角的 layer 渲染成图片,添加到贝塞尔矩形中。这种方法效率更高,但是 layer
的布局一旦改变,贝塞尔曲线都需要手动地重新绘制,稍微麻烦。
最后我们总结一下,常见触发离屏渲染的情况有以下6种:
layer
设置了使用了 mask
蒙版。
layer
设置了圆角裁剪(masksToBounds
+cornerRadius
),且包含 sub layer。
layer
设置了组不透明度allowsGroupOpacity
,并且不透明度opacity
小于1。
layer
设置了投影shadowXX
。
layer
设置了光栅化shouldRasterize
。
使用了 blur 模糊效果UIVisualEffectView
。
另外,我们可以通过打开Xcode
-> Debug
-> View Debuging
-> Rendering
-> Color Offscreen Rendered Yellow
开关,检查离屏渲染情况。
Apple Core Animation Programming Guide
文章到这里就结束了,如果你有什么意见和建议欢迎给我留言。你可以加我及时获取最新资料。
作者:老青菜
链接:https://juejin.cn/post/6901973358657667085