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的机制的,但是我觉得为了保证通用牺牲一些性能还是在所难免的,但是没想到这个版本竟然这么废。
之前虽然研究过NGUI的UIWidget, UIDrawCall,UIGeometry和 UIPanel等基础脚本(NGUI所见即所得之UIWidget , UIGeometry & UIDrawCall,NGUI所见即所得之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函数
- 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 > 0)
- {
- 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变得更少
由于时间关系(马上2:30了),就只能写到这里,如果你有NGUI的任何问题,欢迎和D.S.Qiu进行交流讨论。
如果您对D.S.Qiu有任何建议或意见可以在文章后面评论,或者发邮件(gd.s.qiu@gmail.com)交流,您的鼓励和支持是我前进的动力,希望能有更多更好的分享。
转载请在文首注明出处:http://dsqiu.iteye.com/blog/2025177
更多精彩请关注D.S.Qiu的博客和微博(ID:静水逐风)
NGUI所见即所得之深入剖析UIPanel,UIWidget,UIDrawCall底层原理的更多相关文章
- NGUI的原理机制:深入剖析UIPanel,UIWidget,UIDrawCall底层原理
这是我去搜狐畅游面试时,面试官问的一个问题.问NGUI的机制原理是什么?就是这个插件是根据什么写出来的.当时没答上来,下面是我从转载过来的,可以研究研究. 之前项目中用的NGUI的版本是3.0.7 f ...
- 剖析Javascript中forEach()底层原理,如何重写forEach()
我们平时用的forEach()一般是这样用的 var myArr = [1,5,8] myArr.forEach((v,i)=>{ console.log(v,i) })//运行后是这样的1 0 ...
- NGUI所见即所得之UIAtlasMaker , UIAtlas (2)
本文的重点就是要将NGUI把多张图片打成一个图集(Atlas)的原理和过程研究下,学习下Unity提供的api和NGUI写的功能以及设计思想. (原文链接) 其它链接:NGUI所见即所得之UIRoot ...
- NGUI 渲染流程深入研究 (UIDrawCall UIGeometry UIPanel UIWidget)
上图是一个简要的NGUI的图形工作流程,UIGeometry被UIWidget实例化之后,通过UIWidget的子类,也就是UISprit,UILabel等,在OnFill()函数里算出所需的Geom ...
- 剖析Qt的事件机制原理
版权声明 请尊重原创作品.转载请保持文章完整性,并以超链接形式注明原始作者“tingsking18”和主站点地址,方便其他朋友提问和指正. QT源码解析(一) QT创建窗口程序.消息循环和WinMai ...
- Spring、Spring依赖注入与编码剖析Spring依赖注入的原理
Spring依赖注入 新建PersonIDao 和PersonDao底实现Save方法: public interface PersonIDao { public void save(); } pub ...
- (转)编码剖析Spring管理Bean的原理
http://blog.csdn.net/yerenyuan_pku/article/details/52832434 在Spring的第一个案例中,我们已经知道了怎么将bean交给Spring容器进 ...
- (转)编码剖析@Resource注解的实现原理
http://blog.csdn.net/yerenyuan_pku/article/details/52860046 上文我们已经学会使用@Resource注解注入属性.学是学会了,但也仅限于会使用 ...
- (转)编码剖析Spring装配基本属性的原理
http://blog.csdn.net/yerenyuan_pku/article/details/52856465 上回我们已经讲到了Spring依赖注入的第一种方式,现在我们来详解第二种方式,须 ...
随机推荐
- 2017-2018-1 JAVA实验站 冲刺 day04
2017-2018-1 JAVA实验站 冲刺 day04 各个成员今日完成的任务 小组成员 今日工作 完成进度 张韵琪 写博客.进行工作总结 100% 齐力锋 找背景音乐 100% 张浩林 游戏操作说 ...
- bzoj1634护花
试题描述: 约翰留下他的N(N<=100000)只奶牛上山采木.他离开的时候,她们像往常一样悠闲地在草场里吃草.可是,当他回来的时候,他看到了一幕惨剧:牛们正躲在他的花园里,啃食着他心爱的美丽花 ...
- python函数 divmod
divmod(a,b)函数 中文说明: divmod(a,b)方法返回的是a//b(除法取整)以及a对b的余数 返回结果类型为tuple 参数: a,b可以为数字(包括复数) from 2. Add ...
- Qt on android 蓝牙开发(控制小车)
因为要做一个用蓝牙控制小车的app,就用着QT搞了下,网上关于QT蓝牙开发的资料比较少,我在这里记录下过程希望对看到了人有所帮助 首先在项目文件里添加 QT += bluetooth 这样就可以用QT ...
- HDUJ 1392 Surround the Trees 凸包
Surround the Trees Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Other ...
- promise和Rxjs的一点区别
promise 代码 let promise = new Promise( (resolve) => { setTimeout(() => { resolve('chen'); },200 ...
- ArcGIS for Android地图上实际距离与对应的屏幕像素值计算
本篇文章主要介绍了"ArcGIS for Android地图上实际距离与对应的屏幕像素值计算",主要涉及到ArcGIS for Android地图上实际距离与对应的屏幕像素值计算方 ...
- React的第一个例子
准备: 官网:https://facebook.github.io/react/downloads.html Github地址:https://github.com/facebook/react 首先 ...
- 解决kylin查询报错:org.apache.kylin.rest.exception.InternalErrorException
报错信息: -- ::, ERROR [Query 12e9c054-760c---b1f06724c9b6-] service.QueryService: : Exception when exec ...
- Python的__getattribute__ vs __getattr__的妙用
这里的属性即包括属性变量,也包括属性方法.即类的变量和方法. 当访问某个实例属性时, getattribute会被无条件调用,如未实现自己的getattr方法,会抛出AttributeError提示找 ...