离屏渲染问题探索

201 阅读3分钟

设置圆角触发离屏渲染?

通常我们会认为只要设置了圆角,就会触发离屏渲染,其实不然,并非所有的圆角都会触发离屏渲染。

  //按钮有设置背景图片
  UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
  btn.frame = CGRectMake(0, 0, 120, 120);
  [btn setImage:[UIImage imageNamed:@"btnback"] forState:UIControlStateNormal];
  btn.layer.cornerRadius = 60;
  btn.clipsToBounds = YES;  
  btn.center = self.view.center;
  [self.view addSubview:btn];

没有设置layer.masksToBounds或者clipsToBounds,其默认值为NO

这里我们运行后打开模拟器的离屏渲染颜色标记:


设置了layer.masksToBounds会发现触发了离屏渲染:


离屏渲染的原因

  • 首先我们了解下渲染流程:



由上渲染流程图我们知道App应用需要进行额外的渲染和合并,此时需要用到offscreenn Buffer组合放到FrameBuffer,最终显示到屏幕。

离屏渲染是需要开辟额外的储存空间,如果使用大量的离屏渲染,性能方面会大大的降低。因此应尽可能的减少离屏渲染。

  • 既然如此,为什么我们仍需要使用离屏渲染呢?

       1、在实现一些特殊效果的时候,需要使用额外的offscreen Buffer 保存中间状态,不得不使用离渲染。如:系统自动触发、圆角、阴影。

       2、效率优势,效果会多次出现在屏幕上,我们可以提取渲染好,保存在offscreenBuffer,从而达到复用的目的。

关于光栅化

shouldRasterize:设置光栅化;CALayer会被光栅化为bitmap,layer的阴影等效果也会被保存到bitmap中,使用时直接读取

btn.imageView.layer.shouldRasterize = YES;

光栅化的使用建议:

  • 如果layer不能被复用,则没有必要打开光栅化;
  • 如果layer不是静态的,需要被频繁修改,比如处于动画之中,那么开启离屏渲染而反而影响效率了;
  • 离屏渲染缓存内容有时间限制,缓存100ms内容,如果没有被使用,那么它会丢弃,无法进行复用了;
  • 离屏渲染内存空间有限。超过2.5倍屏幕像素大小的话,便会失效,且无法进行复用了;

圆⻆触发的离屏渲染offscreenRendering


绘制涂层背景时使用的半径,可设置动画。

设置layer.cornerRadius只会设置backgroundColor和border的圆角。不会设置内容的圆角,除非同时设置了layer.masksToBounds为True(对应View中的clipsTopBounds属性)。

btn.layer.masksToBounds = YES;iOS上的圆⻆处理⼿段参考⽅案

iOS上的圆⻆处理⼿段参考⽅案

  • 方案一、

// 设置圆角
btn.imageView.layer.cornerRadius = 60.0;
// 设置裁剪
btn.imageView.clipsToBounds = YES;

  • 方案二、

- (UIImage *)rundedCornerImageWithConnerRadius:(CGFloat)cornerRadius{
    CGFloat width  = self.frame.size.width;
    CGFloat height = self.frame.size.width;
    CGFloat scale = [UIScreen mainScreen].scale;
    
    //防止圆角半径小于0,或大于宽/高中较小值的一半
    if (cornerRadius < 0) {
        cornerRadius = 0;
    }else if(cornerRadius > MIN(width, height)){
        cornerRadius = MIN(width, height) / 2;
    }
    
    UIImage *image = [UIImage new];
    CGRect imageFrame = CGRectMake(0, 0, width, height);
    UIGraphicsBeginImageContextWithOptions(self.frame.size, NO, scale);
    [[UIBezierPath bezierPathWithRoundedRect:imageFrame cornerRadius:cornerRadius]addClip];
    [self drawRect:imageFrame];
    image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    return image;
}

常⻅触发离屏渲染的⼏种情况

  • 使⽤了mask的layer(layer.mask) 
  • 需要进⾏裁剪的layer(layer.masksToBounds/view.clipsToBounds) 
  • 设置了组透明度为YES,并且透明度不为1的layer(layer.allowsGroupOpacity/layer.opacity) 
  • 添加了投影的layer(layer.shadow*) 
  • 采⽤了光栅化的layer(layer.shouldRasterize) 
  • 绘制了⽂字的layer(UILabel,CATextLayer,CoreText等)


点我了解更多渲染架构探索