一、概念
屏幕顯示圖像的原理:
顯示器如何顯示圖像,需要顯示的圖像經(jīng)過 CRT 電子槍以極快的速度一行一行的掃描,掃描出來就呈現(xiàn)出了一幀畫面,隨后電子槍又回到初始位置循環(huán)掃描,形成了我們看到的圖片或視頻。
為了讓顯示器的顯示與視頻控制器同步,顯示器會用硬件時鐘產(chǎn)生一系列的定時信號。當電子槍換到新的一行時,準備進行掃描時,顯示器會發(fā)出一個水平同步信號,簡稱 HSync,而當一幀畫面繪制完成后,電子槍回復(fù)到原位,準備畫下一幀前,顯示器會發(fā)出一個垂直同步信號,簡稱 VSync。顯示器通常以固定頻率進行刷新,這個刷新頻率就是 VSync 信號產(chǎn)生的頻率。
GPU 屏幕渲染有兩種方式:
(1)On-Screen Rendering(當期屏幕渲染)
指的是 GPU 的渲染操作是在當前用于顯示的屏幕緩沖區(qū)進行。
(2)Off-Screen Rendering(離屏渲染)
指的是 GPU 在當前屏幕緩沖區(qū)以外開辟一個緩沖區(qū)進行渲染操作。
當前屏幕渲染不需要額外創(chuàng)建新的緩存,也不需要開啟新的上下文,相對于離屏渲染性能更好。但是受當前屏幕渲染的局限因素限制(只有自身上下文、屏幕緩存有限等),當前屏幕渲染有些情況下的渲染解決不了的,就使用離屏渲染。
相比于當前屏幕渲染,離屏渲染的代價是很高的,主要體現(xiàn)在兩個方面:
(1)創(chuàng)建新緩沖區(qū)
要想進行離屏渲染,首先要創(chuàng)建一個新的緩沖區(qū)。
(2)上下文切換
離屏渲染的整個過程,需要多次切換上下文環(huán)境:先是從當前屏幕(On-Screen)切換到離屏(Off-Screen),等離屏渲染結(jié)束以后,將離屏緩沖區(qū)的渲染結(jié)果顯示到屏幕上又需要將上下文環(huán)境從離屏切換到當前屏幕。而上下文環(huán)境的切換時要付出很大代價的。
由于垂直同步的機制,如果在一個 VSync 時間內(nèi),CPU 或者 GPU 沒有完成內(nèi)容提交,則那一幀就會被丟棄,等待下一次機會再顯示,而這時顯示屏會保留之前的內(nèi)容不變。這就是界面卡頓的原因。
既然離屏渲染這么耗性能,為什么有這套機制呢?
有些效果被認為不能直接呈現(xiàn)于屏幕,而需要在別的地方做額外的處理預(yù)合成。圖層屬性的混合體沒有預(yù)合成之前不能直接在屏幕中繪制,所以就需要屏幕外渲染。屏幕外渲染并不意味著軟件繪制,但是它意味著圖層必須在被顯示之前在一個屏幕外上下文中被渲染(不論是 CPU 還是 GPU )。
二、UIView 與 CALayer 關(guān)系
UIView 繼承自 UIResponder ,可以處理系統(tǒng)傳遞過來的事件,如:UIApplication、UIViewcontroller、UIView 以及所有從 UIView 派生出來的 UIKit 類。每個 UIView 內(nèi)部都有一個 CALayer 提供內(nèi)容的繪制和顯示,并且作為內(nèi)部 RootLayer 的代理試圖。
CALayer 繼承自 NSObject 類,負責顯示 UIView 提供的內(nèi)容 contents。CALayer 有三個視覺元素:背景色、內(nèi)容和邊框,其中,內(nèi)容的本質(zhì)是一個 CGImage。
下圖為 CALayer 的結(jié)構(gòu)圖:

界面渲染過程:
RunLoop 有一個60fps的回調(diào),即每16.7ms繪制一次屏幕,所以 view 的繪制必須在這個時間內(nèi)完成,view 內(nèi)容的繪制是 CPU 工作,然后把繪制的內(nèi)容交給 GPU 渲染,包括多個 View 的拼接(compositing)、紋理的渲染(Textture)等,最后顯示在屏幕上。但是,如果無法是16.7ms內(nèi)完成繪制,就會出現(xiàn)丟幀的問題,一般情況下,如果幀率保證在30fps以上,界面卡頓不明顯,那么就需要在33.4ms內(nèi)完成 View 的繪制,而低于這個幀率,就會產(chǎn)生卡頓的效果,影響體驗。
渲染的過程如下:
-UIView 的 Layer 層有一個 Contents,指向一塊緩存,即 backing store
-UIView 繪制時,會調(diào)用 drawRect 方法,通過 context 將數(shù)據(jù)寫入 backing store
-在 backing store 寫完后,通過 render server 交給 GPU 去渲染,將 backing store 中的 bitmap 數(shù)據(jù)顯示在屏幕上

下面的情況會引發(fā)離屏渲染:
-為圖層設(shè)置遮罩(layer.mask)
-將圖層的 layer.masksToBounds/views.clipToBounds 屬性設(shè)置為 true
-將圖層 layer.allowsGroupOpacity 屬性設(shè)置為 YES 和 layer.opacity 小于1.0
-為圖層設(shè)置陰影 (layer.shadow *)
-為圖層設(shè)置 layer.shouldRasterize=true
-具有 layer.cornerRadius, ?layer.edgeAntialiasingMask, ? layer,allowsEdgeAntialiasing的圖層
-文本(任何種類,包括 UILabel, CATextLayer, Core Text 等)
-使用 CGContext 在 drawRect:方法中繪制大部分情況下會導(dǎo)致離屏渲染,甚至僅僅是一個空的實現(xiàn)
參考文章:
http://www.itdecent.cn/p/cff0d1b3c915
https://www.cnblogs.com/fishbay/p/7576176.html
http://www.itdecent.cn/p/15b8b1844e9c