本系列文章的重點(diǎn)是關(guān)注在總結(jié)iOS圖形圖像的原理和性能優(yōu)化的常規(guī)解決方案痴鳄。
事先聲明是趴,本文絕大多數(shù)概念和內(nèi)容均來(lái)源于已有素材狐榔,但是均經(jīng)過(guò)作者消化后總結(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ò)展開來(lái)感覺(jué)進(jìn)入了一個(gè)浩大的領(lǐng)域睬涧。這個(gè)話題的火熱程度可以舉個(gè)簡(jiǎn)單例子:在簡(jiǎn)書上搜索“Quartz 2D” 或者 “Core Graphic”就有大約5、6百篇文章旗唁,如果搜索“iOS 優(yōu)化”甚至有超過(guò)1萬(wàn)條記錄畦浓。經(jīng)過(guò)前后大約2-3周的零碎時(shí)間,我仔細(xì)搜集整理了20余篇優(yōu)秀資料總結(jié)出本文检疫,給自己留個(gè)研究筆記讶请,也希望能幫助到需要的人。
知己知彼屎媳,百戰(zhàn)不殆夺溢。
掌握任何一件事情都需要從原理入手。我們先來(lái)鞏固iOS 2D Graphic相關(guān)的基本概念和知識(shí)烛谊。
1. 關(guān)于iOS 圖像處理的基本概念
1.1 Graphic Frameworks
首先看一張來(lái)自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的文檔里废累,看起來(lái)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 上归榕,幾乎所有的東西都是通過(guò) Core Animation 繪制出來(lái),然而在 OS X 上吱涉,繞過(guò) Core Animation 直接使用 Core Graphics 繪制的情況并不少見刹泄。對(duì)于一些專門的應(yīng)用,尤其是游戲怎爵,程序可能直接和 OpenGL/OpenGL ES 交流特石。
順便提一下,也許你會(huì)在網(wǎng)上看到類似這樣一幅圖:
我個(gè)人覺(jué)得這幅圖是有一定問(wèn)題的鳖链,因?yàn)檫@會(huì)給人一個(gè)錯(cuò)覺(jué)姆蘸,好像圖形的處理對(duì)于CPU和GPU而言是分離的,但是實(shí)際上芙委,CPU處理完的數(shù)據(jù)最終都需要提交到GPU逞敷。不管是iOS還是Mac,最終所有的繪圖操作都會(huì)通過(guò)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)行顯示愕秫。一般來(lái)說(shuō)慨菱,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去處理圖像,也就是說(shuō)這個(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 訪問(wèn)數(shù)據(jù)嘹狞,需要將數(shù)據(jù)從 RAM 移動(dòng)到 VRAM 上,一些大型的紋理卻會(huì)非常耗時(shí)誓竿。
舉個(gè)例子磅网,比如顯示文本,對(duì) CPU來(lái)說(shuō)會(huì)調(diào)用 Core Text 和 Core Graphics 框架更緊密的集成來(lái)根據(jù)文本生成一個(gè)位圖烤黍。一旦準(zhǔn)備好知市,它將會(huì)被作為一個(gè)紋理上傳到 GPU 并準(zhǔn)備顯示出來(lái)。當(dāng)你滾動(dòng)或者在屏幕上移動(dòng)文本時(shí)速蕊,同樣的紋理能夠被復(fù)用嫂丙,CPU 只需簡(jiǎn)單的告訴 GPU 新的位置就行了,所以 GPU 就可以重用存在的紋理了规哲。CPU 并不需要重新渲染文本跟啤,并且位圖也不需要重新上傳到 GPU。
在這里唉锌,有必要再討論下Quartz隅肥。Quartz這個(gè)名詞其實(shí)是一個(gè)從Mac上過(guò)來(lái)的歷史遺留物,而其本身是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),從某種意義上來(lái)說(shuō)篙挽,Quartz 和CG可以互相混淆荆萤,事實(shí)上,上文的作者也嘲笑了一句“如果Apple的目的是想把大家給搞糊涂那么他們成功地做到了铣卡!”
上面主要針對(duì)的是CA層链韭,CG層和GPU、CPU之間的關(guān)系煮落,那么我們?cè)購(gòu)腃A層往上走敞峭,來(lái)看看CA層和UI層之間的關(guān)系。
1.2 UIView 和 CALayer
“每一個(gè)成功的男人背后都有一個(gè)女人蝉仇!”
你在iOS上能看到的旋讹,觸碰到的所有東西,都是UIView展示出來(lái)的轿衔。你剛開始接觸iOS開發(fā)的時(shí)候沉迹,使用的最多的,也是UIView害驹,看起來(lái)UIView無(wú)限風(fēng)光鞭呕。但是如果認(rèn)為UIView是繪制出的圖像那你就錯(cuò)了!在iOS中宛官,每一個(gè)UIView的背后都有一個(gè)默認(rèn)的CALayer葫松,真正為圖像做出貢獻(xiàn)的,其實(shí)是這個(gè)layer摘刑,UIView只是作為一個(gè)視窗容器进宝,用來(lái)精簡(jiǎn)封裝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來(lái)說(shuō),系統(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)就都在這一層上更改屬性來(lái)完成動(dòng)畫的分動(dòng)作橙困;
- render tree:這是直接對(duì)應(yīng)于提交到render server上進(jìn)行顯示的樹瞧掺,屏幕上的內(nèi)容對(duì)應(yīng)于該層。
這讓我想起了一句老話:“每一個(gè)成功的男人背后都有一個(gè)成功的女人”凡傅,UIView的無(wú)限風(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ì)問(wèn)猫态,既然有了CALayer為啥還要UIView庶溶,這不多余么?懂鸵!事實(shí)上偏螺,這種看似多余的設(shè)計(jì),其背后的藝術(shù)確是非常精妙的匆光,關(guān)于這個(gè)問(wèn)題套像,網(wǎng)上有很多介紹,但是我仍然認(rèn)為终息,在設(shè)計(jì)精髓上夺巩,這篇風(fēng)趣的文章是解釋的最深刻的,簡(jiǎn)單的說(shuō)周崭,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撒妈。在上面我們提到過(guò),每一個(gè)UIView的Layer都有一個(gè)對(duì)應(yīng)的Backing Store作為其存儲(chǔ)Content的實(shí)際內(nèi)容排监,而這些內(nèi)容其實(shí)就是一個(gè)CGImage數(shù)據(jù)(更確切的說(shuō)踩身,是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),接下來(lái)我們看看Graphic的基本工作原理瞒瘸。
2.1 圖像的繪制
* 2.1.1 Core Animation Pipeline *
前文中的Graphic Framework一節(jié)已經(jīng)說(shuō)到App通過(guò)Core Animation調(diào)度GPU和CPU坷备,最終繪制圖像到屏幕上。那么具體到繪制細(xì)節(jié)中情臭,都需要經(jīng)過(guò)哪些具體的步驟呢省撑?2014年的WWDC上Apple向我們提供了一些技術(shù)細(xì)節(jié)可以讓我們管窺一斑。從App調(diào)用Core Animation開始俯在,一直到最終顯示竟秫,是一個(gè)嚴(yán)格遵循時(shí)間順序的管道(Pipeline)過(guò)程,叫做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)如输。一般而言視頻控制器都是通過(guò)VSync信號(hào)來(lái)進(jìn)行顯示同步的鼓黔,每一個(gè)VSync信號(hào)間隔是固定的央勒,這個(gè)時(shí)間是16.67ms,也就是1秒鐘刷新60幀请祖。在 VSync 信號(hào)到來(lái)后,系統(tǒng)圖形服務(wù)會(huì)通過(guò) 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)到來(lái)時(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竟纳,無(wú)論是通過(guò) setContents 將一個(gè) image 傳給 layer,還是通過(guò)
drawRect:
或drawLayer: inContext:
來(lái)畫出來(lái)的疚鲤。所以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删性,但還顯示原來(lái)的內(nèi)容。它實(shí)際上沒(méi)做任何工作焕窝,所以多次調(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ù)出來(lái)榕茧,繪圖會(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池充,也就是說(shuō),它會(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匈睁,但是圖層卻沒(méi)有申請(qǐng)一個(gè)后備存儲(chǔ)。取而代之的是使用一個(gè) CGImageRef 作為他的內(nèi)容桶错,并且渲染服務(wù)將會(huì)把圖片的數(shù)據(jù)繪制到幀的緩沖區(qū)航唆,比如,繪制到顯示屏院刁。在這種情況下糯钙,將不會(huì)繼續(xù)重新繪制。我們只是簡(jiǎn)單的將位圖數(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ù)姑荷,通過(guò) IPC(inter-Process Communication)傳遞給 render server。這是一個(gè)遞歸操作锣枝,根據(jù)打包的Layer層級(jí)復(fù)雜度來(lái)決定遞歸的次數(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è)備的耗電和性能的平衡問(wèn)題胶坠。究其原因君账,是因?yàn)镚PU在運(yùn)算時(shí)對(duì)數(shù)據(jù)帶寬消耗的極高要求:OPENGL的虛擬管線需要大量的顯存帶寬來(lái)支持, 為了減少這個(gè)兇殘的帶寬需求,大多數(shù)移動(dòng)GPU都使用了tiled-based渲染沈善。在最基礎(chǔ)的層面乡数,這些GPU將幀緩存(framebuffer),包括深度緩存闻牡,多采樣緩存等等净赴,從主內(nèi)存移到了一塊超高速的on-chip存儲(chǔ)器上,計(jì)算芯片就能以遠(yuǎn)低于常規(guī)消耗的電能來(lái)讀寫存儲(chǔ)器罩润。但是on-chip的存儲(chǔ)器都不可能很大玖翅,否則GPU芯片的大小將大的嚇人,在有些GPU中小到只能容納16x16個(gè)像素割以,于是將OPENGL的幀緩存切割成16x16的小塊(這就是tile-based渲染的命名由來(lái))金度,然后一次就渲染一塊。對(duì)于每一塊tile: 將有用的幾何體提交進(jìn)去严沥,當(dāng)渲染完成時(shí)审姓,將tile的數(shù)據(jù)拷貝回主內(nèi)存。這樣祝峻,帶寬的消耗就只來(lái)自于寫回主內(nèi)存了魔吐,那是一個(gè)較小的消耗扎筒,消耗極高的深度/模板測(cè)試和顏色混合完全的在計(jì)算芯片上就完成了。
有關(guān)Tile Based Rendering的更多內(nèi)容酬姆,有興趣可以簡(jiǎn)單翻一下這篇譯文《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 場(chǎng)景中幾何圖形是如何處理的骨宠。一個(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í)際繪圖過(guò)程中挥吵,由于多個(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)在屏幕上的基本過(guò)程,但這都是靜態(tài)的圖片的繪制嗦锐,那么動(dòng)畫Animation是怎么做到的呢嫌松?
Apple的做法很簡(jiǎn)單直接,對(duì)于每一個(gè)Animation奕污,前期的準(zhǔn)備階段和單獨(dú)的圖像提交基本相同萎羔,但是Render Server在渲染時(shí),將根據(jù)Core Animation的動(dòng)畫參數(shù)自動(dòng)計(jì)算出動(dòng)畫所需要的每一幀圖像碳默,然后一幀一幀的渲染顯示贾陷,最終呈現(xiàn)的就是動(dòng)畫效果缘眶。也就是說(shuō)App只需要告訴Render Server動(dòng)畫的起始和終止?fàn)顟B(tài),它自動(dòng)將
中間過(guò)程計(jì)算出來(lái)并替你完成commit的動(dòng)作髓废。聯(lián)想到前文中關(guān)于Layer Tree的3份不同Copy巷懈,你只需要操作起始和終止?fàn)顟B(tài)的Modal Layer屬性,在Presentation Layer中即完成所有中間過(guò)程的計(jì)算慌洪。
我不知道Core Animation的這套機(jī)制和老喬的Pixar動(dòng)畫公司是否有著千絲萬(wàn)縷的關(guān)系顶燕,但是無(wú)論如何你都能在這套機(jī)制設(shè)計(jì)看到傳統(tǒng)動(dòng)畫制作的思路。
一個(gè)Animation的完整過(guò)程如下圖:
3. iOS Graphic的性能考量
我們現(xiàn)在已經(jīng)了解了iOS 2D圖形繪制的基本過(guò)程了冈爹。在這些內(nèi)容中磷斧,你或許已經(jīng)看到了時(shí)間對(duì)于圖形繪制的重要性椿访,尤其讓你注意到的听系,應(yīng)該是那個(gè)“VSync”信號(hào)术浪。
上面一節(jié)已經(jīng)提到的Pipeline過(guò)程是序列化同步進(jìn)行的,在每一個(gè)VSync信號(hào)開始時(shí)剂买,所有的參與者都會(huì)并發(fā)的執(zhí)行下一個(gè)序列惠爽,如果在一個(gè) VSync 時(shí)間內(nèi)(16.67ms)每一個(gè)步驟都有條不紊的完整執(zhí)行癌蓖,那么整個(gè)顯示過(guò)程就能順利的執(zhí)行下去:
但是瞬哼,如果應(yīng)為某種原因?qū)е履骋徊交蛘叨嗖教幚頃r(shí)間過(guò)長(zhǎng),CPU 或者 GPU 沒(méi)有完成內(nèi)容提交租副,則那一幀就會(huì)被丟棄坐慰,等待下一次機(jī)會(huì)再顯示,而這時(shí)顯示屏?xí)A糁暗膬?nèi)容不變用僧。這就是界面卡頓的原因结胀。對(duì)應(yīng)到上圖,就是某個(gè)時(shí)間條超長(zhǎng)而越過(guò)了VSync信號(hào)的邊界责循,這就是掉幀:
另外糟港,上一節(jié)中,我們提到過(guò)Animation的3個(gè)步驟院仿,對(duì)于普通動(dòng)畫而言秸抚,只需要一次1-2步的計(jì)算,Render Server在后續(xù)的第3步中歹垫,保證每一幀能夠在16ms內(nèi)提交到GPU就OK了剥汤,但是對(duì)于Scrolling來(lái)說(shuō),有一個(gè)特殊的地方是排惨,每一次Scroll的動(dòng)作吭敢,都會(huì)單獨(dú)觸發(fā)上述1-3步的完整過(guò)程:也就是說(shuō),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í)就來(lái)自于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ī)制與圖像渲染過(guò)程》
- 《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 完稿于南京。