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:静水逐风)

相关文章
相关标签/搜索