GPU渲染機(jī)制
CPU 計(jì)算好顯示內(nèi)容提交到 GPU拭卿,GPU 渲染完成后將渲染結(jié)果放入幀緩沖區(qū)妖枚,隨后視頻控制器會(huì)按照 VSync 信號(hào)逐行讀取幀緩沖區(qū)的數(shù)據(jù)匾荆,經(jīng)過可能的數(shù)模轉(zhuǎn)換傳遞給顯示器顯示俯在。
GPU屏幕渲染方式
GPU屏幕渲染方式有兩種:
On-Screen Rendering
(當(dāng)前屏幕渲染)
指的是GPU的渲染操作是在當(dāng)前用于顯示的屏幕緩沖區(qū)進(jìn)行搔确。-
Off-Screen Rendering
(離屏渲染)
指的是GPU在當(dāng)前屏幕緩沖區(qū)以外開辟一個(gè)緩沖區(qū)進(jìn)行渲染操作彼棍。當(dāng)前屏幕渲染不需要額外創(chuàng)建新的緩存,也不需要開啟新的上下文膳算,相對(duì)于離屏渲染性能更好座硕。但是受當(dāng)前屏幕渲染的局限因素限制(只有自身上下文、屏幕緩存有限等)涕蜂,當(dāng)前屏幕渲染有些情況下的渲染解決不了的华匾,就需要使用到離屏渲染。
相比于當(dāng)前屏幕渲染机隙,離屏渲染的代價(jià)是很高的蜘拉,主要體現(xiàn)在兩個(gè)方面:
-
創(chuàng)建新的緩沖區(qū)
要想進(jìn)行離屏渲染,首先要?jiǎng)?chuàng)建一個(gè)新的緩沖區(qū)有鹿。 -
上下文切換
離屏渲染的整個(gè)過程旭旭,需要多次切換上下文環(huán)境:先是從當(dāng)前屏幕(On-Screen)切換到離屏(Off-Screen),等到離屏渲染結(jié)束以后葱跋,將離屏緩沖區(qū)的渲染結(jié)果顯示到屏幕上又需要將上下文從離屏切換到當(dāng)前屏幕持寄。而上下文環(huán)境的切換是要付出很大代價(jià)的。
-
創(chuàng)建新的緩沖區(qū)
既然離屏渲染這么耗性能年局,為什么有這套機(jī)制呢际看?
有些效果被認(rèn)為不能直接呈現(xiàn)于屏幕,而需要在別的地方做額外的處理預(yù)合成矢否。圖層屬性的混合體沒有預(yù)合成之前不能直接在屏幕中繪制仲闽,就需要屏幕外渲染。屏幕外渲染并不意味著軟件繪制僵朗,但是它意味著圖層必須在被顯示之前在一個(gè)屏幕外上下文中被渲染(不論CPU還是GPU)赖欣。
離屏渲染的觸發(fā)
以下情況或操作會(huì)觸發(fā)離屏渲染:
- 1屑彻、masks(遮罩),為圖層設(shè)置遮罩
layer.mask
- 2顶吮、圖層截取,將圖層的
layer.masksToBounds
或view.clipsToBounds
屬性設(shè)置為true
- 3社牲、透明設(shè)置,將圖層
layer.allowsGroupOpacity
屬性設(shè)置為YES
和layer.opacity
小于1.0 - 4悴了、shadows(陰影)搏恤,為圖層
設(shè)置陰影l(fā)ayer.shadow
- 5、開啟光柵化湃交,設(shè)置
layer.shouldRasterize
為true
- 6熟空、設(shè)置圓角,
layer.cornerRadius
- 7搞莺、設(shè)置抗鋸齒息罗,
layer.edgeAntialiasingMask
,layer.allowsEdgeAntialiasing
- 8、文本才沧,(任何種類迈喉,包括UILabel, CATextLayer, Core Text等)
- 9、漸變
- 10温圆、特殊的離屏渲染:CPU渲染
如果重寫了drawRect
方法挨摸,并且使用任何Core Graphics
的技術(shù)進(jìn)行了繪制操作,就涉及到了CPU渲染岁歉。整個(gè)渲染過程由CPU在App內(nèi)同步的完成油坝,渲染得到的bitmap最后再交由GPU用于顯示。CoreGraphic
通常是線程安全的刨裆,所以可以進(jìn)行異步繪制,顯示的時(shí)候再放回主線程彬檀。
光柵化概念
其中shouldRasterize(光柵化)
是比較特別的一種:
光柵化概念:將圖轉(zhuǎn)化為一個(gè)個(gè)柵格組成的圖像帆啃。
光柵化特點(diǎn):每個(gè)元素對(duì)應(yīng)幀緩沖區(qū)中的一像素。
shouldRasterize = YES
在其他屬性出發(fā)離屏渲染的同時(shí)窍帝,會(huì)將光柵化后的內(nèi)容緩存起來努潘,如果對(duì)應(yīng)的layer及其sublayer沒有發(fā)生改變,在下一幀可以直接復(fù)用坤学。shouldRasterize = YES疯坤,這將隱式的創(chuàng)建一個(gè)位圖,各種陰影遮罩等效果也會(huì)保存到位圖中并緩存起來深浮,從而減少渲染的頻度(不是矢量圖)压怠。
相當(dāng)于光柵化是把GPU的操作轉(zhuǎn)到CPU上了,生成位圖緩存飞苇,直接讀取復(fù)用菌瘫。
當(dāng)你使用光柵化時(shí)蜗顽,你可以開啟Color Hits Green and Misses Red
來檢查該場(chǎng)景下光柵化操作是否是一個(gè)好的選擇。綠色表示緩存被復(fù)用雨让,紅色表示緩存在被重復(fù)創(chuàng)建雇盖。
如果光柵化的層變紅的太頻繁那么光柵化對(duì)優(yōu)化可能沒有多少用處。位圖緩存從內(nèi)存中刪除又重新創(chuàng)建得太過頻繁栖忠。紅色表明緩存重建得太遲崔挖。可以針對(duì)性的選擇某個(gè)較小而較深的層結(jié)構(gòu)進(jìn)行光柵化庵寞,來嘗試減少渲染時(shí)間狸相。
注意:對(duì)于經(jīng)常變動(dòng)的內(nèi)容,這個(gè)時(shí)候不要開啟皇帮,否則會(huì)造成性能的浪費(fèi)卷哩。例如我們?nèi)粘=?jīng)常打交道的TableViewCell,因?yàn)門ableViewCell的重繪是很頻繁的(因?yàn)镃ell的復(fù)用)属拾,如果Cell的內(nèi)容不斷變化将谊,則Cell需要不斷重繪,如果此時(shí)設(shè)置了cell.layer可光柵化渐白,則會(huì)造成大量的離屏渲染尊浓,降低圖形性能。
離屏渲染的檢測(cè)
怎么檢測(cè)離屏渲染呢纯衍?我們可以利用Instruments的Core Animation來檢測(cè)離屏渲染栋齿。通過選擇Xcode --> Debug --> View Debugging -->Rendering 選擇離屏渲染屬性,運(yùn)行項(xiàng)目即可檢測(cè)離屏渲染襟诸。具體各個(gè)屬性解釋可以看這篇文章:iOS Instrument使用之Core Animation
我們來看看跟離屏渲染相關(guān)的幾個(gè)屬性設(shè)置:
-
Color Offscreen-Rendered Yellow
開啟后會(huì)把那些需要離屏渲染的圖層高亮成黃色瓦堵,這就意味著黃色圖層可能存在性能問題。 -
Color Hits Green and Misses Red
如果shouldRasterize
被設(shè)置成YES歌亲,對(duì)應(yīng)的渲染結(jié)果會(huì)被緩存菇用,如果圖層是綠色,就表示這些緩存被復(fù)用陷揪;如果是紅色就表示緩存會(huì)被重復(fù)創(chuàng)建惋鸥,這就表示該處存在性能問題了。
該選擇哪種渲染方式悍缠?
1卦绣、盡量使用當(dāng)前屏幕渲染
離屏渲染、CPU渲染可能帶來性能問題飞蚓,一般情況下滤港,我們要盡量使用當(dāng)前屏幕渲染。
2趴拧、離屏渲染和CPU渲染
由于GPU的浮點(diǎn)運(yùn)算能力比CPU強(qiáng)蜗搔,CPU渲染的效率可能不如離屏渲染劲藐;但如果僅僅是實(shí)現(xiàn)一個(gè)簡單的效果,直接使用CPU渲染的效率又可能比離屏渲染好樟凄,畢竟離屏渲染要涉及到緩沖區(qū)創(chuàng)建和上下文切換等耗時(shí)操作聘芜。
離屏渲染優(yōu)化
設(shè)置圓角
方法一
我們通常會(huì)采用這種方式來設(shè)置圓角:
/**
設(shè)置cornerRadius>0且clipToBounds為YES,再添加子視圖
*/
- (void)setCorner1{
self.avatarImageView.layer.cornerRadius = self.avatarImageView.bounds.size.width/2;
self.avatarImageView.clipsToBounds = YES;
// 或通過設(shè)置 layer.masksToBounds = YES
// self.avatarImageView.layer.masksToBounds = YES;
// 如果再添加子視圖會(huì)觸發(fā)離屏渲染,不添加則不會(huì)
[self.avatarImageView addSubview:self.titleLabel];
}
我們通常設(shè)置圓角會(huì)通過設(shè)置layer.cornerRadius和layer.masksToBounds = YES來設(shè)置。這樣設(shè)置在視圖沒有子視圖的情況下是不會(huì)觸發(fā)離屏渲染的缝龄,有子視圖就會(huì)觸發(fā)離屏渲染汰现。有子視圖的情況下還需要尋找別的方式來避免離屏渲染。
其實(shí)在iOS9.0之前UIimageView跟UIButton像上面這樣設(shè)置圓角都會(huì)觸發(fā)離屏渲染叔壤。
iOS9.0系統(tǒng)優(yōu)化之后UIButton像上面這樣設(shè)置圓角還是會(huì)觸發(fā)離屏渲染瞎饲,而UIImageView里png圖片設(shè)置圓角不會(huì)觸發(fā)離屏渲染了,如果設(shè)置其他陰影效果之類的還是會(huì)觸發(fā)離屏渲染的炼绘。
方法二
利用CoreGraphics
畫一個(gè)圓形上下文嗅战,然后把圖片繪制上去,得到一個(gè)圓形的圖片俺亮,達(dá)到切圓角的目的驮捍。
- (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
設(shè)置圓角,利用的是UIBezierPath
和CAShapeLayer
來完成脚曾,不過這種方式也會(huì)造成離屏渲染东且。
CAShapeLayer *mask = [[CAShapeLayer alloc] init];
mask.opacity = 1.0;
mask.path = [UIBezierPath bezierPathWithOvalInRect:self.avatarImageView.bounds].CGPath;
self.avatarImageView.layer.mask = mask;
設(shè)置陰影
陰影可以通過設(shè)置layer
層的shadowXXX
屬性,就可以很方便的UIView
添加陰影效果本讥,但是不同的設(shè)置方式可能產(chǎn)生性能方面的問題珊泳。下面介紹一下不同方式對(duì)性能的影響。
方法一
通過設(shè)置下面的4個(gè)屬性拷沸,就可以添加陰影色查,不過這種方式會(huì)造成離屏渲染
。因?yàn)槔L制陰影而不指定陰影路徑撞芍,在繪制陰影的過程中就會(huì)產(chǎn)生大量的離屏渲染综慎,非常消耗性能,從而造成UI卡頓勤庐。
如下方式設(shè)置陰影造成離屏渲染
的原因是:iOS會(huì)先繪制目標(biāo)的陰影,然后繪制目標(biāo)本身好港,在沒有指定陰影的繪制路徑時(shí)愉镰,iOS視圖在每次繪制前都會(huì)遞歸的精確計(jì)算每個(gè)子層陰影的路徑,這會(huì)非常消耗性能钧汹,也是導(dǎo)致卡頓的根源丈探。
// 設(shè)置陰影顏色
shadowImgView.layer.shadowColor = [UIColor redColor].CGColor;
// 設(shè)置陰影透明度
shadowImgView.layer.shadowOpacity = 0.8f;
// 設(shè)置陰影偏移量,默認(rèn)是(0,-3)拔莱,向上偏移
shadowImgView.layer.shadowOffset = CGSizeMake(5, 5);
// 設(shè)置陰影半徑
shadowImgView.layer.shadowRadius = 5.f;
方法二
為了減少因?yàn)闆]有設(shè)置shadowPath
造成繪制陰影時(shí)大量重復(fù)繪制的問題碗降,我們可以指定陰影的繪制路徑隘竭,這樣在繪制陰影時(shí),就可以在多個(gè)layer層共享同一個(gè)路徑的陰影讼渊,以此來提高性能动看。
如果不指定路徑shadowPath
,就會(huì)使用layer
層的alpha
通道的混合爪幻,而如果指定陰影的路徑菱皆,就會(huì)在多個(gè)layer
之間共享同一路徑,以此來提高性能挨稿。
有關(guān)什么是layer
層的混合仇轻,可以這樣理解:iOS在渲染每一幀時(shí),都會(huì)計(jì)算每一個(gè)像素的顏色奶甘,如果上層layer不透明篷店,就只取上層layer的顏色;而如果上層layer存在透明度時(shí)(alpha通道)臭家,則需要混合每一層的顏色來計(jì)算最終的顏色疲陕。如果layer越多,計(jì)算量就越大侣监,也就比較耗性能鸭轮。所以,在開發(fā)中橄霉,要盡量減少視圖的透明層窃爷。具體代碼如下:
// 設(shè)置陰影顏色
shadowImgView.layer.shadowColor = [UIColor redColor].CGColor;
// 設(shè)置陰影透明度
shadowImgView.layer.shadowOpacity = 0.8f;
// 設(shè)置陰影偏移量,默認(rèn)是(0,-3)姓蜂,向上偏移
shadowImgView.layer.shadowOffset = CGSizeMake(5, 5);
// 設(shè)置陰影半徑
shadowImgView.layer.shadowRadius = 5.f;
// 設(shè)置陰影路徑
UIBezierPath *path = [UIBezierPath bezierPathWithRect:shadowImgView.bounds];
// 如果是圓形view按厘,則使用下面的圓形路徑
//UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:shadowImgView.bounds];
shadowImgView.layer.shadowPath = path.CGPath;
注意:xib拖出來的控件是沒法像上面這樣設(shè)置陰影的,具體設(shè)置陰影的方法看這篇文章iOS xib設(shè)置陰影
參考文章:
1.ios中的離屏渲染與相關(guān)性能檢測(cè)優(yōu)化
2.iOS離屏渲染之優(yōu)化分析
3.iOS性能優(yōu)化-離屏渲染
4.iOS"離屏渲染"整理總結(jié)
5.iOS的陰影繪制及性能優(yōu)化