引言
接上一篇文章对UIPanel的分析。定量的分析哪些因素会影响NGUI drawcall的增减。
SortWidgets
FillAllDrawCalls的部分代码
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;
}
...
可以看到FillAllDrawCalls遍历widgets,并对比当前材质,贴图,shader是否与当前的widget相同,如果其中任意一项不同,则会新增一个drawcall。而在遍历之前调用了SortWidgets()接口:
/// <summary>
/// Immediately sort all child widgets.
/// </summary>
public void SortWidgets ()
{
mSortWidgets = false;
widgets.Sort(UIWidget.PanelCompareFunc);
}
// 排序因子
/// <summary>
/// Static widget comparison function used for inter-panel depth sorting.
/// </summary>
[System.Diagnostics.DebuggerHidden]
[System.Diagnostics.DebuggerStepThrough]
static public int PanelCompareFunc (UIWidget left, UIWidget right)
{
if (left.mDepth < right.mDepth) return -1;
if (left.mDepth > right.mDepth) return 1;
Material leftMat = left.material;
Material rightMat = right.material;
if (leftMat == rightMat) return 0;
if (leftMat == null) return 1;
if (rightMat == null) return -1;
return (leftMat.GetInstanceID() < rightMat.GetInstanceID()) ? -1 : 1;
}
可以看到PanelCompareFunc 中排序的依据为深度,对于材质仅做了最简单的判空。并没有对影响drawcall产生的因素材质,贴图和shader进行判断。
那么可以进行一个大胆的假设,widget的深度及材质,贴图,shader会影响drawcall的数量
创建测试环境,可以看到空场景中包含2个drawcall:
添加3个拥有相同深度,材质,贴图的UITexture:
此时的drawcall为3个:
深度的影响
分别将tex1,tex2,tex3的深度设置为0,1,2,可以看到drawcall的数量没有变化。
材质的影响
三个texture的材质依次为:
此时的drawcall为5个:
调整任意两个相邻texture的材质,当材质相同时,drawcall的数量为4:
图示为调整tex2的材质为Backdrop的情况:
调整不相邻texture的材质,当材质相同时,drawcall的数量不会变化
可以得到结论:NGUI会对widgets列表中相邻元素根据材质是否相同进行合并处理,对于widgets列表中不连续的元素则不会进行合并。
此处可以通过修改PanelCompareFunc 来优化:
static public int PanelCompareFunc (UIWidget left, UIWidget right)
{
//原排序规则
//if (left.mDepth < right.mDepth) return -1;
//if (left.mDepth > right.mDepth) return 1;
//Material leftMat = left.material;
//Material rightMat = right.material;
//if (leftMat == rightMat) return 0;
//if (leftMat == null) return 1;
//if (rightMat == null) return -1;
//return (leftMat.GetInstanceID() < rightMat.GetInstanceID()) ? -1 : 1;
//依据材质的排序规则
Material leftMat = left.material;
Material rightMat = right.material;
if (leftMat == rightMat)
{
if (left.mDepth < right.mDepth) return -1;
if (left.mDepth > right.mDepth) return 1;
}
//if (leftMat == rightMat) return 0;
if (leftMat == null) return 1;
if (rightMat == null) return -1;
return (leftMat.GetInstanceID() < rightMat.GetInstanceID()) ? -1 : 1;
}
重新测试上述示例
可以看到widgets列表中tex元素的排列顺序发生了改变。
可以看到减少了1个drawcall,修改后的排序规则,理论上可以达到drawcall数量等于material数量的“理想情况”。
贴图的影响
上面的结论为什么说是“理想情况”呢?
别忘了影响drawcall的因素除了深度,材质之外还有贴图和shader。在上面例子的基础上,修改3个texture的Texture为不同。
drawcall又变回了5个。
所以“理想情况”隐含了贴图与shader都一致的条件。
PanelCompareFunc 更完备的写法:
static public int PanelCompareFunc (UIWidget left, UIWidget right)
{
//原排序规则
//if (left.mDepth < right.mDepth) return -1;
//if (left.mDepth > right.mDepth) return 1;
//Material leftMat = left.material;
//Material rightMat = right.material;
//if (leftMat == rightMat) return 0;
//if (leftMat == null) return 1;
//if (rightMat == null) return -1;
//return (leftMat.GetInstanceID() < rightMat.GetInstanceID()) ? -1 : 1;
//依据材质的排序规则
Material leftMat = left.material;
Material rightMat = right.material;
//增加了对texture与shader的判断
if (leftMat == rightMat && left.mainTexture == right.mainTexture && leftMat.shader == rightMat.shader)
{
if (left.mDepth < right.mDepth) return -1;
if (left.mDepth > right.mDepth) return 1;
}
//if (leftMat == rightMat) return 0;
if (leftMat == null) return 1;
if (rightMat == null) return -1;
return (leftMat.GetInstanceID() < rightMat.GetInstanceID()) ? -1 : 1;
}
总结
对于NGUI drawcall数量的优化:
- 重写PanelCompareFunc 排序规则,加入对于材质,贴图,shader的判断;
- 在规划UI界面时,需要统筹规划各个UIPanel的深度。
参考文章:
NGUI所见即所得之UIPanel