Search Unity

Resolved How to replace Mesh with new BRG API?

Discussion in 'Graphics for ECS' started by Fribur, Oct 6, 2022.

  1. Fribur

    Fribur

    Joined:
    Jan 5, 2019
    Posts:
    136
    I cannot figure out how to unregister a Mesh dynamically during runtime, replace it with another one, and set it. This is the (DOTS-based) test code I am using to fetch from a perfectly fine rendered entity the mesh.:
    Code (CSharp):
    1.  
    2. var renderMeshArray = entityManager.GetSharedComponentManaged<RenderMeshArray>entity);
    3. var meshInfo = entityManager.GetComponentData<MaterialMeshInfo>(entity);
    4. var hybridRenderer = World.GetExistingSystemManaged<EntitiesGraphicsSystem>();
    5. hybridRenderer.UnregisterMesh(meshInfo.MeshID);  <--error
    6. hybridRenderer.RegisterMesh(newMesh);
    7. GameObject.Destroy(renderMeshArray.Meshes[0]);
    8. renderMeshArray.Meshes[0] = newMesh;
    9. entityManager.SetSharedComponentManaged(entity, renderMeshArray);
    10.  
    Getting the error "Trying to unregister an invalid BRGMeshID". If I comment out this line and just destroy the mesh, the editor crashes. Any advice?
     
    Last edited: Oct 6, 2022
    heu3becteh likes this.
  2. JussiKnuuttila

    JussiKnuuttila

    Unity Technologies

    Joined:
    Jun 7, 2019
    Posts:
    351
    One possible cause is that the MaterialMeshInfo still contains a load time mesh index at the time you are running the code. It can store two kinds of IDs:
    • Load time mesh indices that are used during baking time. These are array indices to RenderMeshArray.
    • Runtime mesh IDs that are used for rendering. These are the same kind returned by RegisterMesh. They are runtime only and cannot be stored to disk (which is why they are not used during baking).
    • MaterialMeshInfo should have a property called IsRuntimeMesh which tells you which of the two it currently contains.
    The EntitiesGraphicsSystem automatically takes care of registering and unregistering meshes inside RenderMeshArray components, trying to unregister meshes inside those will probably lead to some kind of error unless you registered the same mesh yourself first, the registering is reference counted.

    To change meshes or materials, this is not necessary though. You should be able to just RegisterMesh your new mesh (remember to Unregister when you are done!) and set the ID directly into MaterialMeshInfo and it should render. RenderMeshArray is not used at rendering time, it is used for loading baked entities.

    You can use RenderMeshArray at runtime if you want, but it is likely to be slower performance than just using MaterialMeshInfo directly because you will have to modify managed shared components and changing them causes structural changes.
     
    heu3becteh and Fribur like this.
  3. Fribur

    Fribur

    Joined:
    Jan 5, 2019
    Posts:
    136
    Many Thanks for your support. Did what you suggest, and in addition I changed how I register the mesh upon first generations (it's a procedurally generated mesh not amendable to baking): in both cases the error "Trying to unregister an invalid BRGMeshID" goes away, but on the flip side (1) the mesh stops rendering immediately upon destroying the old mesh and (2) the editor crashes when I hit stop play (crash log attached). Do I have to wait for a frame before destroying the old Mesh so that the EntitiesGraphicsSystem can do whatever it is doing upon mesh changes?

    EDIT: solution to (1) was to set the new updated MaterialMeshInfo:
    entityManager.SetComponentData(entity, updatedMaterialMeshInfo);
    . The the editor crash bug is still there when I destroy the old Mesh after doing all those changes to the entity.

    Here the change I did to the initial Mesh registration:
    Code (CSharp):
    1. //as per HybridURPSamples: does this even Register the Material and Mesh with the entitiesRenderer BRG?
    2. RenderMeshUtility.AddComponents(spawned, _entityManager, renderMeshDescription, renderMeshArray, MaterialMeshInfo.FromRenderMeshArrayIndices(0, 0));
    3.  
    4. //alternative way to register:
    5. var hybridRenderer = World.GetExistingSystemManaged<EntitiesGraphicsSystem>();
    6. var meshID = hybridRenderer.RegisterMesh(earthMesh);
    7. var materialID = hybridRenderer.RegisterMaterial(earthTextureMaterial);
    8. var materialMeshInfo = new MaterialMeshInfo { MaterialID = materialID, MeshID = meshID };
    9. RenderMeshUtility.AddComponents(spawned, _entityManager, renderMeshDescription, renderMeshArray, materialMeshInfo);
     

    Attached Files:

    Last edited: Oct 6, 2022
    tmonestudio likes this.
  4. JussiKnuuttila

    JussiKnuuttila

    Unity Technologies

    Joined:
    Jun 7, 2019
    Posts:
    351
    You should not destroy the old mesh, since there's a reference to it in RenderMeshArray. Entities Graphics automatically registers (and unregisters on unload) all Meshes and Materials in all RenderMeshArrays.

    The attached crash log looks like it's crashing when the BatchRendererGroup that Entities Graphics uses is being destroyed. Probably what happens is that:
    • Upon destruction, there is a mesh that has not been unregistered.
    • BatchRendererGroup attempts to cleanup any unregistered meshes.
    • The cleanup crashes, because the mesh has already been destroyed, and the memory is not valid.
    I would suggest just leaving the old Mesh as is in the RenderMeshArray (i.e. don't unregister or destroy it), and put the IDs for the new Mesh and Material in the MaterialMeshInfo. It looks like this is exactly what your alternative way is doing, and I think that should work.

    In this kind of use case, it's not required to use RenderMeshArray at all (just use only MaterialMeshInfo), but the RenderMeshUtility is currently lacking a suitable AddComponents overload for that, so that needs to be done manually if you want to do that with the experimental release.
     
    heu3becteh and Fribur like this.
  5. Fribur

    Fribur

    Joined:
    Jan 5, 2019
    Posts:
    136
    Awesome: thanks so much for your insights! My only BIG concern now are memory leaks when I am not permitted anymore to cleanup procedurally generated meshes that are not needed anymore: in my project I am generating thousands of them, and I only introduced this mesh destruction because the memory usage was skyrocketing into double digit GB with minutes and it looked to me like Unity is not cleaning those useless meshes up as long as there is still reference to them somewhere? So from that angle I would find it concerning that BRG maintains references to those zombie meshes.

    EDIT: found a way to destroy the old mesh. The trick is to call renderMeshArray.ResetHash128() after replacing the mesh. Here the complete code I am using now to replace a mesh at (any) index of a given SharedRenderMeshComponent. A bit convoluted but works. Open to any suggestions how to improve this. (main goal is to prevent unused procedural generated meshes to clog up the memory)
    Code (CSharp):
    1. protected override void OnCreate()
    2. {
    3.     [...]
    4.     hybridRenderer = World.GetExistingSystemManaged<EntitiesGraphicsSystem>();  
    5.     [...]
    6. }
    7.  
    8. protected override void OnUpdate()
    9. {
    10.     [...]
    11.     Debug.Log($"Setting new Mesh");
    12.     var newMesh = SpawnHelper.GetMesh(m_vertices, m_indices, m_bounds[0], MeshLayouts.TextureLayout);
    13.  
    14.     //update MaterialMeshInfo
    15.     var materialMeshInfo = entityManager.GetComponentData<MaterialMeshInfo>(entity);
    16.     hybridRenderer.UnregisterMesh(materialMeshInfo.MeshID);
    17.     materialMeshInfo.MeshID = hybridRenderer.RegisterMesh(newMesh);
    18.     entityManager.SetComponentData(entity, materialMeshInfo);
    19.  
    20.     //update RenderMeshArray
    21.     var renderMeshArray = entityManager.GetSharedComponentManaged<RenderMeshArray>(entity);
    22.     var oldMesh = renderMeshArray.Meshes[0];
    23.     renderMeshArray.Meshes[0] = newMesh;
    24.     renderMeshArray.ResetHash128();
    25.     entityManager.SetSharedComponentManaged(entity, renderMeshArray);
    26.     GameObject.Destroy(oldMesh);
    27.     [...]
    28. }
     
    Last edited: Oct 6, 2022
  6. JussiKnuuttila

    JussiKnuuttila

    Unity Technologies

    Joined:
    Jun 7, 2019
    Posts:
    351
    I would suggest that you don't place any procedural mesh that you are planning to destroy into a RenderMeshArray at any point.

    Instead, you could use a trivial mesh like a cube mesh in the RenderMeshArray as a cheap placeholder, and then manually Register your procedural meshes and just put their IDs directly into MaterialMeshInfo. After you are done with those, it should be safe to Unregister and Destroy them, as EntitiesGraphicsSystem will not automatically do anything with them if they are not put into a RenderMeshArray.

    Any mesh placed inside a RenderMeshArray will be automatically registered by EntitiesGraphicsSystem, and it will keep a reference to it as long as it exists in a RenderMeshArray. Once there are no more RenderMeshArrays with a mesh, it will automatically unregister, but this happens with a small delay as this processing is done once per frame.

    It should work to update the RenderMeshArray like you do (although you might need to wait one frame before calling Destroy to avoid problems), but updating a shared component like that is not cheap, it will do structural changes and move the entity around.
     
    dwulff, heu3becteh and Fribur like this.
  7. Fribur

    Fribur

    Joined:
    Jan 5, 2019
    Posts:
    136
    Got it now, Thanks you for sharing in detail how it works.

    To this end, it would be nice if you could change GetMesh(BatchMeshID mesh) from "internal" to "public" (line 2332 in EntitiesGraphicsSystem.cs). Same with GetMaterial(BatchMaterialID material) in line 2333.

    Using now an ICleanupComponentData to detect destroyed entities that had "MaterialMeshInfo". That ICleanupComponentData contains a copy of the MaterialMeshInfo, making it easy to implement a cleanup after Entity destruction (Unregister and Destroy the mesh). Would not like to implement my own tracking between BatchMeshID and associated Mesh/Material across systems, when BRG offers this already (as a public method), so would be great if EntitiesGraphicsSystem could just keep it public (and maybe not change the method name from GetRegisteredMaterial to GetMaterial?).
     
    Last edited: Oct 7, 2022
    StefanWo likes this.
  8. Fribur

    Fribur

    Joined:
    Jan 5, 2019
    Posts:
    136
    @JussiKnuuttila: in the recent 1.0.0-pre.15 update GetMesh() and GetMaterial() are still private: Are there any concerns making them public? I use this API in a cleanup system to destroy procedural meshes that are not needed anymore after Unregistering them from the EntityGraphicsSystem. Or can I rely on the UntityEngine to destroy those meshes eventually? They have no references to them anymore and should be destroyed, but in my experience they where not and memory usage was exploding.

    Code (CSharp):
    1.  
    2. Entities
    3.     .WithNone<MaterialMeshInfo>()
    4.     .WithAll<AreaColorMeshState>()
    5.     .WithoutBurst()
    6.     .WithStructuralChanges()
    7.     .ForEach((Entity e, in AreaColorMeshState state1) =>
    8. {
    9.     var oldMesh = hybridRenderer.GetMesh(state1.meshID);
    10.     hybridRenderer.UnregisterMesh(state1.meshID);
    11.     GameObject.Destroy(oldMesh);
    12.     _entityManager.RemoveComponent<AreaColorMeshState>(e);
    13. }).Run();
    14.  
     
    heu3becteh, bb8_1 and tmonestudio like this.
  9. JussiKnuuttila

    JussiKnuuttila

    Unity Technologies

    Joined:
    Jun 7, 2019
    Posts:
    351
    We are planning to make the GetMesh/GetMaterial APIs available in a future release.

    Unity will not automatically destroy meshes, so you will need to manually destroy meshes that you have procedurally created.
     
    heu3becteh, bb8_1 and Fribur like this.
  10. kite3h

    kite3h

    Joined:
    Aug 27, 2012
    Posts:
    197
    What should we do if we use Meshes and Materials that are not currently included in the MeshRenderArray when we newly enter a region?

    Would it be better to create a new RenderMeshArray component? Or is it better to RegisterMesh on the RenderMeshArray and remember the starting index to use?

    What is a better choice if using the existing DOTS entities subscene?

    Is it better to loadasync a separate SubScene ?

    Is it impossible to separate RenderMeshArray by Section?
     
  11. kite3h

    kite3h

    Joined:
    Aug 27, 2012
    Posts:
    197
    If non destructive layer geometries are to be made into RenderMeshArray, is it better to prepare a few trival meshes and use them as patches? If so, how are textures managed? Is it okay to use a virtual texture as a displacement?
     
  12. JussiKnuuttila

    JussiKnuuttila

    Unity Technologies

    Joined:
    Jun 7, 2019
    Posts:
    351
    In general, making modifications to shared components is somewhat expensive, as it will always involve structural changes.

    If you know that some of your entities will use procedurally generated meshes to begin with, then likely the highest performance option is to manually RegisterMesh such meshes, and place the IDs directly into MaterialMeshInfo. This way, no modifications to RenderMeshArray will be required.
     
    vildauget likes this.