性能优化

离屏渲染机制描述及界面优化

2019-03-16  本文已影响0人  荒漠现甘泉

GPU渲染机制

CPU 计算好显示内容提交到 GPU,GPU 渲染完成后将渲染结果放入帧缓冲区,随后视频控制器会按照 VSync 信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器显示。


ios_screen_display.png ios_vsync_runloop.png

GPU屏幕渲染方式

GPU屏幕渲染方式有两种:

既然离屏渲染这么耗性能,为什么有这套机制呢?

有些效果被认为不能直接呈现于屏幕,而需要在别的地方做额外的处理预合成。图层属性的混合体没有预合成之前不能直接在屏幕中绘制,就需要屏幕外渲染。屏幕外渲染并不意味着软件绘制,但是它意味着图层必须在被显示之前在一个屏幕外上下文中被渲染(不论CPU还是GPU)。

离屏渲染的触发

以下情况或操作会触发离屏渲染:

光栅化概念

其中shouldRasterize(光栅化)是比较特别的一种:

光栅化概念:将图转化为一个个栅格组成的图像。
光栅化特点:每个元素对应帧缓冲区中的一像素。

shouldRasterize = YES在其他属性出发离屏渲染的同时,会将光栅化后的内容缓存起来,如果对应的layer及其sublayer没有发生改变,在下一帧可以直接复用。shouldRasterize = YES,这将隐式的创建一个位图,各种阴影遮罩等效果也会保存到位图中并缓存起来,从而减少渲染的频度(不是矢量图)。

相当于光栅化是把GPU的操作转到CPU上了,生成位图缓存,直接读取复用。

当你使用光栅化时,你可以开启Color Hits Green and Misses Red来检查该场景下光栅化操作是否是一个好的选择。绿色表示缓存被复用,红色表示缓存在被重复创建。

如果光栅化的层变红的太频繁那么光栅化对优化可能没有多少用处。位图缓存从内存中删除又重新创建得太过频繁。红色表明缓存重建得太迟。可以针对性的选择某个较小而较深的层结构进行光栅化,来尝试减少渲染时间。

注意:对于经常变动的内容,这个时候不要开启,否则会造成性能的浪费。例如我们日常经常打交道的TableViewCell,因为TableViewCell的重绘是很频繁的(因为Cell的复用),如果Cell的内容不断变化,则Cell需要不断重绘,如果此时设置了cell.layer可光栅化,则会造成大量的离屏渲染,降低图形性能。

离屏渲染的检测

怎么检测离屏渲染呢?我们可以利用Instruments的Core Animation来检测离屏渲染。通过选择Xcode --> Debug --> View Debugging -->Rendering 选择离屏渲染属性,运行项目即可检测离屏渲染。具体各个属性解释可以看这篇文章:iOS Instrument使用之Core Animation

离屏渲染检测.png

我们来看看跟离屏渲染相关的几个属性设置:

该选择哪种渲染方式?

1、尽量使用当前屏幕渲染

离屏渲染、CPU渲染可能带来性能问题,一般情况下,我们要尽量使用当前屏幕渲染。

2、离屏渲染和CPU渲染

由于GPU的浮点运算能力比CPU强,CPU渲染的效率可能不如离屏渲染;但如果仅仅是实现一个简单的效果,直接使用CPU渲染的效率又可能比离屏渲染好,毕竟离屏渲染要涉及到缓冲区创建和上下文切换等耗时操作。

离屏渲染优化

设置圆角

方法一
我们通常会采用这种方式来设置圆角:

/**
 设置cornerRadius>0且clipToBounds为YES,再添加子视图
 */
- (void)setCorner1{
    self.avatarImageView.layer.cornerRadius = self.avatarImageView.bounds.size.width/2;
    self.avatarImageView.clipsToBounds = YES;
    // 或通过设置 layer.masksToBounds = YES
//    self.avatarImageView.layer.masksToBounds = YES;

    // 如果再添加子视图会触发离屏渲染,不添加则不会
    [self.avatarImageView addSubview:self.titleLabel];
}

我们通常设置圆角会通过设置layer.cornerRadius和layer.masksToBounds = YES来设置。这样设置在视图没有子视图的情况下是不会触发离屏渲染的,有子视图就会触发离屏渲染。有子视图的情况下还需要寻找别的方式来避免离屏渲染。

其实在iOS9.0之前UIimageView跟UIButton像上面这样设置圆角都会触发离屏渲染。

iOS9.0系统优化之后UIButton像上面这样设置圆角还是会触发离屏渲染,而UIImageView里png图片设置圆角不会触发离屏渲染了,如果设置其他阴影效果之类的还是会触发离屏渲染的。

方法二
利用CoreGraphics画一个圆形上下文,然后把图片绘制上去,得到一个圆形的图片,达到切圆角的目的。

- (UIImage *)drawCircleImage:(UIImage*)image
{
    CGFloat side = MIN(image.size.width, image.size.height);
    
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(side, side), false, [UIScreen mainScreen].scale);
    CGContextAddPath(UIGraphicsGetCurrentContext(), [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, side, side)].CGPath);
    CGContextClip(UIGraphicsGetCurrentContext());
    
    CGFloat marginX = -(image.size.width - side) * 0.5;
    CGFloat marginY = -(image.size.height - side) * 0.5;
    [image drawInRect:CGRectMake(marginX, marginY, image.size.width, image.size.height)];
    
    CGContextDrawPath(UIGraphicsGetCurrentContext(), kCGPathFillStroke);
    
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    return newImage;
}

方法三
利用mask设置圆角,利用的是UIBezierPathCAShapeLayer来完成,不过这种方式也会造成离屏渲染。

    CAShapeLayer *mask = [[CAShapeLayer alloc] init];
    mask.opacity = 1.0;
    mask.path = [UIBezierPath bezierPathWithOvalInRect:self.avatarImageView.bounds].CGPath;
    self.avatarImageView.layer.mask = mask;

设置阴影

阴影可以通过设置layer层的shadowXXX属性,就可以很方便的UIView添加阴影效果,但是不同的设置方式可能产生性能方面的问题。下面介绍一下不同方式对性能的影响。

方法一

通过设置下面的4个属性,就可以添加阴影,不过这种方式会造成离屏渲染。因为绘制阴影而不指定阴影路径,在绘制阴影的过程中就会产生大量的离屏渲染,非常消耗性能,从而造成UI卡顿。

如下方式设置阴影造成离屏渲染的原因是:iOS会先绘制目标的阴影,然后绘制目标本身,在没有指定阴影的绘制路径时,iOS视图在每次绘制前都会递归的精确计算每个子层阴影的路径,这会非常消耗性能,也是导致卡顿的根源。

    // 设置阴影颜色
    shadowImgView.layer.shadowColor = [UIColor redColor].CGColor;
    // 设置阴影透明度
    shadowImgView.layer.shadowOpacity = 0.8f;
    // 设置阴影偏移量,默认是(0,-3),向上偏移
    shadowImgView.layer.shadowOffset = CGSizeMake(5, 5);
    // 设置阴影半径
    shadowImgView.layer.shadowRadius = 5.f;

方法二

为了减少因为没有设置shadowPath造成绘制阴影时大量重复绘制的问题,我们可以指定阴影的绘制路径,这样在绘制阴影时,就可以在多个layer层共享同一个路径的阴影,以此来提高性能。

如果不指定路径shadowPath,就会使用layer层的alpha通道的混合,而如果指定阴影的路径,就会在多个layer之间共享同一路径,以此来提高性能。

有关什么是layer层的混合,可以这样理解:iOS在渲染每一帧时,都会计算每一个像素的颜色,如果上层layer不透明,就只取上层layer的颜色;而如果上层layer存在透明度时(alpha通道),则需要混合每一层的颜色来计算最终的颜色。如果layer越多,计算量就越大,也就比较耗性能。所以,在开发中,要尽量减少视图的透明层。具体代码如下:

    // 设置阴影颜色
    shadowImgView.layer.shadowColor = [UIColor redColor].CGColor;
    // 设置阴影透明度
    shadowImgView.layer.shadowOpacity = 0.8f;
    // 设置阴影偏移量,默认是(0,-3),向上偏移
    shadowImgView.layer.shadowOffset = CGSizeMake(5, 5);
    // 设置阴影半径
    shadowImgView.layer.shadowRadius = 5.f;
    // 设置阴影路径
    UIBezierPath *path = [UIBezierPath bezierPathWithRect:shadowImgView.bounds];
    // 如果是圆形view,则使用下面的圆形路径
    //UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:shadowImgView.bounds];
    shadowImgView.layer.shadowPath = path.CGPath;

注意:xib拖出来的控件是没法像上面这样设置阴影的,具体设置阴影的方法看这篇文章iOS xib设置阴影

参考文章:

1.ios中的离屏渲染与相关性能检测优化
2.iOS离屏渲染之优化分析
3.iOS性能优化-离屏渲染
4.iOS"离屏渲染"整理总结
5.iOS的阴影绘制及性能优化

上一篇下一篇

猜你喜欢

热点阅读