iOS-離屏渲染詳解

README:

引言: 一款優(yōu)秀的app,流暢很關(guān)鍵,用戶使用60的fps的app,跟使用30的fps的app感受是完全不一樣的.類似于 半糖 這種優(yōu)秀的應(yīng)用肯定花了大把精力去優(yōu)化界面.網(wǎng)上關(guān)于優(yōu)化的界面的文章一搜一大把.本文并不是講界面優(yōu)化的,優(yōu)化的話推薦下面幾篇文章;

YYKit作者: "iOS 保持界面流暢的技巧" (我相信認(rèn)真看一定有收獲!)

離屏渲染的優(yōu)化 (這篇文章很強(qiáng))

jim: 淺談iOS中的視圖優(yōu)化

有些東西研究起來挺費(fèi)勁的.但是想要更多的收獲,還真就必須得研究.知識(shí)零零散散,時(shí)間久了再次用起來難免會(huì)生疏,又得重新找資料,看文章.很麻煩.索性就整理個(gè)比較全面的知識(shí)點(diǎn)供以后復(fù)習(xí).


  • 屏幕渲染

  • OpenGL中,GPU屏幕渲染有兩種方式.
  1. On-Screen Rendering (當(dāng)前屏幕渲染)

    指的是GPU的渲染操作是在當(dāng)前用于顯示的屏幕緩沖區(qū)進(jìn)行.

  2. Off-Screen Rendering (離屏渲染)

    指的是在GPU在當(dāng)前屏幕緩沖區(qū)以外開辟一個(gè)緩沖區(qū)進(jìn)行渲染操作.

幾個(gè)名詞 "GPU" " 緩沖區(qū)" .

不知道GPU的就自行百度吧 (- - 寶寶說不清). 說下緩沖區(qū).要明白緩沖區(qū),首先就得要知道圖像顯示出來的原理,本文的第一篇博客里面介紹的很詳細(xì).
顯示器 顯示出來的圖像是經(jīng)過 CRT電子槍一行一行的掃描.(可以是橫向的也可以是縱向 ,具體CRT電子槍又是什么,百度文庫(kù)介紹的很詳細(xì).),掃描出來就呈現(xiàn)了一幀畫面,隨后電子槍又會(huì)回到初始位置循環(huán)掃描,為了讓顯示器的顯示跟視頻控制器同步,當(dāng)電子槍新掃描一行的時(shí)候.準(zhǔn)備掃描的時(shí)候,會(huì)發(fā)送一個(gè) 水平同步信號(hào)(HSync信號(hào)),而當(dāng)一幀畫面繪制完成后,電子槍回復(fù)到原位丰辣,準(zhǔn)備畫下一幀前骡尽,顯示器會(huì)發(fā)出一個(gè)垂直同步信號(hào)(vertical synchronization簡(jiǎn)稱 VSync),顯示器一般是固定刷新頻率的,這個(gè)刷新的頻率其實(shí)就是VSync信號(hào)產(chǎn)生的頻率. 然后CPU計(jì)算好frame等屬性,就將計(jì)算好的內(nèi)容提交給GPU去渲染,GPU渲染完成之后就會(huì)放入幀緩沖區(qū),然后視頻控制器會(huì)按照VSync信號(hào)逐行讀取幀緩沖區(qū)的數(shù)據(jù),經(jīng)過可能的數(shù)模轉(zhuǎn)換傳遞給顯示器.就顯示出來了.
原理圖就不放了,過一遍概念.

離屏渲染的代價(jià)很高,想要進(jìn)行離屏渲染,首選要?jiǎng)?chuàng)建一個(gè)新的緩沖區(qū),屏幕渲染會(huì)有一個(gè)上下文環(huán)境的一個(gè)概念,離屏渲染的整個(gè)過程需要切換上下文環(huán)境,先從 當(dāng)前屏幕切換到離屏,等結(jié)束后,又要將上下文環(huán)境切換回來.這也是為什么會(huì)消耗性能的原因了.
孝凌。由于垂直同步的機(jī)制叙量,如果在一個(gè) VSync 時(shí)間內(nèi)虹脯,CPU 或者 GPU 沒有完成內(nèi)容提交吴趴,則那一幀就會(huì)被丟棄涝婉,等待下一次機(jī)會(huì)再顯示,而這時(shí)顯示屏?xí)A糁暗膬?nèi)容不變倦沧。這就是界面卡頓的原因唇撬。


那有個(gè)問題: 為什么離屏渲染這么耗性能,為什么有這套機(jī)制呢?

當(dāng)使用圓角,陰影展融,遮罩的時(shí)候窖认,圖層屬性的混合體被指定為在未預(yù)合成之前(下一個(gè)VSync信號(hào)開始前)不能直接在屏幕中繪制,所以就需要屏幕外渲染告希。 你可以這么理解. 老板叫我短時(shí)間間內(nèi)做一個(gè) app.我一個(gè)人能做,但是時(shí)間太短,所以我得讓我朋友一起來幫著我做.(性能消耗: 也就是耗 你跟你朋友之間溝通的這些成本,多浪費(fèi)啊).但是沒辦法 誰(shuí)讓你做不完呢.
這么一講會(huì)不會(huì)比較明白點(diǎn).


  • 哪些操作會(huì)觸發(fā) 離屏渲染?

官方公開的的資料里關(guān)于離屏渲染的信息最早是在 2011年的 WWDC扑浸, 在多個(gè) session 里都提到了盡量避免會(huì)觸發(fā)離屏渲染的效果,包括:mask, shadow, group opacity, edge antialiasing燕偶。

  • shouldRasterize(光柵化)

  • masks(遮罩)

  • shadows(陰影)

  • edge antialiasing(抗鋸齒)

  • group opacity(不透明)

  • 復(fù)雜形狀設(shè)置圓角等

  • 漸變

  • Text(UILabel, CATextLayer, Core Text, etc)...


介紹一些屬性.

  • shouldRasterize(光柵化): 將圖轉(zhuǎn)化為一個(gè)個(gè)柵格組成的圖象喝噪。 光柵化特點(diǎn):每個(gè)元素對(duì)應(yīng)幀緩沖區(qū)中的一像素。

shouldRasterize = YES在其它屬性觸發(fā)離屏渲染的同時(shí),會(huì)將光柵化后的內(nèi)容緩存起來,如果對(duì)應(yīng)的layer或者 sublayers沒有發(fā)生改變,在下一幀的時(shí)候可以直接復(fù)用,從而減少渲染的頻率.
當(dāng)使用光柵化是, 可以開啟 "Color Hits Green and Misses Red"來檢查該場(chǎng)景下是否適合選擇光柵化,綠色表示緩存被復(fù)用,紅色表示緩存在被重復(fù)創(chuàng)建.對(duì)于經(jīng)常變動(dòng)的內(nèi)容,不要開啟,否則會(huì)造成性能的浪費(fèi).

如果cell里面的內(nèi)容不斷變化(cell的復(fù)用),如果設(shè)置了cell.layer.shouldRaseterize = YES則會(huì)降低圖形性能,造成離屏渲染.

  • mask(遮罩).

mask是layer的一個(gè)屬性.

/ A layer whose alpha channel is used as a mask to select between the
 * layer's background and the result of compositing the layer's
 * contents with its filtered background. Defaults to nil. When used as
 * a mask the layer's `compositingFilter' and `backgroundFilters'
 * properties are ignored. When setting the mask to a new layer, the
 * new layer must have a nil superlayer, otherwise the behavior is
 * undefined. Nested masks (mask layers with their own masks) are
 * unsupported. */

@property(nullable, strong) CALayer *mask;

大概的意思. 當(dāng)透明度改變的時(shí)候,這個(gè) mask 就是覆蓋上去的那個(gè)陰影.該層的layer的alpha決定了多少層背景跟內(nèi)容通過并顯示,完全,或者部分不透明的像素允許潛在的內(nèi)容 通過并顯示.
默認(rèn)是nil,當(dāng)配置一個(gè) 遮罩的時(shí)候,記得設(shè)置 遮罩的大小,位置.已確保跟蓋圖層對(duì)齊.(這是官方文檔說的 如果不對(duì)齊會(huì)怎樣.you can try. 我試過是只能顯示對(duì)齊的那一部分.)
如果你想給這個(gè)屬性賦值,前提是必須沒有 superLayer,如果有superLayer,這個(gè)行為則是無效的.(你也可以嘗試一下反的.)

tip:用mask可以做一些轉(zhuǎn)場(chǎng)動(dòng)畫.這里就不介紹了.

那說了這么多,他就是生來會(huì)觸發(fā)離屏渲染的.所以要謹(jǐn)慎 設(shè)置透明度. 提到透明度,另外補(bǔ)充一個(gè)概念.)

Color Blended Layers
用jim的話來介紹它就是

屏幕上的每個(gè)像素點(diǎn)的顏色是由當(dāng)前像素點(diǎn)上的多層layer(如果存在)共同決定的指么,GPU會(huì)進(jìn)行計(jì)算出混合顏色的RGB值酝惧,最終顯示在屏幕上榴鼎。而這需要讓GPU計(jì)算,所以我們要盡量避免設(shè)置alpha晚唇,這樣GPU會(huì)忽略下面所有的layer巫财,節(jié)約計(jì)算量。再提一下opaque這個(gè)屬性缺亮,網(wǎng)上普遍認(rèn)為view.opaque = YES翁涤,GPU就不會(huì)進(jìn)行圖層混合計(jì)算了。而這個(gè)結(jié)論是錯(cuò)誤的萌踱,其實(shí)view.opaque事實(shí)上并沒什么卵用葵礼。
如果你真的想達(dá)到這個(gè)效果,可以用layer.opaque,這個(gè)才是正確的做法

  • shadows(陰影.)
    略過.... (自己可以隨便嘗試一下)

再介紹一下edge antialiasing(抗鋸齒) 這個(gè)吧. 因?yàn)橹白约阂矝]接觸過,很多人估計(jì)也是沒接觸過吧.

177286BA-E7BE-4CFE-B992-AF18B30FE1DE.png

翻譯:

是否允許執(zhí)行反鋸齒邊緣并鸵。
默認(rèn)的值是 NO.(不使用抗鋸齒,也有人叫反鋸齒),當(dāng) Value 為YES的時(shí)候, 在layer的 edgeAntialiasingMask屬性layer依照這個(gè)值允許抗鋸齒邊緣,(參照這個(gè)值) 可以在info.plist里面開啟這個(gè)屬性.


0068582C-92E8-4A80-AAD8-C89E22668964.png

放一個(gè) plist 常見的key值表 info.plist配置表 (樓主真的好...)

說了這么多. 那抗鋸齒又是啥????

抗鋸齒的概念 (隨便看看.)
在我們iOS中表現(xiàn) 參考這里


    CALayer *layer = [CALayer layer]; 
    layer.position = CGPointMake(100, 100);
    layer.bounds = CGRectMake(0,0, 100, 100);
    layer.backgroundColor = [UIColor redColor].CGColor;
    //layer.allowsEdgeAntialiasing = YES;
    [self.view.layer addSublayer:layer]; 

正常添加layer 在view上是這樣的.

模擬器不旋轉(zhuǎn).png

下一步.

 CGFloat angle = M_PI / 30.0;
[layer setTransform:CATransform3DRotate(layer.transform, angle, 0.0, 0.0, 1.0)];

在模擬器表現(xiàn)是這樣的.

模擬器NO.png

如果 layer.allowsEdgeAntialiasing = YES;

--- 在模擬器上是

模擬器YES.png

--- 在真機(jī)上.

真機(jī)YES.png

可見真機(jī)效果跟模擬器還是有差距的,(模擬器邊緣比真機(jī)模糊,)官方文檔也有提到這點(diǎn)

Use antialiasing when drawing a layer that is not aligned to pixel boundaries. This option allows for more sophisticated rendering in the simulator but can have a noticeable impact on performance.

這是UIView的抗鋸齒,在模擬器上還是有性能的消耗的.

看看效果就行,具體的不研究太多了,知道怎么避免就行.


  • group opacity(不透明)
組不透明.png

大概的意思就是 這個(gè)屬性決定了Core Animation框架下 子layers從他們Superlayer.繼承過來的不透明度.
iOS 6之前是默認(rèn)NO,iOS7以后就默認(rèn) 是YES. 文檔也是說可以在模擬器上呈現(xiàn).但是對(duì)性能有明顯的影響.
這里我就不測(cè)試了.這個(gè)屬性過一遍,重點(diǎn)是下一個(gè)屬性.


  • 復(fù)雜形狀設(shè)置圓角.

我們?cè)陂_發(fā)中經(jīng)常會(huì)對(duì)一些圖片或者按鈕進(jìn)行圓角處理,需求還是特別多的,設(shè)置圓角有多種方法,我列一下常見的方式.

  1. 設(shè)置layer層的圓角大小.經(jīng)常我們還會(huì)設(shè)置masksToBounds,
//按正方形來算.長(zhǎng)的一半就是半徑.按照這個(gè)去設(shè)置就是圓角了,長(zhǎng)方形的話則按短的那一邊
_imageView.layer.cornerRadius = iamgeView.width/2;
_imageView.layer.masksToBounds = YES;

這樣做對(duì)于少量的圖片鸳粉,這個(gè)沒有什么問題,但是數(shù)量比較多的時(shí)候,UITableView滑動(dòng)可能不是那么流暢园担,屏幕的幀數(shù)下降届谈,影響用戶體驗(yàn)。

  1. 使用layer的mask遮罩和CAShapLayer
    創(chuàng)建圓形的CAShapeLaer對(duì)象,設(shè)置為View的mask屬性,這樣也可以達(dá)到圓角的效果,但是前面提到過了,使用mask屬性會(huì)離屏渲染,不僅這樣,還曾加了一個(gè) CAShapLayer對(duì)象.著實(shí)不可以取.

  2. 使用帶圓形的透明圖片.(求個(gè)切圖大師 - - ).

  3. CoreGraphics自定義繪制圓角.

提到CoreGraphics,還有一種 特殊的"離屏渲染"方式 不得不提,那就是drawRect方法.觸發(fā)的方式:
如果我們重寫了 drawRect方法,并且使用CoreGraphics技術(shù)去繪制.就設(shè)計(jì)到了CPU渲染,整個(gè)渲染,由CPU在app內(nèi)同步完成,渲染之后再交給GPU顯示.(這種方式對(duì)性能的影響不是很高)

tip:CoreGraphic通常是線程安全的弯汰,所以可以進(jìn)行異步繪制艰山,然后在主線程上更新.

- (void)display {
   dispatch_async(backgroundQueue, ^{
       CGContextRef ctx = CGBitmapContextCreate(...);
       CGImageRef img = CGBitmapContextCreateImage(ctx);
       CFRelease(ctx);
       dispatch_async(mainQueue, ^{
           layer.contents = img;
       });
   });
}
  • 如何檢測(cè)我的項(xiàng)目里面離屏渲染了

  • 之前看了一些文章說在intruments里面的 CoreAnimation里面有工具.檢測(cè).(沒找著.求補(bǔ)充)

打開的正確方式:
模擬器的 debug -> 選取 color Offscreen-Rendered.

BB55F33F-97CC-4C28-B3F1-22456A2A7BD8.png

開啟后會(huì)把那些需要離屏渲染的圖層高亮成黃色,這就意味著黃色圖層可能存在性能問題咏闪。

正常:是這樣的


正常渲染.png

有問題的圖層:

調(diào)試.png

可以看見我設(shè)置了圓角的imageView有問題.

項(xiàng)目開發(fā)中怎么去處理?

拋出一個(gè)問題: 需求就是有很多圓角那我們項(xiàng)目中應(yīng)該怎么去處理圓角呢?

  1. 使用YYWebImage去處理
  2. iOS中圓角圖片的處理

相信看完兩篇文章,多少都會(huì)能收獲一點(diǎn)!

有些人說:

iOS 9.0 之后UIButton設(shè)置圓角會(huì)觸發(fā)離屏渲染曙搬,而UIImageView里png圖片設(shè)置圓角不會(huì)觸發(fā)離屏渲染了,如果設(shè)置其他陰影效果之類的還是會(huì)觸發(fā)離屏渲染的(這句話不知道誰(shuí)說的.自己有沒有去嘗試呢???)

結(jié)論: 經(jīng)過測(cè)試

70915C7C-7523-4008-9A88-B5682407926D.png

大家可以看到,
UIButton 的 masksToBounds = YES下發(fā)生離屏渲染與 背景圖存不存在有關(guān)系, 如果沒有給按鈕設(shè)置 btn.image = [UIImage imageName:@"xxxxx"]; 是不會(huì)產(chǎn)生離屏渲染的 .

關(guān)于 UIImageView,現(xiàn)在測(cè)試發(fā)現(xiàn)(現(xiàn)版本: iOS10),在性能的范圍之內(nèi),給UIImageView設(shè)置圓角是不會(huì)觸發(fā)離屏渲染的,但是同時(shí)給UIImageView設(shè)置背景色則肯定會(huì)觸發(fā).觸發(fā)離屏渲染跟 png.jpg格式并無關(guān)聯(lián)(可能采取的壓縮格式不同,這里不做探討,這里我給出結(jié)果是沒有關(guān)系)


  • 總結(jié):

  • 對(duì)于網(wǎng)上一些文章得出的結(jié)論,我覺得大家得理性分析,并不是每個(gè)人都是對(duì)的,只有經(jīng)過自己實(shí)踐才能得出較好的定論.本文也是,我希望哪里有理解錯(cuò)或者其它什么錯(cuò)誤請(qǐng)?zhí)岢?認(rèn)真臉.),人無完人,我希望在學(xué)習(xí)的道路上能碰見更多一起進(jìn)步的人!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鸽嫂,一起剝皮案震驚了整個(gè)濱河市纵装,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌据某,老刑警劉巖橡娄,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異癣籽,居然都是意外死亡挽唉,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門筷狼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來橱夭,“玉大人,你說我怎么就攤上這事桑逝。” “怎么了俏让?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵楞遏,是天一觀的道長(zhǎng)茬暇。 經(jīng)常有香客問我,道長(zhǎng)寡喝,這世上最難降的妖魔是什么糙俗? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮预鬓,結(jié)果婚禮上巧骚,老公的妹妹穿的比我還像新娘。我一直安慰自己格二,他們只是感情好劈彪,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著顶猜,像睡著了一般沧奴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上长窄,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天滔吠,我揣著相機(jī)與錄音,去河邊找鬼挠日。 笑死疮绷,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的嚣潜。 我是一名探鬼主播冬骚,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼郑原!你這毒婦竟也來了唉韭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤犯犁,失蹤者是張志新(化名)和其女友劉穎属愤,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體酸役,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡住诸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了涣澡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贱呐。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖入桂,靈堂內(nèi)的尸體忽然破棺而出奄薇,到底是詐尸還是另有隱情,我是刑警寧澤抗愁,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布馁蒂,位于F島的核電站呵晚,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏沫屡。R本人自食惡果不足惜饵隙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望沮脖。 院中可真熱鬧金矛,春花似錦、人聲如沸勺届。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)涮因。三九已至废睦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間养泡,已是汗流浹背嗜湃。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留澜掩,地道東北人购披。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像肩榕,于是被迫代替她去往敵國(guó)和親刚陡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

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