版本記錄
| 版本號(hào) | 時(shí)間 |
|---|---|
| V1.0 | 2017.06.15 |
前言
我們要在界面上看到東西,是因?yàn)榭丶讳秩镜狡聊簧系脑?,說到渲染大家都知道包括當(dāng)屏渲染和離屏渲染,這里就和大家說一下onScreen和offScreen。
onScreen渲染
顧名思義,就是將視圖直接渲染到屏幕上。onscreen render指的是GPU在當(dāng)前用于顯示的屏幕緩沖區(qū)進(jìn)行渲染。
offScreen渲染
一、基本概念
offscreen rendring指的是在圖像在繪制到當(dāng)前屏幕前,需要先進(jìn)行一次渲染,之后才繪制到當(dāng)前屏幕。 那么為什么offscreen渲染會(huì)耗費(fèi)大量資源呢? 原因是顯卡需要另外alloc一塊內(nèi)存來進(jìn)行渲染,渲染完畢后在繪制到當(dāng)前屏幕,而且對(duì)于顯卡來說,onscreen到offscreen的上下文環(huán)境切換是非常昂貴的(涉及到OpenGL的pipelines和barrier等)。
??與當(dāng)前屏幕渲染相比,離屏渲染的代價(jià)是很高的,主要體現(xiàn)在兩個(gè)方面:
- 創(chuàng)建新緩沖區(qū):要想進(jìn)行離屏渲染,首先要?jiǎng)?chuàng)建一個(gè)新的緩沖區(qū)。
- 上下文切換:離屏渲染的整個(gè)過程,需要多次切換上下文環(huán)境。先是從當(dāng)前屏幕(On-Screen)切換到離屏(Off-Screen);等到離屏渲染結(jié)束以后,將離屏緩沖區(qū)的渲染結(jié)果顯示到屏幕上又需要將上下文環(huán)境從離屏切換到當(dāng)前屏幕。但是,上下文環(huán)境的切換是要付出很大代價(jià)的。
二、離屏渲染的幾種情況
- Any layer with a mask (layer.mask)
- Any layer with layer.masksToBounds / view.clipsToBounds being true
- Any layer with layer.allowsGroupOpacity set to YES and layer.opacity is less than 1.0
- Any layer with a drop shadow (layer.shadow*).
- Any layer with layer.shouldRasterize being true
- Any layer with layer.cornerRadius, layer.edgeAntialiasingMask, layer.allowsEdgeAntialiasing
Text (any kind, including UILabel, CATextLayer, Core Text, etc). - Most of the drawing you do with CGContext in drawRect:. Even an empty implementation will be rendered offscreen.
下面我就給大家翻一下,英語不好,翻譯不對(duì)的地方請(qǐng)指出。
- 帶有遮罩的layer
- layer.masksToBounds和view.clipsToBounds設(shè)置為YES
- layer.allowsGroupOpacity設(shè)置為YES或者layer.opacity<1.0
- layer帶有陰影shadow
- layer.shouldRasterize設(shè)置為YES
- layer設(shè)置shouldRasterize=Y(jié)ES之后,會(huì)把被光柵化的圖層保存成位圖并緩存起來,其中圓角或者陰影之類的效果也是直接保存到位圖當(dāng)中,當(dāng)需要渲染到屏幕上的時(shí)候只需要到緩存中去取對(duì)應(yīng)的位圖進(jìn)行顯示就行了,加快了整個(gè)渲染過程??梢酝ㄟ^勾選instruments core animation中的Color Hits Green and Misses Red選項(xiàng)來查看圖層是否被緩存了,如果圖層顯示為綠色則表示已經(jīng)被緩存起來了,也就是這個(gè)緩沖區(qū)的內(nèi)容被復(fù)用了,不用在去重新創(chuàng)建緩沖區(qū),反之則是用紅色標(biāo)示。設(shè)置shouldRasterize之后,如果滑動(dòng)過程中發(fā)現(xiàn)都是紅色的證明就有問題了。也可以這么表述:如果在設(shè)置為YES后,在快速滾動(dòng)的時(shí)候,綠色較少,而紅色較多,說明并不可觀,表示不建議使用光柵化。相反,如果在快速滾動(dòng)的時(shí)候,基本都是綠色的,只有極少數(shù)偶爾出現(xiàn)紅色,那么使用光柵化來優(yōu)化是可以達(dá)到很好的效果的。
- layer設(shè)置了 layer.cornerRadius, layer.edgeAntialiasingMask, layer.allowsEdgeAntialiasing Text(任何種類,包括UILabel、CATextLayer、CoreText等)
- 在drawRect中利用上下文CGContext繪圖,即使什么都沒畫,只是空的實(shí)現(xiàn)也會(huì)導(dǎo)致離屏渲染。
大家都知道離屏渲染多了,系統(tǒng)性能就會(huì)降低,變的很卡。所以要盡量避免以下幾點(diǎn):
盡量將視圖背景色和superView的背景色一致/背景色不要為clearColor(會(huì)引起圖層混合)。
假如最上層的view是不透明的,那直接使用這個(gè)view的對(duì)應(yīng)顏色之就可以,但如果view是透明的,在計(jì)算像素的顏色值時(shí)就需要計(jì)算它下面圖層,透明的視圖越多,計(jì)算量就越大,因此也會(huì)對(duì)圖形的性能產(chǎn)生一定的影響,所以可以的話也盡量減少透明圖層的數(shù)目。
-
光柵化對(duì)于那些有很多子view嵌套在一起、view的層級(jí)復(fù)雜或者有很復(fù)雜特效效果的圖層有很明顯的提升,因?yàn)檫@些內(nèi)容都被緩存到位圖當(dāng)中了。但是使用光柵化需要注意一些內(nèi)容:
- 適用于內(nèi)容基本不變的圖層
假如圖層的內(nèi)容經(jīng)常變化,比如cell里面有涉及到動(dòng)畫之類的,那么緩存的內(nèi)容就無效了,GPU需要重新創(chuàng)建緩存區(qū),導(dǎo)致離屏渲染,這又涉及到OpenGL的上下文環(huán)境切換,反而降低性能。 - 不要過度使用
緩存區(qū)的大小被設(shè)置為屏幕大小的2.5倍,假如過分使用同樣會(huì)導(dǎo)致大量的離屏渲染。 - 如果緩存的內(nèi)容超過100ms沒有被使用則會(huì)被回收。
- 適用于內(nèi)容基本不變的圖層
避免離屏渲染。將圖層的alpha設(shè)為1(如何其不為1,圖層合成過程中,將引起大量的計(jì)算),uiview的圓角,遮罩 也會(huì)引起離屏渲染。
減少不必要的drawrect()操作。
處理好UIView 與 CALayer的關(guān)系。UIView層次不要太多。因?yàn)檫@樣會(huì)加多合成。
將線程安全的操作挪到非主線程。比如繪制圖片等。
三、界面卡頓的原因
可能大家有所疑惑,為什么有的時(shí)候說性能低界面卡,到底是什么原因,其實(shí)可以從軟硬件結(jié)合的角度講。
原因:GPU垂直同步信號(hào)(VSync,硬件產(chǎn)生的),固定1/60S發(fā)出一次。在此時(shí)間間隔內(nèi),CPU、GPU需要準(zhǔn)備好數(shù)據(jù)。在 VSync 信號(hào)到來后,系統(tǒng)圖形服務(wù)會(huì)通過 CADisplayLink 等機(jī)制通知 App,App 主線程開始在 CPU 中計(jì)算顯示內(nèi)容,比如視圖的創(chuàng)建、布局計(jì)算、圖片解碼、文本繪制等。隨后 CPU 會(huì)將計(jì)算好的內(nèi)容提交到 GPU 去,由 GPU 進(jìn)行變換、合成、渲染。隨后 GPU 會(huì)把渲染結(jié)果提交到幀緩沖區(qū)去,等待下一次 VSync 信號(hào)到來時(shí)顯示到屏幕上。由于垂直同步的機(jī)制,如果在一個(gè) VSync 時(shí)間內(nèi),CPU 或者 GPU 沒有完成內(nèi)容提交,則那一幀就會(huì)被丟棄,等待下一次機(jī)會(huì)再顯示,而這時(shí)顯示屏?xí)A糁暗膬?nèi)容不變。這就是界面卡頓的原因。即FPS降低。
- CPU做的工作:對(duì)象的創(chuàng)建;調(diào)整;layout;布局的計(jì)算;文本的計(jì)算,渲染;圖片的渲染,解碼,渲染等。
- GPU做的工作:紋理的渲染,圖形混合,圖形生成。(離屏渲染發(fā)生在GPU中)。
四、離屏渲染的分類
離屏渲染分為兩種:一種是CPU的渲染,另外一種就是GPU渲染。
CPU離屏渲染
使用CPU來完成渲染操縱,通常在你使用:
- drawRect (如果沒有自定義繪制的任務(wù)就不要在子類中寫一個(gè)空的drawRect方法,因?yàn)橹灰獙?shí)現(xiàn)了該方法,就會(huì)為視圖分配一個(gè)寄宿圖,這個(gè)寄宿圖的像素尺寸等于視圖大小乘以 contentsScale的值,造成資源浪費(fèi))。
- 使用Core Graphics。
上面的兩種情況使用的就是CPU離屏渲染,首先分配一塊內(nèi)存,然后進(jìn)行渲染操作生成一份bitmap位圖,整個(gè)渲染過程會(huì)在你的應(yīng)用中同步的進(jìn)行,接著再將位圖打包發(fā)送到iOS里一個(gè)單獨(dú)的進(jìn)程--render server,理想情況下,render server將內(nèi)容交給GPU直接顯示到屏幕上。
GPU離屏渲染
使用GPU在當(dāng)前屏幕緩沖區(qū)以外開辟一個(gè)新的緩沖區(qū)進(jìn)行繪制,通常發(fā)生的情況有:
- 設(shè)置cornerRadius, masks, shadows,edge antialiasing(抗鋸齒)等
- 設(shè)置layer.shouldRasterize = YES
具體的渲染過程如下圖所示。

通常大家說的離屏渲染指的是GPU這塊(當(dāng)然CPU這塊也會(huì)有影響,也需要消耗一定的資源),比如修改了layer的陰影或者圓角,GPU需要做額外的渲染操作。通常GPU在做渲染的時(shí)候是很快的,但是涉及到offscreen-render的時(shí)候情況就可能有些不同,因?yàn)樾枰~外開辟一個(gè)新的緩沖區(qū)進(jìn)行渲染,然后繪制到當(dāng)前屏幕的過程需要做onscreen跟offscreen上下文之間的切換,這個(gè)過程的消耗會(huì)比較昂貴,涉及到OpenGL的pipeline跟barrier,而且offscreen-render在每一幀都會(huì)涉及到,因此處理不當(dāng)肯定會(huì)對(duì)性能產(chǎn)生一定的影響,所以可以的話盡量減少offscreen-render的圖層,查看哪些圖層需要離屏渲染可以用Instruments的Core Animation工具進(jìn)行檢測,Color Offscreen-Rendered Yellow選項(xiàng)會(huì)將對(duì)應(yīng)的圖層標(biāo)記為黃色。
五、提高性能的幾種方式
- 對(duì)于圓角可以使用一張中間圓形透明的圖覆蓋在上面,雖然這會(huì)引入blending操作,但是大部分情況下性能會(huì)比離屏渲染好。
- 讓你的view層次結(jié)構(gòu)平坦一些,因?yàn)镺penGL在渲染layer的時(shí)候,在碰到有子層級(jí)layer的時(shí)候可能需要停下來把兩者合成到一個(gè)buffer里再接著渲染。(When the OpenGL renderer goes to draw each layer, it may have to stop for some subhierarchies and composite them into a single buffer).
- 延遲加載圖片
有時(shí)候在邊滾動(dòng)邊設(shè)置圖片的時(shí)候可能會(huì)有一定的影響,因此可以在滾動(dòng)的時(shí)候imageview不執(zhí)行setimage的操作,滾動(dòng)停止的時(shí)候才加載圖片,由于滾動(dòng)的時(shí)候NSRunloop是處于UITrackingRunLoopMode模式下,可以采用如下的方式,將設(shè)置圖片放到NSDefaultRunLoopMode模式下才進(jìn)行:
UIImage *downloadedImage = ...;
[self.avatarImageView performSelector:@selector(setImage:)
withObject:downloadedImage
afterDelay:0
inModes:@[NSDefaultRunLoopMode]];
圖片加載的極限優(yōu)化方式:FastImageCache
五、offScreen檢測
??利用instruments可以檢測offScreen,在Instruments->Core Animation下,如下圖。

注意:要在真機(jī)上才可以。
參考文章
后記
未完,待續(xù),希望大家能喜歡~~~