你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> iOS圖形渲染分析

iOS圖形渲染分析

編輯:IOS開發基礎

這次主要要講一些關於繪圖方面的東西,涉及的方面可能會比較多一點,也是前段時間項目中有不少這方面的知識所以花了點時間研究了一下。文章的內容主要分為兩部分,第一部分是關於iOS上一個Chart的第三方庫的一些進階使用;第二部分是在第一部分上研究的iOS上的繪圖原理以及性能方面的探索。這篇文章的目的主要是為以後講關於繪圖方面的知識拋磚引玉吧,因為後面的時間會講一些關於性能監測以及性能優化方面的內容,繪圖的性能也占了裡面很大的一部分。

第一部分

Charts(MPAndroidChart iOS版)

MPAndroidChart是一個Andriod上被經常使用的繪制曲線的第三方庫,danielgindi把這個庫使用swift成功移植到了iOS/tvOS/OSX上,它幾乎保留的Android的所有功能,同時代碼結構上也和Android的非常的相似。個人感覺就是名字取的不好,就只是叫Charts,是在太不直觀了。

然後只要講一些關於這個庫的一些進階使用吧,其實應該算是一些擴展方面的使用。對於那些基礎的使用我就不講了,同學們自行查看Demo應該就可以明白了。好,接下來我們看一張設計圖:

1.png

pic_1.png

其實我們很少情況下可以直接使用Charts的基礎功能就完成我們所想要的效果,所以基本上的情況下我們都需要對原始的Charts進行擴展才能滿足我們的需求。這裡有一點需要注意的是不到萬不得已最好永遠都不要去改動第三方的庫源碼,采用繼承的方式永遠要優於修改,無論是對於擴展性來言還是以後第三方庫的更新等等(在你每次想修改源碼之前最好問問自己是不是真的看懂了源碼的原理,作者完全沒有幫你預留修改的擴張?基本上優秀的第三方庫的擴展性是很魯棒)。

言歸正傳上圖中有三個點需要我們在原有的庫上進行擴展才可以完成我們的功能。

關於背景色的渲染

關於背景色的渲染,也就是我們標注的Note:One這部分的內容,Charts的基礎功能其實只能做到兩個點之間畫上背景色,並不能做到像上圖那樣在同一個區間中分開畫上兩種顏色,所以很明顯我們只能自己去實現這個需求,繼承一個新的Chart然後重寫它的背景繪制方法,我們來看一張演變的過程圖。

2.jpg

pic_2.jpg
  • 默認狀態是Charts可以直接支持的能力;

  • 轉變狀態其實就是計算出每兩個坐標區間之間需要分開顏色的子區間,以子區間的坐標進行背景繪制;

  • 改進狀態就是在轉變狀態下對繪制范圍以及背景漸變做了約束;

實際上從轉變狀態到改進狀態雖然看上去比較簡單,實際上還是有許多工作需要完成,這裡介紹一下我采用的方案。

轉變狀態相對比較簡單,就是計算每兩個坐標的子區間然後繪制一個大Rect就可以了。

改進狀態相對比較復雜,我們需要計算出個單位區間的坐標,每一個單位區間其實都是一個梯形,也就是說我們只需要通過畫一個三角形和一個矩形就可以畫出一個單位區間,以此類推我們就可以繪制出整個背景,其實就是基本一個線性公式.值得注意的一點是關於漸變色的渲染我們都應該從頂部至底部的方向保證不會出現顏色上的差異

y = kx + b;
關於第二行的X坐標以及底部的標注數據

關於第二行的X坐標以及底部的標注數據,也就是我們標注的Note:Two和Note:Three這兩部分的內容。

當然以來基礎額Charts肯定無法完成我們的需求,實現思路其實和繪制背景類似,我們重寫繪制X坐標的函數,在原來的基礎上利用獲得的坐標值在下方再添加一行我們需要的內容。底部的標注數據的繪制也是同一個原理,在特定的位置在添加標注數據的貼圖就可以完成我們的需求。

關於Charts這部分的使用其實每個人面對的問題可能都是不一樣的,每個設計都是不一樣的,所以我們無法涵蓋所有問題的解決方案各自是什麼,當你發現這些庫的基礎功能無法滿足你的需要時,通常遇到這種問題我的建議是兩個:

  • 類似於繪圖這類的第三方庫它都會有很好的擴展性,你要做的就是去讀源碼,摸清它的原理在這個基礎上去繼承擴展你需要的功能,最要永遠不要去做改動源碼這種事。

  • 換位思考,想想作者如果是你的話,你會怎麼去實現這個功能,把思路理清楚然後在動手,不要邊做邊想會比較好。


第二部分

這一部分內容我也是網上看了不少別人的博客後一些理解,之前對於這塊也不是很了解,這次是理論知識而且比較粗淺,下次會分享一些實際性能優化方案,和更深層次的一些東西。

UIView和CALayer

關於UIView和CALayer的關系這裡大致講主要的幾點:

  • 每個UIView都包含一個CALayer在背後提供內容的繪制和顯示(一個layer可能包含多個子layer),並且UIView的bound,frame都由內部的Layer所提供。兩者都有樹狀層級結構,layer內部有SubLayers,View內部有SubViews.但是Layer比View多了個AnchorPoint,AnchorPoint相比Postion的區別在於position點是相對suerLayer的,anchorPoint點是相對layer的,兩者都是中心點的位置,只是相對參照物不同罷了。

  • 在View顯示的時候,UIView做為Layer的CALayerDelegate,View的顯示內容由內部的CALayer的display。

  • View可以接受並處理事件,而Layer不可以,因為UIKit使用UIResponder作為響應對象。

  • layer內部維護著三分layer tree,分別是presentLayer Tree(動畫樹),modeLayer Tree(模型樹), Render Tree(渲染樹),在做 iOS動畫的時候,我們修改動畫的屬性,在動畫的其實是Layer的presentLayer的屬性值,而最終展示在界面上的其實是提供View的modelLayer。


界面的繪制和渲染

UIView是如何到顯示的屏幕上的。

這件事要從RunLoop開始,RunLoop是一個60fps的回調,也就是說每16.7ms繪制一次屏幕,也就是我們需要在這個時間內完成view的緩沖區創建,view內容的繪制這些是CPU的工作;然後把緩沖區交給GPU渲染,這裡包括了多個View的拼接(Compositing),紋理的渲染(Texture)等等,最後Display到屏幕上。但是如果你在16.7ms內做的事情太多,導致CPU,GPU無法在指定時間內完成指定的工作,那麼就會出現卡頓現象,也就是丟幀。

60fps是Apple給出的最佳幀率,但是實際中我們如果能保證幀率可以穩定到30fps就能保證不會有卡頓的現象,60fps更多用在游戲上。所以如果你的應用能夠保證33.4ms繪制一次屏幕,基本上就不會卡了。

總的來說,UIView從Draw到Render的過程有如下幾步:

  • 每一個UIView都有一個layer,每一個layer都有個content,這個content指向的是一塊緩存,叫做backing store。

  • UIView的繪制和渲染是兩個過程,當UIView被繪制時,CPU執行drawRect,通過context將數據寫入backing store。

  • 當backing store寫完後,通過render server交給GPU去渲染,將backing store中的bitmap數據顯示在屏幕上。

下圖就是從CPU到GPU的過程

3.jpeg

pic_5.jpeg

其實說到底CPU就是做繪制的操作把內容放到緩存裡,GPU負責從緩存裡讀取數據然後渲染到屏幕上。

就如同下圖的所示

4.jpeg

pic_4.jpeg

整個過程也就是一件事:CPU將准備好的bitmap放到RAM裡,GPU去搬這快內存到VRAM中處理。
而這個過程GPU所能承受的極限大概在16.7ms完成一幀的處理,所以最開始提到的60fps其實就是GPU能處理的最高頻率。

因此,GPU的挑戰有兩個:

  • 將數據從RAM搬到VRAM中

  • 將Texture渲染到屏幕上

這兩個中瓶頸基本在第二點上。渲染Texture基本要處理這麼幾個問題:

合成(Compositing):

Compositing是指將多個紋理拼到一起的過程,對應UIKit,是指處理多個view合到一起的情況(drawRect只有當addsubview情況下才會觸發)

[self.view addsubview:subview]

如果view之間沒有疊加,那麼GPU只需要做普通渲染即可。 如果多個view之間有疊加部分,GPU需要做blending。

尺寸(Size):

這個問題,主要是處理image帶來的,假如內存裡有一張400x400的圖片,要放到100x100的imageview裡,如果不做任何處理,直接丟進去,問題就大了,這意味著,GPU需要對大圖進行縮放到小的區域顯示,需要做像素點的sampling,這種smapling的代價很高,又需要兼顧pixel alignment。計算量會飙升。

離屏渲染(Offscreen Rendering And Mask):

我們來看一下關於iOS中圖形繪制框架的大致結構

5.jpeg

pic_3.jpeg

UIKit是iOS中用來管理用戶圖形交互的框架,但是UIKit本身構建在CoreAnimation框架之上,CoreAnimation分成了兩部分OpenGL ES和Core Graphics,OpenGL ES是直接調用底層的GPU進行渲染;Core Graphics是一個基於CPU的繪制引擎;

我們平時所說的硬件加速其實都是指OpenGL,Core Animation/UIKit基於GPU之上對計算機圖形合成以及繪制的實現,由於CPU是渲染能力要低於GPU,所以當采用CPU繪制時動畫時會有明顯的卡頓。

但是其中的有些繪制會產生離屏渲染,額外增加GPU以及CPU的繪制渲染。

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

  • On-Screen Rendering即當前屏幕渲染,指的是GPU的渲染操作是在當前用於顯示的屏幕緩沖區中進行。

  • Off-Screen Rendering即離屏渲染,指的是GPU在當前屏幕緩沖區以外新開辟一個緩沖區進行渲染操作。

離屏渲染的代價主要包括兩方面內容:

  • 創建新的緩沖區

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

為什麼需要離屏渲染?

目的在於當使用圓角,陰影,遮罩的時候,圖層屬性的混合體被指定為在未預合成之前不能直接在屏幕中繪制,即當主屏的還沒有繪制好的時候,所以就需要屏幕外渲染,最後當主屏已經繪制完成的時候,再將離屏的內容轉移至主屏上。

離屏渲染的觸發方式:

  • shouldRasterize(光柵化)

  • masks(遮罩)

  • shadows(陰影)

  • edge antialiasing(抗鋸齒)

  • group opacity(不透明)

上述的一些屬性設置都會產生離屏渲染的問題,大大降低GPU的渲染性能。

CPU渲染:

以上所說的都是離屏渲染發生在OpenGL SE也就是GPU中,但是CPU也會發生特殊的渲染,我們的CPU渲染,也就是我們使用Core Graphics的時候,但是要注意的一點的是只有在我們重寫了drawRect方法,並且使用任何Core Graphics的技術進行了繪制操作,就涉及到了CPU渲染。整個渲染過程由CPU在App內 同步地 完成,渲染得到的bitmap最後再交由GPU用於顯示。

理論上CPU渲染應該不算是標准意義上的離屏渲染,但是由於CPU自身做渲染的性能也不好,所以這種方式也是需要盡量避免的。

分析

所以對於當屏渲染,離屏渲染和CPU渲染的來說,當屏渲染永遠是最好的選擇,但是考慮到GPU的浮點運算能力要比CPU強,但是由於離屏渲染需要重新開辟緩沖區以及屏幕的上下文切換,所以在離屏渲染和CPU渲染的性能比較上需要根據實際情況作出選擇。


總結

其實第一部分的實現當時並沒有太多考慮性能上的一些問題,所以具體繪圖性能方面的優化,我會在下次的文章中闡述,也是我們App中實際遇到的一些情況以及對應的解決方案。

引用

http://vizlabxt.github.io/blog/2012/10/22/UIView-Rendering/(理解UIView的繪制)



文章轉自 北辰明的簡書
  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved