iOS下的圖像渲染原理

ios下的圖像渲染原理

CPU與GPU

  • CPU(Central Processing Unit):現代計算機的三大核心部分之一,做爲整個系統的運算和控制單元。CPU 內部的流水線結構使其擁有必定程度的並行計算能力。
  • GPU(Graphics Processing Unit):一種可進行繪圖運算工做的專用微處理器。GPU 可以生成 2D/3D 的圖形圖像和視頻,從而可以支持基於窗口的操做系統、圖形用戶界面、視頻遊戲、可視化圖像應用和視頻播放。GPU 具備很是強的並行計算能力。

使用 GPU 渲染圖形的根本緣由就是速度問題。GPU 優秀的並行計算能力使其可以快速將圖形結果計算出來並在屏幕的全部像素中進行顯示。ios

屏幕圖像的顯示原理

介紹屏幕圖像顯示的原理,須要先從 CRT 顯示器原理提及,以下圖所示。CRT 的電子槍從上到下逐行掃描,掃描完成後顯示器就呈現一幀畫面。而後電子槍回到初始位置進行下一次掃描。爲了同步顯示器的顯示過程和系統的顯示控制器,顯示器會用硬件時鐘產生一系列的定時信號。當電子槍換行進行掃描時,顯示器會發出一個水平同步信號(horizonal synchronization),簡稱 HSync;而當一幀畫面繪製完成後,電子槍回覆到原位,準備畫下一幀前,顯示器會發出一個垂直同步信號(vertical synchronization),簡稱 VSync。顯示器一般以固定頻率進行刷新,這個刷新率就是 VSync 信號產生的頻率。雖然如今的顯示器基本都是液晶顯示屏了,但其原理基本一致。緩存

屏幕圖像的顯示原理

下圖所示爲常見的 CPU、GPU、顯示器工做方式。CPU 計算好顯示內容提交至 GPU,GPU 渲染完成後將渲染結果存入幀緩衝區,顯示控制器會按照 VSync 信號逐幀讀取幀緩衝區的數據,通過數據轉換後最終由顯示器進行顯示。markdown

CPU、GPU、顯示器工做方式

雙緩衝機制

因此,顯示一個畫面須要兩步完成:app

  • CPU把須要顯示的畫面數據計算出來
  • 顯示器把這些數據顯示出來

這兩步工做都須要時間,而且能夠並行執行,由於具體執行這兩個過程的硬件是相互獨立的(CPU/顯卡顯示控制器)。可是這兩個工做的耗時是不一樣的。 CPU 以及顯卡每秒能計算出的畫面數量是根據硬件性能決定的。 可是顯示器每秒刷新頻率是固定的(通常是60hz,因此每隔16.667ms就會刷新一次)。框架

因爲存在兩邊速率不統一的問題,因此引入了幀緩衝區(FrameBuffer)的概念。函數

最簡單的狀況下,幀緩衝區只有一個。此時,幀緩衝區的讀取和刷新都都會有比較大的效率問題。爲了解決效率問題,GPU 一般會引入兩個緩衝區,即 雙緩衝機制。在這種狀況下,GPU 會預先渲染一幀放入一個緩衝區中,用於顯示控制器的讀取。當下一幀渲染完畢後,GPU 會直接把顯示控制器的指針指向第二個緩衝器。oop

根據蘋果的官方文檔描述,iOS 設備會始終使用 Vsync + Double Buffering(垂直同步+雙緩衝) 的策略。佈局

雙緩衝機制

屏幕撕裂

雙緩衝雖然能解決效率問題,但會引入一個新的問題。當顯示控制器還未讀取完成時,即屏幕內容剛顯示一半時,GPU 將新的一幀內容提交到幀緩衝區並把兩個緩衝區進行交換後,顯示控制器就會把新的一幀數據的下半段顯示到屏幕上,形成畫面撕裂現象,以下圖:性能

屏幕撕裂

爲了解決這個問題,GPU 一般有一個機制叫作垂直同步(簡寫也是 V-Sync),當開啓垂直同步後,GPU 會等待顯示器的 VSync 信號發出後,才進行新的一幀渲染和緩衝區更新。這樣能解決畫面撕裂現象,也增長了畫面流暢度,但須要消費更多的計算資源,也會帶來部分延遲。優化

drawing-with-vsync

drawing-without-vsync

掉幀

開啓了垂直同步後,理想情況下 CPU 和 GPU 能夠在16ms內處理完每一幀的渲染。可是若是顯卡的幀率小於屏幕的刷新率,CPU 和 GPU 處理完一幀的渲染的時間超過了16ms,就會發生掉幀的狀況。那一幀會被丟棄,等待下一次機會再顯示,而這時顯示屏會保留以前的內容不變。這就是界面卡頓的緣由。

此時顯示控制器佔用一個 Buffer ,GPU 佔用一個 Buffer 。兩個Buffer都被佔用,致使 CPU 空閒下來浪費了資源,由於垂直同步的緣由,只有到了 VSync 信號的時間點, CPU 才能觸發繪製工做。

double-buffering

三緩衝機制

在Android4.1系統開始,引入了三緩衝+垂直同步的機制。因爲多加了一個 Buffer,實現了 CPU 跟 GPU 並行,即可以作到了只在開始掉一幀,後續卻不掉幀,雙緩衝充分利用16ms作到低延時,三緩衝保障了其穩定性。

triple-buffering

iOS的渲染框架

iOS App 的圖形渲染使用了 Core GraphicsCore AnimationCore Image 等框架來繪製可視化內容,這些軟件框架相互之間也有着依賴關係。這些框架都須要經過 OpenGL 來調用 GPU 進行繪製,最終將內容顯示到屏幕之上。

ios-rendering-framework-relationship

UIKit

UIKit 是 iOS 開發者最經常使用的框架,能夠經過設置 UIKit 組件的佈局以及相關屬性來繪製界面。

事實上, UIKit 自身並不具有在屏幕成像的能力,其主要負責對用戶操做事件的響應(UIView 繼承自 UIResponder),事件響應的傳遞大致是通過逐層的 視圖樹遍歷實現的。

Core Animation

Core Animation 源自於 Layer Kit,動畫只是 Core Animation 特性的冰山一角。

Core Animation 是一個複合引擎,其職責是 儘量快地組合屏幕上不一樣的可視內容,這些可視內容可被分解成獨立的圖層(即 CALayer),這些圖層會被存儲在一個叫作圖層樹的體系之中。從本質上而言,CALayer 是用戶所能在屏幕上看見的一切的基礎。

Core Graphics

Core Graphics 基於 Quartz 高級繪圖引擎,主要用於運行時繪製圖像。開發者可使用此框架來處理基於路徑的繪圖,轉換,顏色管理,離屏渲染,圖案,漸變和陰影,圖像數據管理,圖像建立和圖像遮罩以及 PDF 文檔建立,顯示和分析。

當開發者須要在 運行時建立圖像 時,可使用 Core Graphics 去繪製。與之相對的是 運行前建立圖像,例如用 Photoshop 提早作好圖片素材直接導入應用。相比之下,咱們更須要 Core Graphics 去在運行時實時計算、繪製一系列圖像幀來實現動畫。

Core Image

Core ImageCore Graphics 偏偏相反,Core Graphics 用於在 運行時建立圖像,而 Core Image 是用來處理 運行前建立的圖像 的。Core Image 框架擁有一系列現成的圖像過濾器,能對已存在的圖像進行高效的處理。

大部分狀況下,Core Image 會在 GPU 中完成工做,但若是 GPU 忙,會使用 CPU 進行處理。

OpenGL ES

OpenGL ES(OpenGL for Embedded Systems,簡稱 GLES),是 OpenGL 的子集。 OpenGL 是一套第三方標準,函數的內部實現由對應的 GPU 廠商開發實現。

Metal

Metal 相似於 OpenGL ES,也是一套第三方標準,具體實現由蘋果實現。大多數開發者都沒有直接使用過 Metal,但其實全部開發者都在間接地使用 MetalCore AnimationCore ImageSceneKitSpriteKit 等等渲染框架都是構建於 Metal 之上的。

當在真機上調試 OpenGL 程序時,控制檯會打印出啓用 Metal 的日誌。根據這一點能夠猜想,Apple 已經實現了一套機制將 OpenGL 命令無縫橋接到 Metal 上,由 Metal 擔任真正於硬件交互的工做。

UIView 與 CALayer

CALayer 是用戶所能在屏幕上看見的一切的基礎,用來存放位圖(Bitmap)UIKit 中的每個 UI 視圖控件(UIView)其實內部都有一個關聯的 CALayer,即 backing layer

因爲這種一一對應的關係,視圖(UIView)層級擁有 視圖樹 的樹形結構,對應 CALayer 層級也擁有 圖層樹 的樹形結構。

視圖(UIView)的職責是 建立並管理 圖層,以確保當子視圖在層級關係中 添加或被移除 時,其關聯的圖層在圖層樹中也有相同的操做,即保證視圖樹和圖層樹在結構上的一致性。

那麼爲何 iOS 要基於 UIView 和 CALayer 提供兩個平行的層級關係呢?

其緣由在於要作 職責分離,這樣也能避免不少重複代碼。在 iOSMac OS X 兩個平臺上,事件和用戶交互有不少地方的不一樣,基於多點觸控的用戶界面和基於鼠標鍵盤的交互有着本質的區別,這就是爲何 iOSUIKitUIView,對應 Mac OS XAppKitNSView 的緣由。它們在功能上很類似,可是在實現上有着顯著的區別。

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;
複製代碼

contents 提供了 layer 的內容,是一個指針類型,在 iOS 中的類型就是 CGImageRef(在 OS X 中還能夠是 NSImage)。CALayer 中的 contents 屬性保存了由設備渲染流水線渲染好的位圖 bitmap(一般也被稱爲 backing store),而當設備屏幕進行刷新時,會從 CALayer 中讀取生成好的 bitmap,進而呈現到屏幕上。

圖形渲染流水線支持從頂點開始進行繪製(在流水線中,頂點會被處理生成紋理),也支持直接使用紋理(圖片)進行渲染。相應地,在實際開發中,繪製界面也有兩種方式:一種是 手動繪製;另外一種是 使用圖片

  • 使用圖片:contents image
  • 手動繪製:custom drawing

Contents Image Contents Image 是指經過 CALayercontents 屬性來配置圖片。然而,contents 屬性的類型爲 id。在這種狀況下,能夠給 contents 屬性賦予任何值,app 仍能夠編譯經過。可是在實踐中,若是 content 的值不是 CGImage ,獲得的圖層將是空白的。

本質上,contents 屬性指向的一塊緩存區域,稱爲 backing store,能夠存放 bitmap 數據。

Custom Drawing Custom Drawing 是指使用 Core Graphics 直接繪製寄宿圖。實際開發中,通常經過繼承 UIView 並實現 -drawRect: 方法來自定義繪製。

雖然 -drawRect: 是一個 UIView 方法,但事實上都是底層的 CALayer 完成了重繪工做並保存了產生的圖片。下圖所示爲 -drawRect: 繪製定義寄宿圖的基本原理。

ios-layer-bitmap-custom-drawing

  • UIView 有一個關聯圖層,即 CALayer
  • CALayer 有一個可選的 delegate 屬性,實現了 CALayerDelegate 協議。UIView 做爲 CALayer 的代理實現了 CALayerDelegae 協議。
  • 當須要重繪時,即調用 -drawRect:CALayer 請求其代理給予一個寄宿圖來顯示。
  • CALayer 首先會嘗試調用 -displayLayer: 方法,此時代理能夠直接設置 contents 屬性。
- (void)displayLayer:(CALayer *)layer;
複製代碼
  • 若是代理沒有實現 -displayLayer: 方法,CALayer 則會嘗試調用 -drawLayer:inContext: 方法。在調用該方法前,CALayer 會建立一個空的寄宿圖(尺寸由 boundscontentScale 決定)和一個 Core Graphics 的繪製上下文,爲繪製寄宿圖作準備,做爲 context 參數傳入。
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
複製代碼
  • 最後,由 Core Graphics 繪製生成的寄宿圖會存入 backing store
UIView

UIView 是 app 中的基本組成結構,定義了一些統一的規範。它會負責內容的渲染以及,處理交互事件。

  • Drawing and animation:繪製與動畫
  • Layout and subview management:佈局與子 view 的管理
  • Event handling:點擊事件處理

CALayerUIView 的屬性之一,負責渲染和動畫,提供可視內容的呈現。UIView 提供了對 CALayer 部分功能的封裝,同時也另外負責了交互事件的處理。

  • 相同的層級結構:咱們對 UIView 的層級結構很是熟悉,因爲每一個 UIView 都對應 CALayer 負責頁面的繪製,因此 CALayer 也具備相應的層級結構。

  • 部分效果的設置:由於 UIView 只對 CALayer 的部分功能進行了封裝,而另外一部分如圓角、陰影、邊框等特效都須要經過調用 layer 屬性來設置。

  • 是否響應點擊事件:CALayer 不負責點擊事件,因此不響應點擊事件,而 UIView 會響應。

  • 不一樣繼承關係:CALayer 繼承自 NSObjectUIView 因爲要負責交互事件,因此繼承自 UIResponder

Core Animation

Core Animation 流水線

事實上,app 自己並不負責渲染,渲染則是由一個獨立的進程負責,即 Render Server 進程。

ios-core-animation-pipeline-steps

App 經過 IPC 將渲染任務及相關數據提交給 Render ServerRender Server 處理完數據後,再傳遞至 GPU。最後由 GPU 調用 iOS 的圖像設備進行顯示。

  • 首先,由 app 處理事件(Handle Events),如:用戶的點擊操做,在此過程當中 app 可能須要更新 視圖樹,相應地,圖層樹 也會被更新。
  • 其次,app 經過 CPU 完成對顯示內容的計算,如:視圖的建立、佈局計算、圖片解碼、文本繪製等。在完成對顯示內容的計算以後,app 對圖層進行打包,並在下一次 RunLoop 時將其發送至 Render Server,即完成了一次 Commit Transaction 操做。
  • Render Server 主要執行 Open GLCore Graphics 相關程序,並調用 GPU。
  • GPU 則在物理層上完成了對圖像的渲染。
  • 最終,GPU 經過 Frame Buffer視頻控制器等相關部件,將圖像顯示在屏幕上。

對上述步驟進行串聯,它們執行所消耗的時間遠遠超過 16.67 ms,所以爲了知足對屏幕的 60 FPS 刷新率的支持,須要將這些步驟進行分解,經過流水線的方式進行並行執行,以下圖所示。

ios-core-animation-pipeline-workflow

圖層樹

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控制presentationLayerfromValuetoValue來改變值,而動畫結束以後,CAAnimation會從layer上被移除,此時屏幕刷新的時候presentationLayer又會同步modelLayer的狀態,modelLayer沒有改變,因此又回到了起點。固然咱們能夠經過設置,繼續影響presentationLayer的狀態。

Core Animation 動畫

Core Animation動畫,即基於事務的動畫,是最多見的動畫實現方式。動畫執行者是專門負責渲染的渲染進程,操做的是呈現樹。咱們應該儘可能使用Core Animation來控制動畫,由於Core Animation是充分優化過的:

基於Layer的繪圖過程當中,Core Animation經過硬件操做位圖(變換、組合等),產生動畫的速度比軟件操做的方式快不少。

基於View的繪圖過程當中,view被改動時會觸發的drawRect:方法來從新繪製位圖,可是這種方式須要CPU在主線程執行,比較耗時。而Core Animation則儘量的操做硬件中已緩存的位圖,來實現相同的效果,從而減小了資源損耗。

非 Core Animation 動畫

CoreA nimation動畫執行者是當前進程,操做的是模型樹。常見的有定時器動畫和手勢動畫。定時器動畫是在定時週期觸發時修改模型樹的圖層屬性;手勢動畫是手勢事件觸發時修改模型樹的圖層屬性。二者都能達到視圖隨着時間不斷變化的效果,即實現了動畫。

Core Animation動畫動畫過程當中實際上不斷改動的是模型樹,而呈現樹僅僅成了模型樹的複製品,狀態與模型樹保持一致。整個過程當中,主要是CPU在主線程不斷調整圖層屬性、佈局計算、提交數據,沒有充分利用到Core Animation強大的動畫控制功能。

相關文章
相關標籤/搜索