UGUI重建

14k words

Desc:

Unity中渲染的物体都是由网格构成的,而网格的绘制单元是图元(点、线、三角面)。
绘制信息都存储在VertexHelper类中,除了顶点外还有法线、UV、顶点色、切线以及相关函数

代码
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

/// <summary>
/// A utility class that can aid in the generation of meshes for the UI.
/// </summary>
/// <remarks>
/// This class implements IDisposable to aid with memory management.
/// </remarks>
/// <example>
/// <code>
/// using UnityEngine;
/// using UnityEngine.UI;
///
/// public class ExampleClass : MonoBehaviour
/// {
/// Mesh m;
///
/// void Start()
/// {
/// Color32 color32 = Color.red;
/// using (var vh = new VertexHelper())
/// {
/// vh.AddVert(new Vector3(0, 0), color32, new Vector2(0f, 0f));
/// vh.AddVert(new Vector3(0, 100), color32, new Vector2(0f, 1f));
/// vh.AddVert(new Vector3(100, 100), color32, new Vector2(1f, 1f));
/// vh.AddVert(new Vector3(100, 0), color32, new Vector2(1f, 0f));
///
/// vh.AddTriangle(0, 1, 2);
/// vh.AddTriangle(2, 3, 0);
/// vh.FillMesh(m);
/// }
/// }
/// }
/// </code>
/// </example>
public class VertexHelper : IDisposable
{
private List<Vector3> m_Positions;
private List<Color32> m_Colors;
private List<Vector2> m_Uv0S;
private List<Vector2> m_Uv1S;
private List<Vector2> m_Uv2S;
private List<Vector2> m_Uv3S;
private List<Vector3> m_Normals;
private List<Vector4> m_Tangents;
private List<int> m_Indices;

private static readonly Vector4 s_DefaultTangent = new Vector4(1.0f, 0.0f, 0.0f, -1.0f);
private static readonly Vector3 s_DefaultNormal = Vector3.back;

public VertexHelper(Mesh m)
{
InitializeListIfRequired();

m_Positions.AddRange(m.vertices);
m_Colors.AddRange(m.colors32);
m_Uv0S.AddRange(m.uv);
m_Uv1S.AddRange(m.uv2);
m_Uv2S.AddRange(m.uv3);
m_Uv3S.AddRange(m.uv4);
m_Normals.AddRange(m.normals);
m_Tangents.AddRange(m.tangents);
m_Indices.AddRange(m.GetIndices(0));
}

...

AddVert
AddTriangle
AddUIVertexQuad
AddUIVertexStream
AddUIVertexTriangleStream
GetUIVertexStream

}

CanvasRenderer负责拿到相关数据进行绘制

UI重建

一个类如果想要重建,必须继承自(ICanvasElement)接口,执行重建操作时,会调用接口中的Rebuild函数进行重建。
CanvasUpdateRegistry类监听了Canvas的willRenderCanvas事件,该事件会每帧调用并执行PerformUpdate函数。
PerformUpdate被调用时会遍历RebuildQueue中需要进行重建的UI元素,并调用元素的Rebuild方法

ICanvasElement

Rebuild函数中需要提供CanvasUpdate类型的参数,作为一个枚举类型,这个参数代表Rebuild的不同阶段

代码
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/// <summary>
/// This is an element that can live on a Canvas.
/// </summary>
public interface ICanvasElement
{
/// <summary>
/// Rebuild the element for the given stage.
/// </summary>
/// <param name="executing">The current CanvasUpdate stage being rebuild.</param>
void Rebuild(CanvasUpdate executing);

/// <summary>
/// Get the transform associated with the ICanvasElement.
/// </summary>
Transform transform { get; }

/// <summary>
/// Callback sent when this ICanvasElement has completed layout.
/// </summary>
void LayoutComplete();

/// <summary>
/// Callback sent when this ICanvasElement has completed Graphic rebuild.
/// </summary>
void GraphicUpdateComplete();

/// <summary>
/// Used if the native representation has been destroyed.
/// </summary>
/// <returns>Return true if the element is considered destroyed.</returns>
bool IsDestroyed();
}


/// <summary>
/// Values of 'update' called on a Canvas update.
/// </summary>
public enum CanvasUpdate
{
/// <summary>
/// Called before layout.
/// </summary>
Prelayout = 0,
/// <summary>
/// Called for layout.
/// </summary>
Layout = 1,
/// <summary>
/// Called after layout.
/// </summary>
PostLayout = 2,
/// <summary>
/// Called before rendering.
/// </summary>
PreRender = 3,
/// <summary>
/// Called late, before render.
/// </summary>
LatePreRender = 4,
/// <summary>
/// Max enum value. Always last.
/// </summary>
MaxUpdateValue = 5
}

CanvasUpdateRegistry

一个继承自ICanvasElement接口的类如果要重建,需要将自身加入到CanvasUpdateRegistry类中的重建队列中(RebuildQueue,并不是数据结构中的队列),CanvasUpdateRegistry中包含两个索引集(IndexedSet,内部使用Dictionary和List存储数据),分别是
IndexedSet m_LayoutRebuildQueue ,布局重建队列,当UI元素的布局需要更新时将其加入队列
IndexedSet m_GraphicRebuildQueue ,图形重建队列,当UI元素的图像需要更新时将其加入队列

该类在构造函数中监听了Canvas的willRenderCanvases事件,这个事件会在渲染前进行每帧调用,函数大致包含以下步骤:

PerformUpdate函数对m_LayoutRebuildQueue中的元素进行排序,依据是父节点的多少。接下来依次将Prelayout、Layout和PostLayout作为参数传递给Rebuild进行布局重建,完成后通知布局队列中的元素重建完成。
调用ClipperRegistry的Cull函数进行裁剪。
进行图形重建,遍历m_GraphicRebuildQueue的值,分别将参数PreRender、LatePreRender作为参数传递给Rebuild函数进行图形重建。
最后通知图形重建完成。

代码
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
private static int SortLayoutList(ICanvasElement x, ICanvasElement y)
{
Transform t1 = x.transform;
Transform t2 = y.transform;

return ParentCount(t1) - ParentCount(t2);
}

CanvasUpdateRegistry.cs

private readonly IndexedSet<ICanvasElement> m_LayoutRebuildQueue = new IndexedSet<ICanvasElement>();
private readonly IndexedSet<ICanvasElement> m_GraphicRebuildQueue = new IndexedSet<ICanvasElement>();
protected CanvasUpdateRegistry()
{
Canvas.willRenderCanvases += PerformUpdate;
}

private void PerformUpdate()
{
UISystemProfilerApi.BeginSample(UISystemProfilerApi.SampleType.Layout);
CleanInvalidItems();

m_PerformingLayoutUpdate = true;

m_LayoutRebuildQueue.Sort(s_SortLayoutFunction);
for (int i = 0; i <= (int)CanvasUpdate.PostLayout; i++)
{
for (int j = 0; j < m_LayoutRebuildQueue.Count; j++)
{
var rebuild = instance.m_LayoutRebuildQueue[j];
try
{
if (ObjectValidForUpdate(rebuild))
rebuild.Rebuild((CanvasUpdate)i);
}
catch (Exception e)
{
Debug.LogException(e, rebuild.transform);
}
}
}

for (int i = 0; i < m_LayoutRebuildQueue.Count; ++i)
m_LayoutRebuildQueue[i].LayoutComplete();

instance.m_LayoutRebuildQueue.Clear();
m_PerformingLayoutUpdate = false;

// now layout is complete do culling...
ClipperRegistry.instance.Cull();

m_PerformingGraphicUpdate = true;
for (var i = (int)CanvasUpdate.PreRender; i < (int)CanvasUpdate.MaxUpdateValue; i++)
{
for (var k = 0; k < instance.m_GraphicRebuildQueue.Count; k++)
{
try
{
var element = instance.m_GraphicRebuildQueue[k];
if (ObjectValidForUpdate(element))
element.Rebuild((CanvasUpdate)i);
}
catch (Exception e)
{
Debug.LogException(e, instance.m_GraphicRebuildQueue[k].transform);
}
}
}

for (int i = 0; i < m_GraphicRebuildQueue.Count; ++i)
m_GraphicRebuildQueue[i].GraphicUpdateComplete();

instance.m_GraphicRebuildQueue.Clear();
m_PerformingGraphicUpdate = false;
UISystemProfilerApi.EndSample(UISystemProfilerApi.SampleType.Layout);
}

Graphic

UI元素将自身添加进重建队列是通过设置“脏数据”的方式实现的,包括布局、材质、顶点三部分
设置布局为脏,则进行布局重建
设置顶点或材质为脏,则进行图形重建

代码
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
Graphics.cs        
/// <summary>
/// Mark the layout as dirty and needing rebuilt.
/// </summary>
/// <remarks>
/// Send a OnDirtyLayoutCallback notification if any elements are registered. See RegisterDirtyLayoutCallback
/// </remarks>
public virtual void SetLayoutDirty()
{
if (!IsActive())
return;

LayoutRebuilder.MarkLayoutForRebuild(rectTransform);

if (m_OnDirtyLayoutCallback != null)
m_OnDirtyLayoutCallback();
}

/// <summary>
/// Mark the vertices as dirty and needing rebuilt.
/// </summary>
/// <remarks>
/// Send a OnDirtyVertsCallback notification if any elements are registered. See RegisterDirtyVerticesCallback
/// </remarks>
public virtual void SetVerticesDirty()
{
if (!IsActive())
return;

m_VertsDirty = true;
CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this);

if (m_OnDirtyVertsCallback != null)
m_OnDirtyVertsCallback();
}

/// <summary>
/// Mark the material as dirty and needing rebuilt.
/// </summary>
/// <remarks>
/// Send a OnDirtyMaterialCallback notification if any elements are registered. See RegisterDirtyMaterialCallback
/// </remarks>
public virtual void SetMaterialDirty()
{
if (!IsActive())
return;

m_MaterialDirty = true;
CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this);

if (m_OnDirtyMaterialCallback != null)
m_OnDirtyMaterialCallback();
}

LayoutRebuilder.cs
private static void MarkLayoutRootForRebuild(RectTransform controller)
{
if (controller == null)
return;

var rebuilder = s_Rebuilders.Get();
rebuilder.Initialize(controller);
if (!CanvasUpdateRegistry.TryRegisterCanvasElementForLayoutRebuild(rebuilder))
s_Rebuilders.Release(rebuilder);
}


CanvasUpdateRegistry.cs
/// <summary>
/// Try and add the given element to the layout rebuild list.
/// </summary>
/// <param name="element">The element that is needing rebuilt.</param>
/// <returns>
/// True if the element was successfully added to the rebuilt list.
/// False if either already inside a Graphic Update loop OR has already been added to the list.
/// </returns>
public static bool TryRegisterCanvasElementForLayoutRebuild(ICanvasElement element)
{
return instance.InternalRegisterCanvasElementForLayoutRebuild(element);
}

private bool InternalRegisterCanvasElementForLayoutRebuild(ICanvasElement element)
{
if (m_LayoutRebuildQueue.Contains(element))
return false;

/* TODO: this likely should be here but causes the error to show just resizing the game view (case 739376)
if (m_PerformingLayoutUpdate)
{
Debug.LogError(string.Format("Trying to add {0} for layout rebuild while we are already inside a layout rebuild loop. This is not supported.", element));
return false;
}*/

return m_LayoutRebuildQueue.AddUnique(element);
}

/// <summary>
/// Try and add the given element to the rebuild list.
/// Will not return if successfully added.
/// </summary>
/// <param name="element">The element that is needing rebuilt.</param>
public static void RegisterCanvasElementForGraphicRebuild(ICanvasElement element)
{
instance.InternalRegisterCanvasElementForGraphicRebuild(element);
}

/// <summary>
/// Try and add the given element to the rebuild list.
/// </summary>
/// <param name="element">The element that is needing rebuilt.</param>
/// <returns>
/// True if the element was successfully added to the rebuilt list.
/// False if either already inside a Graphic Update loop OR has already been added to the list.
/// </returns>
public static bool TryRegisterCanvasElementForGraphicRebuild(ICanvasElement element)
{
return instance.InternalRegisterCanvasElementForGraphicRebuild(element);
}

private bool InternalRegisterCanvasElementForGraphicRebuild(ICanvasElement element)
{
if (m_PerformingGraphicUpdate)
{
Debug.LogError(string.Format("Trying to add {0} for graphic rebuild while we are already inside a graphic rebuild loop. This is not supported.", element));
return false;
}

return m_GraphicRebuildQueue.AddUnique(element);
}

加入重建队列后,CanvasUpdateRegistry就会在PerformUpdate函数中调用它的Rebuild进行重建,满足条件的情况下,将更新元素的几何网格和材质

代码
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
/// <summary>
/// Rebuilds the graphic geometry and its material on the PreRender cycle.
/// </summary>
/// <param name="update">The current step of the rendering CanvasUpdate cycle.</param>
/// <remarks>
/// See CanvasUpdateRegistry for more details on the canvas update cycle.
/// </remarks>
public virtual void Rebuild(CanvasUpdate update)
{
if (canvasRenderer == null || canvasRenderer.cull)
return;

switch (update)
{
case CanvasUpdate.PreRender:
if (m_VertsDirty)
{
UpdateGeometry();
m_VertsDirty = false;
}
if (m_MaterialDirty)
{
UpdateMaterial();
m_MaterialDirty = false;
}
break;
}
}

UpdaetGeometry:
用于确定元素的网格信息,这些信息包括顶点、三角面、uv、顶点色等,他们会被填充到s_VertexHelper中,并最终调用CanvasRenderer.SetMesh(workerMesh)设置Mesh信息

代码
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
    [Obsolete("Use OnPopulateMesh(VertexHelper vh) instead.", false)]
/// <summary>
/// Callback function when a UI element needs to generate vertices. Fills the vertex buffer data.
/// </summary>
/// <param name="m">Mesh to populate with UI data.</param>
/// <remarks>
/// Used by Text, UI.Image, and RawImage for example to generate vertices specific to their use case.
/// </remarks>
protected virtual void OnPopulateMesh(Mesh m)
{
OnPopulateMesh(s_VertexHelper);
s_VertexHelper.FillMesh(m);
}

/// <summary>
/// Callback function when a UI element needs to generate vertices. Fills the vertex buffer data.
/// </summary>
/// <param name="vh">VertexHelper utility.</param>
/// <remarks>
/// Used by Text, UI.Image, and RawImage for example to generate vertices specific to their use case.
/// </remarks>
protected virtual void OnPopulateMesh(VertexHelper vh)
{
var r = GetPixelAdjustedRect();
var v = new Vector4(r.x, r.y, r.x + r.width, r.y + r.height);

Color32 color32 = color;
vh.Clear();
vh.AddVert(new Vector3(v.x, v.y), color32, new Vector2(0f, 0f));
vh.AddVert(new Vector3(v.x, v.w), color32, new Vector2(0f, 1f));
vh.AddVert(new Vector3(v.z, v.w), color32, new Vector2(1f, 1f));
vh.AddVert(new Vector3(v.z, v.y), color32, new Vector2(1f, 0f));

vh.AddTriangle(0, 1, 2);
vh.AddTriangle(2, 3, 0);
}

/// <summary>
/// Call to update the geometry of the Graphic onto the CanvasRenderer.
/// </summary>
protected virtual void UpdateGeometry()
{
if (useLegacyMeshGeneration)
{
DoLegacyMeshGeneration();
}
else
{
DoMeshGeneration();
}
}

private void DoMeshGeneration()
{
if (rectTransform != null && rectTransform.rect.width >= 0 && rectTransform.rect.height >= 0)
OnPopulateMesh(s_VertexHelper);
else
s_VertexHelper.Clear(); // clear the vertex helper so invalid graphics dont draw.

var components = ListPool<Component>.Get();
GetComponents(typeof(IMeshModifier), components);

for (var i = 0; i < components.Count; i++)
((IMeshModifier)components[i]).ModifyMesh(s_VertexHelper);

ListPool<Component>.Release(components);

s_VertexHelper.FillMesh(workerMesh);
canvasRenderer.SetMesh(workerMesh);
}

private void DoLegacyMeshGeneration()
{
if (rectTransform != null && rectTransform.rect.width >= 0 && rectTransform.rect.height >= 0)
{
#pragma warning disable 618
OnPopulateMesh(workerMesh);
#pragma warning restore 618
}
else
{
workerMesh.Clear();
}

var components = ListPool<Component>.Get();
GetComponents(typeof(IMeshModifier), components);

for (var i = 0; i < components.Count; i++)
{
#pragma warning disable 618
((IMeshModifier)components[i]).ModifyMesh(workerMesh);
#pragma warning restore 618
}

ListPool<Component>.Release(components);
canvasRenderer.SetMesh(workerMesh);
}

参考

链接参考