iOS開發(fā)-視圖渲染與性能優(yōu)化

前言

關(guān)于iOS的視圖渲染流程,以及性能優(yōu)化的建議。
源于WWDC視頻
我假設(shè)你是一個這樣的開發(fā)者:

  • 了解OpenGL ES;
  • 了解view hierarchy;
  • 了解instruments;

view hierarchy和instruments網(wǎng)上資料很多,OpenGL ES的你可以看OpenGL ES文集

視圖渲染

UIKit是常用的框架,顯示、動畫都通過CoreAnimation。
CoreAnimation是核心動畫,依賴于OpenGL ES做GPU渲染,CoreGraphics做CPU渲染;
最底層的GraphicsHardWare是圖形硬件。



下圖是另外一種表現(xiàn)的形式。在屏幕上顯示視圖,需要CPU和GPU一起協(xié)作。一部數(shù)據(jù)通過CoreGraphics、CoreImage由CPU預(yù)處理。最終通過OpenGL ES將數(shù)據(jù)傳送到 GPU,最終顯示到屏幕。

CoreImage支持CPU、GPU兩種處理模式。

顯示邏輯

  • 1、CoreAnimation提交會話,包括自己和子樹(view hierarchy)的layout狀態(tài)等;
  • 2、RenderServer解析提交的子樹狀態(tài),生成繪制指令;
  • 3、GPU執(zhí)行繪制指令;
  • 4、顯示渲染后的數(shù)據(jù);


提交流程(以動畫為例)

第2步為prepare to commit animation (layoutSubviews,drawRect:);


1、布局(Layout)

調(diào)用layoutSubviews方法;
調(diào)用addSubview:方法;

會造成CPU和I/O瓶頸;

2、顯示(Display)

通過drawRect繪制視圖;
繪制string(字符串);

會造成CPU和內(nèi)存瓶頸;
每個UIView都有CALayer,同時圖層有一個像素存儲空間,存放視圖;調(diào)用-setNeedsDisplay的時候,僅會設(shè)置圖層為dirty。
當(dāng)渲染系統(tǒng)準(zhǔn)備就緒,調(diào)用視圖的-display方法,同時裝配像素存儲空間,建立一個CoreGraphics上下文(CGContextRef),將上下文push進(jìn)上下文堆棧,繪圖程序進(jìn)入對應(yīng)的內(nèi)存存儲空間。

UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(10, 10)];
[path addLineToPoint:CGPointMake(20, 20)];
[path closePath];
path.lineWidth = 1;
[[UIColor redColor] setStroke];
[path stroke];

在-drawRect方法中實現(xiàn)如上代碼,UIKit會將自動生成的CGContextRef 放入上下文堆棧。
當(dāng)繪制完成后,視圖的像素會被渲染到屏幕上;當(dāng)下次再次調(diào)用視圖的-setNeedsDisplay,將會再次調(diào)用-drawRect方法。

3、準(zhǔn)備提交(Prepare)

解碼圖片;
圖片格式轉(zhuǎn)換;

GPU不支持的某些圖片格式,盡量使用GPU能支持的圖片格式;

4、提交(Commit)

打包layers并發(fā)送到渲染server;
遞歸提交子樹的layers;
如果子樹太復(fù)雜,會消耗很大,對性能造成影響;

盡可能簡化viewTree;

當(dāng)顯示一個UIImageView時,Core Animation會創(chuàng)建一個OpenGL ES紋理,并確保在這個圖層中的位圖被上傳到對應(yīng)的紋理中。當(dāng)你重寫-drawInContext方法時,Core Animation會請求分配一個紋理,同時確保Core Graphics會將你在-drawInContext中繪制的東西放入到紋理的位圖數(shù)據(jù)中。

Tile-Based 渲染

這里有PDF文檔
Tiled-Based 渲染是移動設(shè)備的主流。整個屏幕會分解成N*Npixels組成的瓦片(Tiles),tiles存儲于SoC 緩存(SoC=system on chip,片上系統(tǒng),是在整塊芯片上實現(xiàn)一個復(fù)雜系統(tǒng)功能,如intel cpu,整合了集顯,內(nèi)存控制器,cpu運(yùn)核心,緩存,隊列、非核心和I/O控制器)。
幾何形狀會分解成若干個tiles,對于每一塊tile,把必須的幾何體提交到OpenGL ES,然后進(jìn)行渲染(光柵化)。完畢后,將tile的數(shù)據(jù)發(fā)送回cpu。

傳送數(shù)據(jù)是非常消耗性能的,相對來說,多次計算比多次發(fā)送數(shù)據(jù)更加經(jīng)濟(jì)高效,但是額外的計算也會產(chǎn)生一些性能損耗。
PS:在移動平臺控制幀率在一個合適的水平可以節(jié)省電能,會有效的延長電池壽命,同時會相對的提高用戶體驗。這里有詳細(xì)的介紹

1、普通的Tile-Based渲染流程

1、CommandBuffer,接受OpenGL ES處理完畢的渲染指令;
2、Tiler,調(diào)用頂點著色器,把頂點數(shù)據(jù)進(jìn)行分塊(Tiling);
3、ParameterBuffer,接受分塊完畢的tile和對應(yīng)的渲染參數(shù);
4、Renderer,調(diào)用片元著色器,進(jìn)行像素渲染;
5、RenderBuffer,存儲渲染完畢的像素;


2、離屏渲染 —— 遮罩(Mask)

1、渲染layer的mask紋理,同Tile-Based的基本渲染邏輯;
2、渲染layer的content紋理,同Tile-Based的基本渲染邏輯;
3、Compositing操作,合并1、2的紋理;


3、離屏渲染 ——UIVisiualEffectView


使用UIBlurEffect,應(yīng)該是盡可能小的view,因為性能消耗巨大。


4、渲染等待

由于每一幀的頂點和像素處理相對獨立,iOS會將CPU處理,頂點處理,像素處理安排在相鄰的三幀中。如圖,當(dāng)一個渲染命令提交后,要在當(dāng)幀之后的第三幀,渲染結(jié)果才會顯示出來。


5、光柵化

把視圖的內(nèi)容渲染成紋理并緩存,可以通過CALayer的shouldRasterize屬性開啟光柵化。
注意,光柵化的元素,總大小限制為2.5倍的屏幕。
更新內(nèi)容時,會啟用離屏渲染,所以更新代價較大,只能用于靜態(tài)內(nèi)容;而且如果光柵化的元素100ms沒有被使用將被移除,故而不常用元素的光柵化并不會優(yōu)化顯示。

6、組透明度

CALayer的allowsGroupOpacity屬性,UIView 的alpha屬性等同于 CALayer opacity屬性。GroupOpacity=YES,子 layer 在視覺上的透明度的上限是其父 layer 的opacity。當(dāng)父視圖的layer.opacity != 1.0時,會開啟離屏渲染。
layer.opacity == 1.0時,父視圖不用管子視圖,只需顯示當(dāng)前視圖即可。

The default value is read from the boolean UIViewGroupOpacity property in the main bundle’s Info.plist file. If no value is found, the default value is YES for apps linked against the iOS 7 SDK or later and NO for apps linked against an earlier SDK.
為了讓子視圖與父視圖保持同樣的透明度,從 iOS 7 以后默認(rèn)全局開啟了這個功能。

性能優(yōu)化

這個是WWDC推薦的檢查項目:



1、幀率一般在多少?

60幀每秒;(TimeProfiler)

2、是否存在CPU和GPU瓶頸? (查看占有率)

更少的使用CPU和GPU可以有效的保存電量;

3、額外的使用CPU來進(jìn)行渲染?

重寫了drawRect會導(dǎo)致CPU渲染;在CPU進(jìn)行渲染時,GPU大多數(shù)情況是處于等待狀態(tài);

4、是否存在過多離屏渲染?

越少越好;離屏渲染會導(dǎo)致上下文切換,GPU產(chǎn)生idle;

5、是否渲染過多視圖?

視圖越少越好;透明度為1的視圖更受歡迎;

6、使用奇怪的圖片格式和大小?

避免格式轉(zhuǎn)換和調(diào)整圖片大小;一個圖片如果不被GPU支持,那么需要CPU來轉(zhuǎn)換。(Xcode有對PNG圖片進(jìn)行特殊的算法優(yōu)化)

7、使用昂貴的特效?

理解特效的消耗,同時調(diào)整合適的大小;例如前面提到的UIBlurEffect;

8、視圖樹上不必要的元素?

理解視圖樹上所有點的必要性,去掉不必要的元素;忘記remove視圖是很常見的事情,特別是當(dāng)View的類比較大的時候。


以上,是8個問題對應(yīng)的工具。遇到性能問題,先分析、定位問題所在,而不是埋頭鉆進(jìn)代碼的海洋。

性能優(yōu)化實例

1、陰影


上面的做法,會導(dǎo)致離屏渲染;下面的做法是正確的做法。

2、圓角


不要使用不必要的mask,可以預(yù)處理圖片為圓形;或者添加中間為圓形透明的白色背景視圖。即使添加額外的視圖,會導(dǎo)致額外的計算;但仍然會快一點,因為相對于切換上下文,GPU更擅長渲染。
離屏渲染會導(dǎo)致GPU利用率不到100%,幀率卻很低。(切換上下文會產(chǎn)生idle time)

3、工具

使用instruments的CoreAnimation工具來檢查離屏渲染,黃色是我們不希望看到的顏色。


使用真機(jī)來調(diào)試,因為模擬器使用的CALayer是OSX的CALayer,不是iOS的CALayer。如果用模擬器調(diào)試,會發(fā)現(xiàn)所有的視圖都是黃色。

總結(jié)

視頻中的這一句話,讓我對iOS的視圖渲染茅塞頓開。

CALayer in CA is two triangles.

文章中關(guān)于Tile-Based架構(gòu),以及像素顯示渲染的理解基于我對OpenGL ES學(xué)習(xí)以及iOS開發(fā)收獲。
iOS開發(fā)收獲很容易找到,但是OpenGL ES相對來說很少。越來越覺得自己花時間去研究是值得的。

Core Animation的核心是OpenGL ES的一個抽象物,CoreAnimation讓你直接使用OpenGL ES的功能,卻不需要處理OpenGL ES的復(fù)雜操作。
可是我仍越過CoreAnimation去學(xué)習(xí)OpenGL ES。因為我不滿足用Apple提供的API拼湊界面。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容