NGUI源码剖析之提高GPU性能

来源:互联网 发布:分析家数据接口 编辑:程序博客网 时间:2024/06/10 14:12

一.使用NGUI

NGUI和UGUI比起来我更喜欢NGUI,因为NGUI可将图片打成图集,而且它和texturepacker配合使用会十分的方便,高效。texturepacker导出一张合图和一个文本,使用NGUI创建一个Atlas,拖拽上去即可。

二.NGUI性能提升。

首先来一段源码:这是UIPanel里面的代码。

void FillAllDrawCalls (){for (int i = 0; i < drawCalls.Count; ++i)UIDrawCall.Destroy(drawCalls[i]);drawCalls.Clear();Material mat = null;Texture tex = null;Shader sdr = null;UIDrawCall dc = null;int count = 0;if (mSortWidgets) SortWidgets();for (int i = 0; i < widgets.Count; ++i){UIWidget w = widgets[i];if (w.isVisible && w.hasVertices)//遍历所有的widget,如果可见就获取该widget的材质、图片、shader{Material mt = w.material;Texture tx = w.mainTexture;Shader sd = w.shader;if (mat != mt || tex != tx || sdr != sd)//如果当前的这三个属性和上次循环有一个不一样,说明是一个新的{//Atlas,并将上次新建的DrawCall对象添加到链表里面。if (dc != null && dc.verts.size != 0){drawCalls.Add(dc);dc.UpdateGeometry(count);//更新刚刚添加的那个drawcall信息dc.onRender = mOnRender;//注册渲染回调mOnRender = null;count = 0;dc = null;//将dc赋值为空,很关键。}mat = mt;//给当前drawcall做标记,好做下一次循环判断tex = tx;sdr = sd;}if (mat != null || sdr != null || tex != null){if (dc == null){dc = UIDrawCall.Create(this, mat, tex, sdr);//如果dc为空,说明遇到新的图集,需新建一个drawcalldc.depthStart = w.depth;dc.depthEnd = dc.depthStart;dc.panel = this;}else{int rd = w.depth;//如果dc不为空,那就将他和上次循环那个共享一个if (rd < dc.depthStart) dc.depthStart = rd;//DrawCall,不需要新建。if (rd > dc.depthEnd) dc.depthEnd = rd;}w.drawCall = dc;++count;if (generateNormals) w.WriteToBuffers(dc.verts, dc.uvs, dc.cols, dc.norms, dc.tans);//将信息写入缓冲区else w.WriteToBuffers(dc.verts, dc.uvs, dc.cols, null, null);if (w.mOnRender != null){if (mOnRender == null) mOnRender = w.mOnRender;else mOnRender += w.mOnRender;}}}else w.drawCall = null;}if (dc != null && dc.verts.size != 0){drawCalls.Add(dc);//将最后一个drawcall添加到渲染列表。因为如果最后一个和上一个属于同一个图集,不会进入dc.UpdateGeometry(count);//下一个循环,也就不会将最后一个drawcall添加到渲染列表。dc.onRender = mOnRender;mOnRender = null;}}

1.通过阅读代码,我们发现NGUI的渲染算法是这样的。

比如我们的ui界面(6个Sprite)由下面几个图集组成的界面:(depth从小到大)

Atlas1/Atlas2/Atlas2/Atlas3/Atlas2/Atlas1

2.按照上面的算法,NGUI会生成多少个DrawCall对象呢(将UIDrawCall类里面的#define SHOW_HIDDEN_OBJECTS注释取消可以看到有多少个)?首先Atlas1是一个,到Atlas2时,由于两个图集使用的图片不一样,材质也不一样,因此NGUI会新建一个DrawCall对象,到了第三个,由于上一个也是Atlas2,就不需要新建一个DrawCall对象。因此就少一个Drawcall,这样下来,一共可以生成5个Drawcall对象。Drawcall对象越多,GPU消耗也就越多。所以我们可以通过以下这种排列来减少Drawcall对象:

Atlas1/Atlas1/Atlas2/Atlas2/Atlas2/Atlas3      

哦?这样好像只剩下3个DrawCall了。所以争取把同一个图集的图片让他们的depth相挨着是一个简单的解决办法。但是,我还有另外一个解决办法。

Atlas1/Atlas1/Atlas1/Atlas1/Atlas1/Atlas1

3.这个办法就是将一个界面下的ui图片打到同一个图集里面。这样就只有一个DrawCall对象了。但是,这样会有一个问题。如果另外一个界面用到了相同的图片,你需要将它复制到新的图集里面,这样会造成内存浪费。所以需要将通用的图片打到一起,组成一个CommonAtlas,比如说UI的一些九宫格的通用图片。其他小图片就复制就复制了,它无非就是在内存和渲染之间找临界点。一般一个界面下来5-10个DrawCall是比较正常的(如果图集较多时)。

三 总结。

      1.合理安排depth可以减少DrawCall数量。

     2.将一个界面的多个元素打到同一个图集里面可以减少DrawCall数量。

     3..不要将动态图和静态图安置到同一个DrawCall里面,因为动态图(比如Spine做的骨骼动画)经常需要传递给GPU运算,每次变化都要这样做,如果和静态图放到了同一个DrawCall,它会将静态UI的顶点、材质信息也传送给GPU。有种拖人(静态ui)下水的意思。

原创粉丝点击