Binning Entities for Batches?

I have a complicated use case that means I can’t easily use typical RenderMesh entities to draw my gameplay entities, so I’m trying to draw my gameplay entities manually using the BatchRendererGroup pipeline. I’m having trouble efficiently batching entities with parallel jobs – I’m running into the limitations of Unity’s native containers, and wondering if anyone has any advice.

Problem Definition:

  • The game is 2D with custom-generated sprites. At startup I build my own meshes/UVs and materials out of sprite sheets, and store these in a lookup table by index.
  • Each gameplay entity is represented by one sprite, and contains a component with an index (say, 0…99) of which sprite that is.
  • Each gameplay entity has a nontrivial position component that describes where it is in the simulation world, and can be converted manually to a Matrix4x4 if it’s close enough to the camera.

Right now my ideal is to create one BatchRendererGroup per sprite type/index (mesh/material), and populate them as needed from the various gameplay entities as they come to and go from the camera’s view. The BatchRendererGroup wants a NativeArray and a count of how many instances. Filling that array efficiently is deceptively complicated.

Things I’d like to avoid:

  • Relying on entity chunks. I’m already planning on using entity chunk organization for spatial decomposition and proximity/range queries, so I can’t move entities around here for rendering.
  • Splitting into Parent/Child entities. This is cache miss city and I’d like to have only one entity per gameplay unit.
protected override void OnUpdate() // This is in a SystemBase class
{
    NativeArray<NativeList<Matrix4x4>> idealStructure = 
        new NativeArray<NativeList<Matrix4x4>>(numOfSprites, Allocator.TempJob);
    AllocateAllNestedLists(idealStructure);

    JobHandle gatherAndBin =
        Entities
            .WithName("GatherAndBin")
            .ForEach((int entityInQueryIndex, in RenderComponent renderComponent) =>
            {
                Matrix4x4 matrix = ComputeMatrix4x4(renderComponent);
                int spriteIndex = renderComponent.SpriteIndex;
                idealStructure[spriteIndex].Add(matrix);
            }).ScheduleParallel(Dependency);

    // vvv This could all be done in a job but is done here for simplicity. vvv
    gatherAndBin.Complete();

    for (int i = 0; i < numOfSprites; ++i)
    {
        batchRendererGroup.SetInstancingData(idealStructure[i].Length, default);
        batchRendererGroup.GetBatchMatrices(i).CopyFrom(idealStructure[i]);
    }

    DoDisposalStuff(idealStructure);
}

Now, I can’t nest native arrays like this, and I can’t access them in parallel like this for read/write either. However, I’ve been scratching my head trying to find a way to do this at all with any level of parallelism. At best it seems the only two things I could get out of the ScheduleParallel are an unsorted queue/stream, or a MultiHashMap, but for either of those data structures there’s no good, parallel way to convert them to arrays of bins/buckets either.

Anyone encounter this problem before and have any advice?

I would advice against using BatchRendererGroup API. It’s a very low level API that requires deep understanding of Unity shader system internals for correct use. More info here: The BatchRendererGroup API | Hybrid Renderer | 0.11.0-preview.44

We are planning to heavily refactor the BatchRendererGroup API. Make it more user friendly and documented. This will however break backwards compatibility with the old API version. I would suggest waiting until we have landed this properly supported API version.

1 Like