iOS 渲染框架

佐籩發表於2018-11-09

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 ESOpenGL的子集。在圖形渲染原理一文中提到過OpenGL是一套第三方標準,函式的內部實現由對應的GPU廠商開發實現。OpenGL / OpenGL ES入門篇,請參考OpenGL/OpenGL ES 入門:圖形API以及專業名詞解析等系列文章

UIView與CALayer的關係

CALayer事實上是使用者所能在螢幕上看見的一切的基礎。為什麼UIKit中的檢視能夠呈現視覺化內容,就是因為UIKit中的每一個UI檢視控制元件其實內部都有一個關聯的CALayer,即backing layer
由於這種一一對應的關係,檢視層級有用檢視樹的樹形結構,對應CALayer層級也擁有圖層樹的樹形結構。

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

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

其原因在於要做職責分離,這樣也能避免很多重複程式碼。在iOS和Mac OSX兩個平臺上,事件和使用者互動有很多地方的不同,基於多點觸控的使用者介面和基於滑鼠鍵盤的互動有著本質的區別,這就是為什麼iOS有UIKitUIView,對應Mac OSX有AppKitNSView的原因。它們在功能上很相似,但是在實現上有著顯著的區別。

實際上,這裡並不是兩個層級關係,而是四個。每一個都扮演著不同的角色。除了檢視樹圖層樹,還有呈現樹渲染樹

CALayer
那麼為什麼CALayer可以呈現視覺化內容呢?因為CALayer基本等同於一個紋理。紋理是GPU進行影像渲染的重要依據。

圖形渲染原理中提到紋理本質上就是一張圖片,因此CALayer也包含一個contents屬性指向一塊快取區,稱為backing store,可以存放點陣圖(Bitmap)。iOS中將該快取區儲存的圖片稱為寄宿圖

iOS 渲染框架

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

對此,iOS中也有兩種相應的實現方式:

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

Contents Image
Contents Image是指通過CALayercontents屬性來配置圖片。然而,contents屬性的型別為id,在這種情況下,可以給contents屬性賦予任何值,app仍可以編譯通過。但是在實踐中,如果contents的值不是CGImage,得到的圖層將是空白的。

既然如此,為什麼要將contents的屬性型別定義為id而非CGImage。因為在Mac OS系統中,該屬性對CGImageNSImage型別的值都起作用,而在iOS系統中,該屬性只對CGImage起作用。

本質上,contents屬性指向的一塊快取區域,稱為backing store,可以存放bitmap資料。

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

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

iOS 渲染框架

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

Core Animation 流水線

介紹一下Core Animation流水線的工作原理:

iOS 渲染框架
事實上,app本身並不負責渲染,渲染則是由一個獨立的程式負責,即Render Server程式。

App通過IPC將渲染任務及相關資料提交給Render ServerRender Server處理完資料後,再傳遞至GPU。最後由GPU呼叫iOS的影像裝置進行顯示。

Core Animation流水線的詳細過程如下:

  • 首先,由app處理事件(Handle Events),如:使用者點選操作,在此過程中app可能需要更新檢視樹,相應地,圖層樹也會被更新。
  • 其次,app通過CPU完成對顯示內容的計算,如:檢視的建立、佈局計算、圖片解碼、文字繪製等。在完成對現實內容的計算之後,app對圖層進行打包,並在下一次RunLoop時將其傳送至Render Server,即完成了一次commit Transaction操作。
  • Render Server主要執行OpenGL、Core Graphics相關程式,並呼叫GPU。
  • GPU則在物理層上完成了對影像的渲染。
  • 最終,GPU通過Frame Buffer、視訊控制器等相關部件,將影像顯示在螢幕上。

對上述步驟進行串聯,他們執行所消耗的時間圓圓超過16.67ms,因此為了滿足對螢幕的60FPS重新整理率的支援,需要將這些步驟進行分解,通過流水線的方式並行執行,如下圖:

iOS 渲染框架

Commit Transaction

Core Animation流水線中,app呼叫Render Server前的最後一步Commit Transaction其實可以細分為4個步驟:

  • Layout
  • Display
  • Prepare
  • Commit

Layout

Layout階段主要進行檢視構建,包括:LayoutSubviews方法的過載,addSubview:方法填充子檢視等。

Display

Display階段主要進行檢視繪製,這裡僅僅是設定成像的圖後設資料。過載檢視的drawRect:方法可以自定義UIView的顯示,其原理是在drawRect:方法內部繪製寄宿圖,該過程使用GPU和記憶體。

Prepare

Prepare階段屬於附加步驟,一般處理影像的解碼和轉碼等操作。

Commit

commit階段主要將圖層進行打包,並將它們傳送至Render Server。該過程會遞迴執行,因為圖層和檢視都是以樹形結構存在。

動畫渲染原理

iOS動畫的渲染也是基於上述Core Animation流水線完成的。這裡我們重點關注app與Render Server的執行流程。

日常開發中,如果不是特別的複雜動畫,一般使用UIView Animation實現,iOS將其處理過程分為如下三部階段:

  • Step1:呼叫animationWithDuration:animations:方法
  • Step2:在Animation Block中進行Layout,Display,Prepare,Commit等步驟。
  • Step3:Render Server根據Animation逐幀進行渲染。

iOS 渲染框架

參考部落格 iOS影像渲染原理

相關文章