目錄
- 離屏渲染的本質(zhì)
- 如何設(shè)置圓角(三種方法)
- Shadow 陰影
- Mask
- GroupOpacity
- EdgeAntialiasing
- Rasterization
- 造成離屏渲染的原因
- 總結(jié)
離屏渲染(Offscreen Render)
繪制像素到屏幕上 應(yīng)該是國(guó)內(nèi)對(duì)離屏渲染這個(gè)概念推廣力度最大的一篇文章了。文章里提到直接將圖層合成到幀的緩沖區(qū)中(在屏幕上)比先創(chuàng)建屏幕外緩沖區(qū),然后渲染到紋理中,最后將結(jié)果渲染到幀的緩沖區(qū)中要廉價(jià)很多。因?yàn)檫@其中涉及兩次昂貴的環(huán)境轉(zhuǎn)換(轉(zhuǎn)換環(huán)境到屏幕外緩沖區(qū),然后轉(zhuǎn)換環(huán)境到幀緩沖區(qū))。觸發(fā)離屏渲染后這種轉(zhuǎn)換發(fā)生在每一幀,在界面的滾動(dòng)過(guò)程中如果有大量的離屏渲染發(fā)生時(shí)會(huì)嚴(yán)重影響幀率。
蘋(píng)果官方公開(kāi)的的資料里關(guān)于離屏渲染的信息最早是在 2011年的 WWDC, 在多個(gè) session 里都提到了盡量避免會(huì)觸發(fā)離屏渲染的效果,包括:mask
, shadow
, group opacity
, edge antialiasing
。
使用 Core Graphics 里的繪制 API 也會(huì)觸發(fā)離屏渲染,比如重寫(xiě) drawRect:。在 WWDC 2011: Understanding UIKit Rendering 這個(gè) session 里演示了Core Animation Instruments
里使用Color Offscreen-Renderd Yellow
選項(xiàng)來(lái)檢測(cè)離屏渲染
。
Designing for iOS: Graphics & Performance這篇文章也提到了使用 Core Graphics API 會(huì)觸發(fā)離屏渲染,隨后蘋(píng)果對(duì)這個(gè)觀(guān)點(diǎn)進(jìn)行了回復(fù),主要意思是:Core Graphics 的繪制 API 的確會(huì)觸發(fā)離屏渲染,但不是那種 GPU 的離屏渲染。使用 Core Graphics 繪制 API 是在 CPU 上執(zhí)行,觸發(fā)的是 CPU 版本的離屏渲染。
本文以Color Offscreen-Renderd Yellow
為觸發(fā)離屏渲染的標(biāo)準(zhǔn),除非還有這個(gè)標(biāo)準(zhǔn)無(wú)法檢測(cè)出來(lái)的引發(fā)離屏渲染的行為。那么 Core Graphics API 是不會(huì)觸發(fā)離屏渲染的,比如重寫(xiě)drawRect:,而除了以上四種效果會(huì)觸發(fā)離屏渲染,使用系統(tǒng)提供的圓角效果也會(huì)觸發(fā)離屏渲染,比如這樣:
view.layer.cornerRadius = 5
view.layer.masksToBounds = true
開(kāi)始之前,先鋪墊一點(diǎn)基礎(chǔ)的東西。
UIView 和 CALayer 的關(guān)系
The Relationship Between Layers and Views的解釋很細(xì)致但是太啰嗦,簡(jiǎn)單來(lái)說(shuō),UIView 是對(duì) CALayer 的一個(gè)封裝。
CALayer 負(fù)責(zé)顯示內(nèi)容contents,UIView 為其提供內(nèi)容,以及負(fù)責(zé)處理觸摸等事件,參與響應(yīng)鏈。CALayer 的結(jié)構(gòu)如下.出自 Layers Have Their Own Background and Border:
CALayer 有三個(gè)視覺(jué)元素,中間的contents屬性是這樣聲明的
var contents: AnyObject?
實(shí)際上它必須是一個(gè)CGImage才能顯示。
當(dāng)使用let view = UIView(frame: CGRectMake(0, 0, 200, 200))生成一個(gè)視圖對(duì)象并添加到屏幕上時(shí),從 CALayer 的結(jié)構(gòu)可以知道,這個(gè)視圖的 layer 的三個(gè)視覺(jué)元素是這樣的:contents為空,背景顏色為空(透明色),前景框?qū)挾葹?的前景框,這個(gè)視圖從視覺(jué)上看什么都看不到。CALayer 文檔第一句話(huà)就是:The CALayer class manages image-based content and allows you to perform animations on that content.
UIView 的顯示內(nèi)容很大程度上就是一張圖片(CGImage)
。
UIImageView
既然直接對(duì) CALayer 的contents
屬性賦值一個(gè)CGImage
便能顯示圖片,所以 UIImageView 就順利成章地誕生了。實(shí)際上 UIImage 就是對(duì) CGImage(或者 CIImage) 的一個(gè)輕量封裝。記得我剛接觸 iOS 時(shí),搞不懂這兩者的區(qū)別,有人這樣對(duì)我說(shuō)過(guò),沒(méi)想到出處是這里:
UIKit 和 Core Graphics 框架的聯(lián)系很緊密,UIKit 里帶CG前綴屬性的類(lèi)基本上是對(duì)應(yīng) Core Graphics 框架里的對(duì)象的封裝,UIKit 里的繪制功能也是 Core Graphics 繪制 API 的封裝。Drawing with Quartz and UIKit列舉了這些對(duì)應(yīng)關(guān)系。界面的內(nèi)容主要是圖像和文字,文字是怎么顯示的?也是使用 Core Graphics 框架繪制出來(lái)的。
接下來(lái),正式開(kāi)始本文的話(huà)題。
一 設(shè)置圓角
RoundedCorner
設(shè)置圓角:
view.layer.cornerRadius = 5
文檔中cornerRadius屬性的說(shuō)明:
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 YES causes the content to be clipped to the rounded corners.
很明了,只對(duì)前景框和背景色起作用,再看 CALayer 的結(jié)構(gòu),如果contents有內(nèi)容或者內(nèi)容的背景不是透明的話(huà),還需要把這部分弄個(gè)角出來(lái),不然合成的結(jié)果還是沒(méi)有圓角,所以才要修改masksToBounds為true(在 UIView 上對(duì)應(yīng)的屬性是clipsToBounds,在 IB 里對(duì)應(yīng)的設(shè)置是「Clip Subiews」選項(xiàng))。前些日子很熱鬧的圓角優(yōu)化文章中的2篇指出是修改masksToBounds為true而非修改cornerRadius才是觸發(fā)離屏渲染的原因,但如果以「Color Offscreen-Renderd Yellow」的特征為標(biāo)準(zhǔn)的話(huà),
這兩個(gè)屬性單獨(dú)作用時(shí)都不是引發(fā)離屏渲染的原因,他倆合體(masksToBounds = true, cornerRadius>0)才是。
方案1 重繪
重繪的方式有多種,都是殊途同歸。實(shí)際中重繪圓角的優(yōu)化方案需要考慮的是,將圖像重新繪制為為圓角圖像相當(dāng)于多了一份拷貝,要不要緩存?A.第一次重繪后將這些圓角圖像緩存在磁盤(pán)里,第二次加載直接使用緩存的圓角圖像;B.直接保存在內(nèi)存里,在內(nèi)存比較吃緊時(shí)顯然不是個(gè)好選擇;C.不緩存,和系統(tǒng)圓角一樣,每次都重繪,浪費(fèi)電量。
方案2
如果不需要對(duì)外部來(lái)源的圖片做圓角,由設(shè)計(jì)師直接畫(huà)成圓角圖片是最方便的;
方案3 混合圖層
在要添加圓角的視圖上再疊加一個(gè)部分透明的視圖,只對(duì)圓角部分進(jìn)行遮擋。VVebo微博客戶(hù)端就是這樣做的,遮擋的部分背景最好與周?chē)尘跋嗤?。多一個(gè)圖層會(huì)增加合成的工作量,但這點(diǎn)工作量與離屏渲染相比微不足道,性能上無(wú)論各方面都和無(wú)效果持平。下面左側(cè)的圖像是 VVebo 里用來(lái)制造圓形頭像的 mask 圖像,實(shí)際中有這種需求的基本是制造圓形頭像,普通的圓角遮罩需要左二這種,左三是通用型。如果疊加的視圖都一樣,可以只加載一次遮罩圖片以減少內(nèi)存占用。
- 實(shí)例代碼如下 (上面覆蓋一個(gè)圓形遮蓋視圖)
- (void)drawRoundedCornerImage {
UIImageView *iconImgV = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
iconImgV.image = [UIImage imageNamed:@"icon"];
[self.view addSubview:iconImgV];
[iconImgV mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(iconImgV.size);
make.top.equalTo(self.view.mas_top).offset(500);
make.centerX.equalTo(self.view);
}];
UIImageView *imgView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
[self.view addSubview:imgView];
[imgView mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(imgView.size);
make.top.equalTo(iconImgV.mas_top);
make.leading.equalTo(iconImgV.mas_leading);
}];
// imgView.image = [self useUIGraphicsDrawAntiRoundedCornerImageWithRadius:10 outerSize:CGSizeMake(150, 150) innerSize:CGSizeMake(100, 100) fillColor:[UIColor whiteColor]];
// 圓形
imgView.image = [self drawCircleRadius:100 outerSize:CGSizeMake(200, 200) fillColor:[UIColor whiteColor]];
}
// 繪制圓形
- (UIImage *)drawCircleRadius:(float)radius outerSize:(CGSize)outerSize fillColor:(UIColor *)fillColor {
UIGraphicsBeginImageContextWithOptions(outerSize, false, [UIScreen mainScreen].scale);
// 1、獲取當(dāng)前上下文
CGContextRef contextRef = UIGraphicsGetCurrentContext();
//2.描述路徑
// ArcCenter:中心點(diǎn) radius:半徑 startAngle起始角度 endAngle結(jié)束角度 clockwise:是否逆時(shí)針
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(outerSize.width * 0.5, outerSize.height * 0.5) radius:radius startAngle:0 endAngle:M_PI * 2 clockwise:NO];
[bezierPath closePath];
// 3.外邊
[bezierPath moveToPoint:CGPointMake(0, 0)];
[bezierPath addLineToPoint:CGPointMake(outerSize.width, 0)];
[bezierPath addLineToPoint:CGPointMake(outerSize.width, outerSize.height)];
[bezierPath addLineToPoint:CGPointMake(0, outerSize.height)];
[bezierPath addLineToPoint:CGPointMake(0, 0)];
[bezierPath closePath];
//4.設(shè)置顏色
[fillColor setFill];
[bezierPath fill];
CGContextDrawPath(contextRef, kCGPathStroke);
UIImage *antiRoundedCornerImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return antiRoundedCornerImage;
}
效果如下
布局如下圖所示
如何在文本視圖類(lèi)上實(shí)現(xiàn)圓角
文本視圖主要是這三類(lèi):UILabel, UITextField, UITextView。其中 UITextField 類(lèi)自帶圓角風(fēng)格的外型,UILabel 和 UITextView 要想顯示圓角需要表現(xiàn)出與周?chē)煌谋尘吧判?。想要?UILabel 和 UITextView 上實(shí)現(xiàn)低成本的圓角(不觸發(fā)離屏渲染),需要保證 layer 的contents呈現(xiàn)透明的背景色,文本視圖類(lèi)的 layer 的contents默認(rèn)是透明的(字符就在這個(gè)透明的環(huán)境里繪制、顯示),此時(shí)只需要設(shè)置 layer 的backgroundColor,再加上cornerRadius就可以搞定了。不過(guò) UILabel 上設(shè)置backgroundColor的行為被更改了,不再是設(shè)定 layer 的背景色而是為contents設(shè)置背景色,UITextView 則沒(méi)有改變這一點(diǎn),實(shí)例代碼如下
- UILabel
- (void)drawUI {
UILabel *radiusLbe = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 200, 44)];
radiusLbe.textColor = [UIColor blackColor];
radiusLbe.text = @"裁剪圓角";
radiusLbe.textAlignment = NSTextAlignmentCenter;
// a裁剪圓角
radiusLbe.layer.backgroundColor = [[UIColor orangeColor] CGColor];
radiusLbe.layer.cornerRadius = 10;
[self.view addSubview:radiusLbe];
}
效果如下
- UITextField
- (void)drawTextF {
UITextField *textF = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 200, 44)];
textF.textColor = [UIColor blackColor];
textF.text = @"裁剪圓角";
textF.textAlignment = NSTextAlignmentCenter;
// a裁剪圓角
textF.layer.backgroundColor = [[UIColor greenColor] CGColor];
textF.layer.cornerRadius = 10;
[self.view addSubview:textF];
}
運(yùn)行效果如下
- UITextView
- (void)drawTextV {
UITextView *textV = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 20, 200)];
textV.textColor = [UIColor whiteColor];
textV.text = @"如何在UITextView視圖上實(shí)現(xiàn)圓角?\n如何在UITextView視圖上實(shí)現(xiàn)圓角?\n如何在UITextView視圖上實(shí)現(xiàn)圓角?\n如何在UITextView視圖上實(shí)現(xiàn)圓角?\n如何在UITextView視圖上實(shí)現(xiàn)圓角?\n如何在UITextView視圖上實(shí)現(xiàn)圓角?\n如何在UITextView視圖上實(shí)現(xiàn)圓角?\n如何在UITextView視圖上實(shí)現(xiàn)圓角?\n如何在UITextView視圖上實(shí)現(xiàn)圓角?\n如何在UITextView視圖上實(shí)現(xiàn)圓角?\n如何在UITextView視圖上實(shí)現(xiàn)圓角?\n如何在UITextView視圖上實(shí)現(xiàn)圓角?\n如何在UITextView視圖上實(shí)現(xiàn)圓角?\n如何在UITextView視圖上實(shí)現(xiàn)圓角?\n如何在UITextView視圖上實(shí)現(xiàn)圓角?\n";
textV.textAlignment = NSTextAlignmentCenter;
// a裁剪圓角
textV.layer.backgroundColor = [[UIColor blueColor] CGColor];
textV.layer.cornerRadius = 10;
[self.view addSubview:textV];
}
運(yùn)行效果如下:
二 Shadow 陰影
陰影直接合成在視圖的下面,視圖結(jié)構(gòu)里并沒(méi)有多出一個(gè)視圖。在沒(méi)有指定陰影路徑時(shí),陰影是沿著視圖的非透明部分?jǐn)U展的,而且 CALayer 的三個(gè)視覺(jué)元素至少有一個(gè)存在時(shí)才會(huì)有陰影。
使用陰影必須保證 layer
的masksToBounds = false
,因此陰影與系統(tǒng)圓角不兼容
。但是注意,只是在視覺(jué)上看不到,對(duì)性能的影響依然。通常這樣實(shí)現(xiàn)一個(gè)陰影:
- (void)drawUI {
UIImageView *iconImgV = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
iconImgV.image = [UIImage imageNamed:@"icon_girl"];
[self.view addSubview:iconImgV];
[iconImgV mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(iconImgV.size);
make.centerY.equalTo(self.view);
make.centerX.equalTo(self.view);
}];
// 設(shè)置陰影
CALayer *imageViewLayer = iconImgV.layer;
imageViewLayer.shadowColor = [[UIColor blackColor] CGColor];
imageViewLayer.shadowOpacity = 1.0; //此參數(shù)默認(rèn)為0,即陰影不顯示
imageViewLayer.shadowRadius = 2.0; //給陰影加上圓角,對(duì)性能無(wú)明顯影響
imageViewLayer.shadowOffset = CGSizeMake(5, 5);
//設(shè)定路徑:與視圖的邊界相同
UIBezierPath *path = [UIBezierPath bezierPathWithRect:iconImgV.bounds];
imageViewLayer.shadowPath = path.CGPath;//路徑默認(rèn)為 nil
}
運(yùn)行效果如下
- 如果添加上裁剪圓角的代碼,則陰影不再顯示
imageViewLayer.cornerRadius = 10;
imageViewLayer.masksToBounds = YES;
運(yùn)行效果如下:
在 OffscreenRenderDemo 里,僅開(kāi)啟陰影(沒(méi)有指定路徑,同屏數(shù)量10個(gè)以上)在滾動(dòng)時(shí)幀率會(huì)大幅下降,檢測(cè)到離屏渲染的黃色特征;指定一個(gè)與邊界相同的簡(jiǎn)單路徑后離屏渲染特征消失,幀率恢復(fù)正常。
下面我們通過(guò)代碼驗(yàn)證
- 沒(méi)有指定與邊界相同的簡(jiǎn)單路徑,發(fā)生離屏渲染,掉幀較厲害
// 設(shè)置陰影
CALayer *imageViewLayer = imgView.layer;
imageViewLayer.shadowColor = [[UIColor blackColor] CGColor];
imageViewLayer.shadowOpacity = 1.0; //此參數(shù)默認(rèn)為0,即陰影不顯示
imageViewLayer.shadowRadius = 2.0; //給陰影加上圓角,對(duì)性能無(wú)明顯影響
imageViewLayer.shadowOffset = CGSizeMake(5, 5);
運(yùn)行效果: 開(kāi)啟離屏渲染監(jiān)視:模擬器 -> Debug -> Color off-screen rendered
FPS 測(cè)試結(jié)果
分析:通過(guò)運(yùn)行結(jié)果效果圖,我們可以發(fā)現(xiàn),在圖片陰影處有黃色,說(shuō)明該處發(fā)生了離屏渲染。而且 GPU 使用率較高,會(huì)造成卡頓掉幀。
- 指定一個(gè)與邊界相同的簡(jiǎn)單路徑后離屏渲染特征消失,幀率恢復(fù)正常。
//設(shè)定路徑:與視圖的邊界相同
UIBezierPath *path = [UIBezierPath bezierPathWithRect:imgView.bounds];
imageViewLayer.shadowPath = path.CGPath;//路徑默認(rèn)為 nil
運(yùn)行效果: 開(kāi)啟離屏渲染監(jiān)視:模擬器 -> Debug -> Color off-screen rendered
FPS 測(cè)試結(jié)果
分析:通過(guò)運(yùn)行結(jié)果效果圖,我們可以發(fā)現(xiàn),在圖片陰影處沒(méi)有黃色,說(shuō)明該處沒(méi)有發(fā)生離屏渲染。而且 GPU 使用率不是很高。
除了指定路徑,實(shí)現(xiàn)良好性能陰影的方法還有:用圓角優(yōu)化里混合圖層的方法模擬陰影的效果:放一個(gè)同樣效果的視圖在要添加陰影程度的視圖的下方;使用 Core Graphics 繪制陰影,不過(guò)除非萬(wàn)不得已沒(méi)人想碰 Core Graphics API。從實(shí)現(xiàn)成本來(lái)講,都不如指定路徑方便。
三 Mask
Mask 效果與混合圖層的效果非常相似,只是使用同一個(gè)遮罩圖像時(shí),mask 與混合圖層的效果是相反的,在 Demo 里使用反向內(nèi)容的遮罩來(lái)實(shí)現(xiàn)圓角。實(shí)現(xiàn) mask 效果使用 CALayer 的layer屬性,在 iOS 8 以上可以使用 UIView 的maskView屬性。
- (void)drawUI {
UIImageView *iconImgV = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 80, 80)];
iconImgV.image = [UIImage imageNamed:@"icon_girl"];
[self.view addSubview:iconImgV];
[iconImgV mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(iconImgV.size);
make.centerY.equalTo(self.view);
make.centerX.equalTo(self.view);
}];
// 設(shè)置Mask
if (@available(iOS 8.0, *)) {
iconImgV.maskView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"RoundMask"]];
} else {
CALayer *maskLayer = [[CALayer alloc] init];
maskLayer.frame = iconImgV.bounds;
maskLayer.contents = (__bridge id _Nullable)([[UIImage imageNamed:@"RoundMask"] CGImage]);
iconImgV.layer.mask = maskLayer;
}
}
運(yùn)行效果如下:
備注:其中icon_girl.png
和RoundMask
分別為下面兩張圖片
如果所有 maskImage 相同的話(huà),使用一個(gè) maskImage 就夠了,不然每次生成一個(gè)新的 UIImage 也會(huì)是一個(gè)性能隱患點(diǎn)。注意:可以使用同一個(gè) maskImage,但不能使用同一個(gè) maskView,不然同時(shí)只會(huì)有一個(gè) mask 效果。
在實(shí)戰(zhàn)項(xiàng)目中檢驗(yàn)
- FPS 幀率檢測(cè)
通過(guò)檢測(cè),F(xiàn)PS 有些掉幀,GPU 使用率較高。
- 開(kāi)啟離屏渲染檢測(cè)
在圓形圖片區(qū)域我們觀(guān)察到有黃色圖形,說(shuō)明發(fā)生了離屏渲染。
總結(jié):Mask 效果無(wú)法取消離屏渲染,使用混合圖層的方法來(lái)模擬 mask 效果,性能各方面都是和無(wú)效果持平。
使用 mask 來(lái)實(shí)現(xiàn)圓角時(shí)也可以不用圖片,而使用 CAShapeLayer 來(lái)指定混合的路徑。
UIBezierPath *roundedRectPath = [UIBezierPath bezierPathWithRoundedRect:iconImgV.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(40, 40)];
CAShapeLayer *shapLayer = [[CAShapeLayer alloc] init];
shapLayer.path = roundedRectPath.CGPath;
iconImgV.layer.mask = shapLayer;
運(yùn)行效果如下:
我們通過(guò)設(shè)置
cornerRadii
值來(lái)設(shè)置角度值,如果為圓角,則為圖片尺寸寬高的一半即可。
總結(jié):同樣的 mask 效果使用 CAShapeLayer 時(shí)相比直接使用 maskImage 在幀率上稍低,CPU 利用率無(wú)明顯變化,但是 GPU 利用率也低一些。
四 GroupOpacity
首先來(lái)看看 GroupOpacity 是什么效果:
GroupOpacity 是指 CALayer 的allowsGroupOpacity屬性,UIView 的alpha屬性等同于 CALayer opacity屬性。開(kāi)啟 GroupOpacity 后,子 layer 在視覺(jué)上的透明度的上限是其父 layer 的opacity
。
從 iOS 7 以后默認(rèn)全局開(kāi)啟了這個(gè)功能,這樣做是為了讓子視圖與其容器視圖保持同樣的透明度。
GroupOpacity 開(kāi)啟離屏渲染的條件是:layer.opacity != 1.0并且有子 layer 或者背景圖。
- 實(shí)例代碼如下
- (void)drawRedView {
UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 50)];
redView.alpha = 0.5;
redView.backgroundColor = [UIColor grayColor];
redView.layer.allowsGroupOpacity = NO;
[self.view addSubview:redView];
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(redView.size);
make.top.equalTo(self.view.mas_top).offset(100);
make.centerX.equalTo(self.view);
}];
// 子視圖
UIView *subView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 150, 30)];
subView.backgroundColor = [UIColor whiteColor];
[redView addSubview:subView];
[subView mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(subView.size);
make.centerX.centerY.equalTo(redView);
}];
}
運(yùn)行效果如下:
圖一是關(guān)閉了
GroupOpacity
屬性,圖二開(kāi)啟了GroupOpacity
屬性(默認(rèn)開(kāi)啟的)
這個(gè)觸發(fā)條件并不需要subLayer.opacity != 1.0,非常容易滿(mǎn)足。然而在 TableView 這樣的視圖里設(shè)置 cell 或 cell.contentView 的alpha屬性小于1并不能檢測(cè)離屏渲染的黃色特征,性能上也沒(méi)有明顯差別。經(jīng)過(guò)摸索發(fā)現(xiàn):只有設(shè)置 tableView 的alpha小于1時(shí)才會(huì)觸發(fā)離屏渲染,對(duì)性能無(wú)明顯影響;設(shè)置 cell 的alpha屬性并不會(huì)對(duì)整體的透明度產(chǎn)生影響,只有設(shè)置 cell.contentView 才有效。
五 EdgeAntialiasing
經(jīng)過(guò)測(cè)試,開(kāi)啟 edge antialiasing(旋轉(zhuǎn)視圖并且設(shè)置layer.allowsEdgeAntialiasing = true) 在 iOS 8 和 iOS 9 上并不會(huì)觸發(fā)離屏渲染,對(duì)性能也沒(méi)有什么影響,也許到現(xiàn)在這個(gè)功能已經(jīng)被優(yōu)化了。
// EdgeAntialiasing
redView.layer.allowsEdgeAntialiasing = YES;
六 Rasterization
除了 GroupOpacity
和 EdgeAntialiasing
,其他效果觸發(fā)的離屏渲染都會(huì)對(duì)性能產(chǎn)生嚴(yán)重影響,離屏渲染真的是一無(wú)是處嗎?不,離屏渲染本來(lái)是個(gè)優(yōu)化設(shè)計(jì)。如何物盡其用?答案是:Rasterization
。
cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = cell.layer.contentsScale
shouldRasterize = false時(shí),離屏渲染的黃色特征僅限于上述自動(dòng)觸發(fā)離屏渲染的效果的部分,shouldRasterize = true后該部分和開(kāi)啟了該屬性的 layer 整體(在這里就是 cell 整體)都有黃色特征,所以開(kāi)啟 Rasterization 是手動(dòng)啟動(dòng)了離屏渲染。
從前面來(lái)看,離屏渲染會(huì)給 GPU 帶來(lái)沉重的負(fù)擔(dān),強(qiáng)制啟動(dòng)豈不是更糟?開(kāi)啟 Rasterization 后,GPU 只合成一次內(nèi)容,然后復(fù)用合成的結(jié)果;合成的內(nèi)容超過(guò) 100ms 沒(méi)有使用會(huì)從緩存里移除,在更新內(nèi)容時(shí)還會(huì)產(chǎn)生更多的離屏渲染。對(duì)于內(nèi)容不發(fā)生變化的視圖,原本拖后腿的離屏渲染就成為了助力;如果視圖內(nèi)容是動(dòng)態(tài)變化的,使用這個(gè)方案有可能讓性能變得更糟。
Core Animation Instruments 有個(gè)
Color Hits Green and Misses Red
的選項(xiàng),開(kāi)啟Rasterization
后開(kāi)啟這個(gè)選項(xiàng),屏幕上綠色的部分表示有渲染緩存可用,紅色的部分表示無(wú)渲染緩存可用。
七 總結(jié)
7.1 造成離屏渲染的原因有
- 設(shè)置
CALayer
的cornerRadius
+masksToBounds
- 設(shè)置
CALayer
的shadowPath
屬性 - 設(shè)置
CALayer
的Mask
屬性 - 設(shè)置
CALayer
的allowsGroupOpacity
屬性設(shè)為YES
并且opacity
小于1 - 設(shè)置
CALayer
的allowsEdgeAntialiasing
屬性
等等.......
7.2 特別注意
- 設(shè)置
shadowPath
陰影的同時(shí),設(shè)置圓角屬性cornerRadius
+masksToBounds
,則陰影不會(huì)顯示。 -
RoundedCorner
在僅指定cornerRadius時(shí)不會(huì)觸發(fā)離屏渲染,僅適用于特殊情況;contents為 nil 或者contents不會(huì)遮擋背景色圓角; -
Shawdow
可以通過(guò)指定路徑來(lái)取消離屏渲染; -
Mask
無(wú)法取消離屏渲染;
以上效果在同等數(shù)量的規(guī)模下,對(duì)性能的影響等級(jí):Shadow > RoundedCorner > Mask > GroupOpacity(迷之效果)。
任何時(shí)候優(yōu)先考慮避免觸發(fā)離屏渲染,無(wú)法避免時(shí)優(yōu)化方案有兩種:
Rasterization:
適用于靜態(tài)內(nèi)容的視圖,也就是內(nèi)部結(jié)構(gòu)和內(nèi)容不發(fā)生變化的視圖,對(duì)上面的所有效果而言,在實(shí)現(xiàn)成本以及性能上最均衡的。即使是動(dòng)態(tài)變化的視圖,開(kāi)啟 Rasterization 后能夠有效降低 GPU 的負(fù)荷,不過(guò)在動(dòng)態(tài)視圖里是否啟用還是看 Instruments 的數(shù)據(jù)。規(guī)避離屏渲染:
用其他手法來(lái)模擬效果,混合圖層是個(gè)性能最好、耗能最少的通用優(yōu)化方案,尤其對(duì)于 rounded corer 和 mask。
本文參考
iOS離屏渲染優(yōu)化
繪制像素到屏幕上