Notifications
Article
Profiler深挖-UI面板(6)
Published 2 years ago
1.6 K
1
UI
UGUI一直是个比较头疼的东西,我相信大部分团队都深受界面“卡”的苦恼,不是打开界面慢,就是操作界面慢。Profiler也增加了对UI的性能分析工具。如下图所示,界面Profiler分为UI和UI Deteails两个。
UGUI的工作原理是这样的,首先它对每个Canvas进行合并网格,但是同一个Canvas未必能Batch在一起。此时UGUI就会按照Hierarchy从上到下的顺序来依次尝试合并,只要满足同材质,同贴图,同shader等要求就会合并在一起。同一个Canvas可能会合并出多个网格。网格合并完以后就需要添加贴图了,因为Atlas虽然合并在了一张图中,所以Sprite对象中根据记录着它的原始大小尺寸关系,就可以找到对应正确的UV信息从而贴上图片,这样就算渲染完毕了。通过这个顺序我们可以得出,渲染一个图片需要2个步骤合并网格和渲染,这恰巧和Profiler给出的UI部分一致。
1)Layout :Layout就是合并网格,但是网格合并只是一方面,它还需要做一些额外的准备工作,比如确定哪些UI可以被Batch在一起。
2)Renderer:也就是渲染部分的开销了。
在实际游戏中,一般在打开一个游戏界面时,可以明显的看到Layout和Renderer会出现尖刺,大部分时间都是卡在了网格合并上,所以要想优化界面的卡顿效率,不得不减少同时参与网格合并的UI。UGUI触发网格合并的时机也是比较简单 ,例如:1)UI坐标大小发生变化 2)UI图片材质发生变化 3)实例化新的UI到Hierarchy中都会立即触发网格合并。
注意一下,UGUI从原理上和NGUI类似,但是UGUI比NGUI效率高包括如下:
1)UGUI的网格合并是在C++中完成的,而NGUI是在C#中完成的。
2)UGUI的网格合并是通过多线程完成的,而NGUI是在主线程中完成的。
3)UGUI的Spreite不会因为更换图集带来修改原始界面Prefab的烦恼。
接着就是UI Details
1)Batches:表示当前渲染所有UI的Batches数量,每个Batches就是一个DrawCall,当然是越小越好了。
2)Vertices:表示当前渲染所有UI的顶点数量。
3)Markers:表示标记UI当前的操作事件,如点击Button时Markers中就会显示Button.onClick事件。
有了以上信息只是大概能知道当前UI的状态,有这些信息还是远远不够的,如下图所示,下方详细面板中能看到每一个Batch合并的完整步骤。
1)Object:会列出当前所有Canvas下每个Batch的数量。
2)Self Batch Count:前面我们讲过一个Canvas可能会有多个Batch,这里会显示每个Canvas的Batch数量总和。
3)Cumulative Batch Count:由于Canvas是可以嵌套子Canvas的,所以这里会显示自身以及所有子Canvas所有Batch数量的总和。
4)Self Vertex Count:Batch后自身的顶点数量,每个Batch就会根据这些顶点生成Mesh。
5)Cumulative Vertex Count:包括所有子对象的顶点数量之和。
6)Batch Breaking Reason:告诉我们为什么引擎没有合并Batch的原因,大致能收到的原因如下:Not Coplanar With Canvas CanvasInjectionIndex: Different Material Instance, Rect clipping, Texture, A8TextureUsage:
7)GameObjectCount:表示同一Batch下有多少游戏对象的数量。
8)GmaeObjects :有游戏对象的数量还不够,我们最好能知道这个Batch下到底有那些游戏对象,并且他们都是什么名字,这样就更方便了。
如下图所示,还有一个快捷键。选择任意一个Batch后右键Find matching objects in scene 就可以在Hierarchy直接选择中参与本次合批的所有游戏对象了。
现在问题又来了,既然已经知道了每个Batch是由那些具体的游戏对象组成,那么能否更方便的显示每个Bach渲染出来的画面呢?如下所示选择Batch后在右侧就可以直接预览了。
如下图所示,现在的背景是这种“格子”的半透明,为了让大家预览的更方便它还提供了Black和White两种背景颜色,切换下就能看到效果。
它还提供了显示标准原图,或者Overdraw以及Composite overdraw 综合overdraw的功能,大家切换下就能看到效果。
最后再给大家拓展一个知识点,文章的开头我们讲过合并网格是比较消耗效率的,通过Profiler我们只能知道每个Batch由那些游戏对象组成。在优化界面的时候我们可能需要知道,界面的下面具体是那个元素发生的移动位置或者更换贴图的操作导致了界面的网格重建。
UGUI的标志性函数Canvas.SendWillRenderCanvas,我们只知道网格进行了重建,但是并不知道是哪个Canvas,由于哪个UI元素引起了Canvas网格的重建。比如,修改RectTransform的属性、Text文本内容、更换Sprite、颜色、都会引起网格重建,通过观察UGUI源码,我们发现了可以通过反射的方式,获取到一些有用的信息,在UGUI源码中CanvasUpdateRegistry.cs类中。
通过注册 Canvas.willRenderCanvases += PerformUpdate; 将需要重建的网格保存在
private readonly IndexedSet<ICanvasElement> m_LayoutRebuildQueue = new IndexedSet<ICanvasElement>();
private readonly IndexedSet<ICanvasElement> m_GraphicRebuildQueue = new IndexedSet<ICanvasElement>();
然后就是遍历重建网格,我们要做的就是将这两个数据捞出来,我的代码是这样的。
using System.Collections.Generic; using System.Reflection; using UnityEngine; using UnityEngine.UI; public class NewBehaviourScript : MonoBehaviour { IList<ICanvasElement> m_LayoutRebuildQueue; IList<ICanvasElement> m_GraphicRebuildQueue; private void Awake() { System.Type type = typeof(CanvasUpdateRegistry); FieldInfo field = type.GetField("m_LayoutRebuildQueue", BindingFlags.NonPublic | BindingFlags.Instance); m_LayoutRebuildQueue = (IList<ICanvasElement>)field.GetValue(CanvasUpdateRegistry.instance); field = type.GetField("m_GraphicRebuildQueue", BindingFlags.NonPublic | BindingFlags.Instance); m_GraphicRebuildQueue = (IList<ICanvasElement>)field.GetValue(CanvasUpdateRegistry.instance); } private void Update() { for (int j = 0; j < m_LayoutRebuildQueue.Count; j++) { var rebuild = m_LayoutRebuildQueue[j]; if (ObjectValidForUpdate(rebuild)) { Debug.LogFormat("{0}引起{1}网格重建", rebuild.transform.name, rebuild.transform.GetComponent<Graphic>().canvas.name); } } for (int j = 0; j < m_GraphicRebuildQueue.Count; j++) { var element = m_GraphicRebuildQueue[j]; if (ObjectValidForUpdate(element)) { Debug.LogFormat("{0}引起{1}网格重建", element.transform.name, element.transform.GetComponent<Graphic>().canvas.name); } } } private bool ObjectValidForUpdate(ICanvasElement element) { var valid = element != null; var isUnityObject = element is Object; if (isUnityObject) valid = (element as Object) != null; //Here we make use of the overloaded UnityEngine.Object == null, that checks if the native object is alive. return valid; } }
如下图所示,修改属性以后就知道某个具体的UI元素引起了某个具体的Canvas发生了网格重建。
目前Profiler中所有主要的面板我们都已经介绍完毕,从下一篇开始我将更详细的介绍一些通过Profiler分析出来的一些实战优化技巧。
Tags:
雨松MOMO
程序员 - Programmer
17
Comments
Wu Xiaomu
a year ago
Programmer
为了CJ门票和外滩之夜门票和Unity周边!
1