README:
引言: 一款優秀的app,流暢很關鍵,用戶使用60的fps的app,跟使用30的fps的app感受是完全不一樣的.類似于 半糖 這種優秀的應用肯定花了大把精力去優化界面.網上關于優化的界面的文章一搜一大把.本文并不是講界面優化的,優化的話推薦下面幾篇文章;
YYKit作者: "iOS 保持界面流暢的技巧" (我相信認真看一定有收獲!)
離屏渲染的優化 (這篇文章很強)
有些東西研究起來挺費勁的.但是想要更多的收獲,還真就必須得研究.知識零零散散,時間久了再次用起來難免會生疏,又得重新找資料,看文章.很麻煩.索性就整理個比較全面的知識點供以后復習.
-
屏幕渲染
OpenGL中,GPU屏幕渲染有兩種方式.
-
On-Screen Rendering (當前屏幕渲染)
指的是GPU的渲染操作是在當前用于顯示的屏幕緩沖區進行.
-
Off-Screen Rendering (離屏渲染)
指的是在GPU在當前屏幕緩沖區以外開辟一個緩沖區進行渲染操作.
幾個名詞 "GPU" " 緩沖區" .
不知道GPU的就自行百度吧 (- - 寶寶說不清). 說下緩沖區.要明白緩沖區,首先就得要知道圖像顯示出來的原理,本文的第一篇博客里面介紹的很詳細.
顯示器 顯示出來的圖像是經過 CRT電子槍一行一行的掃描.(可以是橫向的也可以是縱向 ,具體CRT電子槍又是什么,百度文庫介紹的很詳細.),掃描出來就呈現了一幀畫面,隨后電子槍又會回到初始位置循環掃描,為了讓顯示器的顯示跟視頻控制器同步,當電子槍新掃描一行的時候.準備掃描的時候,會發送一個 水平同步信號(HSync信號),而當一幀畫面繪制完成后,電子槍回復到原位,準備畫下一幀前,顯示器會發出一個垂直同步信號(vertical synchronization簡稱 VSync),顯示器一般是固定刷新頻率的,這個刷新的頻率其實就是VSync信號產生的頻率. 然后CPU計算好frame等屬性,就將計算好的內容提交給GPU去渲染,GPU渲染完成之后就會放入幀緩沖區,然后視頻控制器會按照VSync信號逐行讀取幀緩沖區的數據,經過可能的數模轉換傳遞給顯示器.就顯示出來了.
原理圖就不放了,過一遍概念.
離屏渲染的代價很高,想要進行離屏渲染,首選要創建一個新的緩沖區,屏幕渲染會有一個上下文環境的一個概念,離屏渲染的整個過程需要切換上下文環境,先從 當前屏幕切換到離屏,等結束后,又要將上下文環境切換回來.這也是為什么會消耗性能的原因了.
。由于垂直同步的機制,如果在一個 VSync 時間內,CPU 或者 GPU 沒有完成內容提交,則那一幀就會被丟棄,等待下一次機會再顯示,而這時顯示屏會保留之前的內容不變。這就是界面卡頓的原因。
那有個問題: 為什么離屏渲染這么耗性能,為什么有這套機制呢?
當使用圓角,陰影,遮罩的時候,圖層屬性的混合體被指定為在未預合成之前(下一個VSync信號開始前)不能直接在屏幕中繪制,所以就需要屏幕外渲染。 你可以這么理解. 老板叫我短時間間內做一個 app.我一個人能做,但是時間太短,所以我得讓我朋友一起來幫著我做.(性能消耗: 也就是耗 你跟你朋友之間溝通的這些成本,多浪費啊).但是沒辦法 誰讓你做不完呢.
這么一講會不會比較明白點.
-
哪些操作會觸發 離屏渲染?
官方公開的的資料里關于離屏渲染的信息最早是在 2011年的 WWDC, 在多個 session 里都提到了盡量避免會觸發離屏渲染的效果,包括:mask, shadow, group opacity, edge antialiasing。
shouldRasterize(光柵化)
masks(遮罩)
shadows(陰影)
edge antialiasing(抗鋸齒)
group opacity(不透明)
復雜形狀設置圓角等
漸變
Text(UILabel, CATextLayer, Core Text, etc)...
介紹一些屬性.
- shouldRasterize(光柵化): 將圖轉化為一個個柵格組成的圖象。 光柵化特點:每個元素對應幀緩沖區中的一像素。
shouldRasterize = YES
在其它屬性觸發離屏渲染的同時,會將光柵化后的內容緩存起來,如果對應的layer
或者 sublayers
沒有發生改變,在下一幀的時候可以直接復用,從而減少渲染的頻率.
當使用光柵化是, 可以開啟 "Color Hits Green and Misses Red"來檢查該場景下是否適合選擇光柵化,綠色表示緩存被復用,紅色表示緩存在被重復創建.對于經常變動的內容,不要開啟,否則會造成性能的浪費.
如果cell里面的內容不斷變化(cell的復用),如果設置了cell.layer.shouldRaseterize = YES
則會降低圖形性能,造成離屏渲染.
- mask(遮罩).
mask是layer的一個屬性.
/ 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;
大概的意思. 當透明度改變的時候,這個 mask 就是覆蓋上去的那個陰影.該層的layer的alpha決定了多少層背景跟內容通過并顯示,完全,或者部分不透明的像素允許潛在的內容 通過并顯示.
默認是nil,當配置一個 遮罩的時候,記得設置 遮罩的大小,位置.已確保跟蓋圖層對齊.(這是官方文檔說的 如果不對齊會怎樣.you can try. 我試過是只能顯示對齊的那一部分.)
如果你想給這個屬性賦值,前提是必須沒有 superLayer,如果有superLayer,這個行為則是無效的.(你也可以嘗試一下反的.)
tip:用mask可以做一些轉場動畫.這里就不介紹了.
那說了這么多,他就是生來會觸發離屏渲染的.所以要謹慎 設置透明度. 提到透明度,另外補充一個概念.)
Color Blended Layers
用jim的話來介紹它就是
屏幕上的每個像素點的顏色是由當前像素點上的多層layer(如果存在)共同決定的,GPU會進行計算出混合顏色的RGB值,最終顯示在屏幕上。而這需要讓GPU計算,所以我們要盡量避免設置alpha,這樣GPU會忽略下面所有的layer,節約計算量。再提一下opaque這個屬性,網上普遍認為view.opaque = YES,GPU就不會進行圖層混合計算了。而這個結論是錯誤的,其實view.opaque事實上并沒什么卵用。
如果你真的想達到這個效果,可以用layer.opaque,這個才是正確的做法
- shadows(陰影.)
略過.... (自己可以隨便嘗試一下)
再介紹一下edge antialiasing(抗鋸齒) 這個吧. 因為之前自己也沒接觸過,很多人估計也是沒接觸過吧.
翻譯:
是否允許執行反鋸齒邊緣。
默認的值是 NO.(不使用抗鋸齒,也有人叫反鋸齒),當 Value 為YES的時候, 在layer的 edgeAntialiasingMask屬性layer依照這個值允許抗鋸齒邊緣,(參照這個值) 可以在info.plist里面開啟這個屬性.
放一個 plist 常見的key值表 info.plist配置表 (樓主真的好...)
說了這么多. 那抗鋸齒又是啥????
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上是這樣的.
下一步.
CGFloat angle = M_PI / 30.0;
[layer setTransform:CATransform3DRotate(layer.transform, angle, 0.0, 0.0, 1.0)];
在模擬器表現是這樣的.
如果 layer.allowsEdgeAntialiasing = YES;
--- 在模擬器上是
--- 在真機上.
可見真機效果跟模擬器還是有差距的,(模擬器邊緣比真機模糊,)官方文檔也有提到這點
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(不透明)
大概的意思就是 這個屬性決定了Core Animation框架下 子layers從他們Superlayer.繼承過來的不透明度.
iOS 6之前是默認NO,iOS7以后就默認 是YES. 文檔也是說可以在模擬器上呈現.但是對性能有明顯的影響.
這里我就不測試了.這個屬性過一遍,重點是下一個屬性.
- 復雜形狀設置圓角.
我們在開發中經常會對一些圖片或者按鈕進行圓角處理,需求還是特別多的,設置圓角有多種方法,我列一下常見的方式.
- 設置layer層的圓角大小.經常我們還會設置masksToBounds,
//按正方形來算.長的一半就是半徑.按照這個去設置就是圓角了,長方形的話則按短的那一邊
_imageView.layer.cornerRadius = iamgeView.width/2;
_imageView.layer.masksToBounds = YES;
這樣做對于少量的圖片,這個沒有什么問題,但是數量比較多的時候,UITableView滑動可能不是那么流暢,屏幕的幀數下降,影響用戶體驗。
使用layer的mask遮罩和CAShapLayer
創建圓形的CAShapeLaer對象,設置為View的mask屬性,這樣也可以達到圓角的效果,但是前面提到過了,使用mask屬性會離屏渲染,不僅這樣,還曾加了一個 CAShapLayer對象.著實不可以取.使用帶圓形的透明圖片.(求個切圖大師 - - ).
CoreGraphics自定義繪制圓角.
提到CoreGraphics,還有一種 特殊的"離屏渲染"方式 不得不提,那就是drawRect方法.觸發的方式:
如果我們重寫了 drawRect方法,并且使用CoreGraphics技術去繪制.就設計到了CPU渲染,整個渲染,由CPU在app內同步完成,渲染之后再交給GPU顯示.(這種方式對性能的影響不是很高)
tip:CoreGraphic通常是線程安全的,所以可以進行異步繪制,然后在主線程上更新.
- (void)display {
dispatch_async(backgroundQueue, ^{
CGContextRef ctx = CGBitmapContextCreate(...);
CGImageRef img = CGBitmapContextCreateImage(ctx);
CFRelease(ctx);
dispatch_async(mainQueue, ^{
layer.contents = img;
});
});
}
-
如何檢測我的項目里面離屏渲染了
之前看了一些文章說在intruments里面的 CoreAnimation里面有工具.檢測.(沒找著.求補充)
打開的正確方式:
模擬器的 debug -> 選取 color Offscreen-Rendered.
開啟后會把那些需要離屏渲染的圖層高亮成黃色,這就意味著黃色圖層可能存在性能問題。
正常:是這樣的
有問題的圖層:
可以看見我設置了圓角的imageView有問題.
項目開發中怎么去處理?
拋出一個問題: 需求就是有很多圓角那我們項目中應該怎么去處理圓角呢?
相信看完兩篇文章,多少都會能收獲一點!
有些人說:
iOS 9.0 之后UIButton設置圓角會觸發離屏渲染,而UIImageView里png圖片設置圓角不會觸發離屏渲染了,如果設置其他陰影效果之類的還是會觸發離屏渲染的(這句話不知道誰說的.自己有沒有去嘗試呢???)
結論: 經過測試
大家可以看到,
UIButton 的 masksToBounds = YES下發生離屏渲染與 背景圖存不存在有關系, 如果沒有給按鈕設置 btn.image = [UIImage imageName:@"xxxxx"];
是不會產生離屏渲染的 .
關于 UIImageView,現在測試發現(現版本: iOS10),在性能的范圍之內,給UIImageView設置圓角是不會觸發離屏渲染的,但是同時給UIImageView設置背景色則肯定會觸發.觸發離屏渲染跟 png.jpg格式并無關聯(可能采取的壓縮格式不同,這里不做探討,這里我給出結果是沒有關系)
-
總結:
對于網上一些文章得出的結論,我覺得大家得理性分析,并不是每個人都是對的,只有經過自己實踐才能得出較好的定論.本文也是,我希望哪里有理解錯或者其它什么錯誤請提出(認真臉.),人無完人,我希望在學習的道路上能碰見更多一起進步的人!
作者:uncleRX
鏈接:http://www.lxweimin.com/p/57e2ec17585b
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權并注明出處。