但願經過這篇文章從頭至尾梳理一下 iOS 中涉及到渲染原理相關的內容,會先從計算機渲染原理講起,慢慢說道 iOS 的渲染原理和框架,最後再深刻探討一下離屏渲染。html
但願能對你們有點幫助~ios
文章Github地址git
對於現代計算機系統,簡單來講能夠大概視做三層架構:硬件、操做系統與進程。對於移動端來講,進程就是 app,而 CPU 與 GPU 是硬件層面的重要組成部分。CPU 與 GPU 提供了計算能力,經過操做系統被 app 調用。github
CPU 和 GPU 其設計目標就是不一樣的,它們分別針對了兩種不一樣的應用場景。CPU 是運算核心與控制核心,須要有很強的運算通用性,兼容各類數據類型,同時也須要能處理大量不一樣的跳轉、中斷等指令,所以 CPU 的內部結構更爲複雜。而 GPU 則面對的是類型統1、更加單純的運算,也不須要處理複雜的指令,但也肩負着更大的運算任務。web
所以,CPU 與 GPU 的架構也不一樣。由於 CPU 面臨的狀況更加複雜,所以從上圖中也能夠看出,CPU 擁有更多的緩存空間 Cache 以及複雜的控制單元,計算能力並非 CPU 的主要訴求。CPU 是設計目標是低時延,更多的高速緩存也意味着能夠更快地訪問數據;同時複雜的控制單元也能更快速地處理邏輯分支,更適合串行計算。面試
而 GPU 擁有更多的計算單元 Arithmetic Logic Unit,具備更強的計算能力,同時也具備更多的控制單元。GPU 基於大吞吐量而設計,每一部分緩存都鏈接着一個流處理器(stream processor),更加適合大規模的並行計算。objective-c
圖像渲染流程粗粒度地大概分爲下面這些步驟:算法
上述圖像渲染流水線中,除了第一部分 Application 階段,後續主要都由 GPU 負責,爲了方便後文講解,先將 GPU 的渲染流程圖展現出來:編程
上圖就是一個三角形被渲染的過程當中,GPU 所負責的渲染流水線。能夠看到簡單的三角形繪製就須要大量的計算,若是再有更多更復雜的頂點、顏色、紋理信息(包括 3D 紋理),那麼計算量是不可思議的。這也是爲何 GPU 更適合於渲染流程。緩存
接下來,具體講解渲染流水線中各個部分的具體任務:
Application 應用處理階段:獲得圖元
這個階段具體指的就是圖像在應用中被處理的階段,此時還處於 CPU 負責的時期。在這個階段應用可能會對圖像進行一系列的操做或者改變,最終將新的圖像信息傳給下一階段。這部分信息被叫作圖元(primitives),一般是三角形、線段、頂點等。
Geometry 幾何處理階段:處理圖元
進入這個階段以後,以及以後的階段,就都主要由 GPU 負責了。此時 GPU 能夠拿到上一個階段傳遞下來的圖元信息,GPU 會對這部分圖元進行處理,以後輸出新的圖元。這一系列階段包括:
Rasterization 光柵化階段:圖元轉換爲像素
光柵化的主要目的是將幾何渲染以後的圖元信息,轉換爲一系列的像素,以便後續顯示在屏幕上。這個階段中會根據圖元信息,計算出每一個圖元所覆蓋的像素信息等,從而將像素劃分紅不一樣的部分。
一種簡單的劃分就是根據中心點,若是像素的中心點在圖元內部,那麼這個像素就屬於這個圖元。如上圖所示,深藍色的線就是圖元信息所構建出的三角形;而經過是否覆蓋中心點,能夠遍歷出全部屬於該圖元的全部像素,即淺藍色部分。
Pixel 像素處理階段:處理像素,獲得位圖
通過上述光柵化階段,咱們獲得了圖元所對應的像素,此時,咱們須要給這些像素填充顏色和效果。因此最後這個階段就是給像素填充正確的內容,最終顯示在屏幕上。這些通過處理、蘊含大量信息的像素點集合,被稱做位圖(bitmap)。也就是說,Pixel 階段最終輸出的結果就是位圖,過程具體包含:
這些點能夠進行不一樣的排列和染色以構成圖樣。當放大位圖時,能夠看見賴以構成整個圖像的無數單個方塊。只要有足夠多的不一樣色彩的像素,就能夠製做出色彩豐富的圖象,逼真地表現天然界的景象。縮放和旋轉容易失真,同時文件容量較大。
在圖像渲染流程結束以後,接下來就須要將獲得的像素信息顯示在物理屏幕上了。GPU 最後一步渲染結束以後像素信息,被存在幀緩衝器(Framebuffer)中,以後視頻控制器(Video Controller)會讀取幀緩衝器中的信息,通過數模轉換傳遞給顯示器(Monitor),進行顯示。完整的流程以下圖所示:
通過 GPU 處理以後的像素集合,也就是位圖,會被幀緩衝器緩存起來,供以後的顯示使用。顯示器的電子束會從屏幕的左上角開始逐行掃描,屏幕上的每一個點的圖像信息都從幀緩衝器中的位圖進行讀取,在屏幕上對應地顯示。掃描的流程以下圖所示:
電子束掃描的過程當中,屏幕就能呈現出對應的結果,每次整個屏幕被掃描完一次後,就至關於呈現了一幀完整的圖像。屏幕不斷地刷新,不停呈現新的幀,就能呈現出連續的影像。而這個屏幕刷新的頻率,就是幀率(Frame per Second,FPS)。因爲人眼的視覺暫留效應,當屏幕刷新頻率足夠高時(FPS 一般是 50 到 60 左右),就能讓畫面看起來是連續而流暢的。對於 iOS 而言,app 應該儘可能保證 60 FPS 纔是最好的體驗。
在這種單一緩存的模式下,最理想的狀況就是一個流暢的流水線:每次電子束從頭開始新的一幀的掃描時,CPU+GPU 對於該幀的渲染流程已經結束,渲染好的位圖已經放入幀緩衝器中。但這種完美的狀況是很是脆弱的,很容易產生屏幕撕裂:
CPU+GPU 的渲染流程是一個很是耗時的過程。若是在電子束開始掃描新的一幀時,位圖尚未渲染好,而是在掃描到屏幕中間時才渲染完成,被放入幀緩衝器中 ---- 那麼已掃描的部分就是上一幀的畫面,而未掃描的部分則會顯示新的一幀圖像,這就形成屏幕撕裂。
解決屏幕撕裂、提升顯示效率的一個策略就是使用垂直同步信號 Vsync 與雙緩衝機制 Double Buffering。根據蘋果的官方文檔描述,iOS 設備會始終使用 Vsync + Double Buffering 的策略。
垂直同步信號(vertical synchronisation,Vsync)至關於給幀緩衝器加鎖:當電子束完成一幀的掃描,將要從頭開始掃描時,就會發出一個垂直同步信號。只有當視頻控制器接收到 Vsync 以後,纔會將幀緩衝器中的位圖更新爲下一幀,這樣就能保證每次顯示的都是同一幀的畫面,於是避免了屏幕撕裂。
可是這種狀況下,視頻控制器在接受到 Vsync 以後,就要將下一幀的位圖傳入,這意味着整個 CPU+GPU 的渲染流程都要在一瞬間完成,這是明顯不現實的。因此雙緩衝機制會增長一個新的備用緩衝器(back buffer)。渲染結果會預先保存在 back buffer 中,在接收到 Vsync 信號的時候,視頻控制器會將 back buffer 中的內容置換到 frame buffer 中,此時就能保證置換操做幾乎在一瞬間完成(其實是交換了內存地址)。
啓用 Vsync 信號以及雙緩衝機制以後,可以解決屏幕撕裂的問題,可是會引入新的問題:掉幀。若是在接收到 Vsync 之時 CPU 和 GPU 尚未渲染好新的位圖,視頻控制器就不會去替換 frame buffer 中的位圖。這時屏幕就會從新掃描呈現出上一幀如出一轍的畫面。至關於兩個週期顯示了一樣的畫面,這就是所謂掉幀的狀況。
如圖所示,A、B 表明兩個幀緩衝器,當 B 沒有渲染完畢時就接收到了 Vsync 信號,因此屏幕只能再顯示相同幀 A,這就發生了第一次的掉幀。
事實上上述策略還有優化空間。咱們注意到在發生掉幀的時候,CPU 和 GPU 有一段時間處於閒置狀態:當 A 的內容正在被掃描顯示在屏幕上,而 B 的內容已經被渲染好,此時 CPU 和 GPU 就處於閒置狀態。那麼若是咱們增長一個幀緩衝器,就能夠利用這段時間進行下一步的渲染,並將渲染結果暫存於新增的幀緩衝器中。
如圖所示,因爲增長了新的幀緩衝器,能夠必定程度上地利用掉幀的空檔期,合理利用 CPU 和 GPU 性能,從而減小掉幀的次數。
手機使用卡頓的直接緣由,就是掉幀。前文也說過,屏幕刷新頻率必需要足夠高才能流暢。對於 iPhone 手機來講,屏幕最大的刷新頻率是 60 FPS,通常只要保證 50 FPS 就已是較好的體驗了。可是若是掉幀過多,致使刷新頻率太低,就會形成不流暢的使用體驗。
這樣看來,能夠大概總結一下
iOS 的渲染框架依然符合渲染流水線的基本架構,具體的技術棧如上圖所示。在硬件基礎之上,iOS 中有 Core Graphics、Core Animation、Core Image、OpenGL 等多種軟件框架來繪製內容,在 CPU 與 GPU 之間進行了更高層地封裝。
GPU Driver:上述軟件框架相互之間也有着依賴關係,不過全部框架最終都會經過 OpenGL 鏈接到 GPU Driver,GPU Driver 是直接和 GPU 交流的代碼塊,直接與 GPU 鏈接。
OpenGL:是一個提供了 2D 和 3D 圖形渲染的 API,它能和 GPU 密切的配合,最高效地利用 GPU 的能力,實現硬件加速渲染。OpenGL的高效實現(利用了圖形加速硬件)通常由顯示設備廠商提供,並且很是依賴於該廠商提供的硬件。OpenGL 之上擴展出不少東西,如 Core Graphics 等最終都依賴於 OpenGL,有些狀況下爲了更高的效率,好比遊戲程序,甚至會直接調用 OpenGL 的接口。
Core Graphics:Core Graphics 是一個強大的二維圖像繪製引擎,是 iOS 的核心圖形庫,經常使用的好比 CGRect 就定義在這個框架下。
Core Animation:在 iOS 上,幾乎全部的東西都是經過 Core Animation 繪製出來,它的自由度更高,使用範圍也更廣。
Core Image:Core Image 是一個高性能的圖像處理分析的框架,它擁有一系列現成的圖像濾鏡,能對已存在的圖像進行高效的處理。
Metal:Metal 相似於 OpenGL ES,也是一套第三方標準,具體實現由蘋果實現。Core Animation、Core Image、SceneKit、SpriteKit 等等渲染框架都是構建於 Metal 之上的。
Render, compose, and animate visual elements. ---- Apple
Core Animation,它本質上能夠理解爲一個複合引擎,主要職責包含:渲染、構建和實現動畫。
一般咱們會使用 Core Animation 來高效、方便地實現動畫,可是實際上它的前身叫作 Layer Kit,關於動畫實現只是它功能中的一部分。對於 iOS app,不管是否直接使用了 Core Animation,它都在底層深度參與了 app 的構建。而對於 OS X app,也能夠經過使用 Core Animation 方便地實現部分功能。
Core Animation 是 AppKit 和 UIKit 完美的底層支持,同時也被整合進入 Cocoa 和 Cocoa Touch 的工做流之中,它是 app 界面渲染和構建的最基礎架構。 Core Animation 的職責就是儘量快地組合屏幕上不一樣的可視內容,這個內容是被分解成獨立的 layer(iOS 中具體而言就是 CALayer),而且被存儲爲樹狀層級結構。這個樹也造成了 UIKit 以及在 iOS 應用程序當中你所能在屏幕上看見的一切的基礎。
簡單來講就是用戶能看到的屏幕上的內容都由 CALayer 進行管理。那麼 CALayer 到底是如何進行管理的呢?另外在 iOS 開發過程當中,最大量使用的視圖控件其實是 UIView 而不是 CALayer,那麼他們二者的關係到底如何呢?
簡單理解,CALayer 就是屏幕顯示的基礎。那 CALayer 是如何完成的呢?讓咱們來從源碼向下探索一下,在 CALayer.h 中,CALayer 有這樣一個屬性 contents:
/** Layer content properties and methods. **/
/* An object providing the contents of the layer, typically a CGImageRef,
* but may be something else. (For example, NSImage objects are
* supported on Mac OS X 10.6 and later.) Default value is nil.
* Animatable. */
@property(nullable, strong) id contents;
複製代碼
An object providing the contents of the layer, typically a CGImageRef.
contents 提供了 layer 的內容,是一個指針類型,在 iOS 中的類型就是 CGImageRef(在 OS X 中還能夠是 NSImage)。而咱們進一步查到,Apple 對 CGImageRef 的定義是:
A bitmap image or image mask.
看到 bitmap,這下咱們就能夠和以前講的的渲染流水線聯繫起來了:實際上,CALayer 中的 contents 屬性保存了由設備渲染流水線渲染好的位圖 bitmap(一般也被稱爲 backing store),而當設備屏幕進行刷新時,會從 CALayer 中讀取生成好的 bitmap,進而呈現到屏幕上。
因此,若是咱們在代碼中對 CALayer 的 contents 屬性進行了設置,好比這樣:
// 注意 CGImage 和 CGImageRef 的關係:
// typedef struct CGImage CGImageRef;
layer.contents = (__bridge id)image.CGImage;
複製代碼
那麼在運行時,操做系統會調用底層的接口,將 image 經過 CPU+GPU 的渲染流水線渲染獲得對應的 bitmap,存儲於 CALayer.contents 中,在設備屏幕進行刷新的時候就會讀取 bitmap 在屏幕上呈現。
也正由於每次要被渲染的內容是被靜態的存儲起來的,因此每次渲染時,Core Animation 會觸發調用 drawRect:
方法,使用存儲好的 bitmap 進行新一輪的展現。
UIView 做爲最經常使用的視圖控件,和 CALayer 也有着千絲萬縷的聯繫,那麼二者之間究竟是個什麼關係,他們有什麼差別?
固然,二者有不少顯性的區別,好比是否可以響應點擊事件。但爲了從根本上完全搞懂這些問題,咱們必需要先搞清楚二者的職責。
Views are the fundamental building blocks of your app's user interface, and the
UIView
class defines the behaviors that are common to all views. A view object renders content within its bounds rectangle and handles any interactions with that content.
根據 Apple 的官方文檔,UIView 是 app 中的基本組成結構,定義了一些統一的規範。它會負責內容的渲染以及,處理交互事件。具體而言,它負責的事情能夠歸爲下面三類
Layers are often used to provide the backing store for views but can also be used without a view to display content. A layer’s main job is to manage the visual content that you provide...
If the layer object was created by a view, the view typically assigns itself as the layer’s delegate automatically, and you should not change that relationship.
而從 CALayer 的官方文檔中咱們能夠看出,CALayer 的主要職責是管理內部的可視內容,這也和咱們前文所講的內容吻合。當咱們建立一個 UIView 的時候,UIView 會自動建立一個 CALayer,爲自身提供存儲 bitmap 的地方(也就是前文說的 backing store),並將自身固定設置爲 CALayer 的代理。
從這兒咱們大概總結出下面兩個核心關係:
有了這兩個最關鍵的根本關係,那麼下面這些常常出如今面試答案裏的顯性的異同就很好解釋了。舉幾個例子:
相同的層級結構:咱們對 UIView 的層級結構很是熟悉,因爲每一個 UIView 都對應 CALayer 負責頁面的繪製,因此 CALayer 也具備相應的層級結構。
部分效果的設置:由於 UIView 只對 CALayer 的部分功能進行了封裝,而另外一部分如圓角、陰影、邊框等特效都須要經過調用 layer 屬性來設置。
是否響應點擊事件:CALayer 不負責點擊事件,因此不響應點擊事件,而 UIView 會響應。
不一樣繼承關係:CALayer 繼承自 NSObject,UIView 因爲要負責交互事件,因此繼承自 UIResponder。
固然還剩最後一個問題,爲何要將 CALayer 獨立出來,直接使用 UIView 統一管理不行嗎?爲何不用一個統一的對象來處理全部事情呢?
這樣設計的主要緣由就是爲了職責分離,拆分功能,方便代碼的複用。經過 Core Animation 框架來負責可視內容的呈現,這樣在 iOS 和 OS X 上均可以使用 Core Animation 進行渲染。與此同時,兩個系統還能夠根據交互規則的不一樣來進一步封裝統一的控件,好比 iOS 有 UIKit 和 UIView,OS X 則是AppKit 和 NSView。
當咱們瞭解了 Core Animation 以及 CALayer 的基本知識後,接下來咱們來看下 Core Animation 的渲染流水線。
整個流水線一共有下面幾個步驟:
Handle Events:這個過程當中會先處理點擊事件,這個過程當中有可能會須要改變頁面的佈局和界面層次。
**Commit Transaction:**此時 app 會經過 CPU 處理顯示內容的前置計算,好比佈局計算、圖片解碼等任務,接下來會進行詳細的講解。以後將計算好的圖層進行打包發給 Render Server
。
**Decode:**打包好的圖層被傳輸到 Render Server
以後,首先會進行解碼。注意完成解碼以後須要等待下一個 RunLoop 纔會執行下一步 Draw Calls
。
**Draw Calls:**解碼完成後,Core Animation 會調用下層渲染框架(好比 OpenGL 或者 Metal)的方法進行繪製,進而調用到 GPU。
**Render:**這一階段主要由 GPU 進行渲染。
**Display:**顯示階段,須要等 render
結束的下一個 RunLoop 觸發顯示。
通常開發當中能影響到的就是 Handle Events 和 Commit Transaction 這兩個階段,這也是開發者接觸最多的部分。Handle Events 就是處理觸摸事件,而 Commit Transaction 這部分中主要進行的是:Layout、Display、Prepare、Commit 等四個具體的操做。
Layout:構建視圖
這個階段主要處理視圖的構建和佈局,具體步驟包括:
layoutSubviews
方法addSubview
方法添加子視圖因爲這個階段是在 CPU 中進行,一般是 CPU 限制或者 IO 限制,因此咱們應該儘可能高效輕量地操做,減小這部分的時間,好比減小非必要的視圖建立、簡化佈局計算、減小視圖層級等。
Display:繪製視圖
這個階段主要是交給 Core Graphics 進行視圖的繪製,注意不是真正的顯示,而是獲得前文所說的圖元 primitives 數據:
drawRect:
方法,那麼會調用重載的 drawRect:
方法,在 drawRect:
方法中手動繪製獲得 bitmap 數據,從而自定義視圖的繪製。注意正常狀況下 Display 階段只會獲得圖元 primitives 信息,而位圖 bitmap 是在 GPU 中根據圖元信息繪製獲得的。可是若是重寫了 drawRect:
方法,這個方法會直接調用 Core Graphics 繪製方法獲得 bitmap 數據,同時系統會額外申請一塊內存,用於暫存繪製好的 bitmap。
因爲重寫了 drawRect:
方法,致使繪製過程從 GPU 轉移到了 CPU,這就致使了必定的效率損失。與此同時,這個過程會額外使用 CPU 和內存,所以須要高效繪製,不然容易形成 CPU 卡頓或者內存爆炸。
Prepare:Core Animation 額外的工做
這一步主要是:圖片解碼和轉換
Commit:打包併發送
這一步主要是:圖層打包併發送到 Render Server。
注意 commit 操做是依賴圖層樹遞歸執行的,因此若是圖層樹過於複雜,commit 的開銷就會很大。這也是咱們但願減小視圖層級,從而下降圖層樹複雜度的緣由。
Render Server 一般是 OpenGL 或者是 Metal。以 OpenGL 爲例,那麼上圖主要是 GPU 中執行的操做,具體主要包括:
使用 Instrument 的 OpenGL ES,能夠對過程進行監控。OpenGL ES tiler utilization 和 OpenGL ES renderer utilization 能夠分別監控 Tiler 和 Renderer 的工做狀況
離屏渲染做爲一個面試高頻問題,時常被說起,下面來從頭至尾講一下離屏渲染。
根據前文,簡化來看,一般的渲染流程是這樣的:
App 經過 CPU 和 GPU 的合做,不停地將內容渲染完成放入 Framebuffer 幀緩衝器中,而顯示屏幕不斷地從 Framebuffer 中獲取內容,顯示實時的內容。
而離屏渲染的流程是這樣的:
與普通狀況下 GPU 直接將渲染好的內容放入 Framebuffer 中不一樣,須要先額外建立離屏渲染緩衝區 Offscreen Buffer,將提早渲染好的內容放入其中,等到合適的時機再將 Offscreen Buffer 中的內容進一步疊加、渲染,完成後將結果切換到 Framebuffer 中。
從上面的流程來看,離屏渲染時因爲 App 須要提早對部份內容進行額外的渲染並保存到 Offscreen Buffer,以及須要在必要時刻對 Offscreen Buffer 和 Framebuffer 進行內容切換,因此會須要更長的處理時間(實際上這兩步關於 buffer 的切換代價都很是大)。
而且 Offscreen Buffer 自己就須要額外的空間,大量的離屏渲染可能早能內存的過大壓力。與此同時,Offscreen Buffer 的總大小也有限,不能超過屏幕總像素的 2.5 倍。
可見離屏渲染的開銷很是大,一旦須要離屏渲染的內容過多,很容易形成掉幀的問題。因此大部分狀況下,咱們都應該儘可能避免離屏渲染。
那麼爲何要使用離屏渲染呢?主要是由於下面這兩種緣由:
對於第一種狀況,也就是不得不使用離屏渲染的狀況,通常都是系統自動觸發的,好比陰影、圓角等等。
最多見的情形之一就是:使用了 mask 蒙版。
如圖所示,因爲最終的內容是由兩層渲染結果疊加,因此必需要利用額外的內存空間對中間的渲染結果進行保存,所以系統會默認觸發離屏渲染。
又好比下面這個例子,iOS 8 開始提供的模糊特效 UIBlurEffectView:
整個模糊過程分爲多步:Pass 1 先渲染須要模糊的內容自己,Pass 2 對內容進行縮放,Pass 3 4 分別對上一步內容進行橫縱方向的模糊操做,最後一步用模糊後的結果疊加合成,最終實現完整的模糊特效。
而第二種狀況,爲了複用提升效率而使用離屏渲染通常是主動的行爲,是經過 CALayer 的 shouldRasterize 光柵化操做實現的。
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.
開啓光柵化後,會觸發離屏渲染,Render Server 會強制將 CALayer 的渲染位圖結果 bitmap 保存下來,這樣下次再須要渲染時就能夠直接複用,從而提升效率。
而保存的 bitmap 包含 layer 的 subLayer、圓角、陰影、組透明度 group opacity 等,因此若是 layer 的構成包含上述幾種元素,結構複雜且須要反覆利用,那麼就能夠考慮打開光柵化。
圓角、陰影、組透明度等會由系統自動觸發離屏渲染,那麼打開光柵化能夠節約第二次及之後的渲染時間。而多層 subLayer 的狀況因爲不會自動觸發離屏渲染,因此相比之下會多花費第一次離屏渲染的時間,可是能夠節約後續的重複渲染的開銷。
不過使用光柵化的時候須要注意如下幾點:
一般來說,設置了 layer 的圓角效果以後,會自動觸發離屏渲染。可是究竟什麼狀況下設置圓角纔會觸發離屏渲染呢?
如上圖所示,layer 由三層組成,咱們設置圓角一般會首先像下面這行代碼同樣進行設置:
view.layer.cornerRadius = 2
複製代碼
根據 cornerRadius - Apple 的描述,上述代碼只會默認設置 backgroundColor 和 border 的圓角,而不會設置 content 的圓角,除非同時設置了 layer.masksToBounds 爲 true(對應 UIView 的 clipsToBounds 屬性):
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’scontents
property; it applies only to the background color and border of the layer. However, setting themasksToBounds
property totrue
causes the content to be clipped to the rounded corners.
若是隻是設置了 cornerRadius 而沒有設置 masksToBounds,因爲不須要疊加裁剪,此時是並不會觸發離屏渲染的。而當設置了裁剪屬性的時候,因爲 masksToBounds 會對 layer 以及全部 subLayer 的 content 都進行裁剪,因此不得不觸發離屏渲染。
view.layer.masksToBounds = true // 觸發離屏渲染的緣由
複製代碼
因此,Texture 也提出在沒有必要使用圓角裁剪的時候,儘可能不去觸發離屏渲染而影響效率:
剛纔說了圓角加上 masksToBounds 的時候,由於 masksToBounds 會對 layer 上的全部內容進行裁剪,從而誘發了離屏渲染,那麼這個過程具體是怎麼回事呢,下面咱們來仔細講一下。
圖層的疊加繪製大概遵循「畫家算法」,在這種算法下會按層繪製,首先繪製距離較遠的場景,而後用繪製距離較近的場景覆蓋較遠的部分。
在普通的 layer 繪製中,上層的 sublayer 會覆蓋下層的 sublayer,下層 sublayer 繪製完以後就能夠拋棄了,從而節約空間提升效率。全部 sublayer 依次繪製完畢以後,整個繪製過程完成,就能夠進行後續的呈現了。假設咱們須要繪製一個三層的 sublayer,不設置裁剪和圓角,那麼整個繪製過程就以下圖所示:
而當咱們設置了 cornerRadius 以及 masksToBounds 進行圓角 + 裁剪時,如前文所述,masksToBounds 裁剪屬性會應用到全部的 sublayer 上。這也就意味着全部的 sublayer 必需要從新被應用一次圓角+裁剪,這也就意味着全部的 sublayer 在第一次被繪製完以後,並不能馬上被丟棄,而必需要被保存在 Offscreen buffer 中等待下一輪圓角+裁剪,這也就誘發了離屏渲染,具體過程以下:
實際上不僅是圓角+裁剪,若是設置了透明度+組透明(layer.allowsGroupOpacity
+layer.opacity
),陰影屬性(shadowOffset
等)都會產生相似的效果,由於組透明度、陰影都是和裁剪相似的,會做用與 layer 以及其全部 sublayer 上,這就致使必然會引發離屏渲染。
除了儘可能減小圓角裁剪的使用,還有什麼別的辦法能夠避免圓角+裁剪引發的離屏渲染嗎?
因爲剛纔咱們提到,圓角引發離屏渲染的本質是裁剪的疊加,致使 masksToBounds 對 layer 以及全部 sublayer 進行二次處理。那麼咱們只要避免使用 masksToBounds 進行二次處理,而是對全部的 sublayer 進行預處理,就能夠只進行「畫家算法」,用一次疊加就完成繪製。
那麼可行的實現方法大概有下面幾種:
drawRect:
,用 CoreGraphics 相關方法,在須要應用圓角時進行手動繪製。不過 CoreGraphics 效率也頗有限,若是須要屢次調用也會有效率問題。總結一下,下面幾種狀況會觸發離屏渲染:
layer.mask
)layer.masksToBounds
/ view.clipsToBounds
)layer.allowsGroupOpacity
/layer.opacity
)layer.shadow*
)layer.shouldRasterize
)UILabel
, CATextLayer
, Core Text
等)不過,須要注意的是,重寫 drawRect:
方法並不會觸發離屏渲染。前文中咱們提到過,重寫 drawRect:
會將 GPU 中的渲染操做轉移到 CPU 中完成,而且須要額外開闢內存空間。但根據蘋果工程師的說法,這和標準意義上的離屏渲染並不同,在 Instrument 中開啓 Color offscreen rendered yellow 調試時也會發現這並不會被判斷爲離屏渲染。
通常來講作點題才能加深理解和鞏固,因此這裏從文章裏簡單提煉了一些,但願能幫到你們:
參考文獻: