这是我去搜狐畅游面试时,面试官问的一个问题。问NGUI的机制原理是什么?就是这个插件是根据什么写出来的。当时没答上来,下面是我从转载过来的,可以研究研究。

之前项目中用的NGUI的版本是3.0.7 f3,开始的时候感觉没有什么问题,直达最近项目UI的完成度比较高时,就突然出现掉帧很严重的现象,即使只有一个UI打开(其他都是active = false的情况下),打开profier,发现UIPanel LateUpdate 竟然占了CPU使用率的50%左右,这太恐怖了,虽然之前看到过有吐槽NGUI的机制的,但是我觉得为了保证通用牺牲一些性能还是在所难免的,但是没想到这个版本竟然这么废。

之前虽然研究过NGUI的UIWidget, UIDrawCall,UIGeometry和 UIPanel等基础脚本(NGUI所见即所得之UIWidget , UIGeometry & UIDrawCallNGUI所见即所得之UIPanel),也大概清楚了NGUI的绘制原理。但对具体的逻辑还是不够清楚,有点凤毛麟角。为了更好的改进NGUI的性能以及更加规范使用NGUI,只有把NGUI的底层吃透。

由于在之前的文章介绍了UIGeometry,UIDrawCall和UIWidget之间的关系,以及UIPanel的管理机制,所以本文主要剖析底层的原理,主要要弄清楚一下问题:

1. transform ,大小(size)的变化的底层绘制影响

2.颜色(包括透明度)变化的底层绘制影响

3.enable 和 disable 状态变化底层的处理

4.UIDrawCall 和 UIPanel 机制的细节

未免读者理不顺,先简单说下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#代码

    public Material baseMaterial
{
get{return mMaterial;}
set
{
if (mMaterial != value)
{
mMaterial = value;
mRebuildMat = true;
}
}
}
public Shader shader
{
get{ return mShader;}
set
{
if (mShader != value)
{
mShader = value;
mRebuildMat = true;
}
}
}

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。

mMesh.vertices = verts.buffer;
mMesh.uv = uvs.buffer;
mMesh.colors32 = cols.buffer;
if (norms != null) mMesh.normals = norms.buffer;
if (tans != null) mMesh.tangents = tans.buffer;

c)OnEnable,Ondisable 和 OnDestroy:销毁了mDynamicMat,可以看出Material比Mesh更简单,不用太考虑内存问题,然后OnDestroy()没有发现调用。

    void OnEnable () { mRebuildMat = true; }

    void OnDisable ()
{
depthStart = int.MaxValue;
depthEnd = int.MinValue;
panel = null;
manager = null;
mMaterial = null;
mTexture = null; NGUITools.DestroyImmediate(mDynamicMat);
mDynamicMat = null;
} void OnDestroy ()
{
NGUITools.DestroyImmediate(mMesh);
}

d)Create , Clear 和 Destroy:Create 先从mInactiveList中取出一个,在附上属性达到重复利用,Destroy是将没用的UIDrawCall从mActiveList移到mInactiveList中:

    static UIDrawCall Create (string name)
{
//省略其他处理
if (mInactiveList.size > )
{
UIDrawCall dc = mInactiveList.Pop();
mActiveList.Add(dc);
if (name != null) dc.name = name;
NGUITools.SetActive(dc.gameObject, true);
return dc;
}
//省略其他处理
// Create the draw call
mActiveList.Add(newDC);
return newDC;
}
static public void Destroy (UIDrawCall dc)
{
if (dc)
{
if (Application.isPlaying)
{
if (mActiveList.Remove(dc))
{
NGUITools.SetActive(dc.gameObject, false);
mInactiveList.Add(dc);
}
}
else
{
mActiveList.Remove(dc);
NGUITools.DestroyImmediate(dc.gameObject);
}
}
}

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变得更少。

NGUI的原理机制:深入剖析UIPanel,UIWidget,UIDrawCall底层原理的更多相关文章

  1. NGUI所见即所得之深入剖析UIPanel,UIWidget,UIDrawCall底层原理

    NGUI所见即所得之深入剖析UIPanel,UIWidget,UIDrawCall底层原理 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处:http.dsqiu.iteye.com 之 ...

  2. 剖析Javascript中forEach()底层原理,如何重写forEach()

    我们平时用的forEach()一般是这样用的 var myArr = [1,5,8] myArr.forEach((v,i)=>{ console.log(v,i) })//运行后是这样的1 0 ...

  3. 虚拟机研究系列-「GC本质底层机制」SafePoint的深入分析和底层原理探究指南

    SafePoint前提介绍 在高度优化的现代JVM里,Safepoint有几种不同的用法.GC safepoint是最常见.大家听说得最多的,但还有deoptimization safepoint也很 ...

  4. (转)编码剖析Spring装配基本属性的原理

    http://blog.csdn.net/yerenyuan_pku/article/details/52856465 上回我们已经讲到了Spring依赖注入的第一种方式,现在我们来详解第二种方式,须 ...

  5. Java面试底层原理

    面试发现经常有些重复的面试问题,自己也应该学会记录下来,最好自己能做成笔记,在下一次面的时候说得有条不紊,深入具体,面试官想必也很开心.以下是我个人总结,请参考: HashSet底层原理:(问了大几率 ...

  6. Spring_day01--课程安排_Spring概念_IOC操作&IOC底层原理&入门案例_配置文件没有提示问题

    Spring_day01 Spring课程安排 今天内容介绍 Spring概念 Spring的ioc操作 IOC底层原理 IOC入门案例 配置文件没有提示问题 Spring的bean管理(xml方式) ...

  7. python进阶(24)Python字典的底层原理以及字典效率

    前言 问题1:python中的字典到底是有序还是无序 问题2:python中字典的效率如何 python字典底层原理   在Python 3.5以前,字典是不能保证顺序的,键值对A先插入字典,键值对B ...

  8. NGUI 渲染流程深入研究 (UIDrawCall UIGeometry UIPanel UIWidget)

    上图是一个简要的NGUI的图形工作流程,UIGeometry被UIWidget实例化之后,通过UIWidget的子类,也就是UISprit,UILabel等,在OnFill()函数里算出所需的Geom ...

  9. 王家林的“云计算分布式大数据Hadoop实战高手之路---从零开始”的第十一讲Hadoop图文训练课程:MapReduce的原理机制和流程图剖析

    这一讲我们主要剖析MapReduce的原理机制和流程. “云计算分布式大数据Hadoop实战高手之路”之完整发布目录 云计算分布式大数据实战技术Hadoop交流群:312494188,每天都会在群中发 ...

随机推荐

  1. ABA problem

    多线程及多进程编程同步时可能出现的问题,如果一个值被P1读取两次,两次的值相同,据此判断该值没有被修改过,但该值可能在两次读取之间被P2修改为另外一个value,并在P1再次读取之前修改回了原值.P1 ...

  2. C++中标准容器Vector,元素操作.insert()小结

    insert() 函数有以下三种用法: iterator insert( iterator loc, const TYPE &val );  //在指定位置loc前插入值为val的元素,返回指 ...

  3. jquery formValidate demo 采用struts 异步方式检验用户名是否存在

    1 login.jsp <%@taglib uri="/struts-tags" prefix="s"%><!DOCTYPE html PUB ...

  4. HibernateDaoSupport和HibernateTemplate

    hibernateTemplate的常用方法: Ø  void delete(Object entity):删除指定持久化实例 Ø  deleteAll(Collection entities):删除 ...

  5. 处理Oracle中杀不掉的锁

    一些ORACLE中的进程被杀掉后,状态被置为"killed",但是锁定的资源很长时间不释放,有时实在没办法,只好重启数据库.现在提供一种方法解决这种问题,那就是在ORACLE中杀不 ...

  6. copy and Xcopy 复制文件到另一地址

    1. copy不能复制文件夹下的文件,而Xcopy可以. 2. Xcopy不能连接文件,而copy可以连接文件 eg : 将A盘中的A.TXT和B.TXT文件连接起来,连接后的文件名为C.TXT    ...

  7. ArcObjects SDK(AE)10.1在vs2012安装的方法

    ArcObjects SDK(以下简称AO)10.1只支持vs2010,如果装了vs2012,再安装AO会提示一串鸡肠(英文),意思是AO10.1只支持vs2010 想在2012下安装,可以通过修改注 ...

  8. 【转】C++ static关键字

    原文:http://blog.csdn.net/hackbuteer1/article/details/7487694 C++的static有两种用法:面向过程程序设计中的static和面向对象程序设 ...

  9. MVC风格

    MVC风格 点击了解很多其它软件体系结构风格 §模型-视图-控制器风格常被简称为MVC风格 §组件:模型.视图.控制器 §连接件:显式调用.隐式调用.其它机制(比如:Http协议) 工作机制: Mod ...

  10. CircleDisplay

    https://github.com/PhilJay/CircleDisplay