iOS系統(tǒng)中離屏渲染利與弊
閱讀需要約8分鐘
前言
性能的優(yōu)化相信是每一個APP工程師所追求的遵岩,而離屏渲染就是一個繞不開的知識點”┲簦現(xiàn)在提出幾個問題幫助大家更快的理解:
- 什么情況下會觸發(fā)離屏渲染,為什么?
- 利柴我、弊都有哪些榕茧?
-
離屏渲染是如何發(fā)生的
先通過模擬器來看看是情況下會發(fā)生離屏渲染咐蝇。
打開模擬器 - Debug - Color Off-screen Rendered開關
提起離屏渲染特姐,下意識的就是會想到cornerRadius
這個屬性,設置圓角就會導致離屏渲染浮梢,事實是這樣嗎跛十?
let view1 = UIView(frame: rect)
view1.backgroundColor = UIColor.red
view1.layer.cornerRadius = 50
view1.layer.borderWidth = 2
view.addSubview(view1)
沒有觸發(fā)離屏渲染
我們先來看看蘋果官方文檔對于cornerRadius
的描述:
Setting the radius to a value greater than 0.0 causes the layer to begin drawing rounded corners on its background. By default, the corner radius does not apply to the image in the layer’s contents property; it applies only to the background color and border of the layer. However, setting the masksToBounds property to true causes the content to be clipped to the rounded corners.
我們發(fā)現(xiàn)設置cornerRadius
大于0時,只為layer的backgroundColor
和border
設置圓角秕硝;而不會對layer的contents
設置圓角芥映,除非同時設置了layer.masksToBounds
為true或者UIView的clipsToBounds
屬性。那我們設置這兩個屬性看一下。
let view1 = UIView(frame: rect)
view1.backgroundColor = UIColor.red
view1.layer.cornerRadius = 50
view1.layer.borderWidth = 2
view1.layer.masksToBounds = true
view1.clipsToBounds = true
view.addSubview(view1)
事實上還是沒有觸發(fā)離屏渲染,這就很奇怪了奈偏。
這時候就需要了解一下幾個屬性的關系了
發(fā)現(xiàn)其實圖中3個屬性坞嘀,對應了3個圖層。我們看到的圖像其實是這3個圖層的合并霎苗,這面會涉及到一個畫家算法
姆吭,下一節(jié)會說到。原來contents是單獨的個圖層唁盏,那我們設置它看一下内狸。
let view1 = UIView(frame: rect)
view1.backgroundColor = UIColor.red
view1.layer.cornerRadius = 50
view1.layer.borderWidth = 2
view1.layer.contents = UIImage(named: s)?.cgImage
view1.layer.masksToBounds = true
view1.clipsToBounds = true
view.addSubview(view1)
在當同時設置了backgroundColor|borderWidth
,cornerRadius
,contents
,masksToBounds
這四個屬性時,導致了離屏渲染厘擂。
我們設置了content后觸發(fā)了離屏渲染昆淡,如果我們只設置content看看會不會觸發(fā)呢?
let view1 = UIView(frame: rect)
view1.layer.cornerRadius = 50
view1.layer.contents = UIImage(named: s)?.cgImage
view1.layer.masksToBounds = true
view1.clipsToBounds = true
view.addSubview(view1)
在其他設置都沒有變的情況下刽严,只設置了content并沒有觸發(fā)離屏渲染昂灵。有沒有隱約感覺到一點觸發(fā)規(guī)律:
- 設置了
cornerRadius
- 同時設置了
backgroundColor或者borderWidth
和content
- 設置了
masksToBounds
或者clipsToBounds
**
可是為什么呢?想要搞清楚就需要了解畫家算法
舞萄。
畫家算法
所謂畫家算法
是計算機將多個圖層由遠到近進行繪制的一種算法眨补。先的圖層會被后的圖層所覆蓋。
這里接涉及到計算機渲染的原理倒脓,其中非常重要的一個點就是:圖層被渲染到畫布
上之后撑螺,當前圖層就會被永久銷毀,所以面對多個圖層時從遠到近繪制崎弃,保證了可視范圍內容的完整甘晤,最后保存到幀緩存區(qū)等待讀取。
-
離屏渲染產生的原理
需要在額外的內存中完成多圖層組合繪制工作
GPU中的離屏渲染
現(xiàn)在我對上圖中增加一個圓角饲做,而上圖是由3個圖層組成的线婚,且圖層渲染到畫布后就會被銷毀,導致GPU沒辦法一次性拿到所有圖層來進行圓角切割.
所以現(xiàn)在就需要創(chuàng)建一塊空間盆均,將3個圖層都保存起來后完成圓角切割塞弊。而這個單獨的空間就叫做offSet-buffer
,離屏渲染就這樣產生了泪姨,當然這個圖層繪制一般是由GPU完成的居砖,也有些特殊情況下CPU也會參與繪制。
CPU中的“離屏渲染”
- 在CoreAnimation 渲染流程中
Display
流程的視圖層繪制中提過驴娃,如果開啟drawRect:
方法就會觸發(fā)CPU的“離屏渲染”,該方法里的所有代碼都是在CPU中進行執(zhí)行循集,知道完成bitmap唇敞,轉存到幀緩存區(qū)中。但是根據(jù)蘋果工程師的說法,CPU的渲染并不是真正意義的離屏渲染疆柔,當然通過Xcode調試也能看出來咒精,該區(qū)域并沒有被標記為黃色,說明Xcode也認為這不屬于離屏渲染旷档。
label沒有觸發(fā)xcode離屏渲染模叙,所以推測繪制文字的 layer (UILabel, CATextLayer, Core Text 等)是使用CPU來進行的渲染,說一下我這樣的猜測的理由:文字渲染更多涉及邏輯計算所以CPU更加適合做這件事鞋屈。
-
常見離屏渲染場景分析
1. cornerRadius+clipsToBounds
同時打開backgroundColor|borderWidth
,cornerRadius
,contents
,masksToBounds
這個情況前面已經分析過了范咨,屬性的打開會導致多個圖層的裁剪,所以必須在offset-buffer中完成全圖圖層裁剪后才可以放入幀緩存區(qū)中厂庇。當然還有其他方法設置圓角但不會觸發(fā)離屏渲染UIBezierPath
渠啊。
UIBezierPath會涉及到CoreGraphics
,在渲染流程
中負責圖層的繪制权旷√骝龋可知使用了UIBezierPath在每一個單圖層繪制的計算中就已經處理了每個圖層的圓角,這時畫在畫布
上的圖層就已經是圓角了拄氯,估避免了離屏渲染躲查。
2. shadow
開啟shadow后會增加一個額外的圖層,這個圖層是在最先被繪制的译柏,可是這時并不知道content的大小镣煮,所以還是沒法分開繪制,需要offSet-buffer
的支持艇纺。
也可以使用shadowPath
提前告知陰影路勁就可以避免離屏渲染怎静。
3. group opacity(組透明度)
這個很好理解,多個圖層都帶著透明度黔衡,在重疊位置會造成顏色的混合蚓聘。重疊后的顏色需要計算,而上一層已經被銷毀
了盟劫,計算機并不知道其顏色所以無法計算夜牡。這時就需要把所有圖層都存到offset-Buffer,不可避免的觸發(fā)了離屏渲染侣签。
4. mask
增加mask涉及到多圖層的重疊塘装,而且有些圖層是有透明度的,原理和group opacity類似影所。
5. shouldRasterize(光柵化)
及時離屏渲染消耗很大蹦肴,但是面對復雜圖層,好不容易繪制好了為什么不想辦法復用
它呢?這時蘋果大大就提供了shouldRasterize
這個屬性猴娩,打開這個屬性造成離屏渲染阴幌,但是渲染好的內容會被緩存起來勺阐,一定條件下就可以復用。如果合理使用這個屬性就可以將性能消耗降到最低矛双。
滿足這幾個條件就可以使用shouldRasterize:
- layer的內容(包括子layer)必須是靜態(tài)的渊抽,因為一旦發(fā)生變化,之前辛苦處理得到的緩存就失效了议忽。如果這件事頻繁發(fā)生懒闷,我們就又回到了“每一幀都需要離屏渲染”的情景,而這正是開發(fā)者需要極力避免的栈幸。針對這種情況愤估,Xcode提供了“Color Hits Green and Misses Red”的選項,幫助我們查看緩存的使用是否符合預期
- 由于系統(tǒng)提供的offset-buffer的空間是有限的:
屏幕尺寸的2.5倍
侦镇。所以無法長時間占用灵疮,系統(tǒng)只提供了100ms
,如果100ms
沒被使用依舊會被釋放壳繁。
6. 文字以及drawRect
CPU的'離屏渲染'時已經說明震捣,不在贅述。
后續(xù)
最初的兩個問題在文中已經有明確的答案闹炉,耐心閱讀可能會有新發(fā)現(xiàn)蒿赢。
推薦閱讀:
極客技術團隊-關于iOS離屏渲染的深入研究