本系列文章的重點(diǎn)是關(guān)注在總結(jié)iOS圖形圖像的原理和性能優(yōu)化的常規(guī)解決方案。
事先聲明,本文絕大多數(shù)概念和內(nèi)容均來源于已有素材,但是均經(jīng)過作者消化后總結(jié)歸納。如果你不想麻煩地自己去網(wǎng)絡(luò)一一搜索資料,那么本文將是一個(gè)很好的總結(jié)筆記。閱讀前提是讀者已經(jīng)基本了解View和Layer的操作。如果你對(duì)iOS 2D圖像優(yōu)化已經(jīng)熟練,可以忽略本文。
前一段時(shí)間在review code的時(shí)候,看到一段對(duì)于Collection View的滑動(dòng)性能有一定影響的處理代碼,以前對(duì)于UI性能這部分有一些了解,但是比較粗淺,于是就想系統(tǒng)學(xué)習(xí)下iOS關(guān)于2D 圖形圖像性能的資料,結(jié)果一翻不得了,相關(guān)內(nèi)容越挖越深,擴(kuò)展開來感覺進(jìn)入了一個(gè)浩大的領(lǐng)域。這個(gè)話題的火熱程度可以舉個(gè)簡單例子:在簡書上搜索“Quartz 2D” 或者 “Core Graphic”就有大約5、6百篇文章,如果搜索“iOS 優(yōu)化”甚至有超過1萬條記錄。經(jīng)過前后大約2-3周的零碎時(shí)間,我仔細(xì)搜集整理了20余篇優(yōu)秀資料總結(jié)出本文,給自己留個(gè)研究筆記,也希望能幫助到需要的人。
知己知彼,百戰(zhàn)不殆。
掌握任何一件事情都需要從原理入手。我們先來鞏固iOS 2D Graphic相關(guān)的基本概念和知識(shí)。
1. 關(guān)于iOS 圖像處理的基本概念
1.1 Graphic Frameworks
首先看一張來自Apple的描述圖形處理模塊的經(jīng)典(爛大街)圖:

最上層是UIKit框架,這是服務(wù)于Application的最前端框架,封裝了所有“UI*”空間類和操作;在其之下是Core Animation框架,借助與這個(gè)框架,Apple向開發(fā)人員提供了非常方便的動(dòng)畫處理功能和圖像渲染功能。再往下,分離成基于GPU繪圖的OpenGL ES層和基于CPU繪圖的Core Graphic層。最底層的就是支持最終繪圖的硬件平臺(tái),包括GPU,CPU,緩存,總線等等。
雖然在Apple的文檔里,看起來Core Graphic的層級(jí)在Core Animation之下,和OpenGL是同一個(gè)level,但其實(shí)它們?nèi)咧g的關(guān)系非常緊密,App和它們之間的交互也更加自由,并非被CA層完全攔截在中間。各個(gè)部分之間的工作關(guān)系如下圖:

GPU Driver 是直接和 GPU 交流的代碼塊,使不同的GPU在下一個(gè)層級(jí)上顯示的更為統(tǒng)一,典型的下一層級(jí)有 OpenGL/OpenGL ES. OpenGL(Open Graphics Library) 是一個(gè)提供了 2D 和 3D 圖形渲染的 API。OpenGL 和 GPU 密切的工作以提高GPU的能力,并實(shí)現(xiàn)硬件加速渲染。OpenGL 之上擴(kuò)展出很多東西。在 iOS 上,幾乎所有的東西都是通過 Core Animation 繪制出來,然而在 OS X 上,繞過 Core Animation 直接使用 Core Graphics 繪制的情況并不少見。對(duì)于一些專門的應(yīng)用,尤其是游戲,程序可能直接和 OpenGL/OpenGL ES 交流。
順便提一下,也許你會(huì)在網(wǎng)上看到類似這樣一幅圖:

我個(gè)人覺得這幅圖是有一定問題的,因?yàn)檫@會(huì)給人一個(gè)錯(cuò)覺,好像圖形的處理對(duì)于CPU和GPU而言是分離的,但是實(shí)際上,CPU處理完的數(shù)據(jù)最終都需要提交到GPU。不管是iOS還是Mac,最終所有的繪圖操作都會(huì)通過OpenGL層去操作GPU。
但是這個(gè)圖也有一個(gè)好處,是它通常意義上詮釋了:Core Animation操作基于GPU進(jìn)行“硬繪圖”的OpenGL ES,和基于CPU進(jìn)行“軟繪圖”的Core Graphic。使用Core Graphic繪制會(huì)讓Core Animation 使用CPU創(chuàng)建一張Content"圖片",CPU處理生成這張圖片后交給GPU進(jìn)行顯示。一般來說,GPU做Rendering,Tilering,Compositing,而CPU更多的時(shí)候是做圖層布局處理和協(xié)助GPU做預(yù)處理(預(yù)繪制,動(dòng)畫準(zhǔn)備,動(dòng)畫提交,計(jì)算動(dòng)畫中間值等等)。根據(jù)這張圖你可以有個(gè)直觀的印象就是:調(diào)用CG開頭的API會(huì)觸發(fā)CPU去處理圖像,也就是說這個(gè)會(huì)涉及到后文中關(guān)于影響性能的一個(gè)重要概念:CPU密集型(CPU bound)操作。
CPU和GPU之間真正的關(guān)系可以看下圖:

GPU 需要將每一個(gè) frame 的紋理(位圖)合成在一起(一秒60次)。每一個(gè)紋理會(huì)占用 VRAM(video RAM),所以需要給 GPU 同時(shí)保持紋理的數(shù)量做一個(gè)限制。GPU 在合成方面非常高效,但是某些合成任務(wù)卻比其他更復(fù)雜,并且 GPU在 16.7ms(1/60s)內(nèi)能做的工作也是有限的。為了讓 GPU 訪問數(shù)據(jù),需要將數(shù)據(jù)從 RAM 移動(dòng)到 VRAM 上,一些大型的紋理卻會(huì)非常耗時(shí)。
舉個(gè)例子,比如顯示文本,對(duì) CPU來說會(huì)調(diào)用 Core Text 和 Core Graphics 框架更緊密的集成來根據(jù)文本生成一個(gè)位圖。一旦準(zhǔn)備好,它將會(huì)被作為一個(gè)紋理上傳到 GPU 并準(zhǔn)備顯示出來。當(dāng)你滾動(dòng)或者在屏幕上移動(dòng)文本時(shí),同樣的紋理能夠被復(fù)用,CPU 只需簡單的告訴 GPU 新的位置就行了,所以 GPU 就可以重用存在的紋理了。CPU 并不需要重新渲染文本,并且位圖也不需要重新上傳到 GPU。
在這里,有必要再討論下Quartz。Quartz這個(gè)名詞其實(shí)是一個(gè)從Mac上過來的歷史遺留物,而其本身是Apple窗口服務(wù)器和描畫技術(shù)的一般叫法。在Apple的《Quartz 2D Programming Guide》中,開篇一句話已經(jīng)基本解釋了Quartz和Core Graphic的關(guān)系:
Quartz 2D is an advanced, two-dimensional drawing engine available for iOS application development and to all Mac OS X application environments outside of the kernel. The Quartz 2D API is part of the Core Graphics framework, so you may see Quartz referred to as Core Graphics or, simply, CG.
Quartz 2D是iPhone OS和Mac OS X環(huán)境下的二維繪圖引擎。它其實(shí)是Core Graphic的一個(gè)核心部分。使用Quartz 2D API,你可以:基于路徑的繪圖,透明度繪圖,遮蓋,陰影,透明層,顏色管理,防鋸齒渲染,生成PDF,以及PDF元數(shù)據(jù)相關(guān)處理。在Mac OS X下,Quartz 2D能與其它圖形圖像技術(shù)相結(jié)合——Core Image,Core Video,OpenGL,以及Quick Time。類似的,在iPhone OS下的Quartz 2D也能與其它的圖像和動(dòng)畫技術(shù)相結(jié)合——Core Animation,OpenGL ES,以及UIKit類。
另外,這篇回答詳細(xì)的討論了和Quartz/Core Graphic相關(guān)的framework的列表:
- CoreGraphics.framework
Quartz 2D : API manages the graphic context and implements drawing.
Quartz Services : API provides low level access to the window server. This includes display hardware, resolution, refresh rate, and others.
- QuartzCore.framework
Core Animation : Objective-C API to do 2D animation.
Core Image: image and video processing (filters, warp, transitions).iOS 5
- Quartz.framework (OS X only)
Image Kit: display and edit images.
PDF Kit: display and edit PDFs.
Quartz Composer: display Quartz Composer compositions.
QuickLookUI: preview media elements.
- 其它一些Quartz技術(shù):
Quartz Extreme: GPU acceleration for Quartz Composer.
QuartzGL (aka "Quartz 2D Extreme"): GPU acceleration for Quartz 2D.
只需要記住一點(diǎn),從某種意義上來說,Quartz 和CG可以互相混淆,事實(shí)上,上文的作者也嘲笑了一句“如果Apple的目的是想把大家給搞糊涂那么他們成功地做到了!”
上面主要針對(duì)的是CA層,CG層和GPU、CPU之間的關(guān)系,那么我們?cè)購腃A層往上走,來看看CA層和UI層之間的關(guān)系。
1.2 UIView 和 CALayer
“每一個(gè)成功的男人背后都有一個(gè)女人!”
你在iOS上能看到的,觸碰到的所有東西,都是UIView展示出來的。你剛開始接觸iOS開發(fā)的時(shí)候,使用的最多的,也是UIView,看起來UIView無限風(fēng)光。但是如果認(rèn)為UIView是繪制出的圖像那你就錯(cuò)了!在iOS中,每一個(gè)UIView的背后都有一個(gè)默認(rèn)的CALayer,真正為圖像做出貢獻(xiàn)的,其實(shí)是這個(gè)layer,UIView只是作為一個(gè)視窗容器,用來精簡封裝layer并對(duì)外提供響應(yīng)操作而已。
iOS中UIView和CALayer的關(guān)系如下圖:

CALayer是UIView的基礎(chǔ),所有實(shí)際的繪圖工作都是Layer向其backing store里繪制bit map完成的。而操作View的絕大多數(shù)圖形屬性,其實(shí)都是直接操作的其擁有的layer屬性,比如frame,bounds,backgroundColor等等。
UIView和CALayer都有自己的樹狀結(jié)構(gòu),它們都可以有自己的SubView和SubLayer:

對(duì)于每一個(gè)使用Core Animation的App來說,系統(tǒng)實(shí)際上維護(hù)這3套不同的Layer 樹狀層級(jí):
- layer tree (modal tree):這里是App的代碼直接操縱的tree,你所修改的各種屬性值都是反映在這個(gè)tree里;
- presentation tree:這是一個(gè)中間層,系統(tǒng)在做動(dòng)畫時(shí),所有動(dòng)畫中間態(tài)就都在這一層上更改屬性來完成動(dòng)畫的分動(dòng)作;
- render tree:這是直接對(duì)應(yīng)于提交到render server上進(jìn)行顯示的樹,屏幕上的內(nèi)容對(duì)應(yīng)于該層。

這讓我想起了一句老話:“每一個(gè)成功的男人背后都有一個(gè)成功的女人”,UIView的無限風(fēng)光其實(shí)離不開CALayer在其背后的支持。但是這種每個(gè)View的背后都有一個(gè)Layer的設(shè)定在OS X上并不總是成立。Apple把support layer的View稱作** “l(fā)ayer backed view” ,在OS X上還有一種叫做 "layer hosting view" **。iOS默認(rèn)的都是layer backed view。其它在此不表。
“既生瑜何生亮?!”
到這里你可能會(huì)問,既然有了CALayer為啥還要UIView,這不多余么?!事實(shí)上,這種看似多余的設(shè)計(jì),其背后的藝術(shù)確是非常精妙的,關(guān)于這個(gè)問題,網(wǎng)上有很多介紹,但是我仍然認(rèn)為,在設(shè)計(jì)精髓上,這篇風(fēng)趣的文章是解釋的最深刻的,簡單的說,UIView和CALayer既是把Respond和Drawing分離,把Content和Action分離,把機(jī)制和策略分離。Layer能夠使得iOS更有效的處理繪圖,高幀率的動(dòng)畫,UIView能夠更方便的處理事件和響應(yīng)鏈。它們彼此合作,互相依賴,缺一不可。另外這篇文章在事件響應(yīng)和修改屬性參數(shù)的效果上稍微更進(jìn)一步的解釋了兩者的區(qū)別。
關(guān)于它們的更多內(nèi)容,可以參考Apple文檔《Core Animation Programming Guide》
這里再提一個(gè)特殊的UIView: UIImageView。UIImageView繼承自UIView,但是其有一個(gè)特殊的屬性UIImage。在上面我們提到過,每一個(gè)UIView的Layer都有一個(gè)對(duì)應(yīng)的Backing Store作為其存儲(chǔ)Content的實(shí)際內(nèi)容,而這些內(nèi)容其實(shí)就是一個(gè)CGImage數(shù)據(jù)(更確切的說,是bitmap數(shù)據(jù)),以供GPU讀取展示。而UIImage其實(shí)是CGImage的一個(gè)輕量級(jí)封裝,于是很自然的,在UIImageView中的UIImage對(duì)象直接將自己的CGImage圖片數(shù)據(jù)作為UIVIew的Content,提供給CALayer。

2. iOS UI上進(jìn)行Graphic和Animation繪制的原理
現(xiàn)在我們已經(jīng)了解了關(guān)于Graphic的一些基本對(duì)象結(jié)構(gòu),接下來我們看看Graphic的基本工作原理。
2.1 圖像的繪制
* 2.1.1 Core Animation Pipeline *
前文中的Graphic Framework一節(jié)已經(jīng)說到App通過Core Animation調(diào)度GPU和CPU,最終繪制圖像到屏幕上。那么具體到繪制細(xì)節(jié)中,都需要經(jīng)過哪些具體的步驟呢?2014年的WWDC上Apple向我們提供了一些技術(shù)細(xì)節(jié)可以讓我們管窺一斑。從App調(diào)用Core Animation開始,一直到最終顯示,是一個(gè)嚴(yán)格遵循時(shí)間順序的管道(Pipeline)過程,叫做Core Animation Pipeline:

可以看到除Display之外幾個(gè)關(guān)鍵的參與者:App, Render Server, GPU。前兩者中Core Animation都有介入,App中Core Animation的作用是做具體繪制前的準(zhǔn)備工作,在Render server中Core Animation更多的是負(fù)責(zé)具體的繪制。GPU主要負(fù)責(zé)硬件階段的具體渲染。Render server其實(shí)是一個(gè)獨(dú)立的進(jìn)程,在 iOS 5 以前這個(gè)進(jìn)程叫 SpringBoard,在 iOS 6 之后叫 BackBoard。
圖中的每一個(gè)豎線代表的是一個(gè)VSync信號(hào)。一般而言視頻控制器都是通過VSync信號(hào)來進(jìn)行顯示同步的,每一個(gè)VSync信號(hào)間隔是固定的,這個(gè)時(shí)間是16.67ms,也就是1秒鐘刷新60幀。在 VSync 信號(hào)到來后,系統(tǒng)圖形服務(wù)會(huì)通過 CADisplayLink 等機(jī)制通知 App,App 主線程開始在 CPU 中計(jì)算顯示內(nèi)容,比如視圖的創(chuàng)建、布局計(jì)算、圖片解碼、文本繪制等。隨后 CPU 會(huì)將計(jì)算好的內(nèi)容提交到 GPU 去,由 GPU 進(jìn)行變換、合成、渲染。隨后 GPU 會(huì)把渲染結(jié)果提交到幀緩沖區(qū)去,等待下一次 VSync 信號(hào)到來時(shí)顯示到屏幕上。
* 2.1.2 Core Animation Commit Transaction *
針對(duì)準(zhǔn)備階段的Commit Transaction部分,可以細(xì)分為4個(gè)步驟:

-
布局Layout:在這個(gè)階段,程序設(shè)置 View / Layer 的層級(jí)信息,設(shè)置 layer 的屬性,如 frame,background color 等等。
[UIView layoutSubViews]和[CALayer layoutSublayers]就是在這個(gè)階段調(diào)用的。 -
顯示Display:在這個(gè)階段程序會(huì)創(chuàng)建 layer 的 backing image,無論是通過 setContents 將一個(gè) image 傳給 layer,還是通過
drawRect:或drawLayer: inContext:來畫出來的。所以drawRect:等函數(shù)是在這個(gè)階段被調(diào)用的。注意不要混淆這里的Display和最終的顯示Display;
關(guān)于使用drawRect和使用ImageView
-
使用 -drawRect :
如果你的視圖類實(shí)現(xiàn)了-drawRect:,他們將像這樣工作:- 當(dāng)你調(diào)用
-setNeedsDisplay,UIKit 將會(huì)在這個(gè)視圖的圖層上調(diào)用-setNeedsDisplay。這為圖層設(shè)置了一個(gè)標(biāo)識(shí),標(biāo)記為 dirty,但還顯示原來的內(nèi)容。它實(shí)際上沒做任何工作,所以多次調(diào)用-setNeedsDisplay并不會(huì)造成性能損失。 - 下面,當(dāng)渲染系統(tǒng)準(zhǔn)備好,它會(huì)調(diào)用視圖圖層的
-display方法.此時(shí),圖層會(huì)裝配它的后備存儲(chǔ)。 - 然后建立一個(gè) Core Graphics 上下文(
CGContextRef),將后備存儲(chǔ)對(duì)應(yīng)內(nèi)存中的數(shù)據(jù)恢復(fù)出來,繪圖會(huì)進(jìn)入對(duì)應(yīng)的內(nèi)存區(qū)域,并使用CGContextRef繪制。當(dāng)你使用 UIKit 的繪制方法,例如:UIRectFill()或者-[UIBezierPath fill]代替你的-drawRect:方法,他們將會(huì)使用這個(gè)上下文。此時(shí),UIKit 將后備存儲(chǔ)的 CGContextRef 推進(jìn)他的 graphics context stack,也就是說,它會(huì)將那個(gè)上下文設(shè)置為當(dāng)前的。(UIGraphicsGetCurrent()將會(huì)返回那個(gè)對(duì)應(yīng)的上下文),這樣,UIKit 使用當(dāng)前上下文將繪圖繪入到圖層的后備存儲(chǔ)。如果你想直接使用 Core Graphics 方法,你可以自己調(diào)用UIGraphicsGetCurrent()得到相同的上下文,并且將這個(gè)上下文傳給 Core Graphics 方法。 - 從現(xiàn)在開始,圖層的后備存儲(chǔ)將會(huì)被不斷的渲染到屏幕上。直到下次再次調(diào)用視圖的
-setNeedsDisplay,將會(huì)依次將圖層的后備存儲(chǔ)更新到視圖上。
- 當(dāng)你調(diào)用
-
不使用 -drawRect:
當(dāng)你用一個(gè) UIImageView 時(shí),事情略有不同,這個(gè)視圖仍然有一個(gè) CALayer,但是圖層卻沒有申請(qǐng)一個(gè)后備存儲(chǔ)。取而代之的是使用一個(gè) CGImageRef 作為他的內(nèi)容,并且渲染服務(wù)將會(huì)把圖片的數(shù)據(jù)繪制到幀的緩沖區(qū),比如,繪制到顯示屏。在這種情況下,將不會(huì)繼續(xù)重新繪制。我們只是簡單的將位圖數(shù)據(jù)以圖片的形式傳給了 UIImageView,然后 UIImageView 傳給了 Core Animation,然后輪流傳給渲染服務(wù)。
-
準(zhǔn)備Prepare:在這個(gè)階段,Core Animation 框架準(zhǔn)備要渲染的 layer 的各種屬性數(shù)據(jù),以及要做的動(dòng)畫的參數(shù),準(zhǔn)備傳遞給 render server。同時(shí)在這個(gè)階段也會(huì)解壓要渲染的 image。(除了用 imageNamed:方法從 bundle 加載的 image 會(huì)立刻解壓之外,其他的比如直接從硬盤讀入,或者從網(wǎng)絡(luò)上下載的 image 不會(huì)立刻解壓,只有在真正要渲染的時(shí)候才會(huì)解壓)。在這個(gè)階段你可以看到類似
CA::Layer::prepare_commit和Render::prepare_image,Render::copy_image,Render::create_image等等這樣的操作; -
提交Commit:在這個(gè)階段,Core Animation 打包 layer 的信息以及需要做的動(dòng)畫的參數(shù),通過 IPC(inter-Process Communication)傳遞給 render server。這是一個(gè)遞歸操作,根據(jù)打包的Layer層級(jí)復(fù)雜度來決定遞歸的次數(shù)。大量連續(xù)遞歸的
CA::Layer::commit_if_needed調(diào)用是這個(gè)階段的顯著特征。
* 2.1.3 GPU Rendering *
當(dāng)App調(diào)用Core Animation(甚至是Core Graphic)將所有準(zhǔn)備工作完成后,將參數(shù)和數(shù)據(jù)提交到下層OpenGL層,再傳輸給GPU做真正的渲染處理:
- 塊渲染 Tile Based Rendering
塊渲染的根本思路是將設(shè)備屏幕分割為包含N*N個(gè)像素的塊狀(Tile)區(qū)域。每一個(gè)Tile可以適配到SoC的cache中。

每一個(gè)屏幕上的圖像對(duì)象,都將被這些Tile再次切割成獨(dú)立的碎塊,然后針對(duì)每一個(gè)碎塊進(jìn)行三角形頂點(diǎn)著色。

塊渲染是目前移動(dòng)GPU的主流渲染方式,因?yàn)檫@種方式更好地適配了移動(dòng)設(shè)備的耗電和性能的平衡問題。究其原因,是因?yàn)镚PU在運(yùn)算時(shí)對(duì)數(shù)據(jù)帶寬消耗的極高要求:OPENGL的虛擬管線需要大量的顯存帶寬來支持, 為了減少這個(gè)兇殘的帶寬需求,大多數(shù)移動(dòng)GPU都使用了tiled-based渲染。在最基礎(chǔ)的層面,這些GPU將幀緩存(framebuffer),包括深度緩存,多采樣緩存等等,從主內(nèi)存移到了一塊超高速的on-chip存儲(chǔ)器上,計(jì)算芯片就能以遠(yuǎn)低于常規(guī)消耗的電能來讀寫存儲(chǔ)器。但是on-chip的存儲(chǔ)器都不可能很大,否則GPU芯片的大小將大的嚇人,在有些GPU中小到只能容納16x16個(gè)像素,于是將OPENGL的幀緩存切割成16x16的小塊(這就是tile-based渲染的命名由來),然后一次就渲染一塊。對(duì)于每一塊tile: 將有用的幾何體提交進(jìn)去,當(dāng)渲染完成時(shí),將tile的數(shù)據(jù)拷貝回主內(nèi)存。這樣,帶寬的消耗就只來自于寫回主內(nèi)存了,那是一個(gè)較小的消耗,消耗極高的深度/模板測試和顏色混合完全的在計(jì)算芯片上就完成了。
有關(guān)Tile Based Rendering的更多內(nèi)容,有興趣可以簡單翻一下這篇譯文《Performance Tunning for Tile-Based Architecture》
- 渲染通道(流水線) Render Pass
Core Animation將包裝的好的數(shù)據(jù)提交給OpenGL之后,后續(xù)的流程基本上就屬于OpenGL ES的范疇了,你在這里可以看到頂點(diǎn)著色器(Vertex Shader)和像素著色器(Pixel Shader)。

頂點(diǎn)著色器定義了在 2D 或者 3D 場景中幾何圖形是如何處理的。一個(gè)頂點(diǎn)指的是 2D 或者 3D 空間中的一個(gè)點(diǎn)。頂點(diǎn)著色器設(shè)置頂點(diǎn)的位置,并且把位置和紋理坐標(biāo)這樣的參數(shù)發(fā)送到Pixel Shader。然后 GPU 使用Pixel Shader在對(duì)象或者圖片的每一個(gè)像素上進(jìn)行計(jì)算,最終計(jì)算出每個(gè)像素的最終顏色。
能夠?qū)崿F(xiàn)Vertex Shader和Pixel Shader的顯卡的圖形處理流水線被稱作為是可編程的,相對(duì)而言,在此之前的圖形處理流水線被稱作為是固定功能(fixed function)。雖然如此,但是實(shí)際上可編程的只有流水線的一部分,正如Vertex Shader 和Pixel Shader的字面意思一樣,現(xiàn)在可編程的部分只有處理頂點(diǎn)的和處理象素的單元。但是這兩個(gè)著色器是OpenGL ES 2.0定義的兩個(gè)缺一不可的單元。
Vertex Shader 和 Pixel Shader在不同的文檔里面有不同的叫法,Nvidia在自己的OpenGL擴(kuò)展中把Vertex Shader叫做Vertex Program、把Pixel Shader叫做Texture Shader,3Dlabs在自己提出一份OpenGL 2.0的提議里面把這兩者分別叫做Vertex Shader和Fragment(片段)Shader
《GPU-Accelerated Image Processing》 和 這段回答 或許能給你更多關(guān)于GPU Shader的內(nèi)容。
- 多通道渲染(Render Passes)示例:Masking
在實(shí)際繪圖過程中,由于多個(gè)Layer的存在,最終圖像實(shí)際上是由多個(gè)Render Pass并發(fā)繪制后再組合渲染的(Compositing and Blending)。以一個(gè)帶有蒙板的組合圖像為例,實(shí)際發(fā)生的Render pass有3個(gè),mask layer和content layer各自渲染,最后再做一次Render pass組合出最終圖像:

2.2 圖層的動(dòng)畫 Animation
到此為止,我們已經(jīng)基本了解了在iOS上,從App遞交一個(gè)繪圖請(qǐng)求開始一直到圖像出現(xiàn)在屏幕上的基本過程,但這都是靜態(tài)的圖片的繪制,那么動(dòng)畫Animation是怎么做到的呢?
Apple的做法很簡單直接,對(duì)于每一個(gè)Animation,前期的準(zhǔn)備階段和單獨(dú)的圖像提交基本相同,但是Render Server在渲染時(shí),將根據(jù)Core Animation的動(dòng)畫參數(shù)自動(dòng)計(jì)算出動(dòng)畫所需要的每一幀圖像,然后一幀一幀的渲染顯示,最終呈現(xiàn)的就是動(dòng)畫效果。也就是說App只需要告訴Render Server動(dòng)畫的起始和終止?fàn)顟B(tài),它自動(dòng)將
中間過程計(jì)算出來并替你完成commit的動(dòng)作。聯(lián)想到前文中關(guān)于Layer Tree的3份不同Copy,你只需要操作起始和終止?fàn)顟B(tài)的Modal Layer屬性,在Presentation Layer中即完成所有中間過程的計(jì)算。
我不知道Core Animation的這套機(jī)制和老喬的Pixar動(dòng)畫公司是否有著千絲萬縷的關(guān)系,但是無論如何你都能在這套機(jī)制設(shè)計(jì)看到傳統(tǒng)動(dòng)畫制作的思路。
一個(gè)Animation的完整過程如下圖:

3. iOS Graphic的性能考量
我們現(xiàn)在已經(jīng)了解了iOS 2D圖形繪制的基本過程了。在這些內(nèi)容中,你或許已經(jīng)看到了時(shí)間對(duì)于圖形繪制的重要性,尤其讓你注意到的,應(yīng)該是那個(gè)“VSync”信號(hào)。
上面一節(jié)已經(jīng)提到的Pipeline過程是序列化同步進(jìn)行的,在每一個(gè)VSync信號(hào)開始時(shí),所有的參與者都會(huì)并發(fā)的執(zhí)行下一個(gè)序列,如果在一個(gè) VSync 時(shí)間內(nèi)(16.67ms)每一個(gè)步驟都有條不紊的完整執(zhí)行,那么整個(gè)顯示過程就能順利的執(zhí)行下去:

但是,如果應(yīng)為某種原因?qū)е履骋徊交蛘叨嗖教幚頃r(shí)間過長,CPU 或者 GPU 沒有完成內(nèi)容提交,則那一幀就會(huì)被丟棄,等待下一次機(jī)會(huì)再顯示,而這時(shí)顯示屏?xí)A糁暗膬?nèi)容不變。這就是界面卡頓的原因。對(duì)應(yīng)到上圖,就是某個(gè)時(shí)間條超長而越過了VSync信號(hào)的邊界,這就是掉幀:

另外,上一節(jié)中,我們提到過Animation的3個(gè)步驟,對(duì)于普通動(dòng)畫而言,只需要一次1-2步的計(jì)算,Render Server在后續(xù)的第3步中,保證每一幀能夠在16ms內(nèi)提交到GPU就OK了,但是對(duì)于Scrolling來說,有一個(gè)特殊的地方是,每一次Scroll的動(dòng)作,都會(huì)單獨(dú)觸發(fā)上述1-3步的完整過程:也就是說,Scrolling要求每次1-3步都必須在16ms內(nèi)完成:

對(duì)于TableView而言,這就意味著每一個(gè)新的行(new row)的處理都至少要在16ms內(nèi)完成,最壞情況下,當(dāng)快速滑動(dòng)的時(shí)候,Render需要在1幀(16ms)內(nèi)更新整個(gè)屏幕的內(nèi)容!這就是為什么關(guān)于ScrollView和TableView的滑動(dòng)性能一直是大家關(guān)注的熱點(diǎn)。
掉幀引起的卡頓感,或許是手機(jī)用戶最敏感的體驗(yàn),iOS用戶一直對(duì)Android引以為豪的體驗(yàn)感,其實(shí)就來自于iOS針對(duì)圖形圖像和系統(tǒng)設(shè)計(jì)的優(yōu)化。(關(guān)于這一點(diǎn),其實(shí)除了iOS本身圖像渲染的設(shè)計(jì)有關(guān),另一個(gè)重要的因素其實(shí)是和Runloop的調(diào)度機(jī)制有關(guān),這點(diǎn)有時(shí)間可以再為Runloop單開一篇)不管怎樣,作為iOS App開發(fā)的你,應(yīng)當(dāng)不遺余力的優(yōu)化你的App的性能,改善用戶的體驗(yàn)。
這部分內(nèi)容,請(qǐng)看下篇 ——《iOS 2D Graphic (2)— Performance 性能優(yōu)化》。
希望對(duì)你有所幫助。
[參考資料]:
- WWDC2012 Session 238 《iOS App Performance: Graphic and Animation》
- [WWDC2014 Session 419《Advanced Graphics and Animation Performance》
- iOS Developer Library:《Drawing and Printing Guide for iOS》
- iOS Developer Library:《Core Animation Programming Guide》
- 《iOS UIView 詳解》
- 《iOS 事件處理機(jī)制與圖像渲染過程》
- 《iOS 視圖---動(dòng)畫渲染機(jī)制探究》
- 《WWDC心得與延伸:iOS圖形性能》
- 《Tile-Based架構(gòu)下的性能調(diào)校》
- 《Getting Pixels onto the Screen》
- StackOverflow: What's the difference between Quartz Core, Core Graphics and Quartz 2D?
2016.7.1 完稿于南京。