一嵌屎、首先讓我們什么是離屏渲染
離屏渲染就是在當前屏幕緩沖區(qū)以外挠将,新開辟一個緩沖區(qū)進行操作胳岂。
離屏渲染出發(fā)的場景有以下:
- 圓角 (
maskToBounds
并用才會觸發(fā),contents
內(nèi)有內(nèi)容) - 圖層蒙版
- 陰影
- 光柵化
為什么要有離屏渲染?
大家高中物理應(yīng)該學過顯示器是如何顯示圖像的:需要顯示的圖像經(jīng)過CRT電子槍以極快的速度一行一行的掃描舔稀,掃描出來就呈現(xiàn)了一幀畫面乳丰,隨后電子槍又會回到初始位置循環(huán)掃描,形成了我們看到的圖片或視頻内贮。
為了讓顯示器的顯示跟視頻控制器同步产园,當電子槍新掃描一行的時候,準備掃描的時發(fā)送一個水平同步信號(HSync信號)夜郁,顯示器的刷新頻率就是HSync信號產(chǎn)生的頻率什燕。然后CPU計算好frame等屬性,將計算好的內(nèi)容交給GPU去渲染竞端,GPU渲染好之后就會放入幀緩沖區(qū)屎即。然后視頻控制器會按照HSync信號逐行讀取幀緩沖區(qū)的數(shù)據(jù),經(jīng)過可能的數(shù)模轉(zhuǎn)換傳遞給顯示器事富,就顯示出來了技俐。具體的大家自行查找資料或詢問相關(guān)專業(yè)人士,這里只參考網(wǎng)上資料做一個簡單的描述统台。
離屏渲染的代價很高雕擂,想要進行離屏渲染,首選要創(chuàng)建一個新的緩沖區(qū)贱勃,屏幕渲染會有一個上下文環(huán)境的一個概念井赌,離屏渲染的整個過程需要切換上下文環(huán)境,先從當前屏幕切換到離屏贵扰,等結(jié)束后仇穗,又要將上下文環(huán)境切換回來。這也是為什么會消耗性能的原因了戚绕。
由于垂直同步的機制仪缸,如果在一個 HSync 時間內(nèi),CPU 或者 GPU 沒有完成內(nèi)容提交列肢,則那一幀就會被丟棄,等待下一次機會再顯示宾茂,而這時顯示屏會保留之前的內(nèi)容不變瓷马。這就是界面卡頓的原因。
為什么要避免離屏渲染跨晴?
CPU GPU 在繪制渲染視圖時做了大量的工作欧聘。離屏渲染發(fā)生在 GPU 層面上,會創(chuàng)建新的渲染緩沖區(qū)端盆,會觸發(fā) OpenGL 的多通道渲染管線怀骤,圖形上下文的切換會造成額外的開銷费封,增加 GPU 工作量。如果 CPU GPU 累計耗時 16.67 毫秒還沒有完成蒋伦,就會造成卡頓掉幀弓摘。
圓角屬性、蒙層遮罩 都會觸發(fā)離屏渲染痕届。指定了以上屬性韧献,標記了它在新的圖形上下文中,在未愈合之前研叫,不可以用于顯示的時候就出發(fā)了離屏渲染锤窑。
二、屏幕上最終顯示的數(shù)據(jù)有兩種加載流程
- On-Screen Rendering:當前屏幕渲染嚷炉,在當前用于顯示的屏幕緩沖區(qū)進行渲染操作
- Off-Screen Rendering:離屏渲染渊啰,在當前屏幕緩沖區(qū)以外新開辟一個緩沖區(qū)進行渲染操作
2.1 首先來講講正常渲染流程On-Screen Rendering
APP中的數(shù)據(jù)經(jīng)過CPU計算和GPU渲染后,將結(jié)果存放在幀緩沖區(qū)申屹,利用視頻控制器從幀緩沖區(qū)中取出绘证,并顯示到屏幕上。
- 在GPU的渲染流程中独柑,顯示到屏幕上的圖像是遵循大畫家算法按照由遠及近的順序迈窟,依次將結(jié)果存儲到幀緩沖區(qū)
- 視屏控制器從幀緩沖區(qū)中讀取一幀數(shù)據(jù),將其顯示到屏幕上后忌栅,會立即丟棄這幀數(shù)據(jù)车酣,不會做任何保留,這樣做的目的是可以節(jié)省空間索绪,且在屏幕上是各自顯示各自的湖员,互相不影響。
2.2 離屏渲染流程Off-Screen Rendering
當App需要進行額外的渲染和合并時瑞驱,例如按鈕設(shè)置圓角娘摔,我們是需要對UIButton這個控件中的所有圖層都進行圓角+裁剪,然后再將合并后的結(jié)果存入幀緩存區(qū)唤反,再從幀緩存中取出交由屏幕顯示凳寺,這時,在正常的渲染流程中彤侍,我們是無法做到對所有圖層進行圓角裁剪的肠缨,因為它是用一個丟一個。所以我們需要提前將處理好的結(jié)果放入離屏緩沖區(qū)盏阶,最后將幾個圖層進行疊加合并晒奕,存放到站緩沖區(qū),最后屏幕上就是我們想實現(xiàn)的效果。
說白了脑慧,離屏緩存區(qū)就是一個臨時的緩沖區(qū)魄眉,用來存放在后續(xù)操作使用,但目前并不使用的數(shù)據(jù)闷袒。
- 離屏渲染再給我們帶來方便的同時坑律,也帶來了嚴重的性能問題。由于離屏渲染中的離屏緩沖區(qū)霜运,是額外開辟的一個存儲空間脾歇,當它將數(shù)據(jù)轉(zhuǎn)存到Frame Buffer時,也是需要耗費時間的淘捡,所以在轉(zhuǎn)存的過程中藕各,仍有掉幀的可能。
- 離屏緩沖區(qū)的空間并不是無限大的焦除, 它是又上限的激况,最大只能是屏幕的2.5倍
2.2.1 離屏渲染消耗性能的原因
需要創(chuàng)建新的緩沖區(qū)
離屏渲染的整個過程,需要多次切換上下文環(huán)境膘魄,先是從當前屏幕(On-Screen)切換到離屏(Off-Screen)乌逐;等到離屏渲染結(jié)束以后,將離屏緩沖區(qū)的渲染結(jié)果顯示到屏幕上创葡,又需要將上下文環(huán)境從離屏切換到當前屏幕
2.2.2 哪些操作會觸發(fā)離屏渲染浙踢?
- 光柵化,layer.shouldRasterize = YES
- 遮罩灿渴,layer.mask
- 圓角洛波,同時設(shè)置 layer.masksToBounds = YES、layer.cornerRadius大于0
- 考慮通過 CoreGraphics 繪制裁剪圓角骚露,或者叫美工提供圓角圖片
- 陰影蹬挤,layer.shadowXXX,如果設(shè)置了 layer.shadowPath 就不會產(chǎn)生離屏渲染
2.2.3 那為什么我們明知有性能問題時棘幸,還是要使用離屏渲染呢焰扳?
- 可以處理一些特殊的效果,這種效果并不能一次就完成误续,需要使用離屏緩沖區(qū)來保存中間狀態(tài)吨悍,不得不使用離屏渲染,這種情況下的離屏渲染是系統(tǒng)自動觸發(fā)的蹋嵌,例如經(jīng)常使用的圓角育瓜、陰影、高斯模糊欣尼、光柵化等
- 可以提升渲染的效率,如果一個效果是多次實現(xiàn)的,可以提前渲染愕鼓,保存到離屏緩沖區(qū)钙态,以達到復用的目的。這種情況是需要開發(fā)者手動觸發(fā)的菇晃。
離屏渲染的另一個原因:光柵化
When the value of this property is YES, the layer is rendered as a bitmap in its local coordinate space and then composited to the destination with any other content.
當我們開啟光柵化時册倒,會將layer渲染成位圖保存在緩存中,這樣在下次使用時磺送,就可以直接復用驻子,提高效率。
針對光柵化的使用估灿,有以下幾個建議:
- 如果layer不能被復用崇呵,則沒有必要開啟光柵化
- 如果layer不是靜態(tài),需要被頻繁修改(例如動畫過程中)馅袁,此時開啟光柵化反而影響效率
- 離屏渲染緩存內(nèi)容有時間限制域慷,如果100ms內(nèi)沒有被使用,那么就會丟棄汗销,無法進行復用
- 離屏渲染的緩存空間有限犹褒,是屏幕的2.5倍,超過2.5倍屏幕像素大小的話也會失效弛针,無法實現(xiàn)復用
三叠骑、圓角中離屏渲染的觸發(fā)時機
在講圓角之前,首先說明下CALayer的構(gòu)成削茁,如圖所示宙枷,它是backgrodColor
、contents
付材、borderWidth&borderColor
構(gòu)成的朦拖。跟我們即將解釋的圓角觸發(fā)離屏渲染息息相關(guān)。
首先我們先開啟離屏渲染的檢測厌衔,在模擬器打開color offscreen-rendered,開啟后會把那些需要離屏渲染的圖層高亮成黃色璧帝,這就意味著黃色圖層可能存在性能問題。
我們寫下如下代碼:
let btn = UIButton(type: .custom)
btn.frame = CGRect(x: 100, y: 200, width: 100, height: 100)
//設(shè)置圓角
btn.layer.cornerRadius = 50
//設(shè)置border寬度和顏色
btn.layer.borderWidth = 2
btn.layer.borderColor = UIColor.red.cgColor
self.view.addSubview(btn)
//設(shè)置背景圖片
btn.setImage(UIImage(named: "update_header"), for: .normal)
此時我們發(fā)現(xiàn)圖片并沒有變成圓形還是一個正方形
針對上面的這個問題富寿,我相信99%的人都能信手拈來睬隶,知道必須要設(shè)置
masksToBounds
為 true
,才會得到我們想要的效果页徐。解決的方法很簡單苏潜,但原理是大部人都沒有去仔細研究的。
其實蘋果的官方文檔有告訴我們,設(shè)置cornerRadius
只會對CALayer
中的backgroundColor
和 boder
設(shè)置圓角觉既,不會設(shè)置contents
的圓角,如果contents
需要設(shè)置圓角栋艳,需要同時將maskToBounds / clipsToBounds
設(shè)置為true
飞袋。
所以我們理解為圓角不生效的根本原因是沒有對contents設(shè)置圓角戳气,而按鈕設(shè)置的image是放在contents里面的,所以看到的界面上的就是image沒有進行圓角裁剪巧鸭。
然而當我們加上maskToBounds / clipsToBounds
修改為true
時瓶您,會出現(xiàn)以下情況,說明此時觸發(fā)了離屏渲染
那為啥會觸發(fā)離屏渲染呢纲仍?
是因為圓角的設(shè)置是需要對所有l(wèi)ayer都進行裁剪的呀袱,而maskToBounds裁剪是應(yīng)用到所有l(wèi)ayer上的。如果從正常渲染的角度來說郑叠,一個個layer是用完即扔的夜赵。而現(xiàn)在我們的圓角設(shè)置需要3個layer疊加合并的,所以將先處理好的layer保存在離屏緩沖區(qū)锻拘,等到最后一個layer處理完油吭,合并進行圓角+裁剪,所以才會觸發(fā)離屏渲染
所以我們可以做以下總結(jié)
- 當只設(shè)置
backgroundColor
署拟、borde
婉宰,而contents
中沒有子視圖時,無論maskToBounds / clipsToBounds
是true
還是false
推穷,都不會觸發(fā)離屏渲染 - 當
contents
中有子視圖時心包,此時設(shè)置cornerRadius+maskToBounds / clipsToBounds
,就會觸發(fā)離屏渲染