NGUI所見即所得之深刻剖析UIPanel,UIWidget,UIDrawCall底層原理函數
By D.S.Qiu性能
尊重他人的勞動,支持原創,轉載請註明出處:http.dsqiu.iteye.com優化
以前項目中用的NGUI的版本是3.0.7 f3,開始的時候感受沒有什麼問題,直達最近項目UI的完成度比較高時,就忽然出現掉幀很嚴重的現象,即便只有一個UI打開(其餘都是active = false的狀況下),打開profier,發現UIPanel LateUpdate 居然佔了CPU使用率的50%左右,這太恐怖了,雖然以前看到過有吐槽NGUI的機制的,可是我以爲爲了保證通用犧牲一些性能仍是在所不免的,可是沒想到這個版本居然這麼廢。ui
以前雖然研究過NGUI的UIWidget, UIDrawCall,UIGeometry和 UIPanel等基礎腳本(NGUI所見即所得之UIWidget , UIGeometry & UIDrawCall,NGUI所見即所得之UIPanel),也大概清楚了NGUI的繪製原理。但對具體的邏輯仍是不夠清楚,有點百裏挑一。爲了更好的改進NGUI的性能以及更加規範使用NGUI,只有把NGUI的底層吃透。spa
因爲在以前的文章介紹了UIGeometry,UIDrawCall和UIWidget之間的關係,以及UIPanel的管理機制,因此本文主要剖析底層的原理,主要要弄清楚一下問題:orm
1. transform ,大小(size)的變化的底層繪製影響blog
2.顏色(包括透明度)變化的底層繪製影響ip
3.enable 和 disable 狀態變化底層的處理內存
4.UIDrawCall 和 UIPanel 機制的細節ci
未免讀者理不順,先簡單說下UIGeometry,UIDrawCall和UIWidget的關係:UIWidget是UI的基礎組件(UILabel,UISprite)的基類,含有組件的基本信息(width,Height,color等),UIGeometry是UIWidget的幾何數據,記錄了頂點座標,貼圖的UVs和顏色等信息,UIDrawCall是將多個UIWidget的UIGeometry組合起來一塊兒繪製,具體的UIWidget若是共用一個UIDrawCall由UIPanel控制,要想了解更多能夠點擊上面的連接的文章查看。
雖然從人的求知慾角度,咱們的疑問是按照上面 1-4 排列的,可是下面倒是從 4開始介紹,只要把4理解透了3,2,1就天然迎刃而解了。
UIDrawCall
UIGeometry相對簡單,這裏就再也不浪費篇幅介紹了,UIDrawCall是繪製的基礎組件,仍是有必要仔細介紹下。
1.成員變量
僅對幾個比較重要又搞不明白的變量進行解析:
a)List<UIDrawCall> mActiveList 和 mInactiveList : 爲何會有兩個List,mAcitveList 保持當前激活的UIDrawCall, mInactiveList主要是用於回收UIDrawCall.Destroy()的UIDrawCall,以達到循環利用避免內存的反覆申請和釋放,減小GC的次數。這個機制前面介紹的 vp_Timer採用這個策略。
b)Material mMaterial 和 mDynamicMat:不是講究節約內存麼,怎麼會有兩個Material,mMaterial就是咱們圖集的材質Material,mDynamicMat是實際採用的Material,由於UIPanel 的 Clipping有 AlphaClipp 和 SoftClip 這兩個是要經過切換Shader來實現的,因此須要對應動態建立一個Material,這個就是mDynamicMat的存在。
c)bool mRebuildMat 和 isDirty:這二者表示UIDrawCall所處的狀態,當改變UIDrawCall的 Material 和 Shader ,mRebuildMat就變爲 true,就會引發 RebuildMaterial()的調用。isDirty若爲 true ,表示UIDrawCall要進行重寫「填充」,調用Set函數
2.幾個重要的函數
a)CreateMaterial, RebuildMaterial 和 UpdateMaterial,這是三個後面包含前面,總之就是完成材質的建立或更新。
b)Set (BetterList<Vector3> verts,BetterList<Vector3> norms,BetterList<Vector4> tans,BetterList<Vector2> uvs,BetterList<Color32> cols),根據verts,norms,tans,uvs,cols從新構建Mesh,MeshRender
c)OnEnable,Ondisable 和 OnDestroy:銷燬了mDynamicMat,能夠看出Material比Mesh更簡單,不用太考慮內存問題,而後OnDestroy()沒有發現調用。
d)Create , Clear 和 Destroy:Create 先從mInactiveList中取出一個,在附上屬性達到重複利用,Destroy是將沒用的UIDrawCall從mActiveList移到mInactiveList中:
UIPanel
以前就介紹過UIPanel,也畫了UIPanel主要函數的調用棧(點擊查看),這裏也簡單羅列下LateUpdate的函數調用:
LateUpdate
UpdateSelf
UpdateTransformMatrix : 調整 worldToLocal 矩陣用於調整其管理的UIWidget的transform,並進一步調整頂點信息,還調整clipOffset的變量
UpdateLayers : 更新LayerMask
UpdateWidgets : 調整UIWidget
UIWidget.UpdateGeometry : 調整UIWidget的幾何(頂點等)信息
OnFill(geometry.verts, geometry.uvs, geometry.cols): 若是顏色(透明度)和大小等改變就從新填充頂點信息
geometry.ApplyTransform : transform發生改變,調整UIGeometry中頂點的位置(矩陣計算)
FillAllDrawCalls or FillDrawCall : 從新構建全部UIDrawCall (當UIWdiget的depth發生變化),不然只調整有UIWidget的UIDrawCall
UpdateDrawCalls : 調整UIPanel管理的UIDrawCall 的 transform 和 clip 等屬性
愈來愈以爲NGUI的代碼組件結構愈來愈清晰,雖然篇幅很長(有1600多行)但理解仍是能夠很簡單的。
UIWidget
UIWidget有一個變量 mChange 和一個函數 MarkAsChange() 很重要,這兩個標記UIWidget是否變化須要進行調整的狀態。
1.當 Anchor , Pivot , Alpha 以及 UILabel 和 UISprite 的一些狀態的改變 mChange = true ,即會調整Geometry信息
2.MarkAsChange 會執行 drawCall.isDirty = true; 這樣就會致使其所屬的 UIDrawCall 須要重寫構建
針對前面 1-3 的疑問進行以下總結:
UIWidget(UILabel , UISprite)的任何變化(transform , drawSize , width , heigth , color , pivot ,anchor 等)變化都會引發繪製該UIWidget進行從新構建——對Mesh的頂點進行刷新,尤爲是depth的變化會使得全部UIDrawCall 進行重寫調整,這是很是耗性能的。
總結:
NGUI的好處就是:合併Mesh和圖集節省DrawCall,因爲影響Mesh的因素太多了,因此會「牽一髮而動全身」,NGUI採起的一個通用的策略,沒有對不一樣的狀況作不一樣的處理,都是採用某個UIDrawCall所有刷新甚至是所有UIDrawCall的刷新,這也是你們吐槽的「重中之重」。
D.S.Qiu認爲針對不用的狀況仍是會有很多優化的,好比改變alpha值,能夠不須要從新調整頂點verts,而只須要單獨調整cols的alpha通道,改變depth也不須要所有調整UIDrawCall,這樣明顯是沒有作到嚴格的管理的。
對此,D.S.Qiu提出2點使用NGUI製做UI的建議:
1)儘可能是UIWidget靜動分離,即靜止的儘可能合成單獨一個UIPanel,會變化的就放在另一個UIPanel
2)儘可能控制UIPanel和UIDrawCall的數量,充分利用圖集的空間,對「夾層」的狀況能夠經過圖集的調整,使得UIDrawCall變得更少
因爲時間關係(立刻2:30了),就只能寫到這裏,若是你有NGUI的任何問題,歡迎和D.S.Qiu進行交流討論。
若是您對D.S.Qiu有任何建議或意見能夠在文章後面評論,或者發郵件(gd.s.qiu@gmail.com)交流,您的鼓勵和支持是我前進的動力,但願能有更多更好的分享。
轉載請在文首註明出處:http://dsqiu.iteye.com/blog/2025177
更多精彩請關注D.S.Qiu的博客和微博(ID:靜水逐風)