一、CPU VS GPU
1. 對于計算機(jī)硬件,繪圖和動畫有哪兩種處理方式?為什么處理圖像盡量使用 GPU?
- CPU:中央處理器
- GPU:圖形處理器
- 總體來說,我們可以用軟件(CPU)做任何事情,但是對于圖像處理,通常用硬件(GPU)會更快,
因?yàn)?GPU 使用圖像對高度和浮點(diǎn)運(yùn)算做了優(yōu)化。
2. 就 CPU 和 GPU 而言,我們所說的動畫性能優(yōu)化,其實(shí)是讓這兩者怎么配合?
- 大多數(shù)動畫性能優(yōu)化都是關(guān)于
合理利用 GPU 和 CPU,使得它們都不會超出負(fù)荷。 - 于是我們首先需要知道 Core Animation 是如何在這兩個處理器之間分配工作的。
3. Core Animation 處在 iOS 的核心地位:應(yīng)用內(nèi)和應(yīng)用間都會用到它,那對于 iOS 它處于什么層級呢?
- 一個簡單的動畫可能同步顯示多個 app 的內(nèi)容,例如當(dāng)在 iPad 上多個程序之間使用手勢切換,會使得多個程序同時顯示在屏幕上。
- 在一個特定的應(yīng)用程序中用代碼實(shí)現(xiàn)它是沒有意義的,因?yàn)樵?iOS 中不可能實(shí)現(xiàn)這種效果(APP 都是被沙箱管理,不能訪問別的視圖)。
- 動畫和屏幕上組合的圖層實(shí)際上被一個單獨(dú)的進(jìn)程管理,而不是你的應(yīng)用程序。這個進(jìn)制就是所謂的
渲染服務(wù)。 - 在 iOS5 和之前的版本這個
渲染服務(wù)叫做SpringBoard 進(jìn)程(同時管理著 iOS 的主屏)。在 iOS6 之后的版本叫做BackBoard。
4. 一個動畫的展示過程?
在應(yīng)用程序內(nèi),當(dāng)運(yùn)行一段動畫時候,這個過程會被分離成如下四個階段
- ①
布局- 這是準(zhǔn)備你的視圖/圖層的層級關(guān)系,以及設(shè)置圖層屬性(位置,背景色,邊框等等)的階段。 - ②
顯示- 這個圖層的寄宿圖片被繪制的階段。繪制可能涉及你的-drawRect:和-drawLayer:inContext:方法的調(diào)用路徑。 - ③
準(zhǔn)備- 這是Core Animation準(zhǔn)備發(fā)送動畫數(shù)據(jù)到渲染服務(wù)的階段。這同時也是 Core Animation將要執(zhí)行一些別的事務(wù)例如解碼動畫過程。 - ④
提交- 這是最后的階段,Core Animation 打包所有圖層和動畫屬性,然后通過 IPC(進(jìn)程間通信)發(fā)送到渲染服務(wù)進(jìn)行顯示。
但是上面四個階段僅僅發(fā)生在你的應(yīng)用程序之內(nèi),在動畫在屏幕上顯示之前仍然有更多的工作。一旦打包的圖層和動畫到達(dá)
渲染服務(wù)進(jìn)程,它們會被反序列化來形成另一個叫做渲染樹的圖層樹。使用這個樹狀結(jié)構(gòu),渲染服務(wù)對動畫的每一幀做出如下工作:
- ⑤ 對所有的圖層屬性計算中間值,設(shè)置 OpenGL 幾何形狀(紋理化的三角形)來執(zhí)行渲染
- ⑥ 在屏幕上渲染可見的三角形
所以一共有六個階段;最后兩個階段在動畫過程中不停地重復(fù)。
5. 上面六個階段,哪些在 CPU?哪些在 GPU?哪些階段開發(fā)者能介入?
- 前五個階段都在軟件層面處理(通過 CPU),只有最后一個被 GPU 執(zhí)行。
- 而且,你真正只能控制前面兩個階段:布局和顯示。Core Animation 框架在內(nèi)部處理剩下的事務(wù),你也控制不了它。
- 這個并不是個問題,因?yàn)樵诓季趾惋@示階段,你可以決定哪些由 CPU 執(zhí)行,哪些交給 GPU 去做。那么該如何判斷呢?
6. GPU 主要干些什么事情?
- GPU 為一個具體的任務(wù)做了優(yōu)化:它用來采集圖片和形狀(三角形),運(yùn)行變換,應(yīng)用紋理和混合然后把它們輸送到屏幕上。
7. 有一些事情會降低(基于 GPU)圖層繪制(至少說兩點(diǎn))?
- 重繪 - 主要由重疊的半透明圖層引起。GPU 的
填充比例(用顏色填充像素的比率)是有限的,所以需要避免重繪(每一幀用相同的像素填充多次)的發(fā)生。 - 離屏繪制 - 這發(fā)生在當(dāng)不能直接在屏幕上繪制,并且必須回到離屏圖片的上下文的時候。
- 過大的圖片 - 如果視圖超過 GPU 支持的2048x2048或者4096x4096尺寸的紋理,就必須要用CPU在圖層每次顯示之前對圖片預(yù)處理,
8. 哪些 CPU 的操作會延遲動畫的開始時間,從而造成卡頓(至少說兩點(diǎn))?
- 布局計算 - 如果你的視圖層級過于復(fù)雜,當(dāng)視圖呈現(xiàn)或者修改的時候,計算圖層幀率就會消耗一部分時間。
- 大量視圖懶加載 - iOS只會當(dāng)視圖控制器的視圖顯示到屏幕上時才會加載它,但是同一瞬間太多視圖需要加載了。
- 解壓圖片 - PNG或者JPEG壓縮之后的圖片文件會比同質(zhì)量的位圖小得多。但是在圖片繪制到屏幕上之前,必須把它擴(kuò)展成完整的未解壓的尺寸(通常等同于圖片寬 x 長 x 4個字節(jié))。
二、離屏渲染
1. 如何通過iOS模擬器判斷一個視圖,是否發(fā)生了離屏渲染?
- 開啟 iOS 模擬器的下面這個選項(xiàng),如果視圖呈換色,就表明發(fā)生了離屏渲染

image.png
2. 為什么有些操作會發(fā)生離屏渲染?
- 圖層的疊加繪制大概遵循
“畫家算法” -
畫家算法:先繪制場景中離觀察者較遠(yuǎn)的物體,再繪制較近的物體。
先繪制紅色部分,再繪制?色部分,最后再繪制灰?部分,即可解決隱藏面消除的問題。即將場景按照物理距離和觀察者的距離遠(yuǎn)近排序,由遠(yuǎn)及近的繪制即可。

image.png
當(dāng)我們設(shè)置了cornerRadius以及masksToBounds進(jìn)行圓角+裁剪時,masksToBounds裁剪屬性會應(yīng)用到所有的圖層上。

image.png
本來我們從后往前繪制,繪制完一個圖層就可以丟棄了。但現(xiàn)在需要依次在 Offscreen Buffer中保存,等待圓角+裁剪處理,即引發(fā)了 離屏渲染 。
3. 為什么離屏渲染會降低效率?
- 要創(chuàng)建離屏緩沖區(qū)
- 要進(jìn)行緩沖區(qū)上下文的切換
- 需要離屏渲染的計算通常比較復(fù)雜(不符合畫家算法)

渲染結(jié)果先經(jīng)過了離屏buffer,再到frame buffer
4. 同時設(shè)置 cornerRadius 和 masksToBounds,也就是圓角 + 裁剪,一定會觸發(fā)離屏渲染嗎?
- 不一定
- 如果僅僅裁剪了
一個背景顏色或者一個圖片,不會觸發(fā)離屏渲染(其實(shí)就是符合畫家算法,不需要另起離屏緩沖區(qū))
5. 小技巧:對于只有一張圖片的 UIButton,如何設(shè)置圓角而不觸發(fā)離屏渲染?
- 下面這種做法
不會觸發(fā)離屏渲染
// 下面這種做法不會觸發(fā)離屏渲染
btn.imageView.clipsToBounds = YES;
btn.imageView.layer.cornerRadius = 30;
- 下面這種做法
會觸發(fā)離屏渲染
// 下面這種做法會觸發(fā)離屏渲染
btn.clipsToBounds = YES;
btn.layer.cornerRadius = 30;