NGUI所見即所得之深刻剖析UIPanel,UIWidget,UIDrawCall底層原理

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 & UIDrawCallNGUI所見即所得之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函數

C#代碼   收藏代碼
  1. public Material baseMaterial  
  2. {  
  3.     get{return mMaterial;}  
  4.     set  
  5.     {  
  6.         if (mMaterial != value)  
  7.         {  
  8.             mMaterial = value;  
  9.             mRebuildMat = true;  
  10.         }  
  11.     }  
  12. }  
  13. public Shader shader  
  14. {  
  15.     getreturn mShader;}  
  16.     set  
  17.     {  
  18.         if (mShader != value)  
  19.         {  
  20.             mShader = value;  
  21.             mRebuildMat = true;  
  22.         }  
  23.     }  
  24. }  

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#代碼   收藏代碼
  1. mMesh.vertices = verts.buffer;  
  2. mMesh.uv = uvs.buffer;  
  3. mMesh.colors32 = cols.buffer;  
  4. if (norms != null) mMesh.normals = norms.buffer;  
  5. if (tans != null) mMesh.tangents = tans.buffer;  

      c)OnEnable,Ondisable 和 OnDestroy:銷燬了mDynamicMat,能夠看出Material比Mesh更簡單,不用太考慮內存問題,而後OnDestroy()沒有發現調用。

C#代碼   收藏代碼
  1. void OnEnable () { mRebuildMat = true; }  
  2.   
  3. void OnDisable ()  
  4. {  
  5.     depthStart = int.MaxValue;  
  6.     depthEnd = int.MinValue;  
  7.     panel = null;  
  8.     manager = null;  
  9.     mMaterial = null;  
  10.     mTexture = null;  
  11.   
  12.     NGUITools.DestroyImmediate(mDynamicMat);  
  13.     mDynamicMat = null;  
  14. }  
  15.   
  16. void OnDestroy ()  
  17. {  
  18.     NGUITools.DestroyImmediate(mMesh);  
  19. }  

       d)Create , Clear 和 Destroy:Create 先從mInactiveList中取出一個,在附上屬性達到重複利用,Destroy是將沒用的UIDrawCall從mActiveList移到mInactiveList中:

C#代碼   收藏代碼
  1. static UIDrawCall Create (string name)  
  2. {         
  3.                //省略其餘處理  
  4.     if (mInactiveList.size > 0)  
  5.     {  
  6.         UIDrawCall dc = mInactiveList.Pop();  
  7.         mActiveList.Add(dc);  
  8.         if (name != null) dc.name = name;  
  9.         NGUITools.SetActive(dc.gameObject, true);  
  10.         return dc;  
  11.     }  
  12.                //省略其餘處理  
  13.     // Create the draw call  
  14.     mActiveList.Add(newDC);  
  15.     return newDC;  
  16. }  
  17. static public void Destroy (UIDrawCall dc)  
  18. {  
  19.     if (dc)  
  20.     {  
  21.         if (Application.isPlaying)  
  22.         {  
  23.             if (mActiveList.Remove(dc))  
  24.             {  
  25.                 NGUITools.SetActive(dc.gameObject, false);  
  26.                 mInactiveList.Add(dc);  
  27.             }  
  28.         }  
  29.         else  
  30.         {  
  31.             mActiveList.Remove(dc);  
  32.             NGUITools.DestroyImmediate(dc.gameObject);  
  33.         }  
  34.     }  
  35. }  

 

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:靜水逐風)

相關文章
相關標籤/搜索