繪制像素到屏幕上
軟件組成
從簡(jiǎn)單的角度來看, 軟件堆棧看起來有點(diǎn)像這樣:

Display的上一層便是圖形處理單元GPU, GPU是一個(gè)專門為圖形高并發(fā)計(jì)算而量身定做的處理單元.
GPU Driver是直接和GPU交流的代碼塊. 不同的GPU是不同的性能怪物, 但是驅(qū)動(dòng)使他們?cè)谙乱粋€(gè)層級(jí)上顯示的更為統(tǒng)一, 典型的下一層級(jí)有OpenGL/OpenGL ES.
OpenGL是一個(gè)提供了2D和3D圖形渲染的API.
OpenGL之上擴(kuò)展出很多東西, 在iOS上, 幾乎所有的東西都是通過Core Animation繪制出來 .
要記住一件事情, GPU是一個(gè)非常強(qiáng)大的圖形硬件, 并且在顯示像素方面起著核心作用. 它連接到CPU,從硬件上講兩者之間存在某種類型的總線, 并且有像OpenGL, Core Animation和Core Graphics 這樣的框架來在GPU和CPU之間精心安排數(shù)據(jù)的傳輸. 為了將像素顯示到屏幕上, 一些處理在CPU上進(jìn)行, 然后數(shù)據(jù)將會(huì)傳送到GPU, 這也需要做一些相應(yīng)的操作, 最終像素顯示到屏幕上.
硬件參與者

正如上面這張簡(jiǎn)單的圖片顯示的:GPU需要將每一個(gè)frame的紋理(位圖)合成在一起(一秒60次). 每一個(gè)紋理會(huì)占用VRAM(video RAM), 所以需要給GPU同時(shí)保持紋理的數(shù)量做一個(gè)限制. GPU在合成方面非常高效, 但是某些合成任務(wù)卻比其他更復(fù)雜, 并且GPU在1/60s內(nèi)能做的工作也是有限的.
為了讓GPU訪問數(shù)據(jù), 需要將數(shù)據(jù)從RAM移動(dòng)到VRAM上. 這就是體積到的上傳數(shù)據(jù)到GPU了, 在一些大的紋理卻會(huì)非常耗時(shí).
最終CPU開始運(yùn)行程序, 可能會(huì)讓CPU從bundle加載一張PNG的圖片并且解壓它. 這所有的事情都在CPU上進(jìn)行, 然后當(dāng)你需要顯示解亞索后的圖片時(shí), 它需要以某種方式上傳到GPU.
合成
在圖形世界中, 合成是一個(gè)描述不同位圖如何放到一起來創(chuàng)建你最終在屏幕上看到圖像的過程, 在許多方面顯得顯而易見, 而讓人忘了背后錯(cuò)綜復(fù)雜的計(jì)算. 假定屏幕上一切事物皆紋理, 一個(gè)紋理就是一個(gè)包含RGBA值得長(zhǎng)方形, 比如, ,每一個(gè)像素里面都包含紅. 綠. 藍(lán)和透明度的值. 在Core Aniimation世界中這就相當(dāng)于一個(gè)CALayer. 在這個(gè)建華的設(shè)置中, 每一個(gè)layer是一個(gè)紋理, 所有的紋理都以某種方式堆疊在彼此的頂部, 對(duì)于屏幕上的每一個(gè)像素, GPU需要算出怎么混合這些紋理來得到像素RGB的值.
如果我們所擁有的是一個(gè)和屏幕大小一樣并且和屏幕像素對(duì)齊的單一紋理, 那么屏幕上每一個(gè)像素相當(dāng)于紋理中的一個(gè)像素, 紋理的最后一個(gè)像素也就是屏幕的最后一個(gè)像素.
如果我們有第二個(gè)紋理放在第一個(gè)紋理之上, 然后GPU會(huì)把第二個(gè)紋理合成到第一個(gè)紋理中, 并且使用正常的混合模式, 便可以用下面這個(gè)公式來計(jì)算每一個(gè)像素:
R = S + D * (1 - Sa)
這個(gè)公式可以這么理解: 結(jié)果的顏色 = 頂端紋理 + 低一層的紋理 * (1 - 頂端紋理的透明度), 在這個(gè)公式中所有的顏色都假定已經(jīng)預(yù)先乘以了他們的透明度.
如果兩個(gè)紋理都完全不透明, alpha = 1, 結(jié)果就是
R = S
結(jié)果就是頂端紋理的顏色, 正是所期待的頂端紋理覆蓋了低一層的紋理.
這只是將紋理中的一個(gè)像素合成到另一個(gè)紋理的像素上. 當(dāng)兩個(gè)紋理覆蓋在一起的時(shí)候, GPU需要為所有像素做這種操作, 許多程序的視圖都有很多層, 因此所有的紋理都需要合成到一起. 經(jīng)過GPU是一塊高度優(yōu)化的硬件來做這件事情, 但這還是會(huì)非常的影響性能的.
不透明VS透明
當(dāng)源紋理是完全不透明的時(shí)候, 目標(biāo)像素就等于源紋理. 這可以省下GPU很大的工作量, 這樣只需簡(jiǎn)單的拷貝源紋理而不需要合成所有的像素值. 但是沒有辦法能告訴GPU紋理上的像素是透明還是不透明的, 只有當(dāng)你作為一名開發(fā)者知道你放什么到CALayer上了, 這也是為什么CALayer有一個(gè)叫做opaque的屬性了. 如果這個(gè)屬性為YES, 那么GPU將不會(huì)做任何合成, 而只是簡(jiǎn)單的從頂端紋理這個(gè)層進(jìn)行拷貝, 不需要考慮它下方的任何東西(因?yàn)槎急凰趽踝×?. 這節(jié)省了GPU相當(dāng)大的工作量, 這也是Instruments種color blended layers 選項(xiàng)中所涉及的. 它允許你看到哪一個(gè)layers被標(biāo)注為透明的, 比如GPU正在為哪一個(gè)layers做合成. 合成不透明的layers 因?yàn)樾枰俚臄?shù)學(xué)計(jì)算而更廉價(jià), 更能提高GPU的性能.
所以如果你知道你的layer是不透明的, 最好確定設(shè)置它的opeaue為YES, 如果你加載一個(gè)沒有alpha通道的圖片, 并且將它顯示在UIImageView上, 這將會(huì)自動(dòng)發(fā)生. 但要記住如果一個(gè)圖片沒有alpha通道和一個(gè)圖片每個(gè)地方的alpha都是100%, 這將會(huì)產(chǎn)生很大的不同,
在后一種情況下, Core Animation需要假定是否存在像素的alpha值不為100%.
像素對(duì)齊VS不重合在一起
到現(xiàn)在我們都在考慮像素完美重合在一起的layers, 當(dāng)所有的像素是對(duì)齊的時(shí)候我們得到相對(duì)簡(jiǎn)單的計(jì)算公式. 每當(dāng)GPU需要計(jì)算出屏幕上一個(gè)像素是什么顏色的時(shí)候, 它只需要考慮在這個(gè)像素之上的所有l(wèi)ayer中對(duì)應(yīng)的單個(gè)像素, 并把這些像素合并到一起, 或者, 如果最頂層的紋理是不透明的(即圖層樹的最底層), 這時(shí)候GPU就可以簡(jiǎn)單的拷貝它的像素到屏幕上了.
當(dāng)一個(gè)layer上所有的像素和屏幕上的像素完美的對(duì)應(yīng)整齊, 那這個(gè)layer就是像素對(duì)齊的. 主要有兩個(gè)原因可能會(huì)造成不對(duì)其. 第一個(gè)便是滾動(dòng): 當(dāng)一個(gè)紋理上下滾動(dòng)的時(shí)候, 紋理的像素便不會(huì)和屏幕的像素排列對(duì)齊. 另一個(gè)原因就是當(dāng)紋理的起點(diǎn)不在一個(gè)像素的邊界上.
在這兩種情況下, GPU需要再做額外的計(jì)算, 它需要將紋理上多個(gè)像素混合起來, 生成一個(gè)用來合成的值, 當(dāng)所有的像素都是對(duì)齊的時(shí)候, GPU只剩下很少的工作要做了.
Masks
一個(gè)圖層可以有一個(gè)和它相關(guān)聯(lián)的mask(蒙版), mask是一個(gè)擁有alpha值得位圖, 當(dāng)像素要和它下面包含的像素合并之前都會(huì)把mask應(yīng)用到圖層的像素上去. 當(dāng)你要設(shè)置一個(gè)圖層的圓角半徑時(shí), 你可以有效的在圖層上面設(shè)置一個(gè)mask, 但是也可以指定任意一個(gè)蒙版. 比如, 一個(gè)字母A形狀的mask, 最終只有在mask中顯示出來的(即圖層中的部分)才會(huì)被渲染出來.
離屏渲染(Offscreen Rendering)
離屏渲染可以被Core Animation自動(dòng)觸發(fā), 或者被應(yīng)用程序強(qiáng)制觸發(fā). 屏幕外的渲染會(huì)合并/渲染圖層樹的一部分到一個(gè)新的緩沖區(qū), 然后該緩沖區(qū)被渲染到屏幕上.
離屏渲染合成計(jì)算是非常昂貴的, 有時(shí)希望強(qiáng)制這種操作. 一種好的方法就是緩存合成的紋理/圖層, 如果你的渲染樹非常復(fù)雜(所有的紋理, 以及如何組合在一起), 你可以強(qiáng)制離屏渲染緩存那些圖層, 然后可以用緩存作為合成的結(jié)果放到屏幕上.
如果你的程序混合了很多圖層, 并且想要他們一起做動(dòng)畫, GPU通常會(huì)為每一幀(1/60s)重復(fù)合成所有的圖層. 當(dāng)使用離屏渲染時(shí), GPU第一次會(huì)混合所有圖層到一個(gè)基于新的紋理的位圖緩存上, 然后使用這個(gè)紋理來繪制到屏幕上, 現(xiàn)在, 當(dāng)這些圖層一起移動(dòng)的時(shí)候, GPU便可以復(fù)用這個(gè)位圖緩存, 并且只需要做很少的工作, 需要注意的是, 只有當(dāng)那些圖層不改變時(shí), 這才可以用. 如果那些圖層改變了, GPU需要重新創(chuàng)建位圖緩存, 可以通過設(shè)置shouldRasterize為YES來觸發(fā)這個(gè)行為.
然而, 這是一個(gè)權(quán)衡. 第一, 這可能會(huì)使事情變得更慢. 創(chuàng)建額外的屏幕外緩沖區(qū)是GPU需要多做的一部操作, 特殊情況下, 這個(gè)位圖可能再也不需要被復(fù)用, 這便是一個(gè)無(wú)用功. 然而, 可以被復(fù)用的位圖, GPU也有可能將它卸載了. 所以你需要計(jì)算GPU的利用率和幀的速率來判斷這個(gè)位圖是否有用.
離屏渲染也可能產(chǎn)生副作用, 如果你正在直接或者間接的將mask應(yīng)用到一個(gè)圖層上, Core Animation為了應(yīng)用這個(gè)mask, 會(huì)強(qiáng)制進(jìn)行屏幕外渲染. 這會(huì)對(duì)GPU產(chǎn)生重負(fù). 通常情況下mask只能被直接渲染到幀的緩沖區(qū)(在屏幕內(nèi)).
Instrument 的Core Animation工具有一個(gè)叫做Color Offscreen - Rendered Yellow的選項(xiàng), 它會(huì)將已經(jīng)被渲染到屏幕外緩沖區(qū)的區(qū)域標(biāo)注為黃色(這個(gè)選項(xiàng)在模擬器中也可以用). 同時(shí)記得檢查Color Hits Green and Misses Red 選項(xiàng) 綠色代表無(wú)論何時(shí)一個(gè)屏幕外緩沖區(qū)被復(fù)用, 而紅色代表當(dāng)緩沖區(qū)被重新創(chuàng)建.
一般情況下, 你需要避免離屏渲染, 因?yàn)檫@是很大的消耗, 直接將圖層合成到幀的緩沖區(qū)中(在屏幕上)比先創(chuàng)建屏幕外緩沖區(qū), 然后渲染到紋理中, 最后將結(jié)果渲染到幀的緩沖區(qū)中要廉價(jià)很多. 因?yàn)檫@其中涉及到兩次昂貴的環(huán)境轉(zhuǎn)化(轉(zhuǎn)換環(huán)境到屏幕外緩沖區(qū), 然后轉(zhuǎn)換環(huán)境到幀緩沖區(qū)).
所以當(dāng)你打開Color Offscreen-Rendered Yellow時(shí)看到黃色, 這便是一個(gè)警告, 但這不一定是不好的. 如果Core Animation能夠復(fù)用屏幕外渲染的結(jié)果, 這便能夠提升性能.
同時(shí)還要注意, rasterized layer 的空間是有限的. 蘋果暗示大概有屏幕大小兩倍的空間來存儲(chǔ)rasterized layer屏幕外緩沖區(qū).
如果你使用layer的方式會(huì)通過屏幕外渲染, 你最好擺脫這種方式. 為layer使用蒙版或者設(shè)置圓角半徑會(huì)造成屏幕外渲染, 產(chǎn)生陰影也會(huì)如此.
至于mask, 圓角半徑(特殊的mask)和clipsToBounds/masksToBounds, 你可以簡(jiǎn)單的為一個(gè)已經(jīng)擁有mask的layer創(chuàng)建內(nèi)容, 比如, 已經(jīng)應(yīng)用了mask的layer使用一張圖片. 如果你想根據(jù)layer的內(nèi)容為其應(yīng)用一個(gè)長(zhǎng)方形mask, 你可以使用contentsRect來代替蒙版.
如果你最后設(shè)置了shouldRasterize為YES, 那也要記住設(shè)置rasterizationScale為contentsScale.
Core Animation OpenGL ES
正如名字所建議的那樣, Core Animation讓你在屏幕上實(shí)現(xiàn)動(dòng)畫, 我們將跳過動(dòng)畫部分, 而集中在繪圖上, 需要注意的是, Core Animation 允許你做非常高效的渲染, 這也是為什么當(dāng)你使用Core Animation時(shí)可以實(shí)現(xiàn)每秒60幀的動(dòng)畫了.
Core Animation的核心是OpenGL ES 的一個(gè)抽象物, 簡(jiǎn)而言之, 它讓你直接使用OpenGL ES的功能, 卻不需要處理OpenGL ES 做復(fù)雜的事情.
Core Animation的layer可以有子layer, 所以最終你得到的是一個(gè)圖層樹. Core Animation所需要做的最繁重的任務(wù)便是判斷出哪些圖層需要被(重新)繪制, 而OpenGL ES 需要做的便是將圖層合并, 顯示到屏幕上.
舉個(gè)例子, 當(dāng)你設(shè)置一個(gè)layer的內(nèi)容為CGImageRef時(shí), Core Animation會(huì)創(chuàng)建一個(gè)OpenGL 紋理, 并確保這個(gè)圖層中的位圖被上傳到對(duì)應(yīng)的紋理中. 以及當(dāng)你重寫- drwaInContext 方法時(shí), Core Animation會(huì)請(qǐng)求分配一個(gè)紋理, 同時(shí)確保Core Graphics會(huì)將你所做的(即你在drawInContext中繪制的東西)放入到紋理的位圖數(shù)據(jù)中. 一個(gè)圖層的性質(zhì)和CALayer的子類會(huì)影響到OpenGL的渲染效果, 許多低等級(jí)的OpenGL ES 行為被簡(jiǎn)單易懂地封裝到CALayer概念中.
Core Animation通過Core Graphics的一端和OpenGL ES 的另一端, 精心策劃基于CPU的位圖繪制. 因?yàn)镃ore Animation處在渲染過程中的重要位置上, 所以你如何使用Core Animation將會(huì)對(duì)性能產(chǎn)生極大的影響.
CPU限制 VS GPU限制
當(dāng)你在屏幕上顯示東西的時(shí)候, 有許多組件參與了其中的工作. 其中, CPU和GPU在硬件中扮演了重要的角色, 在他們命名中P和U分別代表了"處理"和"單元", 當(dāng)需要在屏幕上進(jìn)行繪制時(shí), 他們都需要做處理, 同時(shí)他們都有資源限制(即CPU和GPU的硬件資源).
為了每秒達(dá)到60幀, 你需要確定CPU和GPU不能過載. 此外, 即使你當(dāng)前能達(dá)到60幀, 你還是要把盡可能多的繪制工作交給GPU做, 而讓CPU盡可能的來執(zhí)行應(yīng)用程序. 通常, GPU的渲染性能要比CPU高效很多, 同時(shí)對(duì)系統(tǒng)的負(fù)載和消耗也更低一些.
既然繪圖性能是基于CPU和GPU的, 那么你需要找出是哪一個(gè)限制你繪圖性能的. 如果你用盡了GPU所有的資源, 也就是說, 是GPU限制了你的性能, 同一樣的, 如果你用盡了CPU, 那就是CPU限制了你的性能.
With - 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繪制.
從現(xiàn)在開始, 圖層的后備存儲(chǔ)將會(huì)被不斷的渲染到屏幕上, 知道下次再次調(diào)用視圖的-setNeedsDisplay, 將會(huì)一次將圖層的后備存儲(chǔ)更新到視圖上.
CALayer
到現(xiàn)在為止, 你需要知道在GPU內(nèi), 一個(gè)CALayer在某種方式上和一個(gè)紋理類似, 圖層有一個(gè)后備存儲(chǔ), 這便是被用來繪制到屏幕上的位圖.
通常, 當(dāng)你使用CALayer時(shí), 你會(huì)設(shè)置它的內(nèi)容為一張圖片. 這到底做了什么? 這樣做會(huì)告訴Core Animation使用圖片的位圖數(shù)據(jù)作為紋理, 如果這個(gè)圖片被壓縮了. Core Animation將會(huì)把這個(gè)圖片解壓縮,然后上傳像素?cái)?shù)據(jù)到GPU.
經(jīng)過還要很多其他種類的圖層, 如果你是用一個(gè)簡(jiǎn)單的沒有設(shè)置上下文的CALayer, 并為這個(gè)CALayer設(shè)置一個(gè)背景顏色, Core Animation并不會(huì)上傳任何數(shù)據(jù)到GPU, 但卻能夠不用任何像素?cái)?shù)據(jù)而在GPU上完成所有的工作, 類似的, 對(duì)于漸變的圖層, GPU是能夠創(chuàng)建漸變的, 而且不需要CPU做任何工作, 并且不需要上傳任何數(shù)據(jù)到GPU.