UGUI合批
- UGUI合批规则概述
 - UGUI性能查看工具
 - 合批部分的特殊例子
 - 一个白色image、蓝色image覆盖了Text,白色image和Text哪个先渲染
 
- Mask合批
 - Mask为什么会产生两个drawcall
 - Mask为什么不能合批
 - Mask注意要点
 
- RectMask2D
 - 为什么RecMask2D比Mask性能更好
 - 主要代码
 - RectMask2D注意要点
 
- 根据应用场景选择使用哪个(Mask、RectMask2D)
 - 只需要一个遮罩
 - 需要多个遮罩,遮罩下有多个子物体
 
UGUI合批规则概述
-  
遍历所有UI元素
 -  
根据深度Depth(优先级最高,-1表示不用渲染)、材质ID、图片ID、渲染顺序对所有UI进行排序
 -  
进行合批处理,比如UI1和UI2(两者必须是紧挨着的)是相同材质、相同图片就可以进行合批,如果UI1和UI2之间有一个中间层UI3(根UI1和UI2材质不同),就会打断合批

 
- Text必须文字的网格覆盖到image上面才算相交,Rect Transform框框相交不算
 - 白色image的深度为0,最先渲染,然后判断Text是否相交,再判断材质ID和图片ID是否相同,如果相同则可以合批,因为不相同,所以Text深度为白色image+1为1(如果Text底下有很多image,则取最大的深度+1)
 - 红色image因为底下有Text和白色image,所以计算出两个深度值分别1和0,取最大值加1,就是2
 - 黄色和蓝色image会进行合批测试操作,深度值都是2(这里只是测试是否能合批,并还没有真正合批)
 - 最后会得到一个排序数组list并把所有深度为-1的值剔除掉,0、1、2、2、2,传给合批部分的程序进行合批操作,判断相邻的元素是否能进行合批,通过判断数组的值是否相等
 
UGUI性能查看工具



合批部分的特殊例子
一个白色image、蓝色image覆盖了Text,白色image和Text哪个先渲染

- 根据分析工具可以看到白色image先渲染,文本后渲染(白色图片ID小一点)
 - 这样得到的数组list,会先是白色image、Text、蓝色image,导致白色和蓝色无法合批
 - 解决办法是把白色image和蓝色image赋值同一个texture

 - 只要是满足合批条件,合批数组紧挨着,虽然没有覆盖,也可以合批
 - 能不能合批,在最后合批数组上才能决定
 
Mask合批
-  
mask无法跟mask外的物体进行合批,但是mask之间可以合批
 -  
mask本身会产生两个drawcall,性能损耗
 
Mask为什么会产生两个drawcall
- 第一个drawcall是mask在设置模板缓存产生的
 - 第二个drawcall是mask在还原模板产生的
 - mask之间可以进行合批,设置模板缓存和还原模板时候,因为图片ID和材质是一样的,所以可以合批
 - 设置模板缓存时候,会把需要显示部分的缓存值设置为1,遮罩的部分设置为0,在渲染的时候会取出我存在当前像素当中的一个模板缓存值,然后判断它呢是否为一。如果为一的话,才进行渲染,如果不为一,就不不渲染了
 - 每一个像素点上,创建了一个这样类似于一个数据缓存,通过它的模板缓存值来判断是否显示,都是像素级别的单位
 - 模板还原也要产生一次drawcall,把每个像素点的模板缓存清除
 
Mask为什么不能合批
因为Mask会产生一个特殊的材质,材质不同就不能合批
 
    public virtual Material GetModifiedMaterial(Material baseMaterial)
    {
        if (!MaskEnabled())
            return baseMaterial;
        var rootSortCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform);
        var stencilDepth = MaskUtilities.GetStencilDepth(transform, rootSortCanvas);
        if (stencilDepth >= 8)
        {
            Debug.LogWarning("Attempting to use a stencil mask with depth > 8", gameObject);
            return baseMaterial;
        }
        int desiredStencilBit = 1 << stencilDepth;
        // if we are at the first level...
        // we want to destroy what is there
        if (desiredStencilBit == 1)
        {
            // StencilMaterial.Add是最主要的方法,这里添加了特殊的材质,因为材质不同,所以不能和mask外的物体合批
            var maskMaterial = StencilMaterial.Add(baseMaterial, 1, StencilOp.Replace, CompareFunction.Always, m_ShowMaskGraphic ? ColorWriteMask.All : 0);
            StencilMaterial.Remove(m_MaskMaterial);
            m_MaskMaterial = maskMaterial;
            var unmaskMaterial = StencilMaterial.Add(baseMaterial, 1, StencilOp.Zero, CompareFunction.Always, 0);
            StencilMaterial.Remove(m_UnmaskMaterial);
            m_UnmaskMaterial = unmaskMaterial;
            graphic.canvasRenderer.popMaterialCount = 1;
            graphic.canvasRenderer.SetPopMaterial(m_UnmaskMaterial, 0);
            return m_MaskMaterial;
        }
        //otherwise we need to be a bit smarter and set some read / write masks
        var maskMaterial2 = StencilMaterial.Add(baseMaterial, desiredStencilBit | (desiredStencilBit - 1), StencilOp.Replace, CompareFunction.Equal, m_ShowMaskGraphic ? ColorWriteMask.All : 0, desiredStencilBit - 1, desiredStencilBit | (desiredStencilBit - 1));
        StencilMaterial.Remove(m_MaskMaterial);
        m_MaskMaterial = maskMaterial2;
        graphic.canvasRenderer.hasPopInstruction = true;
        var unmaskMaterial2 = StencilMaterial.Add(baseMaterial, desiredStencilBit - 1, StencilOp.Replace, CompareFunction.Equal, 0, desiredStencilBit - 1, desiredStencilBit | (desiredStencilBit - 1));
        StencilMaterial.Remove(m_UnmaskMaterial);
        m_UnmaskMaterial = unmaskMaterial2;
        graphic.canvasRenderer.popMaterialCount = 1;
        graphic.canvasRenderer.SetPopMaterial(m_UnmaskMaterial, 0);
        return m_MaskMaterial;
    }
 
Mask注意要点
- Mask剔除的部分还是会影响深度计算的,从而影响合批,增加drawcall次数
 - Mask剔除的部分还是会drawcall,只不过mask把绘制的像素剔除了
 - Mask下的子物体可以正常进行合批
 - mask之间只要满足合批条件,那么他们之间的元素也是能够进行合批的

 
RectMask2D
- RectMask2D本身不占用drawcall
 - 因为实际的具体逻辑是在canvas render里进行的,涉及到渲染具体的操作都是内部用c++类做的,性能更好,这里通过性能分析渲染的面和点判断遮盖的部分是否被渲染出来

 
为什么RecMask2D比Mask性能更好
- RectMask2D本身不占用drawcall,Mask本身有两次drawcall
 - RectMask2D对于被遮罩的部分并不会绘制,Mask是把遮罩的部分剔除了
 
主要代码
public virtual void PerformClipping()
{
    if (ReferenceEquals(Canvas, null))
    {
        return;
    }
    //TODO See if an IsActive() test would work well here or whether it might cause unexpected side effects (re case 776771)
    // if the parents are changed
    // or something similar we
    // do a recalculate here
    if (m_ShouldRecalculateClipRects)
    {
        MaskUtilities.GetRectMasksForClip(this, m_Clippers);
        m_ShouldRecalculateClipRects = false;
    }
    // get the compound rects from
    // the clippers that are valid
    bool validRect = true;
    // 获取需要剪切的区域
    Rect clipRect = Clipping.FindCullAndClipWorldRect(m_Clippers, out validRect);
    // If the mask is in ScreenSpaceOverlay/Camera render mode, its content is only rendered when its rect
    // overlaps that of the root canvas.
    RenderMode renderMode = Canvas.rootCanvas.renderMode;
    bool maskIsCulled =
        (renderMode == RenderMode.ScreenSpaceCamera || renderMode == RenderMode.ScreenSpaceOverlay) &&
        !clipRect.Overlaps(rootCanvasRect, true);
    if (maskIsCulled)
    {
        // Children are only displayed when inside the mask. If the mask is culled, then the children
        // inside the mask are also culled. In that situation, we pass an invalid rect to allow callees
        // to avoid some processing.
        clipRect = Rect.zero;
        validRect = false;
    }
    if (clipRect != m_LastClipRectCanvasSpace)
    {
        foreach (IClippable clipTarget in m_ClipTargets)
        {
            //设置剪切效果
            clipTarget.SetClipRect(clipRect, validRect);
        }
        foreach (MaskableGraphic maskableTarget in m_MaskableTargets)
        {
            maskableTarget.SetClipRect(clipRect, validRect);
            maskableTarget.Cull(clipRect, validRect);
        }
    }
    else if (m_ForceClip)
    {
        foreach (IClippable clipTarget in m_ClipTargets)
        {
            clipTarget.SetClipRect(clipRect, validRect);
        }
        foreach (MaskableGraphic maskableTarget in m_MaskableTargets)
        {
            maskableTarget.SetClipRect(clipRect, validRect);
            if (maskableTarget.canvasRenderer.hasMoved)
                maskableTarget.Cull(clipRect, validRect);
        }
    }
    else
    {
        foreach (MaskableGraphic maskableTarget in m_MaskableTargets)
        {
            //Case 1170399 - hasMoved is not a valid check when animating on pivot of the object
            maskableTarget.Cull(clipRect, validRect);
        }
    }
    m_LastClipRectCanvasSpace = clipRect;
    m_ForceClip = false;
    UpdateClipSoftness();
}
 
RectMask2D注意要点
- 遮罩的部分因为没有绘制,所以不影响深度计算,不影响合批
 - 会打断合批,一个RectMask2D下的子物体不可以跟另一个RectMask2D下的子物体进行合批,但RectMask2D下的子物体可以进行合批
 - RectMask2D组件的Image之间可以进行合批
 
根据应用场景选择使用哪个(Mask、RectMask2D)
只需要一个遮罩
选择用RectMask2D性能更好
需要多个遮罩,遮罩下有多个子物体
选择用Mask可以对不同Mask之间的子物体进行合批,这种情况就会比RectMask2D性能更好



















![[米联客-安路飞龙DR1-FPSOC] FPGA基础篇连载-25 ADC模块FEP-DAQ9248采集显示波形方案](https://i-blog.csdnimg.cn/direct/d28fc31fe5f2450e8a16f5154dc6a5d9.png)