iOS中的离屏渲染(Off-Screen Rendering)

ios下的离屏渲染

离屏渲染(OffScreen Rendering) 这个概念对于iOS开发者来讲并不陌生,对App的性能优化和面试中不止一次的遇到,今天咱们再来聊一聊这个问题。ios

原本是想写在上一篇 iOS下的图像渲染原理 中的,感受篇幅有点长了,影响阅读体验,因此单写了一篇。面试

什么是离屏渲染

在讨论离屏渲染以前,咱们先来看看正常的渲染逻辑。算法

frame-buffer

这里省略了其余的渲染细节。GPU 以60 FPS的帧率将渲染结果存储到帧缓冲区(Frame Buffer), 屏幕把每一帧图像以60 Hz的频率刷新显示。缓存

那么离屏渲染的大体流程是是什么样的呢?性能优化

off-screen-buffer

若是有时由于面临一些限制,没法把渲染结果直接写入Frame Buffer,而是先暂存在另外的内存区域,以后再写入Frame Buffer,那么这个过程被称之为离屏渲染markdown

GPU 离屏渲染

经过上一篇iOS下的图像渲染原理的讲解,咱们知道主要的渲染操做都是由 GPU CoreAnimationRender Server模块,经过调用显卡驱动所提供的OpenGL/Metal接口来执行的。多线程

对于每一层layer,Render Server会遵循画家算法,按次序输出到Frame Buffer,后一层覆盖前一层,就能获得最终的显示结果。函数

可是有一些状况下,并无这么简单。GPU 的Render Server遵循画家算法,一层一层的进行输出,可是在某一层渲染完成后,没法在回过头来处理或者改变其中的某个部分,由于在这以前的全部层像素数据,已经在渲染结束后被永久覆盖/丢弃了。post

若是咱们想对某一层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 来检查哪些图层触发了离屏渲染。

模拟器offscreen-rendering检查

触发了离屏渲染的图层会被标记为黄色。

模拟器offscreen-rendering演示

触发离屏渲染的场景

经过设置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一旦被设置为YESRender 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];
复制代码

模拟器offscreen-rendering演示2

此时的确不会触发离屏渲染,也达到了圆角的目的。

在实际项目中,通常会使用一张图片做为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];
复制代码

模拟器offscreen-rendering演示3

能够看到,这里仍然没有发生离屏渲染。那么离屏渲染到底和什么有关系呢?

仍是上面的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];
复制代码

模拟器offscreen-rendering演示4

在同时设置了backgroundColorsetImage:以后,这里触发了离屏渲染。

总结

关于性能优化,就是平衡 CPU 与 GPU 的负载工做,由于要作的事情就那么多。当 GPU 忙不过来的时候,咱们能够利用 CPU 的空闲来渲染而后提交给 GPU 显示,来提升总体的渲染效率。渲染不是CPU的强项,调用CoreGraphics会消耗其至关一部分计算时间,通常来讲 CPU 渲染都在后台线程完成(这也是AsyncDisplayKit的主要思想),而后再回到主线程上,把渲染结果传回CoreAnimation。这样一来,多线程间数据同步会增长必定的复杂度。CPU渲染速度不够快,所以只适合渲染静态的元素,如文字、图片。做为渲染结果的bitmap数量较大,很容易致使OOM。若是你选择使用 CPU 来作渲染,那么就没有理由再触发 GPU 的离屏渲染了。

相关文章
相关标签/搜索