筆記-iOS渲染框架

006.jpg

UIKit

UIKit是iOS開發(fā)最常用的框架,可以通過設(shè)置UIKit組件的布局以及相關(guān)屬性來繪制界面。
事實(shí)上,UIKit自身并不具備在屏幕成像的能力,其主要負(fù)責(zé)對(duì)用戶操作事件的響應(yīng)(UIView繼承自UIResponder),事件響應(yīng)的傳遞大體是經(jīng)過逐層的視圖樹遍歷實(shí)現(xiàn)的。

Core Animation

Core Animation源自于Layer Kit,動(dòng)畫只是Core Animation的冰山一角。
Core Animation是一個(gè)復(fù)合引擎,其職責(zé)是盡可能快地組合屏幕上不同的可視內(nèi)容,這些可視內(nèi)容可被分解成獨(dú)立的圖層(即CALayer),這些圖層會(huì)被存儲(chǔ)在一個(gè)叫做圖層樹的體系之中。從本質(zhì)上而言,CALayer是用戶所能在屏幕上看見的一切的基礎(chǔ)。

Core Graphics

Core Graphics基于Quartz高級(jí)繪圖引擎,主要用于運(yùn)行時(shí)繪制圖像。開發(fā)者可以使用此框架來處理基于路徑的繪圖,轉(zhuǎn)換,顏色管理,離屏渲染,圖案,漸變和陰影,圖像數(shù)據(jù)管理,圖像創(chuàng)建和圖像遮罩以及PDF文檔創(chuàng)建,顯示和分析。

當(dāng)開發(fā)者需要在運(yùn)行時(shí)創(chuàng)建圖像時(shí),可以使用Core Graphics去繪制。與之相對(duì)的是運(yùn)行前創(chuàng)建圖像,例如用Photoshop提前做好圖片素材直接導(dǎo)入應(yīng)用。相比之下,我們更需要Core Graphics去在運(yùn)行時(shí)實(shí)時(shí)計(jì)算、繪制一系列圖像幀來實(shí)現(xiàn)動(dòng)畫。

Core Image

Core ImageCore Graphics恰恰相反,Core Graphics用于在運(yùn)行時(shí)創(chuàng)建圖像,而Core Image用于處理運(yùn)行前創(chuàng)建的圖像。Core Image框架擁有一系列現(xiàn)成的圖像過濾器,能對(duì)一寸照的圖像進(jìn)行高效的處理。
大部分情況下,Core Image會(huì)在GPU中完成工作,如果GPU忙,會(huì)使用CPU進(jìn)行處理。

OpenGL ES

OpenGL ESOpenGL的子集。在圖形渲染原理一文中提到過OpenGL是一套第三方標(biāo)準(zhǔn),函數(shù)的內(nèi)部實(shí)現(xiàn)由對(duì)應(yīng)的GPU廠商開發(fā)實(shí)現(xiàn)。

UIView與CALayer的關(guān)系

CALayer事實(shí)上是用戶所能在屏幕上看見的一切的基礎(chǔ)。為什么UIKit中的視圖能夠呈現(xiàn)可視化內(nèi)容,就是因?yàn)?code>UIKit中的每一個(gè)UI視圖控件其實(shí)內(nèi)部都有一個(gè)關(guān)聯(lián)的CALayer,即backing layer。
由于這種一一對(duì)應(yīng)的關(guān)系,視圖層級(jí)有用視圖樹的樹形結(jié)構(gòu),對(duì)應(yīng)CALayer層級(jí)也擁有圖層樹的樹形結(jié)構(gòu)。

其中,視圖的職責(zé)是創(chuàng)建并管理圖層,以確保當(dāng)子視圖在層級(jí)關(guān)系中添加或被移除時(shí),其關(guān)聯(lián)的圖層在圖層樹中也有相同的操作,即保證視圖樹和圖層樹在結(jié)構(gòu)上的一致性。

為什么iOS要基于UIView和CALayer提供兩個(gè)平行的層級(jí)關(guān)系呢?

其原因在于要做職責(zé)分離,這樣也能避免很多重復(fù)代碼。在iOS和Mac OSX兩個(gè)平臺(tái)上,事件和用戶交互有很多地方的不同,基于多點(diǎn)觸控的用戶界面和基于鼠標(biāo)鍵盤的交互有著本質(zhì)的區(qū)別,這就是為什么iOS有UIKitUIView,對(duì)應(yīng)Mac OSX有AppKitNSView的原因。它們?cè)诠δ苌虾芟嗨疲窃趯?shí)現(xiàn)上有著顯著的區(qū)別。

實(shí)際上,這里并不是兩個(gè)層級(jí)關(guān)系,而是四個(gè)。每一個(gè)都扮演著不同的角色。除了視圖樹圖層樹,還有呈現(xiàn)樹渲染樹

CALayer
那么為什么CALayer可以呈現(xiàn)可視化內(nèi)容呢?因?yàn)?code>CALayer基本等同于一個(gè)紋理。紋理是GPU進(jìn)行圖像渲染的重要依據(jù)。

圖形渲染原理中提到紋理本質(zhì)上就是一張圖片,因此CALayer也包含一個(gè)contents屬性指向一塊緩存區(qū),稱為backing store,可以存放位圖(Bitmap)。iOS中將該緩存區(qū)保存的圖片稱為寄宿圖。

image

圖形渲染流水線支持從頂點(diǎn)開始進(jìn)行繪制(在流水線中,頂點(diǎn)會(huì)被處理生成紋理),也支持直接使用紋理(圖片)進(jìn)行渲染。相應(yīng)地,在實(shí)際開發(fā)中,繪制界面也有兩種方式: 一種是手動(dòng)繪制;另一種是使用圖片。

對(duì)此,iOS中也有兩種相應(yīng)的實(shí)現(xiàn)方式:

  • 使用圖片:contents image
  • 手動(dòng)繪制:custom drawing

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

既然如此,為什么要將contents的屬性類型定義為id而非CGImage。因?yàn)樵贛ac OS系統(tǒng)中,該屬性對(duì)CGImageNSImage類型的值都起作用,而在iOS系統(tǒng)中,該屬性只對(duì)CGImage起作用。

本質(zhì)上,contents屬性指向的一塊緩存區(qū)域,稱為backing store,可以存放bitmap數(shù)據(jù)。

Custom Drawing
Custom Drawing是指使用Core Graphics直接繪制寄宿圖。實(shí)際開發(fā)中,一般通過繼承UIView并實(shí)現(xiàn)-drawRect:方法來自定義繪制。

雖然-drawRect:是一個(gè)UIView方法,但事實(shí)上都是底層的CALayer完成了重繪工作并保存了產(chǎn)生的圖片。
下圖所示為drawRect:繪制定義寄宿圖的基本原理

image

  • UIView有一個(gè)關(guān)聯(lián)圖層,即CALayer
  • CALayer有一個(gè)可選的delegate屬性,實(shí)現(xiàn)了CALayerDelegate協(xié)議。UIView作為CALayer的代理實(shí)現(xiàn)了CALayerDelegate協(xié)議。
  • 當(dāng)需要重繪時(shí),即調(diào)用-drawRect:,CALayer請(qǐng)求其代理給予一個(gè)寄宿圖來顯示。
  • CALayer首先會(huì)嘗試調(diào)用-displayLayer:方法,此時(shí)代理可以直接設(shè)置contents屬性。
- (void)displayLayer:(CALayer *)layer;
  • 如果代理沒有實(shí)現(xiàn)-displayLayer:方法,CALayer則會(huì)嘗試調(diào)用-drawLayer:inContext:方法。在調(diào)用該方法前,CALayer會(huì)創(chuàng)建一個(gè)空的寄宿圖(尺寸由boundscontentScale決定)和一個(gè)Core Graphics的繪制上下文,為繪制寄宿圖做準(zhǔn)備,作為ctx參數(shù)傳入。
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
  • 最后,有Core Graphics繪制生成的寄宿圖會(huì)存入backing store。

Core Animation 流水線

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

image

事實(shí)上,app本身并不負(fù)責(zé)渲染,渲染則是由一個(gè)獨(dú)立的進(jìn)程負(fù)責(zé),即Render Server進(jìn)程。

App通過IPC將渲染任務(wù)及相關(guān)數(shù)據(jù)提交給Render Server。Render Server處理完數(shù)據(jù)后,再傳遞至GPU。最后由GPU調(diào)用iOS的圖像設(shè)備進(jìn)行顯示。

Core Animation流水線的詳細(xì)過程如下:

  • 首先,由app處理事件(Handle Events),如:用戶點(diǎn)擊操作,在此過程中app可能需要更新視圖樹,相應(yīng)地,圖層樹也會(huì)被更新。
  • 其次,app通過CPU完成對(duì)顯示內(nèi)容的計(jì)算,如:視圖的創(chuàng)建、布局計(jì)算、圖片解碼、文本繪制等。在完成對(duì)現(xiàn)實(shí)內(nèi)容的計(jì)算之后,app對(duì)圖層進(jìn)行打包,并在下一次RunLoop時(shí)將其發(fā)送至Render Server,即完成了一次commit Transaction操作。
  • Render Server主要執(zhí)行OpenGL、Core Graphics相關(guān)程序,并調(diào)用GPU。
  • GPU則在物理層上完成了對(duì)圖像的渲染。
  • 最終,GPU通過Frame Buffer、視頻控制器等相關(guān)部件,將圖像顯示在屏幕上。

對(duì)上述步驟進(jìn)行串聯(lián),他們執(zhí)行所消耗的時(shí)間圓圓超過16.67ms,因此為了滿足對(duì)屏幕的60FPS刷新率的支持,需要將這些步驟進(jìn)行分解,通過流水線的方式并行執(zhí)行,如下圖:


image

Commit Transaction

Core Animation流水線中,app調(diào)用Render Server前的最后一步Commit Transaction其實(shí)可以細(xì)分為4個(gè)步驟:

  • Layout
  • Display
  • Prepare
  • Commit

Layout

Layout階段主要進(jìn)行視圖構(gòu)建,包括:LayoutSubviews方法的重載,addSubview:方法填充子視圖等。

Display

Display階段主要進(jìn)行視圖繪制,這里僅僅是設(shè)置成像的圖元數(shù)據(jù)。重載視圖的drawRect:方法可以自定義UIView的顯示,其原理是在drawRect:方法內(nèi)部繪制寄宿圖,該過程使用GPU和內(nèi)存。

Prepare

Prepare階段屬于附加步驟,一般處理圖像的解碼和轉(zhuǎn)碼等操作。

Commit

commit階段主要將圖層進(jìn)行打包,并將它們發(fā)送至Render Server。該過程會(huì)遞歸執(zhí)行,因?yàn)閳D層和視圖都是以樹形結(jié)構(gòu)存在。

動(dòng)畫渲染原理

iOS動(dòng)畫的渲染也是基于上述Core Animation流水線完成的。這里我們重點(diǎn)關(guān)注app與Render Server的執(zhí)行流程。

日常開發(fā)中,如果不是特別的復(fù)雜動(dòng)畫,一般使用UIView Animation實(shí)現(xiàn),iOS將其處理過程分為如下三部階段:

  • Step1:調(diào)用animationWithDuration:animations:方法
  • Step2:在Animation Block中進(jìn)行Layout,Display,Prepare,Commit等步驟。
  • Step3:Render Server根據(jù)Animation逐幀進(jìn)行渲染。
image

參考博客 iOS圖像渲染原理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容