實(shí)現(xiàn)DrawRect 為什么消耗性能

對于這個問題我困惑了好長時間,一直想不清楚為什么,對于網(wǎng)上所說的 drawRect是基于CPU 和DrawRect 調(diào)用感覺不是很理解:

現(xiàn)在我簡單的說下我的理解:

首先看一下CPU和GPU是如何工作的:

布局- 這是準(zhǔn)備你的視圖/圖層的層級關(guān)系,以及設(shè)置圖層屬性(位置,背景色,邊框等等)的階段。

顯示- 這是圖層的寄宿圖片被繪制的階段。繪制有可能涉及你的-drawRect:和-drawLayer:inContext:方法的調(diào)用路徑。

準(zhǔn)備- 這是Core Animation準(zhǔn)備發(fā)送動畫數(shù)據(jù)到渲染服務(wù)的階段。這同時也是Core Animation將要執(zhí)行一些別的事務(wù)例如解碼動畫過程中將要顯示的圖片的時間點(diǎn)。

提交- 這是最后的階段,Core Animation打包所有圖層和動畫屬性,然后通過IPC(內(nèi)部處理通信)發(fā)送到渲染服務(wù)進(jìn)行顯示。

但是這些僅僅階段僅僅發(fā)生在你的應(yīng)用程序之內(nèi),在動畫在屏幕上顯示之前仍然有更多的工作。一旦打包的圖層和動畫到達(dá)渲染服務(wù)進(jìn)程,他們會被反序列化來形成另一個叫做渲染樹的圖層樹(在第一章“圖層樹”中提到過)。使用這個樹狀結(jié)構(gòu),渲染服務(wù)對動畫的每一幀做出如下工作:

對所有的圖層屬性計(jì)算中間值,設(shè)置OpenGL幾何形狀(紋理化的三角形)來執(zhí)行渲染

在屏幕上渲染可見的三角形

所以一共有六個階段;最后兩個階段在動畫過程中不停地重復(fù)。前五個階段都在軟件層面處理(通過CPU),只有最后一個被GPU執(zhí)行。而且,你真正只能控制前兩個階段:布局和顯示。Core Animation框架在內(nèi)部處理剩下的事務(wù),你也控制不了它。

但是在布局和顯示階段我可以決定哪些事情交給CPU去做,哪些事情交給GPU去做(下面這些是從網(wǎng)上找的,可以了解下CPU和GPU的工作原理)

GPU相關(guān)的操作

GPU為一個具體的任務(wù)做了優(yōu)化:它用來采集圖片和形狀(三角形),運(yùn)行變換,應(yīng)用紋理和混合然后把它們輸送到屏幕上?,F(xiàn)代iOS設(shè)備上可編程的GPU在這些操作的執(zhí)行上又很大的靈活性,但是Core

Animation并沒有暴露出直接的接口。除非你想繞開Core

Animation并編寫你自己的OpenGL著色器,從根本上解決硬件加速的問題,那么剩下的所有都還是需要在CPU的軟件層面上完成。

寬泛的說,大多數(shù)CALayer的屬性都是用GPU來繪制。比如如果你設(shè)置圖層背景或者邊框的顏色,那么這些可以通過著色的三角板實(shí)時繪制出來。如果對一個contents屬性設(shè)置一張圖片,然后裁剪它 - 它就會被紋理的三角形繪制出來,而不需要軟件層面做任何繪制。

但是有一些事情會降低(基于GPU)圖層繪制,比如:

太多的幾何結(jié)構(gòu) -

這發(fā)生在需要太多的三角板來做變換,以應(yīng)對處理器的柵格化的時候?,F(xiàn)代iOS設(shè)備的圖形芯片可以處理幾百萬個三角板,所以在Core

Animation中幾何結(jié)構(gòu)并不是GPU的瓶頸所在。但由于圖層在顯示之前通過IPC發(fā)送到渲染服務(wù)器的時候(圖層實(shí)際上是由很多小物體組成的特別重量級的對象),太多的圖層就會引起CPU的瓶頸。這就限制了一次展示的圖層個數(shù)(見本章后續(xù)“CPU相關(guān)操作”)。

重繪 - 主要由重疊的半透明圖層引起。GPU的填充比率(用顏色填充像素的比率)是有限的,所以需要避免重繪(每一幀用相同的像素填充多次)的發(fā)生。在現(xiàn)代iOS設(shè)備上,GPU都會應(yīng)對重繪;即使是iPhone 3GS都可以處理高達(dá)2.5的重繪比率,并任然保持60幀率的渲染(這意味著你可以繪制一個半的整屏的冗余信息,而不影響性能),并且新設(shè)備可以處理更多。

離屏繪制 -

這發(fā)生在當(dāng)不能直接在屏幕上繪制,并且必須繪制到離屏圖片的上下文中的時候。離屏繪制發(fā)生在基于CPU或者是GPU的渲染,或者是為離屏圖片分配額外內(nèi)存,以及切換繪制上下文,這些都會降低GPU性能。對于特定圖層效果的使用,比如圓角,圖層遮罩,陰影或者是圖層光柵化都會強(qiáng)制Core

Animation提前渲染圖層的離屏繪制。但這不意味著你需要避免使用這些效果,只是要明白這會帶來性能的負(fù)面影響。

過大的圖片 - 如果視圖繪制超出GPU支持的2048x2048或者4096x4096尺寸的紋理,就必須要用CPU在圖層每次顯示之前對圖片預(yù)處理,同樣也會降低性能。

CPU相關(guān)的操作

大多數(shù)工作在Core Animation的CPU都發(fā)生在動畫開始之前。這意味著它不會影響到幀率,所以很好,但是他會延遲動畫開始的時間,讓你的界面看起來會比較遲鈍。

以下CPU的操作都會延遲動畫的開始時間:

布局計(jì)算 - 如果你的視圖層級過于復(fù)雜,當(dāng)視圖呈現(xiàn)或者修改的時候,計(jì)算圖層幀率就會消耗一部分時間。特別是使用iOS6的自動布局機(jī)制尤為明顯,它應(yīng)該是比老版的自動調(diào)整邏輯加強(qiáng)了CPU的工作。

視圖懶加載 -

iOS只會當(dāng)視圖控制器的視圖顯示到屏幕上時才會加載它。這對內(nèi)存使用和程序啟動時間很有好處,但是當(dāng)呈現(xiàn)到屏幕上之前,按下按鈕導(dǎo)致的許多工作都會不能被及時響應(yīng)。比如控制器從數(shù)據(jù)庫中獲取數(shù)據(jù),或者視圖從一個nib文件中加載,或者涉及IO的圖片顯示,都會比CPU正常操作慢得多。

Core Graphics繪制 - 如果對視圖實(shí)現(xiàn)了-drawRect:方法,或者CALayerDelegate的-drawLayer:inContext:方法,那么在繪制任何東西之前都會產(chǎn)生一個巨大的性能開銷。為了支持對圖層內(nèi)容的任意繪制,Core Animation必須創(chuàng)建一個內(nèi)存中等大小的寄宿圖片。然后一旦繪制結(jié)束之后,必須把圖片數(shù)據(jù)通過IPC傳到渲染服務(wù)器。在此基礎(chǔ)上,Core Graphics繪制就會變得十分緩慢,所以在一個對性能十分挑剔的場景下這樣做十分不好。

解壓圖片 - PNG或者JPEG壓縮之后的圖片文件會比同質(zhì)量的位圖小得多。但是在圖片繪制到屏幕上之前,必須把它擴(kuò)展成完整的未解壓的尺寸(通常等同于圖片寬 x 長 x 4個字節(jié))。為了節(jié)省內(nèi)存,iOS通常直到真正繪制的時候才去解碼圖片(14章“圖片IO”會更詳細(xì)討論)。根據(jù)你加載圖片的方式,第一次對圖層內(nèi)容賦值的時候(直接或者間接使用UIImageView)或者把它繪制到Core Graphics中,都需要對它解壓,這樣的話,對于一個較大的圖片,都會占用一定的時間。

當(dāng)圖層被成功打包,發(fā)送到渲染服務(wù)器之后,CPU仍然要做如下工作:為了顯示屏幕上的圖層,Core

Animation必須對渲染樹種的每個可見圖層通過OpenGL循環(huán)轉(zhuǎn)換成紋理三角板。由于GPU并不知曉Core

Animation圖層的任何結(jié)構(gòu),所以必須要由CPU做這些事情。這里CPU涉及的工作和圖層個數(shù)成正比,所以如果在你的層級關(guān)系中有太多的圖層,就會導(dǎo)致CPU沒一幀的渲染,即使這些事情不是你的應(yīng)用程序可控的

說到這里大家可能還不太理解 DrawRect ,為什么消耗性能只是告訴大家個人認(rèn)為DrawRect 消耗性能跟 繪圖是基于CPU的沒有關(guān)系,

因?yàn)槲覀冊谀承┣闆r下為了實(shí)現(xiàn)滑動過程流暢度的優(yōu)化,會使用UIGraphicsBeginImageContextWithOptions()等方法在子線程中實(shí)現(xiàn)繪圖,然后得到UIimage然后回到主線程中賦值給UIImageView(必須回到主線程,因?yàn)閁I必須在主線程中操作,uikit 不是線程安全的),這樣來實(shí)現(xiàn)異步繪圖然后減少主線程中的操作 來增加界面流暢度

所以實(shí)現(xiàn)DrawRect消耗性能跟CoreGraphics 這個框架是基于CPU沒有關(guān)系 ,因?yàn)槲覀兤綍r做優(yōu)化的時候也是用CoreGraphics 改框架,的那到底是什么使得DrawRect消耗性能呢?

界面再調(diào)用 disPlay的時候會生成一個后備存儲也就是我們所說的上下文,我們調(diào)用一些set fill 等方法實(shí)際都是把門做的一些 顏色 、線的寬度、路徑存儲到上下文,而最后我們調(diào)用去渲染 CTFrameDraw 這個方法也是比較耗時的,為了支持對圖層內(nèi)容的任意繪制,Core Animation必須創(chuàng)建一個內(nèi)存中等大小的寄宿圖片。然后一旦繪制結(jié)束之后,必須把圖片數(shù)據(jù)通過IPC傳到渲染服務(wù)器 在此基礎(chǔ)上,Core Graphics繪制就會變得十分緩慢。還有就是 這個后備存儲會被不斷的渲染到屏幕上 ,直到下次調(diào)用 setNeedsPlay. 個人感覺這個從后備存儲不斷渲染到屏幕應(yīng)該比較消耗性能。

因?yàn)槲覀兤綍r用的UIimageView ,layer圖層卻沒有申請一個后備存儲。取而代之的是使用一個 CGImageRef 作為他的內(nèi)容,并且渲染服務(wù)將會把圖片的數(shù)據(jù)繪制到幀的緩沖區(qū),比如,繪制到顯示屏。從幀緩沖區(qū),去取出然后顯示,省去了重新繪制,在這種情況下,將不會繼續(xù)重新繪制。我們只是簡單的將位圖數(shù)據(jù)以圖片的形式傳給了 UIImageView,然后 UIImageView 傳給了 Core Animation,然后輪流傳給渲染服務(wù)。(其實(shí)繪圖相當(dāng)于cpu做渲染,也叫離屏渲染,但是性能的影響,遠(yuǎn)小于GPU的離屏渲染,所以在GPU使用負(fù)荷較大的時候可以把部分計(jì)算和渲染交給cpu)

以上僅是小弟的個人理解:如有不正確的還請指出

QQ :604226033

郵箱:fyqwangye@163.com

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容