C#

阅读UGUI源码

Graphic

Posted by Bob on September 21, 2020
[GraphicRegistry]

GraphicRegistry管理所有Graphic,内部通过IndexedSet维护数据

    public class GraphicRegistry
    {
        private readonly Dictionary<Canvas, IndexedSet<Graphic>> m_Graphics = new Dictionary<Canvas, IndexedSet<Graphic>>();
    }

    internal class IndexedSet<T> : IList<T>
    {
        //This is a container that gives:
        //  - Unique items
        //  - Fast random removal
        //  - Fast unique inclusion to the end
        //  - Sequential access
        //Downsides:
        //  - Uses more memory
        //  - Ordering is not persistent
        //  - Not Serialization Friendly.

        //We use a Dictionary to speed up list lookup, this makes it cheaper to guarantee no duplicates (set)
        //When removing we move the last item to the removed item position, this way we only need to update the index cache of a single item. (fast removal)
        //Order of the elements is not guaranteed. A removal will change the order of the items.

        readonly List<T> m_List = new List<T>();
        Dictionary<T, int> m_Dictionary = new Dictionary<T, int>();
    }
[Graphic]

仅当OnEnable(),OnCanvasHierarchyChanged(),OnTransformParentChanged() 时触发查找第一个激活启用的Canvas,并以此为key将Graphic注册到GraphicRegistry。

当动画改变时触发SetAllDirty。

在组件激活(IsActive())的情况下,SetLayoutDirty触发LayoutRebuilder.MarkLayoutForRebuild(rectTransform),SetVerticesDirty触发CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this),SetMaterialDirty触发CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this)。

当RectTransform/Color/Material/Parent/Animation改变时会触发重建。

Rebuild主要是几何和材质重建。

 public virtual void Rebuild(CanvasUpdate update)
        {
            if (canvasRenderer.cull)
                return;

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

protected virtual void UpdateGeometry()
{
            if (useLegacyMeshGeneration)
                DoLegacyMeshGeneration();
            else
                DoMeshGeneration();
}
[UpdateGeometry]

UI组件宽高非0才构建VertexHelper数据,各个组件重写OnPopulateMesh构建VertexHelper数据,遍历IMeshModifier组件(描边,投影等)修改VertexHelper数据,最终VertexHelper数据填充到workerMesh(static)然后交给canvasRenderer去渲染。

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);
}
[UpdateMaterial]

获取材质后,经过挂载的所有IMaterialModifier组件(做一些材质特效)修改后,和mainTexture一起交给canvasRenderer渲染。


        /// <summary>
        /// Returns the material used by this Graphic.
        /// </summary>
        public virtual Material material
        {
            get
            {
                return (m_Material != null) ? m_Material : defaultMaterial;
            }
            set
            {
                if (m_Material == value)
                    return;

                m_Material = value;
                SetMaterialDirty();
            }
        }

        public virtual Material materialForRendering
        {
            get
            {
                var components = ListPool<Component>.Get();
                GetComponents(typeof(IMaterialModifier), components);

                var currentMat = material;
                for (var i = 0; i < components.Count; i++)
                    currentMat = (components[i] as IMaterialModifier).GetModifiedMaterial(currentMat);
                ListPool<Component>.Release(components);
                return currentMat;
            }
        }

        /// <summary>
        /// Update the renderer's material.
        /// </summary>
        protected virtual void UpdateMaterial()
        {
            if (!IsActive())
                return;

            canvasRenderer.materialCount = 1;
            canvasRenderer.SetMaterial(materialForRendering, 0);
            canvasRenderer.SetTexture(mainTexture);
        }
[CanvasUpdateRegistry]

CanvasUpdateRegistry管理Layout重建队列和Graphic重建队列


    public class CanvasUpdateRegistry
    {

        private readonly IndexedSet<ICanvasElement> m_LayoutRebuildQueue = new IndexedSet<ICanvasElement>();
        private readonly IndexedSet<ICanvasElement> m_GraphicRebuildQueue = new IndexedSet<ICanvasElement>();

    }

Canvas在渲染前会调用willRenderCanvases,执行PerformUpdate


 Canvas.willRenderCanvases += PerformUpdate;

PerformUpdate执行流程

1.清除队列中空对象和已销毁的对象

2.Layout队列中元素根据其父节点个数排序

3.Layout队列中元素( Prelayout = 0,Layout = 1,PostLayout = 2)逐个Rebuild

4.LayoutComplete

5.ClipperRegistry做Cull裁剪

6.Graphic队列中元素(PreRender = 3,LatePreRender = 4,MaxUpdateValue = 5)逐个Rebuild

7.GraphicUpdateComplete

[ClipperRegistry]

ClipperRegistry维护裁剪者队列,IClipper是裁剪者,IClippable是可裁剪对象,ClipperRegistry


    public class ClipperRegistry
    {
       
        readonly IndexedSet<IClipper> m_Clippers = new IndexedSet<IClipper>();

         public void Cull()
        {
            for (var i = 0; i < m_Clippers.Count; ++i)
            {
                m_Clippers[i].PerformClipping();
            }
        }
    }

    public interface IClipper
    {
        void PerformClipping();
    }

    public interface IClippable
    {
        GameObject gameObject { get; }
        void RecalculateClipping();
        RectTransform rectTransform { get; }
        void Cull(Rect clipRect, bool validRect);
        void SetClipRect(Rect value, bool validRect);
    }
[MaskableGraphic]

MaskableGraphic在Graphic基础上实现了被裁剪与遮罩

Cpu裁剪:RectMask2D实现PerformClipping方法,先通过Clipping.FindCullAndClipWorldRect计算出一个最小裁剪矩形(持续每帧计算子节点的裁剪区域),然后遍历所有IClippable去做裁剪,最后触发canvasRenderer.cull处理。

Gpu裁剪:Mask是通过创建新的材质来裁剪。


public abstract class MaskableGraphic : Graphic, IClippable, IMaskable, IMaterialModifier
{
    
}

public class RectMask2D : UIBehaviour, IClipper, ICanvasRaycastFilter
{
}


public class Mask : UIBehaviour, ICanvasRaycastFilter, IMaterialModifier
{

}