离屏渲染--原理及解决方案

概述

离屏渲染(offscreen-rendering)问题是iOS性能优化中需要解决的一个问题,一般来说,少量的离屏渲染其实对我们APP的性能并没有太大影响,但是如果有大量离屏渲染问题,就会导致明显的掉帧和卡顿,离屏渲染就成为了我们APP优化必须解决的一个问题。
本文将从渲染的角度对离屏渲染产生的原因进行阐述。

离屏渲染和在屏渲染(onscreen-rendering)

在理解这这两个概念前,我们先了解iOS渲染的流程。我们通过UIKit设置界面的颜色、位置后,会经由Core Animation,再通过OpenGL ES/Metal绘制图像放到GPU的帧缓冲区中,在下一次垂直信号到来时将帧缓冲区的内容渲染到屏幕上,这就渲染的流程,帧缓冲区的大小也正好等于屏幕像素点的大小。


渲染流程

正常来说,GPU不需要额外开辟一个缓冲空间来进行渲染操作,而这种在当前屏幕缓冲区进行的渲染操作称为在屏渲染。但是在某些情况下,GPU需要额外开辟一块缓冲区进行渲染操作,这种情况称为离屏渲染。

离屏渲染的检测

模拟器:Debug->Color Offscreen-Rendered (离屏渲染的图层高亮成黄,可能存在性能问题)
真机:1.Xcode->Debug->View Debugging->Rendering->Color Offscreen-Rendered Yellow
2.Instrument->Core Animation->Color Offscreen-Rendered Yellow

离屏渲染产生的原因

1.系统触发
通常我们在渲染一个layer的内容时,通常采用油画算法,用先远后近的方式,将一层一层的图层由下至上渲染到屏幕上,正常情况,已经绘制好的内容是可以直接渲染到屏幕上的,并不需要额外的空间保存。但如果一个图层经过着色管道后产生的结果不能马上渲染到屏幕上,而是需要与另一个图层经过管道得到的结果再次进行计算、混合后产生特殊效果才能渲染到屏幕上时,GPU就需要额外开辟一个缓冲区保存这两次中间结果,这个缓冲区就称为离屏缓冲区,这个时候就产生了离屏渲染。
2.手动触发
除了系统触发的离屏渲染,我们也可以通过设置layer的shouldRasterize为YES来触发离屏渲染,在某些场景下,打开 shouldRasterize 可以将一个layer反复利用,从而达到提升效率的优势。

并不是所用的场景下开启光栅化(shouldRasterize)都可以提升效率,如果layer不能被复用,或者layer不是静态,会被频繁修改(比如处于动画之中),那么打开光栅化了反而有可能影响了效率。
另外,离屏渲染缓存内容有时间和空间限制,缓存内容如果100ms内没有被使用,那么它就会被丢弃,如果离屏缓冲区的大小超过2.5倍屏幕大小的话也会失效,无法进行复用。

离屏渲染对性能的影响

相对于在屏渲染,离屏渲染需要开辟一个新的缓冲区,另外离屏渲染在整个过程中需要多次切换上下文环境,如果一帧画面中,存在多个需要的离屏渲染的view,那么GPU的执行时间将会被拉长,如果无法在下一个垂直同步信号到来时完成计算,那么将会产生卡顿,造成性能问题。

iOS离屏渲染产生的场景

通常iOS在以下场景是有可能产生离屏渲染的:

  1. layer使用了cornerRadius+masksToBounds设置圆角
  2. layer设置阴影
  3. layer使用了mask
  4. layer设置了组透明度
  5. layer采用了光栅化
  6. 绘制了文字的layer(UILabel,CATextLayer,Core Text等)

解决layer圆角产生离屏渲染的问题

1.产生的场景
并不是每一个使用了cornerRadius+masksToBounds设置圆角的layer的圆角都会产生离屏渲染,如果一个layer只有一个图层,那么通过cornerRadius+masksToBounds设置圆角,是不会产生离屏渲染的,比如下面两个view:

    UIImageView *imgView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 44, 100, 100)];
    imgView.image = [UIImage imageNamed:@"img"];
    imgView.layer.cornerRadius = 5;
    imgView.clipsToBounds = YES;

或者:

    UIImageView *imgView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 44, 100, 100)];
    imgView.backgroundColor = UIColor.redColor;
    imgView.layer.cornerRadius = 5;
    imgView.clipsToBounds = YES;

如果一个View只有一个图层,通过cornerRadius+masksToBounds并不会产生离屏渲染,但是如果给例子中的imgView同时设置了背景颜色和图片,或者设置了边框,又或者给imgView添加子View时,就会产生离屏渲染了。

    imgView.image = [UIImage imageNamed:@"img"];
    imgView.backgroundColor = UIColor.redColor;
    imgView.layer.cornerRadius = 5;
    imgView.clipsToBounds = YES;
    imgView.image = [UIImage imageNamed:@"img"];
    imgView.layer.borderWidth = 1.f;
    imgView.layer.cornerRadius = 5;
    imgView.clipsToBounds = YES;
    imgView.image = [UIImage imageNamed:@"img"];
    imgView.layer.borderWidth = 1.f;
    imgView.layer.cornerRadius = 5;
    imgView.clipsToBounds = YES;
    
    UIView *subView = [[UIView alloc] initWithFrame:CGRectMake(120, 44, 40, 40)];
    subView.backgroundColor = UIColor.blueColor;
    [imgView addSubview:subView];

以上代码均会产生离屏渲染。

2.产生的原因

了解这个问题前,我们需要知道,一个layer是由backgroundColor、contents、border三部分组成的

layer的组成

而从cornerRadius的官方介绍看,单纯设置cornerRadius只能裁剪backgroud和border部分,contents需要陪着masksToBounds才能设置圆角。所以UIImageView图片内容的裁剪,实际上backgroud或者border的mask与其进行混合后产生的结果。


cornerRadius官方文档

如果layer中只有一个layer的contents,或者只有background和border,那么GPU计算出圆角结果后并不需要与其他图层进行混合或者其他运算,GPU不需要开辟额外缓冲区保存结果,在它渲染完成后就可以将其清除。
但是如果同时设置backgroud和contents,GPU绘制出背景图后,并不能马上将其提交渲染到屏幕上,额需要开辟离屏缓冲区保存背景图,等待GPU绘制完成contents时,在与background内容进行混合运算得到新的图层后,才能渲染到屏幕上,而这个时候,就产生了离屏渲染。

3.解决方案
1.让UI切圆角的图片,省时省力。
2.使用UIBezierPath+CoreGraphics。

imgView.image = [UIImage imageNamed:@"img"];
    UIGraphicsBeginImageContextWithOptions(imgView.bounds.size, NO, 1.0);
    [[UIBezierPath bezierPathWithRoundedRect:imgView.bounds cornerRadius:5] addClip];
    [imgView drawRect:imgView.bounds];
    imgView.image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

3.使用UIBezierPath+CAShapeLayer

    imgView.image = [UIImage imageNamed:@"img"];
    UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:imgView.bounds cornerRadius:5];
    CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
    maskLayer.frame = imgView.bounds;
    maskLayer.path = maskPath.CGPath;
    imgView.layer.mask = maskLayer;

4.采用三方库如YYImage

YYImage内部实际使用了UIBezierPath+CoreGraphics生成了新的圆角图片。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352