CanvasUpdateSystem 画布刷新系统

        制作UI过程,添加Canvas组件将会打断和之前元素DrawCall的合并,每个Canvas都会开始一个全新的DrawCall,当Canvas需要重绘的时候会调用SendWillRenderCanvases()方法。

Canvas.cs(部分源码)

//部分源码
public sealed class Canvas : Behaviour
{
    public delegate void WillRenderCanvases();
    
    //公有事件,在CanvasUpdateRegistry.cs的构造函数里,为willRenderCanvases事件添加了一个监听PerformUpdate。
    //在渲染(所有)Canvas之前会抛出willRenderCanvases事件,继而调用到CanvasUpdateRegistry.cs的PerformUpdate方法
    public static event Canvas.WillRenderCanvases willRenderCanvases;
    
    //强制更新所有Canvas, 当Canvas需要重新绘制时调用,可在外部主动调用
    public static void ForceUpdateCanvases()
    {
        Canvas.SendWillRenderCanvases();
    }
    
    private static void SendWillRenderCanvases()
    {
        if (Canvas.willRenderCanvases != null)
        {
            Canvas.willRenderCanvases();
        }
    }
}

CanvasUpdateRegistry.cs

//部分源码
//这个枚举在后面遍历元素Rebuild的时候会用到
public enum CanvasUpdate
{
    //布局的三个阶段
    Prelayout = 0,
    Layout = 1,
    PostLayout = 2,
    //渲染的两个阶段
    PreRender = 3,
    LatePreRender = 4,
    //
    MaxUpdateValue = 5
}

public class CanvasUpdateRegistry
{
    //布局重建队列
    private readonly IndexedSet<ICanvasElement> m_LayoutRebuildQueue = new IndexedSet<ICanvasElement>();
    
    //图形重建队列
    //简单说明下IndexedSet,是一个容器,内部使用List和Dictionary实现了快速增删改查
    private readonly IndexedSet<ICanvasElement> m_GraphicRebuildQueue = new IndexedSet<ICanvasElement>();
    
    //为什么UI发生变化(布局变化、顶点变化、材质变化)一定要加入到这两个等待重建队列?
    //一个UI界面同一帧内可能有很多元素发生变化,同步随时重建肯定会卡死
    //所以需要加入到待重建队列等待一个时机(下一帧开始渲染之前)统一来处理
    
    protected CanvasUpdateRegistry()
    {
        //在构造里监听了Canvas.willRenderCanvases,调用this.PerformUpdate()
        Canvas.willRenderCanvases += PerformUpdate;
    }

    private void PerformUpdate()
    {
        UISystemProfilerApi.BeginSample(UISystemProfilerApi.SampleType.Layout);
        CleanInvalidItems();
        //一个UI对象(如Image),RectTransform和Graphics同时发生修改时,更新的含义不同,需要区分对待
        //所以会维护两个队列m_LayoutRebuildQueue和m_GraphicRebuildQueue
        //1、修改了Image的宽高,这样Mesh网格的顶点会发生变化,此时需要加入m_LayoutRebuildQueue队列处理
        //2、修改了Image的Sprite,并不会影响顶点位置信息,此时需要加入m_GraphicRebuildQueue队列处理
        //所以在遍历队列更新的时候会分层处理
        //1、重建布局:分别以Prelayout、Layout、PostLayout为参数重建
        //2、重建图形:分别以PreRender和LatePreRender为参数重建
        //保证Image知道现在是要更新布局,还是更新渲染,并且明确处于什么阶段

        m_PerformingLayoutUpdate = true;
        
        //需要重建布局的元素(RectTransform变化),根据父物体的数量来排序
        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;

        // 布局构建结束后,开始进行Mask2D裁剪
        ClipperRegistry.instance.Cull();

        m_PerformingGraphicUpdate = true;

        //需要重新布局的Graphices元素(Image Text RawImage发生变化)
        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))
                        //重建UI元素
                        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);
    }
}

CanvasUpdateSystem 画布刷新流程(类图)

ICanvasElement

  • 重建方法:void Rebuild(CanvasUpdate executing);
  • 布局重建完成:void LayoutComplete();
  • 图像重建完成:void GraphicUpdateComplete();
  • 检查Element是否无效:bool IsDestroyed();

CanvasUpdateRegistry 被初始化时(构造函数)向Canvas中注册了更新函数(PerformUpdate),以用来响应重建。

Canvas在渲染前会调用willRenderCanvases,即执行PerformUpdate ,流程如下:

脏标记

        标记延迟执行,优化重新渲染的手段。
        详情请见:游戏设计模式:脏标识模式

例如在Graphic 中存在三种脏标分别代表三种等待重建

  • 尺寸改变时(RectTransformDimensions):LayoutRebuild 布局重建
  • 尺寸、颜色改变时:Vertices to GraphicRebuild 图像重建
  • 材质改变时:Material to GraphicRebuild 图像重建
  •  层级改变、应用动画属性(DidApplyAnimationProperties) :All to Rebuild 重建所有
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

热血枫叶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值