1. 程式人生 > >iOS離屏渲染優化(附DEMO)

iOS離屏渲染優化(附DEMO)

本文授權轉載,作者:seedante(簡書

離屏渲染(Offscreen Render)

objc.io出品的Getting Pixels onto the Screen的翻譯版《繪製畫素到螢幕上》應該是國內對離屏渲染這個概念推廣力度最大的一篇文章了。文章裡提到「直接將圖層合成到幀的緩衝區中(在螢幕上)比先建立螢幕外緩衝區,然後渲染到紋理中,最後將結果渲染到幀的緩衝區中要廉價很多。因為這其中涉及兩次昂貴的環境轉換(轉換環境到螢幕外緩衝區,然後轉換環境到幀緩衝區)。」觸發離屏渲染後這種轉換髮生在每一幀,在介面的滾動過程中如果有大量的離屏渲染髮生時會嚴重影響幀率。

蘋果官方公開的的資料裡關於離屏渲染的資訊最早是在 2011年的 WWDC, 在多個 session 裡都提到了儘量避免會觸發離屏渲染的效果,包括:mask, shadow, group opacity, edge antialiasing。

最初應該是從英文開發者那裡傳開的:使用 Core Graphics 裡的繪製 API 也會觸發離屏渲染,比如重寫 drawRect:。為什麼幾年前會產生這樣的認識不得而知。在 WWDC 2011: Understanding UIKit Rendering 這個 session 裡演示了「Core Animation Instruments」裡使用「Color Offscreen-Renderd Yellow」選項來檢測離屏渲染,在 WWDC 2014: Advanced Graphics and Animations for iOS Apps 也專門演示了這個工具。

37334-909659db842314aa.png

Core Animation Instruments Debug Options

Designing for iOS: Graphics & Performance這篇文章也提到了使用 Core Graphics API 會觸發離屏渲染,這引出了 Andy Matuschak,蘋果 iOS 4.1-8 時期 UIKit 組成員 ,WWDC 2011: Understanding UIKit Rendering 主講人之一,對這個觀點的回覆,主要意思是:「Core Graphics 的繪製 API 的確會觸發離屏渲染,但不是那種 GPU 的離屏渲染。使用 Core Graphics 繪製 API 是在 CPU 上執行,觸發的是 CPU 版本的離屏渲染。」

本文以「Color Offscreen-Renderd Yellow」為觸發離屏渲染的標準,除非還有這個標準無法檢測出來的引發離屏渲染的行為。那麼 Core Graphics API 是不會觸發離屏渲染的,比如重寫drawRect:,而除了以上四種效果會觸發離屏渲染,使用系統提供的圓角效果也會觸發離屏渲染,比如這樣:

1 2 view.layer.cornerRadius = 5 view.layer.masksToBounds = true

圓角優化前段時間在微博上刷了好一陣,不想湊熱鬧,不過這個話題必須講一講。

開始之前,先鋪墊一點基礎的東西。

UIView 和 CALayer 的關係

37334-b25adbf4848acdd9.png

出自 WWDC 2012: iOS App Performance: Graphics and Animations

CALayer 負責顯示內容contents,UIView 為其提供內容,以及負責處理觸控等事件,參與響應鏈。CALayer 的結構如下,出自 Layers Have Their Own Background and Border

layer_border_background_2x.jpg

CALayer 構成

CALayer 有三個視覺元素,中間的contents屬性是這樣宣告的:var contents: AnyObject?,實際上它必須是一個CGImage才能顯示。

當使用let view = UIView(frame: CGRectMake(0, 0, 200, 200))生成一個檢視物件並新增到螢幕上時,從 CALayer 的結構可以知道,這個檢視的 layer 的三個視覺元素是這樣的:contents為空,背景顏色為空(透明色),前景框寬度為0的前景框,這個檢視從視覺上看什麼都看不到。CALayer 文件第一句話就是:「The CALayer class manages image-based content and allows you to perform animations on that content.」UIView 的顯示內容很大程度上就是一張圖片(CGImage)。

UIImageView

既然直接對 CALayer 的contents屬性賦值一個CGImage便能顯示圖片,所以 UIImageView 就順利成章地誕生了。實際上 UIImage 就是對 CGImage(或者 CIImage) 的一個輕量封裝。記得我剛接觸 iOS 時,搞不懂這兩者的區別,有人這樣對我說過,沒想到出處是這裡:

37334-39ff6458c82ddd96.jpg

出自 WWDC 2012: iOS App Performance: Graphics and Animations

UIKit 和 Core Graphics 框架的聯絡很緊密,UIKit 裡帶CG字首屬性的類基本上是對應 Core Graphics 框架裡的物件的封裝,UIKit 裡的繪製功能也是 Core Graphics 繪製 API 的封裝。Drawing with Quartz and UIKit列舉了這些對應關係。介面的內容主要是影象和文字,文字是怎麼顯示的?也是使用 Core Graphics 框架繪製出來的。

接下來,正式開始本文的話題。

RoundedCorner

設定圓角:

1 view.layer.cornerRadius = 5

這行程式碼做了什麼?文件中cornerRadius屬性的說明:

Setting the radius to a value greater than 0.0 causes the layer to begin drawing rounded corners on its background. By default, the corner radius does not apply to the image in the layer’s contents property; it applies only to the background color and border of the layer. However, setting the masksToBounds property to YES causes the content to be clipped to the rounded corners.

很明瞭,只對前景框和背景色起作用,再看 CALayer 的結構,如果contents有內容或者內容的背景不是透明的話,還需要把這部分弄個角出來,不然合成的結果還是沒有圓角,所以才要修改masksToBounds為true(在 UIView 上對應的屬性是clipsToBounds,在 IB 裡對應的設定是「Clip Subiews」選項)。前些日子很熱鬧的圓角優化文章中的2篇指出是修改masksToBounds為true而非修改cornerRadius才是觸發離屏渲染的原因,但如果以「Color Offscreen-Renderd Yellow」的特徵為標準的話,這兩個屬性單獨作用時都不是引發離屏渲染的原因,他倆合體(masksToBounds = true, cornerRadius>0)才是。

系統圓角需要裁剪 layer 中間的contents,這其中裁剪工作和離屏渲染對效能的影響哪個佔的比重大?我對此有點疑問。雖然系統圓角下裁剪工作和離屏渲染無法拆分,但可以單獨測試出裁剪工作對效能的影響。我使用上面提到的某篇優化圓角的文章提供的 Demo 在快速滾動下得到的幀率如下,在此基礎上驗證測試:

37334-dbbb2680e41ab4af.png

基礎幀率

圖中括號內的數量代表滾動時同屏下圓角效果的個數。同時測試了圓角半徑對效能的影響,兩者沒有關係,cornerRadius分別為0.1和10的時候無明顯差別。使用「Color Offscreen-Renderd Yellow」來檢測時,只有圓角部分才會有黃色特徵,因此在cornerRadius = 0.1的時候基本觀測不到,如果你對cornerRadius和masksToBounds合體才能觸發離屏渲染有疑問,對比幀率就知道了。

這個 Demo 裡的優化方案是重繪圓角,作者給出了他在 iPhone 6 上的測試結果,非常好。奇怪的是 Demo 裡沒有將繪製圓角的工作放到後臺,文章裡沒有對此進行解釋,不過這個 Demo 在我服役多年的 iPad mini 1代(iOS 9.3.1)上的執行結果是無法讓人滿意的,顯然應該放在後臺重繪再切換到主執行緒設定內容。做個對比測試,前臺圓角:主執行緒繪製圓角(Demo 的優化方法),後臺圓角:將原 Demo 的繪製操作放到後臺執行緒然後切換到主執行緒,同屏圓角數量為24個,對比結果:

37334-94ae50f779207daf.png

圓角對比

前臺圓角的效能稍好於系統圓角,後臺圓角的表現和無圓角持平。經過測試,masksToBounds=true和cornerRadius>0在單獨作用的時候對效能基本沒有影響(針對無圓角,前臺圓角和後臺圓角),且單獨作用下無法觀察到離屏渲染時的黃色特徵,也就是說只有系統圓角才觸發了離屏渲染。

對比上面的測試結果,眼看就要得出「在系統圓角中(阻塞主執行緒的)裁剪工作是影響效能的主要因素,黑鍋不該離屏渲染來背。」的結論來了。檢視效能出現問題時,要分清瓶頸是在 CPU 還是 GPU 上,使用 GPU Driver Instruments 來檢測。以下測試中同屏圓角數量在24個左右:

1464163253469587.png

系統圓角: 幀率很低,CPU 利用率較低,GPU 利用率很高

1464163265756986.png

前臺圓角:幀率比上面稍好,不穩定,CPU 利用率起伏很大,高峰接近100%,低谷在20%以下,GPU 利用率很低

1464163278760659.png

後臺圓角:幀率非常好,CPU 利用率起伏非常大,高峰超過120%,低谷在10%以下,GPU 利用率很低

慘遭打臉!有點意外的是前臺圓角的 CPU 使用率和後臺圓角一樣起伏都很大。重繪圓角時,繪製工作是由 CPU 完成的,這可能成為效能的瓶頸,在系統圓角下 GPU 是瓶頸,由於無法將離屏渲染和我所謂的裁剪工作分開,之前試圖用自行繪製圓角妄圖證明系統圓角里裁剪圓角的工作是影響效能主因的對比測試是沒有意義的。

Mastering UIKit Performance 裡介紹離屏渲染時也舉了圓角的例子,他給出的程式碼並沒有在後臺繪製圓角,另一方面他表示繪製圓角的程式碼只會執行一次(在實際使用時的確應該這樣設計,只繪製一次,後續直接使用重繪的結果),但從貼出來的程式碼來看繪製程式碼無法只執行一次(畢竟是 Demo,沒有優化這一點,實際上就變成了和系統圓角一樣,滾動的每一幀都在重繪),這樣一來就變成了在主執行緒進行手工繪製圓角,優化效率不高,而且從最後貼出的幀率截圖來看並沒有達到結論所說的那樣高幀率以及穩定性。由於這篇文章並沒有開發原始碼,無法探明其中的差異。他的測試硬體是 iPhone 4(iOS 7.1.1),而我的 iPad mini 1代與 iPhone 4相差兩年,上面的 Demo 裡的測試硬體是 iPhone 6,又相差2年,考慮到硬體效能的差異,重繪圓角應該放到後臺才是最優解。

OffscreenRenderDemo

還有其他的幾個效果需要測試,所以還是要寫個 Demo 的:OffscreenRenderDemo,裡面包括本文涉及的所有效果演示以及優化方案。測試的 Demo 還是老一套,TableView 配合影象和文字,長這樣,接下來的效果測試都主要集中在左側的兩個 UIImageView 上,尺寸都為(80, 80),cell 高度為100。

1464163325347860.png

介面元素

測試環境為:

  • iPad mini 1st generation with iOS 9.3.1

  • Xcode 7.3 with Swift 2.2

  • OS X 10.11.4

在 Demo 裡實現了圓角的優化,這個話題還沒有結束呢,上一節只是證明了介面滾動過程中大量的離屏渲染確實是幀率殺手。再放圖就特別佔地方了,接下來就用表格來呈現資料,資料是我目測計算出來的,會有誤差,而且是單次測試,但是量級是沒有問題的。接下來的描述中:左右代表在某個值附近浮動,以下代表都接近某個值,很少有超過的,以上代表絕大部分在某個值以上,但超過幅度不大。

OffscreenRenderDemo 的基準效能:

1464163367300632.png

CPU 的利用率很難用平均數值呈現,從上面也可以看到 CPU 的利用率是週期性的波動,這是這類 Demo 的特點,這導致很難對比兩次測試中的 CPU 利用率。上面的表格裡標註的波動範圍僅能當作 CPU 是否是效能瓶頸的參考,而不能與其他測試進行對比。上面的圖裡 CPU 的取樣間隔是1ms,FPS 和 GPU 的利用率的取樣間隔是1s,這些是預設值。如果你希望增大 CPU 取樣間隔時間來形成類似的柱狀圖,基本上沒有意義,這裡的資料是累計利用率,稍不注意看到的都超過100%。觸發離屏渲染的效果的瓶頸主要是 GPU,CPU 的利用率偏低,當然,檢視效能跟 CPU 和 GPU 都有關,後面的效果會對 CPU 的利用率做出說明。

在我的 Demo 裡,後臺繪製圓角自不必說和無任何效果下的效能非常接近,在主執行緒繪製圓角的效能只是略微下降。這與上一個 Demo 的相關情況相差很大,上面的結果顯示在同螢幕圓角數量24個的情況下,平均幀率勉強在40左右,修正為20個測試一次,平均幀率依然在40附近徘徊。我的 Demo 在主執行緒以及後臺執行緒繪製圓角時 CPU 的利用率也不像上一個 Demo 那樣變化劇烈。由於程式碼的差異,這些情況很難說明什麼,但再次證明一點,為了高幀率,後臺繪製才是最優解。

大部分賺星星的方案都採用了重繪圓角,重繪的方式有多種,都是殊途同歸。實際中重繪圓角的優化方案需要考慮的是,將影象重新繪製為為圓角影象相當於多了一份拷貝,要不要快取?A.第一次重繪後將這些圓角影象快取在磁盤裡,第二次載入直接使用快取的圓角影象;B.直接儲存在記憶體裡,在記憶體比較吃緊時顯然不是個好選擇;C.不快取,和系統圓角一樣,每次都重繪,浪費電量。

說了這麼多,重繪方案與其他的優化方案相比,並沒有什麼優勢。來看看其他方案:

  • 如果不需要對外部來源的圖片做圓角,由設計師直接畫成圓角圖片是最方便的;

  • 混合圖層:在要新增圓角的檢視上再疊加一個部分透明的檢視,只對圓角部分進行遮擋。VVebo微部落格戶端就是這樣做的,遮擋的部分背景最好與周圍背景相同。多一個圖層會增加合成的工作量,但這點工作量與離屏渲染相比微不足道,效能上無論各方面都和無效果持平。下面左側的影象是 VVebo 裡用來製造圓形頭像的 mask 影象,實際中有這種需求的基本是製造圓形頭像,普通的圓角遮罩需要左二這種,左三是通用型。如果疊加的檢視都一樣,可以只加載一次遮罩圖片以減少記憶體佔用。

1464163407690151.png

遮罩

除了用軟體畫出來儲存在專案裡,直接用程式碼畫出來也是很簡單的。即使不熟悉 Core Graphics 的 API,搜尋出來的重繪圓角的程式碼看懂是很容易的,但要繪製出上面的圖形還是有點棘手。這種事情多試試就好了:在一個設定opaque = false的 CGContext 裡,設定填充顏色然後用兩條貝塞爾曲線圍成一個封閉區域,最後從這個繪製環境匯出影象即可。我寫了個函式來生成區域圓角遮罩影象:Draw a transparent image

如何在文字檢視類上實現圓角?文字檢視主要是這三類:UILabel, UITextField, UITextView。其中 UITextField 類自帶圓角風格的外型,UILabel 和 UITextView 要想顯示圓角需要表現出與周圍不同的背景色才行。想要在 UILabel 和 UITextView 上實現低成本的圓角(不觸發離屏渲染),需要保證 layer 的contents呈現透明的背景色,文字檢視類的 layer 的contents預設是透明的(字元就在這個透明的環境裡繪製、顯示),此時只需要設定 layer 的backgroundColor,再加上cornerRadius就可以搞定了。不過 UILabel 上設定backgroundColor的行為被更改了,不再是設定 layer 的背景色而是為contents設定背景色,UITextView 則沒有改變這一點,所以在 UILabel 上實現圓角要這麼做:

1 2 3 //不要這麼做:label.backgroundColor = aColor 以及不要在 IB 裡為 label 設定背景色 label.layer.backgroundColor = aColor label.layer.cornerRadius = 5

Shadow

visual-shadow_2x.jpg

Layer displaying the shadow properties

陰影直接合成在檢視的下面,檢視結構裡並沒有多出一個檢視。在沒有指定陰影路徑時,陰影是沿著檢視的非透明部分擴充套件的,而且 CALayer 的三個視覺元素至少有一個存在時才會有陰影。

使用陰影必須保證 layer 的masksToBounds = false,因此陰影與系統圓角不相容。但是注意,只是在視覺上看不到,對效能的影響依然。通常這樣實現一個陰影:

1 2 3 4

相關推薦

iOS渲染優化DEMO

本文授權轉載,作者:seedante(簡書) 離屏渲染(Offscreen Render) objc.io出品的Getting Pixels onto the Screen的翻譯版《繪製畫素到螢幕上》應該是國內對離屏渲染這個概念推廣力度最大的一篇文章了。文

iOS渲染優化分析

iOS離屏渲染之優化分析 在進行iOS的應用開發過程中,有時候會出現卡頓的問題,雖然iOS裝置的效能越來越高,但是卡頓的問題還是有可能會出現,而離屏渲染是造成卡頓的原因之一。因此,本文主要分析一下離屏渲染產生的原因及避免的方法,最後介紹一下Xcode自帶的分析離屏渲染的工具Instruments

iOS渲染

離屏渲染概念 當layer不做觸發離屏渲染的操作時,是可以直接放入緩衝區中讓GPU直接渲染在螢幕內的。但是當你設定圓角、陰影、遮罩、邊界反鋸齒、設定組不透明、光柵化等觸發離屏渲染的操作後,layer繪製以後的幀不能直接放入到GPU讀取幀所在的緩衝區了。因此需要再建立一個新的緩衝區

iOS 渲染的研究

本文轉載自:https://www.jianshu.com/p/6d24a4c29e18 感覺寫得很受用,拿過來學習下,分享下,記錄下,可以時不時看看。如果作者認為侵權,可隨時聯絡我刪除。 GPU渲染機制: CPU 計算好顯示內容提交到 GPU,GPU 渲染完成後將渲染結果放入幀緩衝

iOS渲染的解釋

深入 reg images rendering api 指定 理解 core mage 重開一個環境(內存、資源、上下文)來完成(部分)圖片的繪制 指的是GPU在當前屏幕緩沖區以外新開辟一個緩沖區進行渲染操作 意為離屏渲染,指的是GPU在當前屏幕緩沖區以外新開辟一個緩沖區進

iOS-68-星星評價、顯示小數點星星評價效果demo

1、上效果圖: 2、 第一個是顯示的7.2分的評分 第二個可以點選選擇評分 3、主要程式碼: - (void)creatStarView{ UIImage *gray = [UIImage imageNamed:@"starGrey"]

Vue.js 實戰教程demo

href 還需要 webapp bsp XA 生命周期 系統 初學 基礎 在實戰之前,你需要對vuejs的基礎語法有一定的了解,可以通過以下幾個途徑進行學習: vue.js官方文檔:https://cn.vuejs.org/v2/guide/index.html vue

Aspnetcore2.0中Entityframeworkcore及Autofac的使用Demo

desc *** 結果 get rtu configure ogg netcore rri 一,通過Entityframeworkcore中DbFirst模式創建模型 這裏只說一下Entityframeworkcore中DbFirst模式創建模型,想了解CodeFirst的

Aspnetcore2.0中Entityframeworkcore及Autofac的使用Demo

-a new 自己 col 用途 spl isp aspnet ide 一,新建Aspnetcore項目 Aspnetcore是微軟家族中年紀較輕的新成員,但他的功能用途是其他前輩們望塵莫及的。想要知道他的功能特性大家可以問度娘也可以去官網查找一些資料。這裏主要給大家說一下

Aspnetcore2.0中Entityframeworkcore及Autofac的使用Demo2018-12-04 10:08

三,使用Autofac替換原有Ioc 首先安裝Autofac兩個外掛類庫: Autofac Autofac.Extensions.DependencyInjection 修改Startup.cs替換框架自帶IOC: // This method gets called by the runti

SpringBoot整合Shiro登入認證和授權demo

SpringBoot整合Shiro登入認證和授權 廢話不多說,直接上程式碼: 程式碼有點多,想直接拿demo的直接拉到底 ps:demo忘了在哪拿的了,在他的基礎上改了一些 步驟一:pom.xml匯入依賴jar包 <dependencies

Android Wear 控制元件——WearableListViewDemo

WearableListView是適用於android小型裝置如智慧手錶顯示列表的元件,可以村子滾動,非常方便的在只能手錶上顯示列表控制元件,每次顯示三個列表在螢幕中間位置。 它繼承RecyclerView,實現了OnScrollListener介面。 public cla

Android MediaPlayer+SurfaceView播放視訊Demo

MediaPlayer,顧名思義是用於媒體檔案播放的元件。Android中MediaPlayer通常與SurfaceView一起使用,當然也可以和其他控制元件諸如TextureView、SurfaceTexture等可以取得holder,用於MediaPlaye

【Android 熱修復與外掛化 一】帶你入門Android外掛化demo

本文為博主Colin原創文章,歡迎轉載。 https://blog.csdn.net/colinandroid/article/details/79431502   一. 背景 Android外掛化作為每個合格的Android程式設計師都必須會的技術,被各大廠廣泛使用。隨著各大廠對

線上瀏覽PDF之PDF.JS demo

解壓開啟,這兩個資料夾是精華 你可以自己看看目錄 我們的目標是:web/viewer.html 先開啟看看: 噢,shit 了(PDF.js預設情況下不可以開啟本地PDF檔案(釋出後可以開啟伺服器檔案),也不可以跨域瀏覽PDF) 我們抱有懷疑的態度,好吧,先看看能不能用: 工具

初試ASP.NET Web API/MVC APIDemo

寫在前面   ASP.NET Web API是​​一個框架,可以很容易構建達成了廣泛的HTTP服務客戶端,包括瀏覽器和移動裝置。是構建RESTful應用程式的理想平臺的.NET框架。   上面是微軟對Web API給出的定義,其中包含兩個關鍵字:HTTP和RESTful,其實從這一方面,大家就可以看出

Android 實現頂層視窗、浮動視窗Demo

//Edited by mythou  private void createFloatView() { Button btn_floatView = new Button() btn_floatView = new Button(ge

學習筆記:Qt與Matlab混合程式設計及遇到的諸多問題DEMO

工具:MATLAB R2014b,Qt 5.6.1, 目標:通過MATLAB寫一個簡單的函式,生成動態連結庫DLL,再在Qt上呼叫 1.在MATLAB主頁新建一個函式 記住函式的名字和儲存的函式檔案的名字要相同,比如我寫了一個函式f,儲存時檔名需要是f.m 2.生

2017最新在swift3.0下整合iOS內購全流程程式碼

最新寫的專案需要iOS內購功能所以就整理了這篇記錄,以便自己翻閱或者希望對讀者有所幫助。 因為之前一直沒做過內購這個模組,所以有所不足,請多多指教,謝謝啦~下面進入正題: 然後就沒然後了。。。下面進行詳細步驟,請仔細看圖片註釋: 1. 第一步

使用CoreData進行資料增刪改查Demo

       本文主要介紹簡單CoreData的使用,從建立工程到進行資料的增刪改查,關於CoreData中的名詞解釋什麼的不做過多介紹。        首先,建立一個CoreData工程,在建立工程的選項處勾選Use CoreData,建立成功後,會在AppDelegat