【NGUI源码剖析】NGUI的drawcall

前言 
在使用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>();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

可以将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
                    }
        ...
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

上述代码出现了另一个列表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>();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

可以看到一个UIPanel可能对应多个UIWidget,而一个UIWidget不一定会对应一个UIDrawCall(可能对应一个UIDrawCall,也可能与其他UIWidget共用一个UIDrawCall)三者间的关系可以用下图表示: 
这里写图片描述

顺藤摸瓜,可以得出函数调用栈: 
这里写图片描述

下一次,我将会着重分析SortWidgets()对于UIWidget与UIDrawCall之间关系的影响。

猜你喜欢

转载自blog.csdn.net/qq_14914623/article/details/80880761