OpenGL

五、从图形成像角度理解离屏渲染

2020-07-08  本文已影响0人  小山93

1. 什么是离屏渲染

在普通的图形渲染的过程中,GPU会遍历图层树,一层一层的叠加渲染到帧缓冲区,每一层渲染完,都会丢弃掉,以减小开销。
但是如果有多个图层需要展示圆角阴影等特殊的效果,那么就不得不临时开辟出离屏渲染缓冲区,来保存这些渲染好的图层,当需要特殊处理的图层都渲染完后,再将这些图层进行统一的特殊效果处理,然后将处理后的结果发送到帧缓冲区。
图形图像显示到屏幕上的过程如下图所示 :


图形图像显示流程.png
1.1 离屏渲染缓冲区的限制
1.2 离屏渲染存在的问题

由于离屏渲染缓冲区占用一定的资源,所以存在性能问题
开辟离屏渲染缓冲区、对图层进行特殊处理、从离屏渲染缓冲区到帧缓冲区都需要时间,所以可能会存在掉帧问题

1.3 离屏渲染的优势

如果某种效果需要多次显示,可以提前在离屏缓冲区渲染,设置 shouldRasterize(光栅化) = YES ,已达到复用的目的。

2. 常见的触发离屏渲染的情况:

3. 接下来探究 UIKit 中圆角裁剪是什么情况下会触发离屏渲染的。

剧透结果:不管是怎么样的布局情况, 满足这三个条件的(圆角 + 裁剪 + 至少有2个层实际参与图层处理),均会触发离屏渲染。

  1. 只有自身1个layer时
  2. 有subview时
  3. 有sublayer时

3.1 只有自身1个layer时

结果先行: 圆角 + 裁剪 + 至少有2个层实际参与图层处理会触发离屏渲染
说明一下,这里的层不是指的CALayer对象, 而是其内部的层级(layer.backgroundColor | layer.border | layer.content),结构如下图所示:

layer 层级分解.png

接下来,我们逐一来看这个3个条件

官方文档中对于圆角的设置,是这样说明的:

设置layer.cornerRadius只会设置layer.backgroundColorlayer.border的圆角,默认情况下不会设置layer.content的圆角,除非设置了layer.maskToBounds = YES或者view.clipToBounds = YES

从文档中,可以看出圆角默认是对边框及背景色生效的, 即不裁剪也是可以显示圆角的。下图的代码中的确没有触发离屏渲染。
由此我们可以确定圆角 + 裁剪 + 2个层(背景色 + 边框)不会触发离屏渲染

圆角 + 裁剪 + 2个层(背景色 + 边框).png

3.2 有subview时

结果先行: 圆角 + 裁剪 + 至少有2个层实际参与图层处理会触发离屏渲染
通过上面的描述,大家应该已经清楚的感知到了,参与处理的图层数量,是是否开启离屏渲染的关键。在含有子视图的情况下,父视图上设置了圆角和裁剪,所以其本身已经是一个参与处理的图层了,这时只要子视图中有一个1层参与了圆角裁剪,就会触发离屏渲染。
“参与处理”这个词其实很重要, 如果子视图比父视图小,裁剪时,裁不到它,它就不算参与, 就不会开启离屏渲染。可以参考以下代码:

子视图小于父视图,不会触发离屏渲染.png 子视图大于等于父视图,会触发离屏渲染.png

3.3 有sublayer时

结果先行: 圆角 + 裁剪 + 至少有2个层实际参与图层处理会触发离屏渲染

父layer + 子layer, 多层参与裁剪,触发离屏渲染.png

三、圆角优化方案

- (UIImage *)yy_imageByRoundCornerRadius:(CGFloat)radius
                                 corners:(UIRectCorner)corners
                             borderWidth:(CGFloat)borderWidth
                             borderColor:(UIColor *)borderColor
                          borderLineJoin:(CGLineJoin)borderLineJoin {
    
    if (corners != UIRectCornerAllCorners) {
        UIRectCorner tmp = 0;
        if (corners & UIRectCornerTopLeft) tmp |= UIRectCornerBottomLeft;
        if (corners & UIRectCornerTopRight) tmp |= UIRectCornerBottomRight;
        if (corners & UIRectCornerBottomLeft) tmp |= UIRectCornerTopLeft;
        if (corners & UIRectCornerBottomRight) tmp |= UIRectCornerTopRight;
        corners = tmp;
    }
    
    UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
    CGContextScaleCTM(context, 1, -1);
    CGContextTranslateCTM(context, 0, -rect.size.height);
    
    CGFloat minSize = MIN(self.size.width, self.size.height);
    if (borderWidth < minSize / 2) {
        UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadii:CGSizeMake(radius, borderWidth)];
        [path closePath];
        
        CGContextSaveGState(context);
        [path addClip];
        CGContextDrawImage(context, rect, self.CGImage);
        CGContextRestoreGState(context);
    }
    
    if (borderColor && borderWidth < minSize / 2 && borderWidth > 0) {
        CGFloat strokeInset = (floor(borderWidth * self.scale) + 0.5) / self.scale;
        CGRect strokeRect = CGRectInset(rect, strokeInset, strokeInset);
        CGFloat strokeRadius = radius > self.scale / 2 ? radius - self.scale / 2 : 0;
        UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadii:CGSizeMake(strokeRadius, borderWidth)];
        [path closePath];
        
        path.lineWidth = borderWidth;
        path.lineJoinStyle = borderLineJoin;
        [borderColor setStroke];
        [path stroke];
    }
    
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

上一篇下一篇

猜你喜欢

热点阅读