Offscreen Rendering

2020-07-07  本文已影响0人  Maji1

Offscreen Rendering(离屏渲染)概念理解:

离屏渲染 是指系统为了绘制圆角(cornerRadius)、阴影(shadow*)、组透明度(allowsGroupOpacity/opacity)、光栅化(shouldRasterize)等等一系列效果,从而额外开辟了 离屏缓冲区(Offscreen Buffer然后将离屏缓冲区中的纹理图片合成到 帧缓冲区(Frame Buffer 最后再进行渲染的行为。

当然,并不是只要存在上述绘制操作就一定会进行离屏渲染。

xocde中如何检测离屏渲染效果:

勾选上Color Offscreen-Rendered Yellow选项后如果存在离屏渲染的控件就会呈现出黄色的标记:

离屏渲染效果图

Offscreen Rendering(离屏渲染)详细流程

首先我们来看一张流程图:


从这张图我们可以清晰的看出,③ 这张图是由 ① 和 ② 合成的。① 和 ② 存在于 离屏缓冲区(Offscreen Buffer,③存在于 帧缓冲区(Frame Buffer

下面我们来了解一下系统自带的“毛玻璃效果”渲染流程,也是离屏渲染

Render Content(渲染内容) -> Capture Content(捕获内容) -> Horizontal Blur(水平模糊) -> Vertical Blur(垂直模糊) -> Compositing Pass(合成过程)
其中Capture Content(捕获内容) 获取的是压缩后的图片,并不是原图。

shouldRasterize光栅化使用建议:

cornerRadius、layer.masksToBounds / view.clipsToBounds

设置layer. cornerRadius 只会设置 backgroundColorborder 的圆角,不会设置 content 的圆角。除非设置了 layer.masksToBoundstrue 或设置 view.clipsToBoundstrue
所以,单纯设置cornerRadius 并不会造成离屏渲染。

再思考一下,设置了layer. cornerRadius,再设置 layer.masksToBoundstrueview.clipsToBoundstrue 就一定会触发离屏渲染吗?下面我们查看一段代码:

 //1.按钮存在背景图片  离屏渲染
    UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeCustom];
    btn1.frame = CGRectMake(100, 30, 100, 100);
    btn1.layer.cornerRadius = 50;
    [self.view addSubview:btn1];
    
    [btn1 setImage:[UIImage imageNamed:@"btn.png"] forState:UIControlStateNormal];
    btn1.clipsToBounds = YES;
    
    //2.按钮不存在背景图片
    UIButton *btn2 = [UIButton buttonWithType:UIButtonTypeCustom];
    btn2.frame = CGRectMake(100, 180, 100, 100);
    btn2.layer.cornerRadius = 50;
    btn2.backgroundColor = [UIColor blueColor];
    [self.view addSubview:btn2];
    btn2.clipsToBounds = YES;
    
    //3.UIImageView 设置了图片+背景色;  离屏渲染
    UIImageView *img1 = [[UIImageView alloc]init];
    img1.frame = CGRectMake(100, 320, 100, 100);
    img1.backgroundColor = [UIColor blueColor];
    [self.view addSubview:img1];
    img1.layer.cornerRadius = 50;
    img1.layer.masksToBounds = YES;
    img1.image = [UIImage imageNamed:@"btn.png"];
    
    //4.UIImageView 只设置了图片,无背景色;
    UIImageView *img2 = [[UIImageView alloc]init];
    img2.frame = CGRectMake(100, 480, 100, 100);
    [self.view addSubview:img2];
    img2.layer.cornerRadius = 50;
    img2.layer.masksToBounds = YES;
    img2.image = [UIImage imageNamed:@"btn.png"];

经过测试我们发现btn1img1会产生离屏渲染,btn2img2并不会产生离屏渲染。

需要离屏渲染的控件,在绘制的时候都需要裁剪不止一个图层,当需要裁剪的图层只有一个时是不会产生离屏渲染的。
我们可以结合上面的介绍想想一下,当需要裁剪的图层只有一个时,系统就会直接将渲染好的纹理放到 帧缓冲区(Frame Buffer,没有必要使用离屏缓冲区(Offscreen Buffer, 然后然后等待runloop 的到来,通过 垂直同步 技术渲染到 屏幕上。


离屏渲染限制


圆角处理方法:

 img.layer.cornerRadius = 50;
 img.layer.masksToBounds = YES;
func createRoundedImage(cornerRadius: CGFloat) -> UIImage? {
        let w = self.size.width
        let h = self.size.height
        let scale = UIScreen.main.scale
        var cornerR = cornerRadius
        if cornerRadius < 0 { return self }
        if (cornerR > min(w, h)) {
            cornerR = min(w, h) * 0.5
        }
        
        let imageFrame = CGRect(x: 0.0, y: 0.0, width: w, height: h)
        UIGraphicsBeginImageContextWithOptions(self.size, false, scale)
        let bezierPath = UIBezierPath(roundedRect: imageFrame, cornerRadius: cornerR)
        bezierPath.addClip()
        self.draw(in: imageFrame)
        
        guard let image = UIGraphicsGetImageFromCurrentImageContext() else {
            return self
        }
        return image
    }

YYImage上绘制圆角的方法

- (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;
}

常见触发离屏渲染的几种情况:

1、使用 了mask的layerlayer.mask
2、需要进行裁剪的layercornerRadius、layer.masksToBounds / view.clipsToBounds
3、设置了组透明度为true,并且透明度不为1的layerallowsGroupOpacity/opacity
4、添加了投影layershow*
5、设置了光栅化的layershouldRasterize
6、绘制了文字的layerUILabelCATextLayerCore Text等)

上一篇 下一篇

猜你喜欢

热点阅读