0
点赞
收藏
分享

微信扫一扫

Unity UGUI Batches合批规则详解

RJ_Hwang 2022-01-24 阅读 195

Unity UGUI Batches合批规则详解

在处理UGUI DrawCall问题的时候,我们经常遇到各式各样的问题。

因为看不到UGUI Batches的源码,所以就一直不清楚它的合批规则到底是怎样的。所以今天就来一个一个问题解答,顺序详解一下它的源码:

那么到底具体是怎样?我来总结一下:

源码剖析:

在ui合批的时候,会先给按照depth(ui的层数)以及materialInstanceID,textureID进行排序,如下调用过程如下:
SortForBatching先对每一个ui进行数据准备,调用PrepareDepthEntries();

void SortForBatching(const RenderableUIInstruction* inputInstructions, UInt32 count, RenderableUIInstruction* outputInstructions, int sortBucketGridSize)
{
   PROFILER_AUTO(gSort, NULL);

   // Create depth block
   dynamic_array<DepthSortEntry> depthEntries(kMemTempAlloc);
   depthEntries.resize_uninitialized(count);

   PrepareDepthEntries(inputInstructions, count, depthEntries.data(), sortBucketGridSize);
   std::sort(depthEntries.data(), depthEntries.data() + count);

   // Generate new sorted UIInstruction array
   for (UInt32 i = 0; i < count; i++)
   {
       Assert(depthEntries[i].depth >= 0);
       const RenderableUIInstruction& instruction = inputInstructions[depthEntries[i].renderIndex];
       outputInstructions[i] = instruction;
   }
}

在PrepareDepthEntries时候:

static void PrepareDepthEntries(const RenderableUIInstruction* uiInstruction, UInt32 count, DepthSortEntry* output, int sortBucketGridSize)
{
    if (count == 0)
        return;

    DepthSortGrid grid;
    grid.Initialize(0);

    int maxDepth = 0;
    for (int i = 0; i < count; ++i)
    {
        // if we have a forced new batch
        if (SortingForceNewBatch(uiInstruction[i]) != NoBreaking)
        {
            // try for a run of forced batches
            int forcedCount = 0;
            for (int j = i; j < count; ++j)
            {
                // TODO: If this is true through the end the instructions then we never increment i and
                // can get into a very long infinite loop as we never hit the i += forcedCount - 1
                if (SortingForceNewBatch(uiInstruction[j]) != NoBreaking)
                {
                    // just set the depth to be the
                    // maxDepth +1
                    ++forcedCount;
                    output[j].renderIndex = uiInstruction[j].renderDepth;
                    output[j].depth = ++maxDepth;
                    continue;
                }

                // we have run out of forced depths...
                // create a new grid for the next set of
                // batchable elements
                Assert(forcedCount > 0);
                i += forcedCount - 1;
                grid.Initialize(++maxDepth);
                break;
            }
        }
        else
        {
            int depth = grid.AddAndGetDepthFor(uiInstruction[i], uiInstruction, sortBucketGridSize);
            maxDepth = std::max(depth, maxDepth);

            output[i].renderIndex = uiInstruction[i].renderDepth;
            output[i].depth = depth;
            output[i].materialInstanceID = uiInstruction[i].materialInstance.GetInstanceID();
            output[i].textureID = uiInstruction[i].textureID;
            output[i].texelSize = uiInstruction[i].texelSize;
        }
    }
}

有一个函数,SortingForceNewBatch(),这个函数会在ui与canvas不共面,或者不是同一个canvas的时候,强制新启一个batch,同时把深度增加。也就是下面这个函数的判断:

inline BatchBreakingReason SortingForceNewBatch(const RenderableUIInstruction& instruction)
{
    if (!instruction.isCoplanarWithCanvas)
        return NotCoplanarWithCanvas;
    if (instruction.isCanvasInjectionIndex)
        return CanvasInjectionIndex;
    return NoBreaking;
}

排序方法如下:

static bool operator<(const DepthSortEntry& a, const DepthSortEntry& b)
{
    // first sort by depths
    if (a.depth != b.depth)
        return a.depth < b.depth;

    // if they're equal, sort by materials
    if (a.materialInstanceID != b.materialInstanceID)
        return a.materialInstanceID < b.materialInstanceID;

    // if they're equal, sort by textures
    if (a.textureID != b.textureID)
        return a.textureID < b.textureID;

    //TODO: we could break 'fast' batching here due
    // to not looking at rect clipping / what the clip
    // rect is. This is unlikely (due to the next step
    // being render order), but it's something to
    // investigate at a later time.

    // all else being equal... sort by render order
    return a.renderIndex < b.renderIndex;
}

我们ui只用了一个canvas,显然,是因为不共面导致的,因此通过调试,发现isCoplanarWithCanvas为false的计算是通过相对于canvas的Z值确定的:

void DoSyncWorldRect(UIInstruction& uiData)
{
    MinMaxAABB worldBounds;
    TransformAABBSlow(uiData.localBounds, uiData.transform, worldBounds);
    ProjectAABBToRect(worldBounds, uiData.globalRect);
    uiData.worldBounds = worldBounds;
    uiData.isCoplanarWithCanvas = CompareApproximately(worldBounds.m_Min.z, 0.0f, 0.001F) && CompareApproximately(worldBounds.m_Max.z, 0.0f, 0.001F);
    uiData.dirtyTypesFlag = kTypeOrder;
}

可以看到,是通过判断worldBounds.m_Max.z worldBounds.m_Min.z是否在0.001f范围内,而这个z是通过旋转影响的,我通过调试发现,在PlaneDistance比较大的时候这个误差比较小,会小于0.001,然而PlaneDistance比较小的时候这个值会大于0.001,我们设置1的时候这个z会达到0.019左右,因此我将0.001改成0.01,发现怎么转向都没问题了。因为转不同的方向不同的ui旋转的计算值的误差不一样,这也就是为什么转不一样的方向drawcall不一样的原因了。

因此大家在优化drawcall的时候一定要注意,防止这种情况的出现,这种情况会导致depth非常大,很多ui都会成为单独的批次。这也算unity的一个坑吧。毕竟z值相差较大也去合批容易造成渲染顺序不正确的问题。

举报

相关推荐

0 条评论