iOS中的离屏渲染(Off-Screen Rendering)
2020/7/9 23:08:48
本文主要是介绍iOS中的离屏渲染(Off-Screen Rendering),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
离屏渲染(OffScreen Rendering)
这个概念对于iOS开发者来说并不陌生,对App的性能优化和面试中不止一次的遇到,今天我们再来聊一聊这个问题。
本来是想写在上一篇 iOS下的图像渲染原理 中的,感觉篇幅有点长了,影响阅读体验,所以单写了一篇。
什么是离屏渲染
在讨论离屏渲染
之前,我们先来看看正常的渲染逻辑。
这里省略了其他的渲染细节。GPU 以60 FPS
的帧率将渲染结果存储到帧缓冲区(Frame Buffer)
, 屏幕把每一帧图像以60 Hz
的频率刷新显示。
那么离屏渲染
的大致流程是是什么样的呢?
如果有时因为面临一些限制,无法把渲染结果直接写入Frame Buffer
,而是先暂存在另外的内存区域,之后再写入Frame Buffer
,那么这个过程被称之为离屏渲染
。
GPU 离屏渲染
通过上一篇iOS下的图像渲染原理的讲解,我们知道主要的渲染操作都是由 GPU CoreAnimation
的Render Server
模块,通过调用显卡驱动所提供的OpenGL/Metal
接口来执行的。
对于每一层layer,Render Server
会遵循画家算法
,按次序输出到Frame Buffer
,后一层覆盖前一层,就能得到最终的显示结果。
但是有一些情况下,并没有这么简单。GPU 的Render Server
遵循画家算法
,一层一层的进行输出,但是在某一层渲染完成后,无法在回过头来处理或者改变其中的某个部分,因为在这之前的所有层像素数据,已经在渲染结束后被永久覆盖/丢弃了。
如果我们想对某一层layer进行叠加/裁剪或者其他复杂的操作,就不得不新开一块内存区域,来处理这些些更加复杂的操作。
CPU 离屏渲染
我们看过一些文章有提到过CPU离屏渲染
,那么什么是CPU离屏渲染
呢?
如果我们在UIView
中实现了-drawRect
方法,就算它的函数体内部实际没有代码,系统也会为这个view
申请一块内存区域,等待CoreGraphics
可能的绘画操作。
这种情况下,新开辟了一块CGContext
,渲染数据暂时存储在了CGContext
中,而没有给到Frame Buffer
。根据上面的定义来说,没有把渲染结果直接给到Frame Buffer
的,那就属于离屏渲染
了。
但是,所有 CPU 执行的光栅化操作,比如图片的解码等等,都无法直接绘制到 GPU 的Frame Buffer
中,多需要一块用来中转的内存区域。当然,我们知道 CPU 并不擅长渲染,所以我们应该尽量避免使用 CPU 渲染。根据苹果的说法,这并非真正意义上的离屏渲染
,并且如果我们重写了-drawRect
方法,使用Xcode
检测,也并不会被标记为离屏渲染
。
如何检测项目中哪些图层触发了离屏渲染?
在模拟器中通过设置 Color Off-Screen Rendered
来检查哪些图层触发了离屏渲染。
触发了离屏渲染的图层会被标记为黄色。
触发离屏渲染的场景
通过设置cornerRadius与masksToBounds达到圆角裁切效果
当需要裁切图层的内容content
,很显然这就需要开辟一块内存来操作了。当只设置cornerRadius
时,不需要裁切内容,只需要一个带圆角的边框,则不会触发离屏渲染。
shadow
阴影依赖layer本身的形状等信息,并且根据画家算法
,阴影需要先画出来,这样来说就需要在单独的内存中先进行依赖的合成计算,再添加到Frame Buffer
,造成离屏渲染
。不过如果我们能够预先告诉CoreAnimation
(通过shadowPath
属性)阴影的几何形状,那么阴影当然可以先被独立渲染出来,不需要依赖layer本体,也就不再需要离屏渲染
了。
group opacity
需要将一组图层画完之后,再整体加上alpha
,最后和底下其他layer的像素进行组合。显然也无法通过一次遍历就得到最终结果。
mask
我们知道mask是应用在layer和其所有子layer的组合之上的,而且可能带有透明度,那么其实和group opacity
的原理类似,不得不在离屏渲染中完成。
UIBlurEffect
渲染出毛玻璃效果,需要先画出原图层,然后capture
原图层,进行水平模糊(Horizontal Blur)和垂直模糊(Vertical Blur),最后进行合成操作。显然这需要在离屏缓冲区中完成。
shouldRasterize
shouldRasterize
一旦被设置为YES
,Render Server
就会强制把layer的渲染结果(包括其子layer,以及圆角、阴影、group opacity
等等)保存在一块内存中,这样一来在下一帧仍然可以被复用,而不会再次触发离屏渲染。有几个需要注意的点
shouldRasterize
的主旨在于降低性能损失,但总是至少会触发一次离屏渲染。如果你的layer本来并不复杂,也没有圆角阴影等等,则没有必要打开shouldRasterize
- 如果layer的子结构非常复杂,渲染一次所需时间较长,可以打开
shouldRasterize
,把layer绘制到一块缓存,然后在接下来复用这个结果,这样就不需要每次都重新绘制整个layer树了 - 离屏渲染缓存有空间上限,最多不超过屏幕总像素的
2.5倍
大小,如果超出了会自动被丢弃,且无法被复用了 - 离屏渲染缓存内容有时间限制,一旦缓存超过
100ms
没有被使用,会自动被丢弃,且无法被复用了 - 如果layer不是静态的,需要被频繁修改,比如处在动画之中,那么开启
shouldRasterize
反而影响效率了
如果你无法仅仅使用Frame Buffer
来画出最终结果,那就只能另开一块内存空间来储存中间结果。
圆角问题
通常情况下,我们会使用 cornerRadius
来设置圆角
view.layer.cornerRadius = 50; 复制代码
我们看过很多文章都在说单独使用 cornerRadius
是不会触发离屏渲染的,先来实现一个非常简单圆角Button,只设置了backgroundColor
,没有setImage:
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom]; btn.frame = (CGRect){ .origin.x = 100, .origin.y = 280, .size.width = 100, .size.height = 100, }; btn.backgroundColor = [UIColor blueColor]; btn.layer.cornerRadius = 50; [self.view addSubview:btn2]; 复制代码
此时的确不会触发离屏渲染,也达到了圆角的目的。
在实际项目中,一般会使用一张图片作为Button
或者ImageView
的背景,这样如果只设置cornerRadius
,是达不到圆角的效果的,还需要设置masksToBounds = YES
imageView.layer.cornerRadius = 50; imageView.layer.masksToBounds = YES; 复制代码
这里用UIImageView
来举例,我们看下效果
UIImageView *imageView = [[UIImageView alloc] init]; imageView.frame = (CGRect){ .origin.x = 100, .origin.y = 400, .size.width = 100, .size.height = 100, }; imageView.layer.cornerRadius = 50; imageView.layer.masksToBounds = YES; imageView.image = [UIImage imageNamed:@"btn.png"]; [self.view addSubview:imageView]; 复制代码
可以看到,这里仍然没有发生离屏渲染。那么离屏渲染到底和什么有关系呢?
还是上面的UIImageView
的案例,我们尝试设置一下它的backgroundColor
UIImageView *imageView = [[UIImageView alloc] init]; imageView.frame = (CGRect){ .origin.x = 100, .origin.y = 400, .size.width = 100, .size.height = 100, }; imageView.layer.cornerRadius = 50; imageView.layer.masksToBounds = YES; imageView.backgroundColor = [UIColor blueColor]; imageView.image = [UIImage imageNamed:@"btn.png"]; [self.view addSubview:imageView]; 复制代码
在同时设置了backgroundColor
和setImage:
之后,这里触发了离屏渲染。
总结
关于性能优化,就是平衡 CPU 与 GPU 的负载工作,因为要做的事情就那么多。当 GPU 忙不过来的时候,我们可以利用 CPU 的空闲来渲染然后提交给 GPU 显示,来提高整体的渲染效率。渲染不是CPU的强项,调用CoreGraphics
会消耗其相当一部分计算时间,一般来说 CPU 渲染都在后台线程完成(这也是AsyncDisplayKit
的主要思想),然后再回到主线程上,把渲染结果传回CoreAnimation
。这样一来,多线程间数据同步会增加一定的复杂度。CPU渲染速度不够快,因此只适合渲染静态的元素,如文字、图片。作为渲染结果的bitmap数量较大,很容易导致OOM。如果你选择使用 CPU 来做渲染,那么就没有理由再触发 GPU 的离屏渲染了。
这篇关于iOS中的离屏渲染(Off-Screen Rendering)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2022-10-05Swift语法学习--基于协议进行网络请求
- 2022-08-17Apple开发_Swift语言地标注释
- 2022-07-24Swift 初见
- 2022-05-22SwiftUI App 支持多语种 All In One
- 2022-05-10SwiftUI 组件参数简写 All In One
- 2022-04-14SwiftUI 学习笔记
- 2022-02-23Swift 文件夹和文件操作
- 2022-02-17Swift中使用KVO
- 2022-02-08Swift 汇编 String array
- 2022-01-30SwiftUI3.0页面反向传值