UGUI合批源码分析及优化

本文档对Unity GUI 批次合并(Batching)源码(基于Unity 4.6版本源码)进行研究,总结UGUI Batching的规则,并提供UI优化建议。

UGUI Batching规则分析:

UGUI以Canvas为单位进行批次生成和渲染,Canvas可以嵌套包含Canvas。

Batching的生成和合并在Canvas::Update里:


UGUI合批源码分析及优化

Batching主要流程如下:

  1. 计算Canvas alpha,包括父Canvas和嵌套Canvas(alpha=0不生成合并批次)。
  2. UI层次结构发生变化时,更新Batch顺序,对Canvas下所有UI元素(CanvasRenderer)按UI层次结构深度优先排序,生成UI Instructions。
  3. 更新所有需要同步数据的renderer UI 数据,包括vertex, color, material, transform, rect, depth(按UI层次结构深度优先排序的深度)等。非活动(IsActive() == false)且不强制更新的UI元素,将不同步数据。

  4. Canvas数据更新时(m_CanvasData.isDirty,如情况都可以引发:层次结构改变,同步关键数据,Canvas.Awake等),计算UI Instructions的depth并排序、生成Batch。

UGUI合批源码分析及优化

  • Mask:Mask实现的具体原理是一个Drawcall来创建Stencil mask(来做像素剔除),然后绘制所有子UI,再在最后一个Drawcall移掉Stencil mask。如果Mask的子节点属于同一个Atlas,那么Mask之间的元素可以进行合并;否则不能合并。Mask外的元素和Mask内的元素,无法合批。
  • 更新:UI层次结构发生变化(orderIsDirty),新增、删除UI或UI子节点都会引起整个Canvas UI顺序更新。因此,应避免频繁删除/增加UI对象,使用GameObject.SetActive。


Depth计算算法:

  • 遍历所有UI元素(已深度优先排序),对当前每一个UI元素CurrentUI,如果不渲染,CurrentUI.depth = -1,如果渲染该UI且底下没有其他UI元素与其相交(rect Intersects),其CurrentUI.depth = 0;
  • 如果CurrentUI下面只有一个的需要渲染的UI元素LowerUI与其相交,且可以Batch(material instance id 和 texture instance id 相同,即与CurrentUI具有相同的Material和Texture),CurrentUI.depth = LowerUI.depth;否则,CurrentUI.depth= LowerUI.depth + 1;
  • 如果CurrentUI下面叠了多个元素,这些元素的最大层是MaxLowerDepth,如果有多个元素的层都是MaxLowerDepth,那么CurrentUI和下面的元素是无法合批的;如果只有一个元素的层是MaxLowerDepth,并且这个元素和CurrentUI的材质、纹理相同,那么它们就能合批。

UGUI合批源码分析及优化

DrawCall合批(Batch):

  • Depth计算完后,依次根据Depth、material ID、texture ID、RendererOrder(即UI层级队列顺序,HierarchyOrder)排序(条件的优先级依次递减),剔除depth == -1的UI元素,得到Batch前的UI 元素队列VisiableList。
  • 对VisiableList中相邻且可以Batch(相同material和texture等)的UI元素合并批次,然后再生成相应mesh数据进行绘制。

UGUI合批源码分析及优化


注意:在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)。