美文网首页
CALayer离屏渲染问题

CALayer离屏渲染问题

作者: conowen | 来源:发表于2018-04-17 20:36 被阅读125次

    CALayer与UIView

    iOS界面中,看到的界面元素基本都是UIView,例如按钮,文本,图片等都是集成自UIView。事实上,UIView并没有绘制的功能,UIView的绘制工作是CoreAnimation框架完成的,也就是UIView的属性变量CALayer完成绘制工作的。

    //摘自UIView的头文件声明
    @property(nonatomic,readonly,strong)                 CALayer  *layer;              
    // returns view's layer. Will always return a non-nil value. view is layer's delegate
    
    

    看这个注释,可以把UIView当作CALayer的delegate,UIView只是来方便管理CALayer而已。同时,可以把CALayer理解为,这是一个图层,类似PS的图层概念,而且一个UIView可以有多个图层。

    UIView继承自UIResponder, 能接收并响应事件, 负责显示内容的管理。而CALayer继承自NSObject, 并不能响应事件, 负责显示内容的绘制。所以更改UIView的frame,bounds或者backgroundColor等这些属性的时候,其实是在更改CALayer的属性。

    UIView与CALayer一样是有层次的关系的,每个UIView和CALayer都可能有子view或者子layer,当子view在层级关系中添加或者被移除的时候,他们关联的layer也同样对应在层级关系树当中有相同的添加或者被移除操作。

    屏幕显示

    移动设备中显示系统一般是由CPU绘制好显示内容,GPU渲染结束后将渲染结果放入帧缓冲区,随后视频控制器会按照VSync信号(垂直同步)读取帧缓冲区的数据,然后通过数模转换,发送给显示器显示。
    如果当一个VSync信号来临的时候,帧缓冲区还没有新的渲染结果,就会使用旧的,而当前帧就会被丢弃。这就是为什么会卡帧的原因,CPU或者GPU绘制渲染跟不上VSync信号频率。

    iOS系统使用的是双缓冲区来缓冲渲染结果,GPU会把渲染结果依次放入两个缓冲区,当视频控制器读取第一个缓冲区的时候,GPU继续渲染好第二帧显示内容放入第二个缓冲区,然后把视频控制器的指针指向第二个缓冲区,这样下一个VSync信号来临的时候,就直接读取第二个缓冲区的显示内容。

    画面撕裂

    由于双缓冲的设计,容易导致当视频控制器正在读取A缓冲区的内容的时候,读到一半,显示器也显示一半内容出来了。此时GPU又刚好渲染完毕,把渲染结果存放在B缓冲区,同时把视频控制器的指针指到B缓冲区。那这时候读取的后一半就是B缓冲区的结果,也就是下一帧的内容,这时候就会出现画面撕裂的情况。要解决这个问题,引入一个机制开启垂直同步,也就是说,GPU渲染完毕,要等待VSync信号更新后才提交缓冲区里面。

    关于水平同步信号HSync和垂直同步VSync的区别,显示系统内部类似电子枪扫描一样。一帧画面需要水平同步信号HSync来控制电子枪的从左到右扫描。扫描完一行后,根据水平同步信号HSync,切换到最左边,开始下一行扫描。当扫描完最后一行,根据垂直同步VSync,回到第一行的最左边位置。

    image.png

    屏幕渲染

    在 OpenGL 中,GPU 屏幕渲染有以下两种方式:

    On-Screen Rendering:
    即当前屏幕渲染,在用于显示的屏幕缓冲区中进行,不需要额外创建新的缓存,也不需要开启新的上下文,所以性能较好,但是受到缓存大小限制等因素,一些复杂的操作无法完成。

    Off-Screen Rendering:
    即离屏渲染,指的是在 GPU 的当前屏幕缓冲区外开辟新的缓冲区进行操作。简单来说就是指的是在图像在绘制到当前屏幕前,需要先进行一次渲染,之后才绘制到当前屏幕。

    相比于当前屏幕渲染,离屏渲染的代价是很高的,主要体现在如下两个方面:

    • 创建新的缓冲区
    • 上下文切换
      离屏渲染的整个过程,需要多次切换上下文环境:先从当前屏幕切换到离屏,等待离屏渲染结束后,将离屏缓冲区的渲染结果显示到到屏幕上,这又需要将上下文环境从离屏切换到当前屏幕。

    离屏渲染的定义问题

    这里提到的offscreen rendering主要讲的是通过GPU执行的offscreen,事实上还有的offscreen rendering是通过CPU来执行的(例如使用Core Graphics, drawRect,下面的添加圆角方法中,第二第三中方法也产生了离屏渲染,只不过是CPU渲染的)。

    其它类似cornerRadios, masks, shadows等触发的offscreen是基于GPU的。许多人有误区,认为offscreen rendering就是software rendering,只是纯粹地靠CPU运算。实际上并不是的,offscreen rendering是个比较复杂,涉及许多方面的内容。

    通俗来说就是,显示系统不能第一时间显示内容,需要开辟buff区预渲染内容才能显示,就是离屏渲染。

    当对CALayer设置以下属性的时候,会导致离屏渲染(基于GPU)问题。
    shouldRasterize(光栅化)
    masks(遮罩)
    shadows(阴影)
    edge antialiasing(抗锯齿)
    group opacity(不透明)

    注意:shouldRasterize = YES 会使视图渲染内容被缓存起来,下次绘制的时候可以直接显示缓存,如果显示的内容不是动态变化的,这样使用缓存的方式,效率也挺不错的。

    由于UIView并没有设置圆角的API,一般设置View的圆角的时候可以使用CALayer来设置,但是这样容易导致离屏渲染问题,当屏幕出现过多圆角的时候,会出现严重卡顿,GPU满载,而CPU空闲的状态。

    当然,你也可以使用CPU渲染,CPU浮点计算能力不如GPU,但是渲染一些简单界面还是没问题的。因为CPU渲染不同于GPU渲染,不会产生离屏渲染问题,自然也就没有上下文切换的效率问题,有些场景效率可能更高。使用CPU渲染,一般就是重写 drawRect 方法,然后使用 Core Graphics 的技术进行了绘制操作,这个渲染过程由 CPU 在 App 内同步地完成,渲染得到的bitmap最后再交由GPU用于显示。

    处理圆角

    一般处理圆角,比较快捷的方式是

    view.layer.cornerRadius = 6.0;
    view.layer.masksToBounds = YES;
    

    但是这种方式会触发两次离屏渲染,对性能影响较大。可以改进一下,如下所示

    • 如果内容不是动态改变,可以使用shouldRasterize = YES,缓存成bitmap未尝不可;
    • 是UIImageView设置圆角的话,可以使用贝赛尔曲线工具UIBezierPath对UIImage进行裁剪成圆角再显示。
    - (void)drawRect:(CGRect)rect {
      CGRect bounds = self.bounds;
      [[UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:8.0] addClip];
      [self.image drawInRect:bounds];
    }
    

    这种方法只会触发一次离屏渲染(基于CPU),但是会到时内存的增加。

    • 利用CoreGraphics绘制出一个圆角矩形的上下文,然后将图片画到上下文中,最后通过上下文获取裁剪好的图片。同样也会触发一次基于CPU的离屏渲染。

    • 直接给当前的view加一层圆角遮罩层,比较局限,对背景有要求。

    • 直接让UI把图片裁剪好

    Blending 图像混合

    这里顺便提一下图像系统中的图像混合,与屏幕渲染一样的是,Blending同样会导致性能问题,但是影响没有离屏渲染这么严重。当两个图层叠加在一起,如果第一个图层的透明的,则最终像素的颜色计算需要将第二个图层也考虑进来。这一过程即为Blending。

    会导致blending的原因:

    • layer(UIView)的Alpha < 1
    • UIImgaeView的image含有Alpha channel(即使UIImageView的alpha是1,但只要image含透明通道,则仍会导致Blending)

    为什么Blending会导致性能的损失?
    原因是很直观的,如果一个图层是不透明的,则系统直接显示该图层的颜色即可。而如果图层是透明的,则会引入更多的计算,因为需要把下面的图层也包括进来,进行混合后颜色的计算。

    参考文章

    Advanced Graphics and Animations for iOS Apps

    相关文章

      网友评论

          本文标题:CALayer离屏渲染问题

          本文链接:https://www.haomeiwen.com/subject/iydekftx.html