通過(guò) 圖形渲染原理 一文挖滤,大致能夠了解圖形渲染過(guò)程中硬件相關(guān)的原理。本文將進(jìn)一步介紹 iOS 開(kāi)發(fā)過(guò)程中圖形渲染原理亿昏。
圖形渲染技術(shù)棧
下圖所示為 iOS App 的圖形渲染技術(shù)棧杠输,App 使用 Core Graphics
、Core Animation
歪玲、Core Image
等框架來(lái)繪制可視化內(nèi)容迁央,這些軟件框架相互之間也有著依賴關(guān)系。這些框架都需要通過(guò) OpenGL 來(lái)調(diào)用 GPU 進(jìn)行繪制读慎,最終將內(nèi)容顯示到屏幕之上漱贱。
iOS 渲染框架
UIKit
UIKit
是 iOS 開(kāi)發(fā)者最常用的框架槐雾,可以通過(guò)設(shè)置 UIKit
組件的布局以及相關(guān)屬性來(lái)繪制界面夭委。
事實(shí)上, UIKit
自身并不具備在屏幕成像的能力募强,其主要負(fù)責(zé)對(duì)用戶操作事件的響應(yīng)(UIView
繼承自 UIResponder
)株灸,事件響應(yīng)的傳遞大體是經(jīng)過(guò)逐層的 視圖樹(shù) 遍歷實(shí)現(xiàn)的。
Core Animation
Core Animation
源自于 Layer Kit
擎值,動(dòng)畫(huà)只是 Core Animation
特性的冰山一角慌烧。
Core Animation
是一個(gè)復(fù)合引擎,其職責(zé)是 盡可能快地組合屏幕上不同的可視內(nèi)容鸠儿,這些可視內(nèi)容可被分解成獨(dú)立的圖層(即 CALayer)屹蚊,這些圖層會(huì)被存儲(chǔ)在一個(gè)叫做圖層樹(shù)的體系之中。從本質(zhì)上而言进每,CALayer
是用戶所能在屏幕上看見(jiàn)的一切的基礎(chǔ)汹粤。
Core Graphics
Core Graphics
基于 Quartz 高級(jí)繪圖引擎,主要用于運(yùn)行時(shí)繪制圖像田晚。開(kāi)發(fā)者可以使用此框架來(lái)處理基于路徑的繪圖嘱兼,轉(zhuǎn)換,顏色管理贤徒,離屏渲染芹壕,圖案汇四,漸變和陰影,圖像數(shù)據(jù)管理踢涌,圖像創(chuàng)建和圖像遮罩以及 PDF 文檔創(chuàng)建通孽,顯示和分析。
當(dāng)開(kāi)發(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ì)算、繪制一系列圖像幀來(lái)實(shí)現(xiàn)動(dòng)畫(huà)钉疫。
Core Image
Core Image
與 Core Graphics
恰恰相反硼讽,Core Graphics
用于在 運(yùn)行時(shí)創(chuàng)建圖像,而 Core Image
是用來(lái)處理 運(yùn)行前創(chuàng)建的圖像 的牲阁。Core Image
框架擁有一系列現(xiàn)成的圖像過(guò)濾器固阁,能對(duì)已存在的圖像進(jìn)行高效的處理。
大部分情況下城菊,Core Image
會(huì)在 GPU 中完成工作备燃,但如果 GPU 忙,會(huì)使用 CPU 進(jìn)行處理凌唬。
OpenGL ES
OpenGL ES
(OpenGL for Embedded Systems并齐,簡(jiǎn)稱 GLES),是 OpenGL 的子集客税。在前面的 圖形渲染原理綜述 一文中提到過(guò) OpenGL 是一套第三方標(biāo)準(zhǔn)况褪,函數(shù)的內(nèi)部實(shí)現(xiàn)由對(duì)應(yīng)的 GPU 廠商開(kāi)發(fā)實(shí)現(xiàn)。
Metal
Metal
類似于 OpenGL ES
更耻,也是一套第三方標(biāo)準(zhǔn)测垛,具體實(shí)現(xiàn)由蘋(píng)果實(shí)現(xiàn)。大多數(shù)開(kāi)發(fā)者都沒(méi)有直接使用過(guò) Metal
秧均,但其實(shí)所有開(kāi)發(fā)者都在間接地使用 Metal
食侮。Core Animation
、Core Image
目胡、SceneKit
锯七、SpriteKit
等等渲染框架都是構(gòu)建于 Metal
之上的。
當(dāng)在真機(jī)上調(diào)試 OpenGL 程序時(shí)讶隐,控制臺(tái)會(huì)打印出啟用 Metal
的日志起胰。根據(jù)這一點(diǎn)可以猜測(cè),Apple 已經(jīng)實(shí)現(xiàn)了一套機(jī)制將 OpenGL 命令無(wú)縫橋接到 Metal
上,由 Metal
擔(dān)任真正于硬件交互的工作效五。
UIView 與 CALayer 的關(guān)系
在前面的 Core Animation
簡(jiǎn)介中提到 CALayer
事實(shí)上是用戶所能在屏幕上看見(jiàn)的一切的基礎(chǔ)地消。為什么 UIKit
中的視圖能夠呈現(xiàn)可視化內(nèi)容?就是因?yàn)?UIKit
中的每一個(gè) UI 視圖控件其實(shí)內(nèi)部都有一個(gè)關(guān)聯(lián)的 CALayer
畏妖,即 backing layer
脉执。
由于這種一一對(duì)應(yīng)的關(guān)系,視圖層級(jí)擁有 視圖樹(shù) 的樹(shù)形結(jié)構(gòu)戒劫,對(duì)應(yīng) CALayer
層級(jí)也擁有 圖層樹(shù) 的樹(shù)形結(jié)構(gòu)半夷。
其中,視圖的職責(zé)是 創(chuàng)建并管理 圖層迅细,以確保當(dāng)子視圖在層級(jí)關(guān)系中 添加或被移除 時(shí)巫橄,其關(guān)聯(lián)的圖層在圖層樹(shù)中也有相同的操作,即保證視圖樹(shù)和圖層樹(shù)在結(jié)構(gòu)上的一致性茵典。
那么為什么 iOS 要基于 UIView 和 CALayer 提供兩個(gè)平行的層級(jí)關(guān)系呢湘换?
其原因在于要做 職責(zé)分離,這樣也能避免很多重復(fù)代碼统阿。在 iOS 和 Mac OS X 兩個(gè)平臺(tái)上彩倚,事件和用戶交互有很多地方的不同,基于多點(diǎn)觸控的用戶界面和基于鼠標(biāo)鍵盤(pán)的交互有著本質(zhì)的區(qū)別扶平,這就是為什么 iOS 有 UIKit
和 UIView
帆离,對(duì)應(yīng) Mac OS X 有 AppKit
和 NSView
的原因。它們?cè)诠δ苌虾芟嗨平岢危窃趯?shí)現(xiàn)上有著顯著的區(qū)別哥谷。
實(shí)際上,這里并不是兩個(gè)層級(jí)關(guān)系概而,而是四個(gè)呼巷。每一個(gè)都扮演著不同的角色囱修。除了 視圖樹(shù) 和 圖層樹(shù)赎瑰,還有 呈現(xiàn)樹(shù) 和 渲染樹(shù)。
CALayer
那么為什么 CALayer
可以呈現(xiàn)可視化內(nèi)容呢破镰?因?yàn)?CALayer
基本等同于一個(gè) 紋理餐曼。紋理是 GPU 進(jìn)行圖像渲染的重要依據(jù)。
在 圖形渲染原理 中提到紋理本質(zhì)上就是一張圖片鲜漩,因此 CALayer
也包含一個(gè) contents
屬性指向一塊緩存區(qū)源譬,稱為 backing store
,可以存放位圖(Bitmap)孕似。iOS 中將該緩存區(qū)保存的圖片稱為 寄宿圖踩娘。
圖形渲染流水線支持從頂點(diǎn)開(kāi)始進(jìn)行繪制(在流水線中,頂點(diǎn)會(huì)被處理生成紋理)喉祭,也支持直接使用紋理(圖片)進(jìn)行渲染窄绒。相應(yīng)地愤惰,在實(shí)際開(kāi)發(fā)中峰弹,繪制界面也有兩種方式:一種是 手動(dòng)繪制;另一種是 使用圖片翘紊。
對(duì)此,iOS 中也有兩種相應(yīng)的實(shí)現(xiàn)方式:
- 使用圖片:contents image
- 手動(dòng)繪制:custom drawing
Contents Image
Contents Image 是指通過(guò) CALayer
的 contents
屬性來(lái)配置圖片藐唠。然而帆疟,contents
屬性的類型為 id
。在這種情況下宇立,可以給 contents
屬性賦予任何值踪宠,app 仍可以編譯通過(guò)。但是在實(shí)踐中妈嘹,如果 content
的值不是 CGImage
殴蓬,得到的圖層將是空白的。
既然如此蟋滴,為什么要將 contents
的屬性類型定義為 id
而非 CGImage
染厅。這是因?yàn)樵?Mac OS 系統(tǒng)中,該屬性對(duì) CGImage
和 NSImage
類型的值都起作用津函,而在 iOS 系統(tǒng)中肖粮,該屬性只對(duì) CGImage
起作用。
本質(zhì)上尔苦,contents
屬性指向的一塊緩存區(qū)域涩馆,稱為 backing store
,可以存放 bitmap 數(shù)據(jù)允坚。
Custom Drawing
Custom Drawing 是指使用 Core Graphics
直接繪制寄宿圖魂那。實(shí)際開(kāi)發(fā)中,一般通過(guò)繼承 UIView
并實(shí)現(xiàn) -drawRect:
方法來(lái)自定義繪制稠项。
雖然 -drawRect:
是一個(gè) UIView
方法涯雅,但事實(shí)上都是底層的 CALayer
完成了重繪工作并保存了產(chǎn)生的圖片。下圖所示為 -drawRect:
繪制定義寄宿圖的基本原理展运。
-
UIView
有一個(gè)關(guān)聯(lián)圖層活逆,即CALayer
。 -
CALayer
有一個(gè)可選的delegate
屬性拗胜,實(shí)現(xiàn)了CALayerDelegate
協(xié)議蔗候。UIView
作為CALayer
的代理實(shí)現(xiàn)了CALayerDelegae
協(xié)議。 - 當(dāng)需要重繪時(shí)埂软,即調(diào)用
-drawRect:
锈遥,CALayer
請(qǐng)求其代理給予一個(gè)寄宿圖來(lái)顯示。 -
CALayer
首先會(huì)嘗試調(diào)用-displayLayer:
方法,此時(shí)代理可以直接設(shè)置contents
屬性所灸。
- (void)displayLayer:(CALayer *)layer;
- 如果代理沒(méi)有實(shí)現(xiàn)
-displayLayer:
方法儿礼,CALayer
則會(huì)嘗試調(diào)用-drawLayer:inContext:
方法。在調(diào)用該方法前庆寺,CALayer
會(huì)創(chuàng)建一個(gè)空的寄宿圖(尺寸由bounds
和contentScale
決定)和一個(gè)Core Graphics
的繪制上下文蚊夫,為繪制寄宿圖做準(zhǔn)備,作為ctx
參數(shù)傳入懦尝。
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
- 最后知纷,由
Core Graphics
繪制生成的寄宿圖會(huì)存入backing store
。
Core Animation 流水線
通過(guò)前面的介紹陵霉,我們知道了 CALayer
的本質(zhì)琅轧,那么它是如何調(diào)用 GPU 并顯示可視化內(nèi)容的呢?下面我們就需要介紹一下 Core Animation 流水線的工作原理踊挠。
事實(shí)上乍桂,app 本身并不負(fù)責(zé)渲染,渲染則是由一個(gè)獨(dú)立的進(jìn)程負(fù)責(zé)效床,即 Render Server
進(jìn)程睹酌。
App 通過(guò) IPC 將渲染任務(wù)及相關(guān)數(shù)據(jù)提交給 Render Server
。Render Server
處理完數(shù)據(jù)后剩檀,再傳遞至 GPU憋沿。最后由 GPU 調(diào)用 iOS 的圖像設(shè)備進(jìn)行顯示。
Core Animation 流水線的詳細(xì)過(guò)程如下:
- 首先沪猴,由 app 處理事件(Handle Events)辐啄,如:用戶的點(diǎn)擊操作,在此過(guò)程中 app 可能需要更新 視圖樹(shù)运嗜,相應(yīng)地壶辜,圖層樹(shù) 也會(huì)被更新。
- 其次担租,app 通過(guò) CPU 完成對(duì)顯示內(nèi)容的計(jì)算砸民,如:視圖的創(chuàng)建、布局計(jì)算翩活、圖片解碼阱洪、文本繪制等。在完成對(duì)顯示內(nèi)容的計(jì)算之后菠镇,app 對(duì)圖層進(jìn)行打包,并在下一次 RunLoop 時(shí)將其發(fā)送至
Render Server
承璃,即完成了一次Commit Transaction
操作利耍。 -
Render Server
主要執(zhí)行 Open GL、Core Graphics 相關(guān)程序,并調(diào)用 GPU - GPU 則在物理層上完成了對(duì)圖像的渲染隘梨。
- 最終程癌,GPU 通過(guò) Frame Buffer、視頻控制器等相關(guān)部件轴猎,將圖像顯示在屏幕上嵌莉。
對(duì)上述步驟進(jìn)行串聯(lián),它們執(zhí)行所消耗的時(shí)間遠(yuǎn)遠(yuǎn)超過(guò) 16.67 ms捻脖,因此為了滿足對(duì)屏幕的 60 FPS 刷新率的支持锐峭,需要將這些步驟進(jìn)行分解,通過(guò)流水線的方式進(jìn)行并行執(zhí)行可婶,如下圖所示沿癞。
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)部繪制寄宿圖铣猩,該過(guò)程使用 CPU 和內(nèi)存钻趋。
Prepare
Prepare
階段屬于附加步驟,一般處理圖像的解碼和轉(zhuǎn)換等操作剂习。
Commit
Commit
階段主要將圖層進(jìn)行打包蛮位,并將它們發(fā)送至 Render Server
。該過(guò)程會(huì)遞歸執(zhí)行鳞绕,因?yàn)閳D層和視圖都是以樹(shù)形結(jié)構(gòu)存在失仁。
動(dòng)畫(huà)渲染原理
iOS 動(dòng)畫(huà)的渲染也是基于上述 Core Animation 流水線完成的。這里我們重點(diǎn)關(guān)注 app 與 Render Server
的執(zhí)行流程们何。
日常開(kāi)發(fā)中萄焦,如果不是特別復(fù)雜的動(dòng)畫(huà),一般使用 UIView
Animation 實(shí)現(xiàn)冤竹,iOS 將其處理過(guò)程分為如下三部階段:
- Step 1:調(diào)用
animationWithDuration:animations:
方法 - Step 2:在 Animation Block 中進(jìn)行
Layout
拂封,Display
,Prepare
鹦蠕,Commit
等步驟冒签。 - Step 3:
Render Server
根據(jù) Animation 逐幀進(jìn)行渲染。
參考
- Getting Pixels onto the Screen钟病,中文版(iOS 開(kāi)發(fā):繪制像素到屏幕)
- 深入理解 iOS Rendering Process
- iOS Core Animation: Advanced Techniques中文譯本
- 關(guān)于drawRect
- iOS 繪圖與動(dòng)畫(huà)原理剖析
- WWDC 2014 Session 419: Advanced Graphics and Animations for iOS Apps
擴(kuò)展閱讀
- 離屏渲染優(yōu)化詳解:實(shí)例示范+性能測(cè)試
- Mastering Offscreen Render
- Optimizing 2D Graphics and Animation Performance
- Polishing Your Interface Rotation Animations
- Core Animation Essentials
- Understanding UIKit Rendering
- iOS: Rendering the UI
- iOS 事件處理機(jī)制與圖像渲染過(guò)程
- iOS 動(dòng)畫(huà)篇:核心動(dòng)畫(huà)
- GPU Framebuffer Memory: Understanding Tiling
- iOS 保持界面流暢的技巧
- OpenGL ES 框架詳細(xì)解析(八) —— OpenGL ES 設(shè)計(jì)指南
- iOS 開(kāi)發(fā)-視圖渲染與性能優(yōu)化
- iOS 視圖萧恕、動(dòng)畫(huà)渲染機(jī)制探究
- iOS 事件處理機(jī)制與圖像渲染過(guò)程
- iOS界面渲染流程
- 界面渲染的整體流程
- iOS圖像處理之Core Graphics和OpenGL ES初見(jiàn)
- WWDC 2012 Session 506: Optimizing 2D Graphics and Animations Performances
- WWDC 2011 Session 421: Core Animation Essentials
- WWDC 2011 Session 129: Practical Drawing for iOS Developers