Runtime mesh changes resulting in nothing rendered?

I am trying to update the mesh on a large number of entities at runtime, based on data in the entity’s other components (for some dynamic terrain). However, no matter what I try, I cannot seem to get the EntitiesGraphicsSystem to actually render the meshes, resulting in a completely empty scene.

Distilled down, my mesh building code looks like this:

private void UpdateDirtyMeshes()
{
    using var dirtyEntities = tileWithDirtyGroundQuery.ToEntityArray(Allocator.Temp);

    if (dirtyEntities.Length == 0)
    {
        // skip updating if nothing needs it
        return;
    }

    // make some mesh data arrays for the job to build the meshes
    var meshDataArrays = new UnsafeList<Mesh.MeshDataArray>(dirtyEntities.Length, Allocator.TempJob);
    for (var i = 0; i < dirtyEntities.Length; i++)
    {
        meshDataArrays.AddNoResize(Mesh.AllocateWritableMeshData(1));
    }

    // pass the data arrays to the job to populate
    var meshBuildJob = new MeshBuildJob
    {
        // other stuff...
        MeshDataArrays = meshDataArrays
    };
    var meshBuildJobHandle = meshBuildJob.Schedule(dirtyEntities.Length, 1, Dependency);
    meshBuildJobHandle.Complete();

    for (var i = 0; i < dirtyEntities.Length; i++)
    {
        // get the "ground" entity which holds the mesh
        var ground = EntityManager.GetComponentData<MapTileGroundEntity>(dirtyEntities[i]).Value;
        var mmi = EntityManager.GetComponentData<MaterialMeshInfo>(ground);

        // in this case, I'm dropping the old mesh and making a new one
        // I have also tried to simply update the existing mesh, but that doesn't work either
        if (mmi.Mesh > 0)
        {
            // unregister the old mesh
            entitiesGraphicsSystem.UnregisterMesh(new BatchMeshID { value = (uint)mmi.Mesh });
        }

        // build and register a new mesh
        var mesh = new Mesh() { name = "GroundMesh_" + ground.Index };
        Mesh.ApplyAndDisposeWritableMeshData(meshDataArrays[i], mesh);
        mesh.RecalculateNormals();
        mesh.RecalculateBounds();
        mesh.RecalculateTangents();

        // for debugging, update a component that shows us the mesh in the inspector
        EntityManager.SetComponentData(ground, new MapTileGroundMesh { Value = mesh });

        // update the bounds on the component
        var bounds = mesh.bounds;
        EntityManager.SetComponentData(entity, new RenderBounds { Value = new() { Center = bounds.center, Extents = bounds.extents } });

        // register the new mesh with the EGS and update the mesh ID
        var meshId = entitiesGraphicsSystem.RegisterMesh(mesh);
        EntityManager.SetComponentData(ground, new MaterialMeshInfo { Mesh = (int)meshId.value, Material = mmi.Material, Submesh = mmi.Submesh });
    }

    // additional work on other parts of the entity, cleanup/disposing things, etc.
    // ...
}

For reference, this entity is built as a prototype and cloned earlier by EntityManager.Instantiate then adding a Parent component to it once built.

The prototype is built like so:

EntityManager entityManager = // ...
Material material = // This is currently using the standard URP "Lit" material

entityManager.AddComponentData(entity, new LocalTransform { Scale = 1 });
entityManager.AddComponent<WorldTransform>(entity);
entityManager.AddComponent<LocalToWorld>(entity);
RenderMeshUtility.AddComponents
    entityManager,
    entity,
    new RenderMeshDescription(ShadowCastingMode.ShadowsOnly),
    new RenderMeshArray(new Material[] { material }, new Mesh[0]),
    new MaterialMeshInfo { Mesh = 0, Material = -1 });
// plus my custom components, too

From my understanding by reading through the scripts, MaterialMeshInfo’s Mesh/Material properties can be positive for a batched mesh’s ID, or negative to index the shared RenderMeshArray. Since they all share a material, it can be in the RenderMeshArray, but I’ll be updating the mesh at runtime and each will have a different mesh (plus don’t want structural changes for doing so), so that’s just going to be zero for now.

After this method executes, I can see all my tiles in the hierarchy window correctly:
8858581--1208512--upload_2023-3-7_15-59-34.png
8858581--1208509--upload_2023-3-7_15-59-19.png
(The appropriate WorldRenderBounds is also correct, per the tile’s transform)

I can also inspect the resulting mesh to find it looks correct when viewed in the inspector’s little viewport. So, all that appears to have worked:
8858581--1208506--upload_2023-3-7_15-55-25.png
(It’s a very simple mesh for now, but will get more complicated once this is working)

Yet, despite that, nothing is rendered in the scene: it is in fact completely empty. I’ve been at this for quite some time and just cannot figure out what the missing piece is; what am I doing wrong?

Unity Info

Unity Editor 2022.2.7f1
Universal Rendering Pipeline 14.0.6
Unity.Entities 1.0.0-pre.47
Unity.Entities.Rendering 1.0.0-pre.44

Looks very similar to what I do and it works. Two ideas: (1) not casting BatchMeshID. (2) check if LocalTransform is set correctly (i.e. that it does not have scale „0“ or invalid rotation)?

Edit: it think your bug is in how you setup MaterialMeshInfo. I set it up using BatchMaterialID and BatchMeshID.

BatchMaterialID materialID = hybridRenderer.RegisterMaterial(yourMaterial)
BatchMeshID meshID = hybridRenderer.RegisterMesh(yourMesh)
entityManager.SetComponentData(prototype, new MaterialMeshInfo(materialID, meshID));
or
_entityManager.SetComponentData(prototype, new MaterialMeshInfo {MeshID = meshID, MaterialID = materialID });
var meshID = hybridRenderer.RegisterMesh(receivingMeshArray[0]);
//I manually add components instead of using RenderMeshUtility function
_entityManager.AddComponent(prototype, renderTypeset);
_entityManager.AddComponent(prototype, areaOrBoxColorTypeset);
_entityManager.AddComponentData(prototype, new AreaColorMeshState { meshID = meshID });//ICleanupComponentData used to track destruction to enable cleanup (unregister mesh, destroy mesh)
_entityManager.SetSharedComponentManaged(prototype, renderMeshDescription.FilterSettings);
_entityManager.SetComponentData(prototype, new MaterialMeshInfo(materialID, meshID));
_entityManager.SetComponentData(prototype, new RenderBounds { Value = receivingMeshArray[0].bounds.ToAABB() });
_entityManager.SetComponentData(prototype, LocalTransform.Identity);//ensures that scale is not zero, rotation is quaternion.identity

You seem to be specifying “ShadowsOnly”, which means that the entities will only be rendered into shadow maps, and not the main output. Have you tried using ShadowCastingMode.On instead?

If that does not help, I would suggest trying Unity’s Frame Debugger or RenderDoc to see whether there is any rendering taking place. If there are draw calls being made but nothing is rendering, that could suggest e.g. something wrong with the transforms. If there are no draw calls, then it suggests a remaining problem in the configuration of the entities.