iOS 2D Graphic(3)—— Offscreen Rendering離屏渲染

前言

在本系列文章的前兩篇中,我們已經了解了iOS Graphic相關的基本概念和影響Graphic性能的諸多因素和優化方案。在** “動畫平滑性優化” **一節中,當時指出了“離屏渲染(Offscreen Rendering)”這個概念。

離屏渲染,顧名思義,是指當前屏幕的繪制操作并不是直接發生在當前的幀緩沖區內,而是會合并/渲染圖層樹的一部分到一個新的緩沖區,然后該緩沖區被渲染到屏幕上。產生離屏渲染的原因有很多,它可以被 Core Animation 自動觸發,也可以被應用程序強制觸發。當圖層屬性的混合體被指定為在未預合成之前不能直接在屏幕中繪制時,屏幕外渲染就被喚起了。屏幕外渲染并不意味著軟件繪制,但是它意味著圖層必須在被顯示之前在一個屏幕外上下文(Context)中被渲染(不論CPU還是GPU)。

一般情況下,你需要避免離屏渲染,因為這是很大的消耗。直接將圖層合成到幀的緩沖區中(在屏幕上)比先創建屏幕外緩沖區,然后渲染到紋理中,最后將結果渲染到幀的緩沖區中要廉價很多。因為這其中涉及兩次昂貴的環境轉換(轉換環境到屏幕外緩沖區,然后轉換環境到幀緩沖區)。


“狹義離屏渲染” vs. “廣義離屏渲染”

“離屏渲染”本身其實是GPU的概念,但是這個概念已經被大家廣泛討論,絕大多數情況下大家討論的已經是以中種更為廣泛的理解。但是從原理上來說,我們應該還是要搞清楚它們的區別。這點Apple UIKit team的Andy Matuschak在這里明確指出了。我在這里稍作整理:

  • 廣義上:
    CPU在后臺進行的bitmap生成。這部分(通過drawRect并且使用任何CoreGraphics來實現的繪制,或使用CoreText[其實就是使用CoreGraphics]繪制)的確涉及到了“離屏繪制”,但是這和我們通常說的那種離屏繪制是有區別的。當你實現drawRect方法或者通過CoeGraphics繪制的時候,其實你是在使用CPU繪制。并且這個繪制的過程會在你的App內同步地進行。基本上你只是調用了一些向位圖緩存內寫入一些二進制信息的方法而已。

  • 狹義上:
    是真正指GPU的off-sceen rendering。這種形式離屏繪制是發生在繪制服務(是獨立的處理過程)并且同時通過GPU執行(這里就不是像前面提到的用CPU了)。當OpengGL的繪制程序在繪制每個layer的時候,有可能因為包含多子層級關系而必須停下來把他們合成到一個單獨的緩存里。你可能認為GPU應該總是比CPU牛逼一點,但是在這里我們還是需要慎重的考慮一下。因為對GPU來說,從當前屏幕(on-screen)到離屏(off-screen)上下文環境的來回切換(這個過程必須flush管線(pipeline)和光柵(rasterizing)),代價是非常大的。因此對一些簡單的繪制過程來說,這個過程有可能用CoreGraphics,全部用CPU來完成反而會比GPU做得更好。所以如果你正在嘗試處理一些復雜的層級,并且在猶豫到底用-[CALayer setShouldRasterize:]還是通過CoreGraphics來繪制層級上的所有內容,唯一的方法就是測試并且進行權衡。


離屏渲染的觸發

關于離屏渲染的觸發條件,目前已經有很多很好的文章做出了總結,各種示例也是層出不窮,后文的參考鏈接里我就放了幾篇很好的文章,這里就不再累述了,我只將基本條件提煉出來做一個簡單的列表:

  • 離屏渲染觸發條件

  • Core Graphics (any class prefixed with CG*) (CPU)

  • The drawRect() method, even with an empty implementation. (CPU)

  • CALayers with a shouldRasterize property set to YES.

  • CALayers using masks (setMasksToBounds) and round corner.

  • dynamic shadows (setShadow*).

  • Any text displayed on screen, including Core Text.

  • Group opacity (UIViewGroupOpacity).

  • edge antialiasing(抗鋸齒)

  • 漸變


降低離屏渲染對性能的影響

離屏渲染合成計算是非常昂貴的, 但有時你也許希望強制這種操作。那么這時某些情況下,你是可以選擇不同的方式來減小性能的影響的:

  • 陰影shadow
    正如上一篇文章提到的,使用setShadowPath而不是直接使用shadow屬性,會大大提升CG server的渲染性能

  • 圓角round corner
    事實上,單獨使用setMasksToBounds和單獨設置圓角并不會觸發離屏渲染,但是兩者結合一定會觸發離屏渲染。iOS 9.0 之前UIimageView跟UIButton設置圓角都會觸發離屏渲染;iOS 9.0 之后UIButton設置圓角會觸發離屏渲染,而UIImageView里png圖片設置圓角不會觸發離屏渲染了,如果設置其他陰影效果之類的還是會觸發離屏渲染的。有時候你想顯示圓角并沿著圖層裁切子圖層的時候,你可能會發現你并不需要沿著圓角裁切,這個情況下用CAShapeLayer就可以避免這個問題了。如果你想要的只是圓角且沿著矩形邊界裁切,同時還不希望引起性能問題。其實你可以用現成的UIBezierPath的構造器+bezierPathWithRoundedRect:cornerRadius:這樣做并不會比直接用cornerRadius更快,但是它避免了性能問題。

  //create shape layer    CAShapeLayer *blueLayer = [CAShapeLayer layer];   
  blueLayer.frame = CGRectMake(50, 50, 100, 100);
  blueLayer.fillColor = [UIColor blueColor].CGColor; 
  blueLayer.path = [UIBezierPath bezierPathWithRoundedRect:
       CGRectMake(0, 0, 100, 100) cornerRadius:20].CGPath;    //add it to our view 
  [self.layerView.layer addSublayer:blueLayer];
  • 光柵化 Rasterize
    這里要解釋一下,使用光柵化確實會觸發離屏渲染,但是并不代表一定會造成性能下降,相反,如果你必須使用drawRect等API進行繪制時,如果Layerd的內容并不經常變化,使用光柵化讓CPU對繪制的內容進行bitmap緩存后反而能夠顯著提高性能;如果你的程序混合了很多圖層,并且想要他們一起做動畫,GPU 通常會為每一幀(1/60s)重復合成所有的圖層。當使用離屏渲染時,GPU 第一次會混合所有圖層到一個基于新的紋理的位圖緩存上,然后使用這個紋理來繪制到屏幕上。現在,當這些圖層一起移動的時候,GPU 便可以復用這個位圖緩存,并且只需要做很少的工作。需要注意的是,只有當那些圖層不改變時,這才可以用。如果那些圖層改變了,GPU 需要重新創建位圖緩存。比如在TableView和CollectionView中,對Cell的內容繪制完畢后進行光柵化,會使得完全處于屏幕中的Cell在滾動時會復用已經緩存的位圖而不會反復繪制。如果經常變化,那么設置 shouldRasterize 是不正確的,因為這時GPU會頻繁的更新緩存而讓空間換時間的trade off失去意義。這就是為什么在本系列上一篇文章(2)中,我們使用手動繪制bitmap然后在drawRect中填充,而不是用shouldRasterize的原因所在。

一個小Demo

不免俗的,我也提供一個小Demo,你可以在Github找到項目的源碼。
在這個Demo中,我分別針對Layer和View,使用了幾種不同的增加陰影的方式,并通過開關控制,你能夠看到幾個不同的屬性共同作用下的不同效果,尤其是圓角和陰影的配合使用方法。其次,我在一個swapView中使用了shouldRasterize,當然這是一個小Demo,所以你可能看不出這里面的性能的顯著變化,但是你仍然可以配合instrument看到不同的狀態下整個頁面中參與離屏渲染的范圍。

shadowTest.gif

總結

至此,本系列文章告一段落。我希望它們能夠從基本概念開始指導你一步步的更深入的理解iOS UI 圖形性能,讓你在將來的工作中能夠少走一些彎路。當然水平有限,歡迎大家討論指正。

2016.8.16 完稿于南京

參考資料:
Mastering UIKit Performance
簡書 -《iOS 離屏渲染的研究》
簡書 -《深刻理解移動端優化之離屏渲染》
簡書 -《圓角繪制引發的離屏渲染》
When should I set layer.shouldRasterize to YES

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,665評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,627評論 2 380

推薦閱讀更多精彩內容

  • 卷首語 歡迎來到 objc.io 的第三期! 這一期都是關于視圖層的。當然視圖層有很多方面,我們需要把它們縮小到幾...
    評評分分閱讀 1,796評論 0 18
  • Core Animation工具用來監測Core Animation性能。它給我們提供了周期性的FPS,并且考慮到...
    F麥子閱讀 830評論 0 1
  • README: 引言: 一款優秀的app,流暢很關鍵,用戶使用60的fps的app,跟使用30的fps的app感受...
    uncleRX閱讀 30,660評論 31 236
  • 2017-4-29 今天晚上和班上的同學排練班級風采大賽的節目。七點左右,下起了小雨。雨越下越大,班上共四十多個人...
    瑤鬼閱讀 202評論 0 0
  • 《心有南安,唯是心安》第一章 心動
    清檸學姐閱讀 421評論 4 7