iOS圓角性能問題


離屏渲染學習筆記

一、概念理解

OpenGL中,GPU屏幕渲染有以下兩種方式:

  • On-Screen Rendering

意為當前屏幕渲染,指的是GPU的渲染操作是在當前用於顯示的屏幕緩沖區中進行。

  • Off-Screen Rendering

意為離屏渲染,指的是GPU在當前屏幕緩沖區以外新開辟一個緩沖區進行渲染操作。

二、離屏渲染的是是非非

相比於當前屏幕渲染,離屏渲染的代價是很高的,主要體現在兩個方面:

  • 創建新緩沖區

要想進行離屏渲染,首先要創建一個新的緩沖區。

  • 上下文切換

離屏渲染的整個過程,需要多次切換上下文環境:先是從當前屏幕(On-Screen)切換到離屏(Off-Screen);等到離屏渲染結束以后,將離屏緩沖區的渲染結果顯示到屏幕上有需要將上下文環境從離屏切換到當前屏幕。而上下文環境的切換是要付出很大代價的。

三、離屏渲染觸發方式

設置了以下屬性時,都會觸發離屏繪制:

  • shouldRasterize(光柵化)
  • masks(遮罩)
  • shadows(陰影)
  • edge antialiasing(抗鋸齒)
  • group opacity(不透明)

需要注意的是,如果shouldRasterize被設置成YES,在觸發離屏繪制的同時,會將光柵化后的內容緩存起來,如果對應的layer及其sublayers沒有發生改變,在下一幀的時候可以直接復用。這將在很大程度上提升渲染性能。

而其它屬性如果是開啟的,就不會有緩存,離屏繪制會在每一幀都發生。

四、另一種特殊的“離屏渲染”

按照之前的說法,如果將不在GPU的當前屏幕緩沖區中進行的渲染都稱為離屏渲染,那么就還有另一種特殊的“離屏渲染”方式:CPU渲染

如果我們重寫了drawRect方法,並且使用任何Core Graphics的技術進行了繪制操作,就涉及到了CPU渲染。整個渲染過程由CPU在App內同步地完成,渲染得到的bitmap最后再交由GPU用於顯示。

五、Instruments

Instruments的Core Animation工具中有幾個和離屏渲染相關的檢查選項:

  • Color Offscreen-Rendered Yellow

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

  • Color Hits Green and Misses Red

如果shouldRasterize被設置成YES,對應的渲染結果會被緩存,如果圖層是綠色,就表示這些緩存被復用;如果是紅色就表示緩存會被重復創建,這就表示該處存在性能問題了。

六、如何抉擇

現在擺在我們面前得有三個選擇:當前屏幕渲染、離屏渲染、CPU渲染,該用哪個呢?這需要根據具體的使用場景來決定。

  • 盡量使用當前屏幕渲染

鑒於離屏渲染、CPU渲染可能帶來的性能問題,一般情況下,我們要盡量使用當前屏幕渲染。

  • 離屏渲染 VS CPU渲染

由於GPU的浮點運算能力比CPU強,CPU渲染的效率可能不如離屏渲染;但如果僅僅是實現一個簡單的效果,直接使用CPU渲染的效率又可能比離屏渲染好,畢竟離屏渲染要涉及到緩沖區創建和上下文切換等耗時操作。

總之,具體的選擇應該由性能測試結果來決定。

一般我們在iOS開發的過程中設置圓角都是如下這樣設置的。

avatarImageView.clipsToBounds = YES; [avatarImageView.layer setCornerRadius:50]; 這樣設置會觸發離屏渲染,比較消耗性能。比如當一個頁面上有十幾頭像這樣設置了圓角 會明顯感覺到卡頓。 注意:png圖片UIImageView處理圓角是不會產生離屏渲染的。(ios9.0之后不會離屏渲染,ios9.0之前還是會離屏渲染)。

所有如果要高性能的設置圓角就需要找另外的方法了。下面是我找到的一些方法並寫了一個例子。github源碼

IMG_1802.PNG

設置圓角的方法
直接使用setCornerRadius
這種就是最常用的,也是最耗性能的。

setCornerRadius設置圓角之后,shouldRasterize=YES光柵化
avatarImageView.clipsToBounds = YES;
[avatarImageView.layer setCornerRadius:50];
avatarImageView.layer.shouldRasterize = YES;
avatarImageViewUrl.layer.rasterizationScale=[UIScreen mainScreen].scale;

//UIImageView不加這句會產生一點模糊shouldRasterize=YES設置光柵化,可以使離屏渲染的結果緩存到內存中存為位圖,使用的時候直接使用緩存,節省了一直離屏渲染損耗的性能。但是如果layer及sublayers常常改變的話,它就會一直不停的渲染及刪除緩存重新創建緩存,所以這種情況下建議不要使用光柵化,這樣也是比較損耗性能的。
//UIImageView不加這句會產生一點模糊shouldRasterize=YES設置光柵化,可以使離屏渲染的結果緩存到內存中存為位圖,使用的時候直接使用緩存,節省了一直離屏渲染損耗的性能。但是如果layer及sublayers常常改變的話,它就會一直不停的渲染及刪除緩存重新創建緩存,所以這種情況下建議不要使用光柵化,這樣也是比較損耗性能的。

直接覆蓋一張中間為圓形透明的圖片(推薦使用)
這種方法就是多加了一張透明的圖片,GPU計算多層的混合渲染blending也是會消耗一點性能的,但比第一種方法還是好上很多的。

Core Graphics繪制圓角
這種方式GPU損耗最低,但是UIButton上不知道怎么繪制,可以用UIimageView添加個點擊手勢當做UIButton使用。
UIGraphicsBeginImageContextWithOptions(avatarImageView.bounds.size, NO, [UIScreen mainScreen].scale);
[[UIBezierPath bezierPathWithRoundedRect:avatarImageView.bounds cornerRadius:50] addClip];[image drawInRect:avatarImageView.bounds];
avatarImageView.image = UIGraphicsGetImageFromCurrentImageContext();UIGraphicsEndImageContext();
這段方法可以寫在SDWebImage的completed回調里,在主線程異步繪制。也可以封裝到UIImageView里,寫了個DSRoundImageView。后台線程異步繪制,不會阻塞主線程。

問題:這種方法圖片很多的話CUP消耗會高,內存占用也會暴增,而且后台線程繪制會比在主線程繪制占用更多的內存,不知道怎么解決?求大神指教!

使用Instruments的Core Animation查看性能
Color Offscreen-Rendered Yellow開啟后會把那些需要離屏渲染的圖層高亮成黃色,這就意味着黃色圖層可能存在性能問題。

Color Hits Green and Misses Red如果shouldRasterize被設置成YES,對應的渲染結果會被緩存,如果圖層是綠色,就表示這些緩存被復用;如果是紅色就表示緩存會被重復創建,這就表示該處存在性能問題了。

用Instruments測試得
第一種方法,UIImageView和UIButton都高亮為黃色。

第二種方法,UIImageView和UIButton都高亮為綠色

第三種方法,無任何高亮,說明沒離屏渲染。這種圓片覆蓋的方法一般只用在底色為純色的時候,如果圓角圖片的父View是張圖片的時候就沒辦法了,而且底色如果是多種顏色的話那要做多張不同顏色的圓片覆蓋。(可以用代碼取底色的顏色值給圓片着色)

第四種方法無任何高亮,說明沒離屏渲染(但是CPU消耗和內存占用會很大)

問題回復:
有回復提到還有一種mask方法。這種方法比第一種方法其實更卡頓。一次mask發生了兩次離屏渲染和一次主屏渲染。 具體可以參考小心別讓圓角成了你列表的幀數殺手

@nerozhao說第四種比第一種更卡。我剛在demo里加了個例子測試了一下,第一種能明顯的感覺到卡頓(ios9.0以上還是不卡的,因為沒有離屏渲染了),第四種還是挺順暢的,有興趣的可以自己試試看。第四種是解決了離屏渲染GPU的問題。

測試例子

可以用Instruments的 GPU Driver進行測試:
Renderer Utilization如果這個值超過了~50%,就意味着你的動畫可能對幀率有所限制,很可能因為離屏渲染或者是重繪導致的過度混合。
Tiler Utilization如果這個值超過了~50%,就意味着你的動畫可能限制於幾何結構方面,也就是在屏幕上有太多的圖層占用了。

Instruments

圖上面一部分是第一種方法的數據,下面一部分是第四種方法的數據。第一種方法的Renderer Utilization 和 Tiler Utilization 基本在90%左右。幀率在20左右。第四種方法的Renderer Utilization 和 Tiler Utilization 基本在20%左右。幀率接近60。幀率越接近60滑動越順暢。
但是經過跟@nerozhao的討論發現第四種Core Graphics繪制圓角會有大量的內存占用,而且每次繪制的時候CUP消耗會很大。
由於@nerozhao使用了UITableView進行測試,因為UITableView滾動的時候是一直在復用的,UIImageView會重復繪制,所以會一直消耗CUP,然后你就能看的明顯的卡頓。@nerozhao在UITableView里圖片的繪制在后台線程進行繪制,解決了卡頓問題,但是由於是在后台線程的異步繪制所以在滾動的時候會看到圖片先是正方形然后再變成圓形。
而我使用的是UIScrollerView進行的測試,只有第一次繪制的時候會占用CUP資源,所以滑動的時候還是挺流暢的,但是內存消耗還是很大。如果是主線程繪制的話會阻塞一點時間的主線程,而后台線程繪制的話內存消耗會更大,特別容易崩潰。
所以第四種方法當圖片特別多的時候很容易Received memory warning導致崩潰


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM