前言
關(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。
當渲染系統(tǒng)準備就緒慢睡,調(diào)用視圖的-display方法逐工,同時裝配像素存儲空間,建立一個CoreGraphics上下文(CGContextRef)漂辐,將上下文push進上下文堆棧泪喊,繪圖程序進入對應(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 放入上下文堆棧袒啼。
當繪制完成后,視圖的像素會被渲染到屏幕上纬纪;當下次再次調(diào)用視圖的-setNeedsDisplay蚓再,將會再次調(diào)用-drawRect方法。
3育八、準備提交(Prepare)
解碼圖片对途;
圖片格式轉(zhuǎn)換;
GPU不支持的某些圖片格式髓棋,盡量使用GPU能支持的圖片格式;
4惶洲、提交(Commit)
打包layers并發(fā)送到渲染server按声;
遞歸提交子樹的layers;
如果子樹太復(fù)雜恬吕,會消耗很大签则,對性能造成影響;
盡可能簡化viewTree铐料;
當顯示一個UIImageView時渐裂,Core Animation會創(chuàng)建一個OpenGL ES紋理豺旬,并確保在這個圖層中的位圖被上傳到對應(yī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運核心,緩存狐援,隊列卜朗、非核心和I/O控制器)。
幾何形狀會分解成若干個tiles咕村,對于每一塊tile场钉,把必須的幾何體提交到OpenGL ES,然后進行渲染(光柵化)懈涛。完畢后逛万,將tile的數(shù)據(jù)發(fā)送回cpu。
傳送數(shù)據(jù)是非常消耗性能的批钠,相對來說宇植,多次計算比多次發(fā)送數(shù)據(jù)更加經(jīng)濟高效,但是額外的計算也會產(chǎn)生一些性能損耗埋心。
PS:在移動平臺控制幀率在一個合適的水平可以節(jié)省電能指郁,會有效的延長電池壽命,同時會相對的提高用戶體驗拷呆。這里有詳細的介紹
1闲坎、普通的Tile-Based渲染流程
1、CommandBuffer茬斧,接受OpenGL ES處理完畢的渲染指令腰懂;
2、Tiler项秉,調(diào)用頂點著色器绣溜,把頂點數(shù)據(jù)進行分塊(Tiling);
3娄蔼、ParameterBuffer怖喻,接受分塊完畢的tile和對應(yīng)的渲染參數(shù)底哗;
4、Renderer锚沸,調(diào)用片元著色器跋选,進行像素渲染;
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處理,頂點處理嫂粟,像素處理安排在相鄰的三幀中娇未。如圖,當一個渲染命令提交后星虹,要在當幀之后的第三幀零抬,渲染結(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蹂楣。當父視圖的layer.opacity != 1.0時,會開啟離屏渲染讯蒲。
layer.opacity == 1.0時痊土,父視圖不用管子視圖,只需顯示當前視圖即可墨林。
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 以后默認全局開啟了這個功能。
性能優(yōu)化
這個是WWDC推薦的檢查項目:
1旭等、幀率一般在多少酌呆?
60幀每秒;(TimeProfiler)
2搔耕、是否存在CPU和GPU瓶頸隙袁? (查看占有率)
更少的使用CPU和GPU可以有效的保存電量;
3度迂、額外的使用CPU來進行渲染藤乙?
重寫了drawRect會導(dǎo)致CPU渲染;在CPU進行渲染時惭墓,GPU大多數(shù)情況是處于等待狀態(tài)坛梁;
4、是否存在過多離屏渲染腊凶?
越少越好划咐;離屏渲染會導(dǎo)致上下文切換,GPU產(chǎn)生idle钧萍;
5褐缠、是否渲染過多視圖?
視圖越少越好风瘦;透明度為1的視圖更受歡迎队魏;
6、使用奇怪的圖片格式和大小胡桨?
避免格式轉(zhuǎn)換和調(diào)整圖片大泄倭薄;一個圖片如果不被GPU支持昧谊,那么需要CPU來轉(zhuǎn)換刽虹。(Xcode有對PNG圖片進行特殊的算法優(yōu)化)
7、使用昂貴的特效呢诬?
理解特效的消耗涌哲,同時調(diào)整合適的大小尚镰;例如前面提到的UIBlurEffect阀圾;
8、視圖樹上不必要的元素钓猬?
理解視圖樹上所有點的必要性稍刀,去掉不必要的元素;忘記remove視圖是很常見的事情敞曹,特別是當View的類比較大的時候账月。
以上,是8個問題對應(yīng)的工具澳迫。遇到性能問題朗徊,先分析昏翰、定位問題所在,而不是埋頭鉆進代碼的海洋。
性能優(yōu)化實例
1痊银、陰影
上面的做法攒岛,會導(dǎo)致離屏渲染谎碍;下面的做法是正確的做法兽叮。
2、圓角
不要使用不必要的mask卒稳,可以預(yù)處理圖片為圓形蹋半;或者添加中間為圓形透明的白色背景視圖。即使添加額外的視圖充坑,會導(dǎo)致額外的計算减江;但仍然會快一點,因為相對于切換上下文捻爷,GPU更擅長渲染辈灼。
離屏渲染會導(dǎo)致GPU利用率不到100%,幀率卻很低也榄。(切換上下文會產(chǎn)生idle time)
3巡莹、工具
使用instruments的CoreAnimation工具來檢查離屏渲染,黃色是我們不希望看到的顏色。
使用真機來調(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學習以及iOS開發(fā)收獲吨述。
iOS開發(fā)收獲很容易找到岩睁,但是OpenGL ES相對來說很少。越來越覺得自己花時間去研究是值得的揣云。
Core Animation的核心是OpenGL ES的一個抽象物捕儒,CoreAnimation讓你直接使用OpenGL ES的功能,卻不需要處理OpenGL ES的復(fù)雜操作邓夕。
可是我仍越過CoreAnimation去學習OpenGL ES刘莹。因為我不滿足用Apple提供的API拼湊界面。