相比於當前屏幕渲染,離屏渲染的代價是很高的,這也是iOS移動端優化的必要部分。
OpenGL中,GPU屏幕渲染有以下兩種方式:
1.On-Screen Rendering
意為當前屏幕渲染,指的是GPU的渲染操作是在當前用於顯示的屏幕緩沖區中進行。
2.Off-Screen Rendering
意為離屏渲染,指的是GPU在當前屏幕緩沖區以外新開辟一個緩沖區進行渲染操作。
一般情況下,OpenGL會將應用提交到Render Server的動畫直接渲染顯示(基本的Tile-Based渲染流程),但對於一些復雜的圖像動畫的渲染並不能直接渲染疊加顯示,而是需要根據Command Buffer分通道進行渲染之後再組合,這一組合過程中,就有些渲染通道是不會直接顯示的;Masking渲染需要更多渲染通道和合並的步驟;而這些沒有直接顯示在屏幕的上的通道就是Offscreen Rendering Pass。
Offscreen Render為什麼卡頓,Offscreen Render需要更多的渲染通道,而且不同的渲染通道間切換需要耗費一定的時間,這個時間內GPU會閒置,當通道達到一定數量,對性能也會有較大的影響。
離屏渲染可以被 Core Animation 自動觸發,或者被應用程序強制觸發。屏幕外的渲染會合並渲染圖層樹的一部分到一個新的緩沖區,然後該緩沖區被渲染到屏幕上。
離屏渲染合成計算是非常昂貴的, 但有時你也許希望強制這種操作。一種好的方法就是緩存合成的紋理/圖層。如果你的渲染樹非常復雜(所有的紋理,以及如何組合在一起),你可以強制離屏渲染緩存那些圖層,然後可以用緩存作為合成的結果放到屏幕上。
如果你的程序混合了很多圖層,並且想要他們一起做動畫,GPU 通常會為每一幀(1/60s)重復合成所有的圖層。當使用離屏渲染時,GPU 第一次會混合所有圖層到一個基於新的紋理的位圖緩存上,然後使用這個紋理來繪制到屏幕上。現在,當這些圖層一起移動的時候,GPU 便可以復用這個位圖緩存,並且只需要做很少的工作。需要注意的是,只有當那些圖層不改變時,這才可以用。如果那些圖層改變了,GPU 需要重新創建位圖緩存。你可以通過設置 shouldRasterize 為 YES 來觸發這個行為。
然而,這是一個權衡。第一,這可能會使事情變得更慢。創建額外的屏幕外緩沖區是 GPU 需要多做的一步操作,特殊情況下這個位圖可能再也不需要被復用,這便是一個無用功了。然而,可以被復用的位圖,GPU 也有可能將它卸載了。所以你需要計算 GPU 的利用率和幀的速率來判斷這個位圖是否有用。
離屏渲染也可能產生副作用。如果你正在直接或者間接的將mask應用到一個圖層上,Core Animation 為了應用這個 mask,會強制進行屏幕外渲染。這會對 GPU 產生重負。通常情況下 mask 只能被直接渲染到幀的緩沖區中(在屏幕內)。
Instrument 的 Core Animation 工具有一個叫做Color Offscreen-Rendered Yellow的選項,它會將已經被渲染到屏幕外緩沖區的區域標注為黃色(這個選項在模擬器中也可以用)。同時記得檢查Color Hits Green and Misses Red選項。綠色代表無論何時一個屏幕外緩沖區被復用,而紅色代表當緩沖區被重新創建。
一般情況下,你需要避免離屏渲染,因為這是很大的消耗。直接將圖層合成到幀的緩沖區中(在屏幕上)比先創建屏幕外緩沖區,然後渲染到紋理中,最後將結果渲染到幀的緩沖區中要廉價很多。因為這其中涉及兩次昂貴的環境轉換(轉換環境到屏幕外緩沖區,然後轉換環境到幀緩沖區)。
所以當你打開Color Offscreen-Rendered Yellow後看到黃色,這便是一個警告,但這不一定是不好的。如果 Core Animation 能夠復用屏幕外渲染的結果,這便能夠提升性能。
同時還要注意,rasterized layer 的空間是有限的。蘋果暗示大概有屏幕大小兩倍的空間來存儲 rasterized layer/屏幕外緩沖區。
如果你使用 layer 的方式會通過屏幕外渲染,你最好擺脫這種方式。為 layer 使用蒙板或者設置圓角半徑會造成屏幕外渲染,產生陰影也會如此。
至於 mask,圓角半徑(特殊的mask)和 clipsToBounds/masksToBounds,你可以簡單的為一個已經擁有 mask 的 layer 創建內容,比如,已經應用了 mask 的 layer 使用一張圖片。如果你想根據 layer 的內容為其應用一個長方形 mask,你可以使用 contentsRect 來代替蒙板。
如果你最後設置了 shouldRasterize 為 YES,那也要記住設置 rasterizationScale 為 contentsScale。
相比於當前屏幕渲染,離屏渲染的代價是很高的,主要體現在兩個方面:
1.創建新緩沖區
要想進行離屏渲染,首先要創建一個新的緩沖區。
2.上下文切換
離屏渲染的整個過程,需要多次切換上下文環境:先是從當前屏幕(On-Screen)切換到離屏(Off-Screen);等到離屏渲染結束以後,將離屏緩沖區的渲染結果顯示到屏幕上有需要將上下文環境從離屏切換到當前屏幕。而上下文環境的切換是要付出很大代價的。
那哪些情況會Offscreen Render呢?
1) drawRect 2) layer.shouldRasterize = true; 3) 有mask或者是陰影(layer.masksToBounds, layer.shadow*); 3.1) shouldRasterize(光柵化) 3.2) masks(遮罩) 3.3) shadows(陰影) 3.4) edge antialiasing(抗鋸齒) 3.5) group opacity(不透明) 4) Text(UILabel, CATextLayer, Core Text, etc)...
注:layer.cornerRadius,layer.borderWidth,layer.borderColor並不會Offscreen Render,因為這些不需要加入Mask。
需要注意的是,如果shouldRasterize被設置成YES,在觸發離屏繪制的同時,會將光柵化後的內容緩存起來,如果對應的layer及其sublayers沒有發生改變,在下一幀的時候可以直接復用。這將在很大程度上提升渲染性能。
而其它屬性如果是開啟的,就不會有緩存,離屏繪制會在每一幀都發生。
如果我們重寫了drawRect方法,並且使用任何Core Graphics的技術進行了繪制操作,就涉及到了CPU渲染。整個渲染過程由CPU在App內同步地
完成,渲染得到的bitmap最後再交由GPU用於顯示。
1.盡量使用當前屏幕渲染
鑒於離屏渲染、CPU渲染可能帶來的性能問題,一般情況下,我們要盡量使用當前屏幕渲染。
2.離屏渲染 VS CPU渲染
由於GPU的浮點運算能力比CPU強,CPU渲染的效率可能不如離屏渲染;但如果僅僅是實現一個簡單的效果,直接使用CPU渲染的效率又可能比離屏渲染好,畢竟離屏渲染要涉及到緩沖區創建和上下文切換等耗時操作。
一方面來說,一部分(通過drawRect並且使用任何CoreGraphics來實現的繪制,或使用CoreText[其實就是使用CoreGraphics]繪制)的確涉及到了“離屏繪制”,但是這和我們通常說的那種離屏繪制是有區別的。當你實現drawRect方法或者通過CoeGraphics繪制的時候,其實你是在使用CPU繪制。並且這個繪制的過程會在你的App內同步地進行。基本上你只是調用了一些向位圖緩存內寫入一些二進制信息的方法而已。
而另外一種形式離屏繪制是發生在繪制服務(是獨立的處理過程)並且同時通過GPU執行(這裡就不是像前面提到的用CPU了)。當OpengGL的繪制程序在繪制每個layer的時候,有可能因為包含多子層級關系而必須停下來把他們合成到一個單獨的緩存裡。你可能認為GPU應該總是比CPU牛逼一點,但是在這裡我們還是需要慎重的考慮一下。因為對GPU來說,從當前屏幕(on-screen)到離屏(off-screen)上下文環境的來回切換(這個過程必須flush管線和光柵),代價是非常大的。因此對一些簡單的繪制過程來說,這個過程有可能用CoreGraphics,全部用CPU來完成反而會比GPU做得更好。所以如果你正在嘗試處理一些復雜的層級,並且在猶豫到底用-[CALayer setShouldRasterize:]還是通過CoreGraphics來繪制層級上的所有內容,唯一的方法就是測試並且進行權衡。
有機會,我們下次再談。