使用 GPU 渲染圖形的根本緣由就是速度問題。GPU 優秀的並行計算能力使其可以快速將圖形結果計算出來並在屏幕的全部像素中進行顯示。ios
介紹屏幕圖像顯示的原理,須要先從 CRT 顯示器原理提及,以下圖所示。CRT 的電子槍從上到下逐行掃描,掃描完成後顯示器就呈現一幀畫面。而後電子槍回到初始位置進行下一次掃描。爲了同步顯示器的顯示過程和系統的顯示控制器,顯示器會用硬件時鐘產生一系列的定時信號。當電子槍換行進行掃描時,顯示器會發出一個水平同步信號(horizonal synchronization),簡稱 HSync;而當一幀畫面繪製完成後,電子槍回覆到原位,準備畫下一幀前,顯示器會發出一個垂直同步信號(vertical synchronization),簡稱 VSync。顯示器一般以固定頻率進行刷新,這個刷新率就是 VSync 信號產生的頻率。雖然如今的顯示器基本都是液晶顯示屏了,但其原理基本一致。緩存
下圖所示爲常見的 CPU、GPU、顯示器工做方式。CPU 計算好顯示內容提交至 GPU,GPU 渲染完成後將渲染結果存入幀緩衝區,顯示控制器會按照 VSync
信號逐幀讀取幀緩衝區的數據,通過數據轉換後最終由顯示器進行顯示。markdown
因此,顯示一個畫面須要兩步完成:app
這兩步工做都須要時間,而且能夠並行執行,由於具體執行這兩個過程的硬件是相互獨立的(CPU/顯卡
和 顯示控制器
)。可是這兩個工做的耗時是不一樣的。 CPU 以及顯卡每秒能計算出的畫面數量是根據硬件性能決定的。 可是顯示器每秒刷新頻率是固定的(通常是60hz
,因此每隔16.667ms就會刷新一次)。框架
因爲存在兩邊速率不統一的問題,因此引入了幀緩衝區(FrameBuffer)
的概念。函數
最簡單的狀況下,幀緩衝區只有一個。此時,幀緩衝區的讀取和刷新都都會有比較大的效率問題。爲了解決效率問題,GPU 一般會引入兩個緩衝區,即 雙緩衝機制
。在這種狀況下,GPU 會預先渲染一幀放入一個緩衝區中,用於顯示控制器的讀取。當下一幀渲染完畢後,GPU 會直接把顯示控制器的指針指向第二個緩衝器。oop
根據蘋果的官方文檔描述,iOS 設備會始終使用 Vsync + Double Buffering
(垂直同步+雙緩衝) 的策略。佈局
雙緩衝雖然能解決效率問題,但會引入一個新的問題。當顯示控制器還未讀取完成時,即屏幕內容剛顯示一半時,GPU 將新的一幀內容提交到幀緩衝區並把兩個緩衝區進行交換後,顯示控制器就會把新的一幀數據的下半段顯示到屏幕上,形成畫面撕裂現象,以下圖:性能
爲了解決這個問題,GPU 一般有一個機制叫作垂直同步
(簡寫也是 V-Sync
),當開啓垂直同步
後,GPU 會等待顯示器的 VSync
信號發出後,才進行新的一幀渲染和緩衝區更新。這樣能解決畫面撕裂現象,也增長了畫面流暢度,但須要消費更多的計算資源,也會帶來部分延遲。優化
開啓了垂直同步
後,理想情況下 CPU 和 GPU 能夠在16ms內處理完每一幀的渲染。可是若是顯卡的幀率小於屏幕的刷新率,CPU 和 GPU 處理完一幀的渲染的時間超過了16ms,就會發生掉幀的狀況。那一幀會被丟棄,等待下一次機會再顯示,而這時顯示屏會保留以前的內容不變。這就是界面卡頓的緣由。
此時顯示控制器佔用一個 Buffer ,GPU 佔用一個 Buffer 。兩個Buffer都被佔用,致使 CPU 空閒下來浪費了資源,由於垂直同步的緣由,只有到了 VSync
信號的時間點, CPU 才能觸發繪製工做。
在Android4.1系統開始,引入了三緩衝+垂直同步
的機制。因爲多加了一個 Buffer,實現了 CPU 跟 GPU 並行,即可以作到了只在開始掉一幀,後續卻不掉幀,雙緩衝
充分利用16ms作到低延時,三緩衝
保障了其穩定性。
iOS App 的圖形渲染使用了 Core Graphics
、Core Animation
、Core Image
等框架來繪製可視化內容,這些軟件框架相互之間也有着依賴關係。這些框架都須要經過 OpenGL
來調用 GPU 進行繪製,最終將內容顯示到屏幕之上。
UIKit
是 iOS 開發者最經常使用的框架,能夠經過設置 UIKit
組件的佈局以及相關屬性來繪製界面。
事實上, UIKit
自身並不具有在屏幕成像的能力,其主要負責對用戶操做事件的響應(UIView
繼承自 UIResponder
),事件響應的傳遞大致是通過逐層的 視圖樹
遍歷實現的。
Core Animation
源自於 Layer Kit
,動畫只是 Core Animation
特性的冰山一角。
Core Animation
是一個複合引擎,其職責是 儘量快地組合屏幕上不一樣的可視內容,這些可視內容可被分解成獨立的圖層(即 CALayer
),這些圖層會被存儲在一個叫作圖層樹
的體系之中。從本質上而言,CALayer
是用戶所能在屏幕上看見的一切的基礎。
Core Graphics
基於 Quartz
高級繪圖引擎,主要用於運行時繪製圖像。開發者可使用此框架來處理基於路徑的繪圖,轉換,顏色管理,離屏渲染,圖案,漸變和陰影,圖像數據管理,圖像建立和圖像遮罩以及 PDF 文檔建立,顯示和分析。
當開發者須要在 運行時建立圖像
時,可使用 Core Graphics
去繪製。與之相對的是 運行前建立圖像
,例如用 Photoshop 提早作好圖片素材直接導入應用。相比之下,咱們更須要 Core Graphics
去在運行時實時計算、繪製一系列圖像幀來實現動畫。
Core Image
與 Core Graphics
偏偏相反,Core Graphics
用於在 運行時建立圖像
,而 Core Image
是用來處理 運行前建立的圖像
的。Core Image
框架擁有一系列現成的圖像過濾器,能對已存在的圖像進行高效的處理。
大部分狀況下,Core Image
會在 GPU 中完成工做,但若是 GPU 忙,會使用 CPU 進行處理。
OpenGL ES(OpenGL for Embedded Systems,簡稱 GLES)
,是 OpenGL
的子集。 OpenGL
是一套第三方標準,函數的內部實現由對應的 GPU 廠商開發實現。
Metal
相似於 OpenGL ES
,也是一套第三方標準,具體實現由蘋果實現。大多數開發者都沒有直接使用過 Metal
,但其實全部開發者都在間接地使用 Metal
。Core Animation
、Core Image
、SceneKit
、SpriteKit
等等渲染框架都是構建於 Metal
之上的。
當在真機上調試 OpenGL
程序時,控制檯會打印出啓用 Metal
的日誌。根據這一點能夠猜想,Apple
已經實現了一套機制將 OpenGL
命令無縫橋接到 Metal
上,由 Metal
擔任真正於硬件交互的工做。
CALayer
是用戶所能在屏幕上看見的一切的基礎,用來存放位圖(Bitmap)
。UIKit
中的每個 UI 視圖控件(UIView
)其實內部都有一個關聯的 CALayer
,即 backing layer
。
因爲這種一一對應的關係,視圖(UIView
)層級擁有 視圖樹
的樹形結構,對應 CALayer
層級也擁有 圖層樹
的樹形結構。
視圖(UIView
)的職責是 建立並管理
圖層,以確保當子視圖在層級關係中 添加或被移除
時,其關聯的圖層在圖層樹中也有相同的操做,即保證視圖樹和圖層樹在結構上的一致性。
那麼爲何 iOS 要基於 UIView 和 CALayer 提供兩個平行的層級關係呢?
其緣由在於要作 職責分離,這樣也能避免不少重複代碼。在 iOS
和 Mac OS X
兩個平臺上,事件和用戶交互有不少地方的不一樣,基於多點觸控的用戶界面和基於鼠標鍵盤的交互有着本質的區別,這就是爲何 iOS
有 UIKit
和 UIView
,對應 Mac OS X
有 AppKit
和 NSView
的緣由。它們在功能上很類似,可是在實現上有着顯著的區別。
在 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;
複製代碼
contents
提供了 layer 的內容,是一個指針類型,在 iOS
中的類型就是 CGImageRef
(在 OS X
中還能夠是 NSImage
)。CALayer
中的 contents
屬性保存了由設備渲染流水線渲染好的位圖 bitmap
(一般也被稱爲 backing store
),而當設備屏幕進行刷新時,會從 CALayer
中讀取生成好的 bitmap
,進而呈現到屏幕上。
圖形渲染流水線支持從頂點開始進行繪製(在流水線中,頂點會被處理生成紋理
),也支持直接使用紋理(圖片)
進行渲染。相應地,在實際開發中,繪製界面也有兩種方式:一種是 手動繪製
;另外一種是 使用圖片
。
Contents Image Contents Image
是指經過 CALayer
的 contents
屬性來配置圖片。然而,contents
屬性的類型爲 id
。在這種狀況下,能夠給 contents
屬性賦予任何值,app 仍能夠編譯經過。可是在實踐中,若是 content
的值不是 CGImage
,獲得的圖層將是空白的。
本質上,contents
屬性指向的一塊緩存區域,稱爲 backing store
,能夠存放 bitmap
數據。
Custom Drawing Custom Drawing
是指使用 Core Graphics
直接繪製寄宿圖
。實際開發中,通常經過繼承 UIView
並實現 -drawRect:
方法來自定義繪製。
雖然 -drawRect:
是一個 UIView
方法,但事實上都是底層的 CALayer
完成了重繪工做並保存了產生的圖片。下圖所示爲 -drawRect:
繪製定義寄宿圖
的基本原理。
UIView
有一個關聯圖層,即 CALayer
。CALayer
有一個可選的 delegate
屬性,實現了 CALayerDelegate
協議。UIView
做爲 CALayer
的代理實現了 CALayerDelegae
協議。-drawRect:
,CALayer
請求其代理給予一個寄宿圖來顯示。CALayer
首先會嘗試調用 -displayLayer:
方法,此時代理能夠直接設置 contents
屬性。- (void)displayLayer:(CALayer *)layer;
複製代碼
-displayLayer:
方法,CALayer
則會嘗試調用 -drawLayer:inContext:
方法。在調用該方法前,CALayer
會建立一個空的寄宿圖(尺寸由 bounds
和 contentScale
決定)和一個 Core Graphics
的繪製上下文,爲繪製寄宿圖作準備,做爲 context
參數傳入。- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
複製代碼
Core Graphics
繪製生成的寄宿圖會存入 backing store
。UIView
是 app 中的基本組成結構,定義了一些統一的規範。它會負責內容的渲染以及,處理交互事件。
CALayer
是 UIView
的屬性之一,負責渲染和動畫,提供可視內容的呈現。UIView
提供了對 CALayer
部分功能的封裝,同時也另外負責了交互事件的處理。
相同的層級結構:咱們對 UIView
的層級結構很是熟悉,因爲每一個 UIView
都對應 CALayer
負責頁面的繪製,因此 CALayer
也具備相應的層級結構。
部分效果的設置:由於 UIView
只對 CALayer
的部分功能進行了封裝,而另外一部分如圓角、陰影、邊框等特效都須要經過調用 layer 屬性來設置。
是否響應點擊事件:CALayer
不負責點擊事件,因此不響應點擊事件,而 UIView
會響應。
不一樣繼承關係:CALayer
繼承自 NSObject
,UIView
因爲要負責交互事件,因此繼承自 UIResponder
。
事實上,app 自己並不負責渲染,渲染則是由一個獨立的進程負責,即 Render Server 進程。
App 經過 IPC 將渲染任務及相關數據提交給 Render Server
。Render Server
處理完數據後,再傳遞至 GPU。最後由 GPU 調用 iOS 的圖像設備進行顯示。
RunLoop
時將其發送至 Render Server
,即完成了一次 Commit Transaction
操做。Render Server
主要執行 Open GL
、Core Graphics
相關程序,並調用 GPU。Frame Buffer
、視頻控制器
等相關部件,將圖像顯示在屏幕上。對上述步驟進行串聯,它們執行所消耗的時間遠遠超過 16.67 ms,所以爲了知足對屏幕的 60 FPS 刷新率的支持,須要將這些步驟進行分解,經過流水線的方式進行並行執行,以下圖所示。
CoreAnimation
做爲一個複合引擎,將不一樣的視圖層組合在屏幕中,而且存儲在圖層樹
中,向咱們展現了全部屏幕上的一切。
整個過程其實經歷了三個樹狀結構,才顯示到了屏幕上:模型樹-->呈現樹-->渲染樹
層級關係樹中除了 視圖樹
和 圖層樹
,還有 呈現樹
和 渲染樹
。他們各自都有各自的職責。
CALayer
的-presentationLayer
方法來訪問對應的呈現樹圖層。注意呈現圖層僅僅當圖層首次被提交(就是首次第一次在屏幕上顯示)的時候建立,因此在那以前調用-presentationLayer
將會返回nil。- (nullable instancetype)presentationLayer;
複製代碼
–modelLayer
將會返回它正在呈現所依賴的CALayer
。一般在一個圖層上調用-modelLayer
會返回self
(實際上咱們已經建立的原始圖層就是一種數據模型)。- (instancetype)modelLayer;
複製代碼
一般,咱們操做的是模型樹modelLayer
,在重繪週期最後,咱們會將模型樹相關內容(層次結構、圖層屬性和動畫)序列化,經過IPC傳遞給專門負責屏幕渲染的渲染進程。渲染進程拿到數據並反序列化出樹狀結構--呈現樹。這個呈現圖層其實是模型圖層的複製,可是它的屬性值表明了在任何指定時刻當前外觀效果。換句話說,能夠經過呈現圖層的值來獲取當前屏幕上真正顯示出來的值。
當模型樹modelLayer
上帶有動畫特徵時,提交到渲染進程後,渲染進程會根據動畫特徵,不斷修改呈現樹presentationLayer
上的圖層屬性,並同時不斷的在屏幕上渲染出來,這樣咱們就看到了動畫。
若是想讓動畫的圖層響應用戶輸入,可使用-hitTest:
方法來判斷指定圖層是否被觸摸,這時候對呈現圖層而不是模型圖層調用-hitTest:
會顯得更有意義,由於呈現圖層表明了用戶當前看到的圖層位置,而不是當前動畫結束以後的位置。
能夠理解爲modelLayer
負責數據的存儲和獲取,presentationLayer
負責顯示。每次屏幕刷新的時候,presentationLayer
會與modelLayer
狀態同步。
當CAAnimation
加到layer上以後,presentationLayer
每次刷新的時候會去CAAnimation
詢問並同步狀態,CAAnimation
控制presentationLayer
從fromValue
到toValue
來改變值,而動畫結束以後,CAAnimation
會從layer上被移除,此時屏幕刷新的時候presentationLayer
又會同步modelLayer
的狀態,modelLayer
沒有改變,因此又回到了起點。固然咱們能夠經過設置,繼續影響presentationLayer
的狀態。
Core Animation
動畫,即基於事務的動畫,是最多見的動畫實現方式。動畫執行者是專門負責渲染的渲染進程,操做的是呈現樹。咱們應該儘可能使用Core Animation
來控制動畫,由於Core Animation
是充分優化過的:
基於Layer
的繪圖過程當中,Core Animation
經過硬件操做位圖(變換、組合等),產生動畫的速度比軟件操做的方式快不少。
基於View
的繪圖過程當中,view
被改動時會觸發的drawRect:
方法來從新繪製位圖,可是這種方式須要CPU在主線程執行,比較耗時。而Core Animation
則儘量的操做硬件中已緩存的位圖,來實現相同的效果,從而減小了資源損耗。
非CoreA nimation
動畫執行者是當前進程,操做的是模型樹。常見的有定時器動畫和手勢動畫。定時器動畫是在定時週期觸發時修改模型樹的圖層屬性;手勢動畫是手勢事件觸發時修改模型樹的圖層屬性。二者都能達到視圖隨着時間不斷變化的效果,即實現了動畫。
非Core Animation
動畫動畫過程當中實際上不斷改動的是模型樹,而呈現樹僅僅成了模型樹的複製品,狀態與模型樹保持一致。整個過程當中,主要是CPU在主線程不斷調整圖層屬性、佈局計算、提交數據,沒有充分利用到Core Animation
強大的動畫控制功能。