UGUI优化:批次合并源码分析及工具
1. 概述
本文档对Unity GUI 批次合并(Batching)源码(基于Unity 4.6版本源码)进行研究,总结UGUI Batching的规则,并提供UI优化建议,以及UI层级显示辅助工具使用说明。
2. UGUI Batching规则分析
UGUI以Canvas为单位进行批次生成和渲染,Canvas可以嵌套包含Canvas。
Batching的生成和合并在canvas::Update里:
Batching主要流程如下:
1)计算Canvas alpha,包括父Canvas和嵌套Canvas(alpha=0不生成及合并批次)。
2)UI层次结构发生变化时,更新Batch顺序,对Canvas下所有UI元素(Canvas renderer)按UI层次结构深度优先排序,生成UI Instructions。
注意1 Mask:其中有mask UI会增加两个UI Instructions(mask / unmask),且不能合并(最终增加两个drawcall,分别使用Increment / Decrement mask Material Instance)。Mask实际上是前后使用或移除Stencil mask(来做像素剔除),在之间画所有子UI元素,mask UI下的子UI元素也无法与mask UI 之外的UI元素合并批次。
注意2更新:UI层次结构发生变化(orderIsDirty),新增、删除UI或UI子节点都会引起整个Canvas UI顺序更新。因此,应避免频繁删除/增加UI对象,使用GameObject.SetActive。
3)更新所有需要同步数据的renderer UI 数据,包括vertex, color, material, transform, rect, depth(步骤2里的排序)等。
非活动(IsActive() == false)且不强制更新的UI元素,将不同步数据。
4)Canvas数据更新时(m_CanvasData.isDirty,如情况都可以引发:层次结构改变,同步关键数据,Canvas.Awake等),计算UI Instructions的depth并排序、生成Batch。
Depth计算算法:
a) 遍历所有UI元素(已深度优先排序),对当前每一个UI元素CurrentUI,如果不渲染,CurrentUI.depth = -1,如果渲染该UI且底下没有其他UI元素与其相交(rect Intersects),其CurrentUI.depth = 0;
b) 如果底下有一个的需要渲染的UI元素LowerUI与CurrentUI相交的情况下,且
可以Batch(material instance id 和 texture instance id 相同),depth_i = LowerUI.depth;
不可以Batch,depth _i= LowerUI.depth + 1;
如果底下有n个UI元素与CurrentUI相交,根据b)计算n个的depth_i取最大的作为CurrentUI的depth: CurrentUI.depth = Max( depth _1, depth _2, depth _3 ... , depth _n)。
Depth计算完后,根据Depth排序UI Instructions,如果Depth相等,依次根据材质ID、texture ID、渲染顺序(即UI层级队列顺序)排序,剔除depth == -1的UI元素,得到Batch前的UI 元素队列VisiableList。
对VisiableList中相邻且可以Batch(相同material和texture等)的UI元素合并批次,然后再生成相应mesh数据进行绘制。
注意:在Depth计算算法中,由于要遍历所有UI元素和已计算的底层UI元素(平方复杂度),源码中使用分组计算包围盒矩形的方法加快计算,即16个UI元素为一组计算Group Rect,检查是否与底层UI元素相交时,先计算是否与底层Group相交,如果相交再与Group中的元素做判定。
因此,UI元素数目过多和层次结构过于复杂,会影响排序和Batch更新速度,合理规划UI元素数量和层次结构可以提高UI性能。
规则小结:
从UGUI批次合并生成规则可以看出,提高UI性能尽量注意以下几点:
尽量避免使用Mask,使用Mask至少增加两个Drawcall,并可能导致本可以Batch的UI元素无法Batch,从而增加更多Drawcall。
避免在UI树形结构下(Canvas下)频繁删除/增加UI对象,UI层次结构发生变化会引起整个Canvas UI顺序更新,特别是复杂的UI树形结构。
避免频繁动态的更新UI元素的Vertex, Rect, Color, Material, Texture等,可能引起Canvas数据更新和Batch更新计算,有可能引起VBO Update(重新提交顶点数据)。
尽可能使用少的UI Material和贴图(使用图集),使得可以Batching。
相邻的UI元素(比如父节点与第一个子节点、相同层次结构下的节点),使用相同材质贴图的UI元素尽可能排在一起,便于合并。
同一父节点下所有子节点,保持相同的层次结构(如List控件下的item),便于底层相同depth下UI元素Batch。
避免UI元素数目过多和层次结构过于复杂影响Batch更新速度。
固定的Text考虑与背景图层合在一张图上(可能不便本地化,但可以减少drawcall)。
3. UI层级显示辅助工具
根据源码中的UI Batching规则,写了个UI层级辅助工具,用于显示UI的层级、批次等数据,便于UI性能优化。使用者可以结合以上规则,分析当前UI元素排列顺序、材质贴图设置,优化UI Batching,减少UI Drawcall。
安装:将附件中UIAssistant.cs放在Editor目录下即可
使用:点击菜单栏Tools/UIAssistant,打开如下图工具。默认分析名为UIRoot下所有UI组件(active状态),或者自定义选择Root UI(canvas,一般用于单独分析)。
UpdateBatching:根据源码算法计算UI元素的层级及批次序号。手动点击触发,当UI树形结构发生变化、激活/不激活时,需要重新计算。
ShowDepth: 会在Hierarchy View 中active的有效UI元素(canvas renderer),以“批次序号/层级序号/材质序号”的方式显示(开/关)。原则上,尽可能的使相同/或相邻的层级,批次序号相同(意味着同一批次),用于辅助分析。
注意:由于嵌套和动态UI结构,上层接口无法取到,导致总体批次有可能偏差(存在这些结构时),但不影响UI层级显示辅助工具可分析局部UI情况;另外,具有mask情况下,目前只做简化处理,没有考虑unmask时UI元素合批情况。
例子1(部分截图):
Btn_Close与Image的相邻层层级(2,3),材质不同,批次不同,尝试是否可以修改材质和图集,减少1个drawcall
修改前:
修改后:
例子2:
前后的Btn_Close与ImageIner_FrameRight材质相同(序号都为0),因Image材质不同,batching中断了,调换一下次序,减少2个drawcall (注:调换UI次序注意交互响应问题,一般适用背景图次序调整,其他情况具体分析)。
4. 小结
Unity UI层级计算和合并批次规则(batching)显复杂,具体UI实现上又受需求等限制,需要UI同学根据情况调整(材质,贴图,层次等),尽可能满足合并批次(batching)规则。另外,以上源码分析基于Unity 4.6版本,使用正常,其他版本兼容。Unity 5以上UGUI有所改动(如mask等),请自行修改使用。
以上如有纰漏,欢迎指正和补充。