前言
在使用unity3d开发实际的项目中,性能优化一直是一项不得不考虑的重点,而其中UI的优化又是绕不过去的坎,很多看似简单的UI为何会占用大量的cpu开销?本文以NGUI这套UI的解决方案为例,从源码出发,分析影响性能的原因,看清问题的本质,对症下药。
NGUI的drawcall
在Unity中,每次引擎准备数据并通知GPU的过程称为一次Draw Call。这一过程是逐个物体进行的,对于每个物体,不只GPU的渲染,引擎重新设置Material/Shader也是一项非常耗时的操作。
说起NGUI的drawcall,UIPanel是一座绕不过的大山,看源码:
/// <summary>
/// List of draw calls created by this panel. Do not attempt to modify this list yourself.
/// </summary>
[System.NonSerialized]
public List<UIDrawCall> drawCalls = new List<UIDrawCall>();
可以将UIDrawCall近似的理解为NGUI层对于drawcall需要用到的数据进行的一层封装,后续会写文章来分析UIDrawCall。
这里看到UIPanel会维护一个UIDrawCall的列表,注释说明了这个列表保存的是当前panel创建的draw calls。跟踪这个列表,查询向列表添加元素的地方会发现FillAllDrawCalls 这样的一个接口:
/// <summary>
/// Fill the geometry fully, processing all widgets and re-creating all draw calls.
/// </summary>
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(); // 对widgets进行排序
for (int i = 0; i < widgets.Count; ++i)
{
UIWidget w = widgets[i];
if (w.isVisible && w.hasVertices)
{
Material mt = w.material;
if (onCreateMaterial != null) mt = onCreateMaterial(w, mt);
Texture tx = w.mainTexture;
Shader sd = w.shader;
// 判断材质,贴图,shader是否与当前的widget相同,若不同则增加一个drawcall
if (mat != mt || tex != tx || sdr != sd)
{
if (dc != null && dc.verts.Count != 0)
{
drawCalls.Add(dc);
dc.UpdateGeometry(count);
dc.onRender = mOnRender;
mOnRender = null;
count = 0;
dc = null;
// 调用UpdateGeometry对Mesh,MeshRenderer,MeshFilter进行设置
// 之后重置dc
}
...
}
上述代码出现了另一个列表widgets:
/// <summary>
/// List of widgets managed by this panel. Do not attempt to modify this list yourself.
/// </summary>
[System.NonSerialized]
public List<UIWidget> widgets = new List<UIWidget>();
可以看到一个UIPanel可能对应多个UIWidget,而一个UIWidget不一定会对应一个UIDrawCall(可能对应一个UIDrawCall,也可能与其他UIWidget共用一个UIDrawCall)三者间的关系可以用下图表示:
顺藤摸瓜,可以得出函数调用栈:
下一次,我将会着重分析SortWidgets()对于UIWidget与UIDrawCall之间关系的影响。