繪制像素到屏幕上
軟件組成
從簡單的角度來看, 軟件堆??雌饋碛悬c像這樣:

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

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