iOS-对离屏渲染的理解

什么是离屏渲染

当图层属性的混合体被指定为在未预合成之前不能直接在屏幕中绘制时,屏幕外渲染就被唤起了。屏幕外渲染并不意味着软件绘制,但是它意味着图层必须在被显示之前在一个屏幕外上下文中被渲染(不论CPU还是GPU)。---摘自iOS核心动画

为什么会触发离屏渲染

触发条件:

  • 圆角(当和maskToBounds一起使用时)
  • 图层蒙板
  • 阴影

简单来讲,当一个视图无法通过一次绘制并完成渲染时,就会触发离屏渲染。具体来讲就是,当一个视图效果需要多个图层配合完成时,此时会开启离屏缓存区(Off-Screen Buffer),来处理并缓存每一个图层的渲染结果,最终组合提交到帧缓存区(Frame Buffer),并完成最终的渲染效果。
举个例子:给UIImageView 设置 mask:

@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIImageView *imageView;
@end
@implementation ViewController
- (void)viewDidLoad
{
  [super viewDidLoad];
  //create mask layer
  self.imageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 350, 240)];
    self.imageView.center = self.view.center;
    self.imageView.image = [UIImage imageNamed:@"linjj"];
    self.imageView.backgroundColor = [UIColor redColor];
    [self.view addSubview:self.imageView];
//    self.imageView.layer.cornerRadius = 15.0;
//    self.imageView.layer.masksToBounds = YES;

    CAShapeLayer *maskLayer = [CAShapeLayer layer];
    maskLayer.path = [self getCustomPath].CGPath;
    self.imageView.layer.mask = maskLayer;
}

效果图:

自定义mask效果图.png

此时打开模拟器的Debug->Color Off-screen Rendered后,会发现一片yellow色,此时触发了离屏渲染:
触发了离屏渲染.png

原因:
首先我们呈现的效果图是一个不规则的图片。这个效果是由一个规则的矩形纹理图层+一个不规则的遮罩层组合而形成的最终效果图,即:

流程图.png
蒙版.png
JJ纹理.png
组合.png

这里可以看到,要想完成这样一个效果,大致是需要分成3步的:

  1. 处理绘制规则纹理图层;
  2. 处理绘制不规则蒙版图层;
  3. 将1和2组合并提交给FrameBuffer,渲染结果。

既然需要分别处理和组合,那么1和2的状态就需要保存,此时就自动触发了Off-screen Buffer,来保存每一步的状态,最终将保存的状态提交到FrameBuffer
关于FrameBuffer,这个里面一定放的是即将要呈现的画面或者是已经处理好的画面(帧)。

触发离屏渲染

  1. self.imageView.layer.masksToBounds = YES
    这个设置为YES,会触发离屏渲染,这里完全也可以理解为给一个layer设置了mask,只不过是这是个特殊的,规则的,系统内部自动添加的mask,同样需要多个步骤+组合,最终到屏幕。通常跟self.imageView.layer.cornerRadius = 15.0一起使用,来设置一个规则的蒙版。
  2. self.imageView.layer.shouldRasterize = YES

When true, the layer is rendered as a bitmap in its local coordinate
space ("rasterized"), then the bitmap is composited into the
destination (with the minificationFilter and magnificationFilter
properties of the layer applied if the bitmap needs scaling).
Rasterization occurs after the layer's filters and shadow effects
are applied, but before the opacity modulation. As an implementation
detail the rendering engine may attempt to cache and reuse the
bitmap from one frame to the next. (Whether it does or not will have
no affect on the rendered output.)

注意关键词:composited into the destination(合成到目标),既然要合成,就一定是多个步骤完成,就需要开启Off-screen Buffer 保存一些绘制状态,最后提交到FrameBuffer渲染。
关于shouldRasterize的使用建议:

  • layer 是可复用的,设置为YES,是可以提高效率的,反正不使用;
  • layer 不需要被频繁的修改,比如处理动画,开始光栅化反而影响效率;
  1. self.imageView.layer.cornerRadius = 15.0

Setting the radius to a value greater than 0.0 causes the layer to begin drawing rounded corners on its background. By default, the corner radius does not apply to the image in the layer’s contents property; it applies only to the background color and border of the layer. However, setting the masksToBounds property to true causes the content to be clipped to the rounded corners.

只设置cornerRadius只会对background colorborder生效,要想让content也生效,需要设置masksToBounds属性,这就回到了上一个问题。

离屏渲染使用注意

  1. 离屏渲染缓存内容是有时间限制的,缓存内容在100ms如果没有被使用会被丢弃,且不能复用;
  2. 离屏渲染的缓存空间是有限的,超过2.5倍的屏幕像素大小,会失效。

避免离屏渲染

使用CAShaperLayer+UIBezierPath画圆角:

- (void)viewDidLoad
{
    [super viewDidLoad];
    //create shape layer
    CAShapeLayer *blueLayer = [CAShapeLayer layer];
    blueLayer.frame = CGRectMake(50, 50, 100, 100);
    blueLayer.fillColor = [UIColor blueColor].CGColor;
    blueLayer.path = [UIBezierPath bezierPathWithRoundedRect:
    CGRectMake(0, 0, 100, 100) cornerRadius:20].CGPath;
    //add it to our view
    [self.layerView.layer addSublayer:blueLayer];
}

对UIImage 切圆角:

- (UIImage *)roundedCornerImageWithCornerRadius:(CGFloat)cornerRadius
{
    CGFloat w = self.size.width;
    CGFloat h = self.size.height;
    CGFloat scale = [UIScreen mainScreen].scale;
    if(cornerRadius < 0){
        cornerRadius = 0;
    }else if (cornerRadius > MIN(w, h)){
        cornerRadius = MIN(w, h) / 2;
    }
    UIImage *image = nil;
    CGRect imageFrame = CGRectMake(0, 0, w, h);
    UIGraphicsBeginImageContextWithOptions(self.size, NO, scale);
    [UIBezierPath bezierPathWithRoundedRect:imageFrame cornerRadius:cornerRadius];
    [self drawInRect:imageFrame];
    image = UIGraphicsGetImageFromCurrentImageContext();
    return image;
}

方案有很多,这里不一一赘述了。。

总结

以上内容,都是自己的一些理解,可能不是很准确。等之后慢慢修正吧。

你可能感兴趣的:(iOS-对离屏渲染的理解)