離屏渲染機(jī)制描述及界面優(yōu)化

GPU渲染機(jī)制

CPU 計(jì)算好顯示內(nèi)容提交到 GPU拭卿,GPU 渲染完成后將渲染結(jié)果放入幀緩沖區(qū)妖枚,隨后視頻控制器會(huì)按照 VSync 信號(hào)逐行讀取幀緩沖區(qū)的數(shù)據(jù)匾荆,經(jīng)過可能的數(shù)模轉(zhuǎn)換傳遞給顯示器顯示俯在。


ios_screen_display.png
ios_vsync_runloop.png

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à)的。

既然離屏渲染這么耗性能年局,為什么有這套機(jī)制呢际看?

有些效果被認(rèn)為不能直接呈現(xiàn)于屏幕,而需要在別的地方做額外的處理預(yù)合成矢否。圖層屬性的混合體沒有預(yù)合成之前不能直接在屏幕中繪制仲闽,就需要屏幕外渲染。屏幕外渲染并不意味著軟件繪制僵朗,但是它意味著圖層必須在被顯示之前在一個(gè)屏幕外上下文中被渲染(不論CPU還是GPU)赖欣。

離屏渲染的觸發(fā)

以下情況或操作會(huì)觸發(fā)離屏渲染:

  • 1屑彻、masks(遮罩),為圖層設(shè)置遮罩layer.mask
  • 2顶吮、圖層截取,將圖層的layer.masksToBoundsview.clipsToBounds屬性設(shè)置為true
  • 3社牲、透明設(shè)置,將圖層layer.allowsGroupOpacity屬性設(shè)置為YESlayer.opacity小于1.0
  • 4悴了、shadows(陰影)搏恤,為圖層設(shè)置陰影l(fā)ayer.shadow
  • 5、開啟光柵化湃交,設(shè)置layer.shouldRasterizetrue
  • 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

離屏渲染檢測(cè).png

我們來看看跟離屏渲染相關(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è)置圓角,利用的是UIBezierPathCAShapeLayer來完成脚曾,不過這種方式也會(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)化

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末钱慢,一起剝皮案震驚了整個(gè)濱河市逮京,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌束莫,老刑警劉巖懒棉,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異览绿,居然都是意外死亡策严,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門饿敲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來妻导,“玉大人,你說我怎么就攤上這事【缶拢” “怎么了术浪?”我有些...
    開封第一講書人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長寿酌。 經(jīng)常有香客問我胰苏,道長,這世上最難降的妖魔是什么份名? 我笑而不...
    開封第一講書人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任碟联,我火速辦了婚禮,結(jié)果婚禮上僵腺,老公的妹妹穿的比我還像新娘鲤孵。我一直安慰自己,他們只是感情好辰如,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開白布普监。 她就那樣靜靜地躺著,像睡著了一般琉兜。 火紅的嫁衣襯著肌膚如雪凯正。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,562評(píng)論 1 305
  • 那天豌蟋,我揣著相機(jī)與錄音廊散,去河邊找鬼。 笑死梧疲,一個(gè)胖子當(dāng)著我的面吹牛允睹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播幌氮,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼缭受,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了该互?” 一聲冷哼從身側(cè)響起米者,我...
    開封第一講書人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎宇智,沒想到半個(gè)月后蔓搞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡随橘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年喂分,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片太防。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蜒车,到底是詐尸還是另有隱情讳嘱,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布酿愧,位于F島的核電站沥潭,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏嬉挡。R本人自食惡果不足惜钝鸽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望庞钢。 院中可真熱鬧拔恰,春花似錦、人聲如沸基括。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽风皿。三九已至河爹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間桐款,已是汗流浹背咸这。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留魔眨,地道東北人媳维。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像冰沙,于是被迫代替她去往敵國和親侨艾。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • chapter one 顧以凡擦著頭發(fā)打開浴室門時(shí),被面前閃過的幾個(gè)黑衣墨鏡男嚇了一跳侥啤。 這是干什么当叭?來...
    sssssex閱讀 192評(píng)論 0 1
  • 夜半,月落無聲 水滴石穿 車輪碾碎舊夢(mèng) 我是遙遠(yuǎn)方的一只鷹 ...
    公子曉揚(yáng)閱讀 193評(píng)論 0 4
  • 文|顏夕遙 01 喜歡妮妮是因?yàn)橐粋€(gè)很私人的原因。她和我同一天生日赁炎,只是比我年幼了幾歲醉箕。 看著這個(gè)青澀的女孩,有的...
    顏夕遙閱讀 4,362評(píng)論 2 37