1肮帐、圖像顯示原理
2咖驮、UI卡頓、掉幀
3训枢、異步繪制
4托修、離屏渲染
圖像顯示原理
圖像的顯示實際上要經(jīng)過CPU的布局、計算恒界,GPU的繪制渲染等操作才能顯示到屏幕上睦刃。
CPU GPU 這兩個硬件實際上是是由總線連接起來的, CPU 中輸出的往往是一個位圖(像素數(shù)組) 在經(jīng)由總線上傳給GPU十酣, GPU在拿到這個位圖時會做相應位圖的渲染 紋理的合成涩拙, 然后把結(jié)果放入幀緩沖區(qū)中, 由視頻控制器提取對應顯示內(nèi)容交給顯示器顯示耸采。
- layout:UI布局frame兴泥,文本計算label的size計算等。
- display:繪制過程drawRect方法虾宇。
- prepare:圖片解碼搓彻。
- commit:提交位圖。
UI卡頓嘱朽、掉幀
一般來說旭贬,頁面滑動的流暢性是60FPS,代表著每秒鐘有六十幀畫面更新搪泳,也就是每一幀畫面的生成時間在1/60秒即16.7ms以內(nèi)就不會卡頓稀轨。在16.7毫秒內(nèi)需要CPU和GPU共同來生成一幀的數(shù)據(jù)。如果時間超過了16.7ms就會產(chǎn)生卡頓森书。
掉幀原因:
在 VSync 信號到來后,系統(tǒng)圖形服務會通過 CADisplayLink 等機制通知 App凛膏,App 主線程開始在 CPU 中計算顯示內(nèi)容杨名,比如視圖的創(chuàng)建、布局計算猖毫、圖片解碼台谍、文本繪制等。隨后 CPU 會將計算好的位圖提交到 GPU 去吁断,由 GPU拿到位圖后 進行變換趁蕊、合成、渲染仔役。隨后 GPU 會把渲染結(jié)果提交到幀緩沖區(qū)去掷伙,等待下一次 VSync 信號到來時顯示到屏幕上。由于垂直同步的機制又兵,如果在一個 VSync 時間內(nèi)任柜,CPU 或者 GPU 沒有完成內(nèi)容提交,則那一幀就會被丟棄沛厨,等待下一次機會再顯示宙地,而這時顯示屏會保留之前的內(nèi)容不變。這就是界面卡頓的原因逆皮。
從上面的圖中可以看到宅粥,CPU 和 GPU 不論哪個阻礙了顯示流程,都會造成掉幀現(xiàn)象电谣。所以開發(fā)時秽梅,也需要分別對 CPU 和 GPU 壓力進行評估和優(yōu)化。
滑動優(yōu)化
CPU (放到子線程操作)
對象的創(chuàng)建辰企、調(diào)整风纠、銷毀
預排版(布局計算、文本計算)
預渲染(文本等異步繪制牢贸,圖片編解碼等)
GPU
紋理渲染(避免離屏渲染济榨,同時可以依托CPU的異步繪制 減輕 GPU的壓力)
視圖混合(減少視圖層級)
UI 視圖繪制原理及異步繪制
通過這個繪制流程圖我們來看UIView的繪制過程:
當調(diào)用[UIView setNeedsDisplay]時赴涵,實際上并沒有立馬進行當前視圖的繪制工作,而是在之后的某一時間才進行當前視圖的實際繪制工作。
當調(diào)用[UIView setNeedsDisplay]之后从橘,系統(tǒng)會立馬調(diào)用layer的同名方法setNeedsDisplay,之后相當于在layer上打上一個臟標記指孤,然后當當前的runloop將要結(jié)束時狈蚤,調(diào)用[CALayer display]進行當前視圖的真正繪制流程。
[CALayer display]內(nèi)部會先判斷這個layer的delegate是否會響應displayLayer:方法整陌,如果不響應就會進入系統(tǒng)繪制流程中拗窃。如果能夠響應瞎领,實際上是提供了異步繪制的入口,也就是給我們進行異步繪制留有余地随夸。
-
系統(tǒng)繪制流程
在drawRectangle:方法當中九默,可以通過上下文堆棧當中取出棧頂?shù)腸ontext,拿到的就是當前控件或者視圖的上下文或者backingStore宾毒,然后layer會判斷它是否有delegate驼修,如果沒有的話,會調(diào)用drawInContext诈铛;如果有的話乙各,會調(diào)用layer的代理方法drawLayer: inContext:,然后做當前視圖的繪制工作幢竹,這一部分發(fā)生在系統(tǒng)內(nèi)部耳峦,然后在一個合適的時機給我們一個回調(diào)方法,即[UIView drawRect:]方法妨退,這個方法默認什么都不做妇萄,而是給我們開個口子,是允許在系統(tǒng)繪制的基礎(chǔ)上再做一些其他的繪制操作咬荷。
無論是哪種分支冠句,之中都是由CALayer上傳到對應backingStore到GPU,這里的backingStore可以理解為最終的位圖幸乒,之后就結(jié)束了系統(tǒng)默認的繪制流程懦底。
系統(tǒng)繪制.png
- 異步繪制流程
實際上就是基于系統(tǒng)開的口子,layer的delegate罕扎,如果遵從了displayLayer方法聚唐,或者說實現(xiàn)了這個方法的話,我們就可以進入到異步繪制的流程當中腔召,在異步繪制當中就需要代理去負責生成對應的bitmap杆查,同時需要我們把位圖作為layer的content屬性提交到layer的content屬性當中。
通過實現(xiàn)layer的代理方法displayLayer
- 代理負責生成對應的bitmap
- 設(shè)置該bitmap作為layer.contents屬性的值
左側(cè)是一個主隊列臀蛛,右側(cè)是一個全局并發(fā)隊列亲桦,假如在某一時機調(diào)用了setNeedsDisplay,在當前runloop將要結(jié)束的時候會由系統(tǒng)調(diào)用視圖所對應layer的display方法浊仆,然后如果代理實現(xiàn)了displayLayer函數(shù)的時候客峭,會調(diào)用代理的displayerLayer函數(shù)方法,然后會通過子線程的切換抡柿,在子線程當中去做位圖的繪制舔琅,此時主線程可以做一些其他的工作。
在全局并發(fā)隊列子線程當中所做的工作主要有這么幾個步驟洲劣,第一個是通過CGBitmapContextCreate() 來創(chuàng)建位圖的上下文备蚓,然后通過CoreGraphic的相關(guān)API做當前UI的一些繪制工作课蔬,之后我們再通過CoreGraphic的相關(guān)函數(shù)CGBitmapContextCreateImage來根據(jù)當前所繪制的上下文生成一張CGImage圖片,再回到主隊列當中郊尝,提交這個位圖购笆,設(shè)置給 CALayer 的content屬性,這樣的話就完成了一個UI控件的異步繪制過程虚循。
離屏渲染
什么是離屏渲染?你對離屏渲染是怎么理解的样傍?
- On-Screen Rendering
意為當前屏幕渲染横缔,指的是GPU的渲染操作是在當前用于顯示的屏幕緩沖區(qū)中進行 - Off-Screen Rendering
意為離屏渲染,指的是GPU在當前屏幕緩沖區(qū)以外新開辟一個緩沖區(qū)進行渲染操作
用一個比較通俗的語言解釋離屏渲染:也就是當我們設(shè)置某一些UI視圖的圖層屬性衫哥,如果說指定成在被未預合成之前茎刚,不能用于直接被顯示的時候,那么就觸發(fā)了離屏渲染撤逢。典型的比如我們設(shè)置視圖的圓角屬性膛锭,包括一些蒙層遮罩。
什么場景下會觸發(fā)離屏渲染蚊荣?
圓角(和maskToBounds一起使用時)
圖層蒙版
陰影
光柵化
- 為何要避免離屏渲染初狰?
CPU和GPU在進行具體的視圖繪制渲染的過程中做了大量的工作。而離屏渲染是發(fā)生在GPU層面的互例,由于離屏渲染使GPU層面上觸發(fā)了OpenGL的多通道渲染管線奢入,產(chǎn)生了額外的開銷,所以需要避免離屏渲染媳叨。
- 為何要避免離屏渲染初狰?
在觸發(fā)離屏渲染時會增加GPU的工作量腥光,而增加GPU的工作量很有可能導致CPU和GPU工作耗時超出16.7ms,可能導致UI的卡頓和掉幀糊秆,所以需要避免離屏渲染武福。
- 創(chuàng)建新的渲染緩沖區(qū)
- 上下文切換