Search Unity

Question Parallel procedural mesh generation with Job System

Discussion in 'C# Job System' started by Kulkejszyn, Aug 27, 2020.

  1. Kulkejszyn

    Kulkejszyn

    Joined:
    Feb 21, 2019
    Posts:
    6
    I am creating voxel based game.
    The world is composed of chunks and each chunk contains 16x16x16 blocks, for each chunk I am creating mesh.
    I have created a ParrarelJob in which I am writing mesh data for each block that is inside the chunk.

    For each side of block, vertices are readed from persistent NativeArrays in which i have saved informations about vertices, triangles and uvs, then they are written to native list( I use list because i don't know how many vertices will be in mesh) with AddRange()
    MeshVertices.AddRange(vertices);
    . I needed to add the attribute `[NativeDisableParallelForRestriction]` to all lists because it was saying that parallel writing is not supported. In a world created with many chunks unity just crashes. But in case if there is only one chunk unity says:

    In my opinion AddRange is overlapping memory with other workers.<br>
    What can i change to avoid these type of error?
     
    bb8_1 likes this.
  2. runner78

    runner78

    Joined:
    Mar 14, 2015
    Posts:
    792
    In my case, i use the new MeshAPI, and use a multi pass approche, first count the faces for the chunks, then create the MeshaDataArray and at least apply the mesh. I can create over 60 chunks with Meshes per frame with 60 fps (Editor with all safetychecks).
     
    MNNoxMortem likes this.
  3. Kulkejszyn

    Kulkejszyn

    Joined:
    Feb 21, 2019
    Posts:
    6
    Are you using ParrarelFor for this?
     
  4. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,269
    4 techniques which all depend on whether you are ALU-bound, memory-bound, lock-bound, or draw call-bound
    1) Make a Mesh and list for each thread rather than merge everything into one (doesn't work if draw call-bound)
    2) Use NativeList.ParallelWriter (Doesn't work if lock-bound)
    3) Use NativeStream to write to separate streams then combine them with NativeStream.ToNativeArray (doesn't work if memory-bound)
    4) Make two passes, the first pass precomputing the size of the lists and the second populating the data (doesn't work if ALU-bound)
     
  5. runner78

    runner78

    Joined:
    Mar 14, 2015
    Posts:
    792
    I use IJobFor ti geneare the voxel data, for the meshbuilding: Entities.ForEach().Schedule() for counting, and Entities.ForEach().ScheduleParallel() to write the mesh data.
     
    Kulkejszyn and MNNoxMortem like this.
  6. Kulkejszyn

    Kulkejszyn

    Joined:
    Feb 21, 2019
    Posts:
    6
    Could you give more informations about these bounds or provide some source to read?
     
  7. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,269
    They are bottlenecks of your system.
    ALU is the part of a CPU core that executes the logic and arithmetic.
    Memory in this context is your RAM to cache speed.
    Locks are atomic instructions which have the potential to traffic jam the cores of your system if used frequently.
    Draw calls are draw commands sent to the GPU and the count is mostly a concern on mobile.
     
    Nyanpas and Kulkejszyn like this.
  8. RoughSpaghetti3211

    RoughSpaghetti3211

    Joined:
    Aug 11, 2015
    Posts:
    1,708
    does anyone have a good simple example of how they created a procedural mesh with Entities.ForEach().ScheduleParallel() or/and IJobParallelFor. Im not familiar with Unity's mesh API so just a good simple example would be very appreciated. Thanks in advance
     
    Opeth001 likes this.
  9. RecursiveEclipse

    RecursiveEclipse

    Joined:
    Sep 6, 2018
    Posts:
    298
    I've worked with the new api a little, nothing procedural, but I have a few utility functions for examples:

    Code (CSharp):
    1. public static NativeArray<float3> GetVertices(Mesh.MeshData meshData) {
    2.     int vertexCount = meshData.vertexCount;
    3.     var vertices = new NativeArray<float3>(vertexCount, Allocator.TempJob);
    4.     meshData.GetVertices(vertices.Reinterpret<Vector3>());
    5.     return vertices;
    6. }
    7.  
    8. public static NativeArray<float3> GetNormals(Mesh.MeshData meshData) {
    9.     int indexCount = meshData.GetSubMesh(0).indexCount;
    10.     var normals = new NativeArray<float3>(indexCount, Allocator.TempJob);
    11.     meshData.GetNormals(normals.Reinterpret<Vector3>());
    12.     return normals;
    13. }
    14.  
    15. /// <summary>
    16. /// Return indices into a vertices array, each element is a vertex of a triangle.
    17. /// </summary>
    18. public static NativeArray<int> GetIndices(Mesh.MeshData meshData) {
    19.     int indexCount = meshData.GetSubMesh(0).indexCount;
    20.     var triangles = new NativeArray<int>(indexCount, Allocator.TempJob);
    21.     meshData.GetIndices(triangles, submesh:0);
    22.     return triangles;
    23. }
    An example usage of MeshDataArray:

    Code (CSharp):
    1. Mesh.MeshDataArray meshDataArray = Mesh.AcquireReadOnlyMeshData(mesh);
    2. Mesh.MeshData meshData = meshDataArray[0];
    3. // MeshUtility here is mine, not unity's
    4. NativeArray<float3> vertices = MeshUtility.GetVertices(meshData);
    5. NativeArray<int> indices = MeshUtility.GetIndices(meshData);
     
    Last edited: Sep 3, 2020
    icauroboros and bb8_1 like this.
  10. RoughSpaghetti3211

    RoughSpaghetti3211

    Joined:
    Aug 11, 2015
    Posts:
    1,708
    Thanks for your reply. So still not 100% on what the best approach is here. Here is where im currently at:

    Create 2 structs

    Code (CSharp):
    1.     public struct PlanetVertexData
    2.     {
    3.         public float3 Position;
    4.         public float3 Normal;
    5.         public float4 Tangent;
    6.         public float4 Color;
    7.         public float2 UVs;
    8.         public float2 UvBarycentric;
    9.     }
    10.  
    11.     public struct PlanetVertexIndexData
    12.     {
    13.         private int _value;
    14.  
    15.         public static implicit operator PlanetVertexIndexData(int v)
    16.         {
    17.             return new PlanetVertexIndexData {_value = v};
    18.         }
    19.  
    20.         public static implicit operator int(PlanetVertexIndexData v)
    21.         {
    22.             return v._value;
    23.         }
    24.     }
    Then jam those into a native array


    Code (CSharp):
    1.                        
    2. var vertexDataArray = new NativeArray<PlanetVertexData>(triangleBuildArray.Length * 3, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
    3. var vertexIndexDataArray = new NativeArray<PlanetVertexIndexData>(triangleBuildArray.Length * 3, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
    4.  
    fill them in a JobParallelFor

    Code (CSharp):
    1.  
    2. FactoryJob = new PlanetGraphUtils.GetTriangleMeshBuildData
    3. {
    4.     GraphInput = g.PlanetGraph,
    5.     Input = triangleBuildArray,
    6.     MapInput = vertexColorMap,
    7.     OutputVertexData = vertexDataArray,
    8.     OutputVertexIndexData = vertexIndexDataArray
    9. }
    10.  
    and then set the mesh buffers on the main thread :(

    Code (CSharp):
    1.  
    2. _markerSetMesh_1.Begin();
    3. // TODO : need to update to 2020.1 new mesh API. This is a major point of GC allocation.
    4. var vertexAttrDescriptor = new[]
    5. {
    6.     new VertexAttributeDescriptor(VertexAttribute.Position),
    7.     new VertexAttributeDescriptor(VertexAttribute.Normal),
    8.     new VertexAttributeDescriptor(VertexAttribute.Tangent, VertexAttributeFormat.Float32, 4),
    9.     new VertexAttributeDescriptor(VertexAttribute.Color, VertexAttributeFormat.Float32, 4),
    10.     new VertexAttributeDescriptor(VertexAttribute.TexCoord0, VertexAttributeFormat.Float32, 2),
    11.     new VertexAttributeDescriptor(VertexAttribute.TexCoord7, VertexAttributeFormat.Float32, 2)
    12. };
    13. _markerSetMesh_1.End();
    14.  
    15. _markerSetMesh_2.Begin();
    16. m.mesh.SetVertexBufferParams(vertexDataArray.Length, vertexAttrDescriptor);
    17. m.mesh.SetVertexBufferData(vertexDataArray, 0, 0, vertexDataArray.Length);
    18. m.mesh.SetIndexBufferParams(vertexIndexDataArray.Length, IndexFormat.UInt32);
    19. m.mesh.SetIndexBufferData(vertexIndexDataArray, 0, 0, vertexIndexDataArray.Length);
    20. m.mesh.subMeshCount = 1;
    21. _markerSetMesh_2.End();
    22.  
    23. _markerSetMesh_3.Begin();
    24. var desc = new SubMeshDescriptor
    25. {
    26.     baseVertex = 0,
    27.     bounds = default,
    28.     indexCount = vertexDataArray.Length,
    29.     indexStart = 0,
    30.     topology = MeshTopology.Triangles
    31. };
    32. _markerSetMesh_3.End();
    33.  
    34. _markerSetMesh_3.Begin();
    35. m.mesh.SetSubMesh(0, desc);
    36. m.mesh.RecalculateBounds();
    37. EntityManager.SetComponentData(e, new RenderBounds
    38. {
    39.     Value = new AABB
    40.     {
    41.         Center = new float3(m.mesh.bounds.center.x, m.mesh.bounds.center.y, m.mesh.bounds.center.z),
    42.         Extents = new float3(m.mesh.bounds.extents.x, m.mesh.bounds.extents.y, m.mesh.bounds.extents.z)
    43.     }
    44. });
    45. _markerSetMesh_3.End();
    46.  
    47.  
    So what would be the best to approach is here. This mesh build needs to be able to build per from or at least over 4 frames so I need speed. Looking at the gitHub examples I seem way off track but im also doing something much different. Im at a point where I need some pro guidance
     
  11. RoughSpaghetti3211

    RoughSpaghetti3211

    Joined:
    Aug 11, 2015
    Posts:
    1,708
  12. RecursiveEclipse

    RecursiveEclipse

    Joined:
    Sep 6, 2018
    Posts:
    298
    MeshDataArray contains a MeshData for each mesh you give Mesh.AcquireReadOnlyMeshData(or writable I think). From MeshData you can get all the arrays in a mesh but as NativeArrays. MeshDataArray need to be disposed, but there is not yet an overload that takes a JobHandle.

    You might get more help from someone in https://forum.unity.com/forums/graphics-experimental-previews.110/ where the thread for the new api is.
     
  13. RoughSpaghetti3211

    RoughSpaghetti3211

    Joined:
    Aug 11, 2015
    Posts:
    1,708
    yea i dont know, pulling my hair out here.

    ArgumentException: Index buffer element #0 (value 4294967295) is out of bounds; mesh only has 183783 vertices.

    Code (CSharp):
    1.                        
    2.                         _markerSetMesh_1.Begin();
    3.                         var vertexAttrDescriptor = new[]
    4.                         {
    5.                             new VertexAttributeDescriptor(VertexAttribute.Position),
    6.                             new VertexAttributeDescriptor(VertexAttribute.Normal),
    7.                             new VertexAttributeDescriptor(VertexAttribute.Tangent, VertexAttributeFormat.Float32, 4),
    8.                             new VertexAttributeDescriptor(VertexAttribute.Color, VertexAttributeFormat.Float32, 4),
    9.                             new VertexAttributeDescriptor(VertexAttribute.TexCoord0, VertexAttributeFormat.Float32, 2),
    10.                             new VertexAttributeDescriptor(VertexAttribute.TexCoord7, VertexAttributeFormat.Float32, 2)
    11.                         };
    12.                         _markerSetMesh_1.End();
    13.  
    14.  
    15.                         _markerSetMesh_2.Begin();
    16.                         var outputMeshData = Mesh.AllocateWritableMeshData(1);
    17.                         Mesh.MeshData outputMesh;
    18.                         outputMesh = outputMeshData[0];
    19.                         outputMesh.SetVertexBufferParams(vertexDataArray.Length, vertexAttrDescriptor);
    20.                         outputMesh.SetIndexBufferParams(vertexIndexDataArray.Length, IndexFormat.UInt32);
    21.                         outputMesh.subMeshCount = 1;
    22.                         _markerSetMesh_2.End();
    23.                      
    24.                         _markerSetMesh_3.Begin();
    25.                         var bounds = new float3x2(new float3(Mathf.Infinity), new float3(Mathf.NegativeInfinity));
    26.                         var sm = new SubMeshDescriptor
    27.                         {
    28.                             baseVertex = 0,
    29.                             bounds = new Bounds((bounds.c0+bounds.c1)*0.5f, bounds.c1-bounds.c0),
    30.                             indexCount = vertexIndexDataArray.Length,
    31.                             indexStart = 0,
    32.                             topology = MeshTopology.Triangles,
    33.                             vertexCount = vertexDataArray.Length
    34.                         };
    35.                         _markerSetMesh_3.End();
    36.                      
    37.                         outputMesh.SetSubMesh(0, sm /*, MeshUpdateFlags.DontRecalculateBounds | MeshUpdateFlags.DontValidateIndices | MeshUpdateFlags.DontNotifyMeshUsers*/);
    38.                         Mesh.ApplyAndDisposeWritableMeshData(outputMeshData,new[]{m.mesh}, MeshUpdateFlags.DontRecalculateBounds | MeshUpdateFlags.DontValidateIndices | MeshUpdateFlags.DontNotifyMeshUsers);
    39.                      
    40.                        
     
  14. RecursiveEclipse

    RecursiveEclipse

    Joined:
    Sep 6, 2018
    Posts:
    298
  15. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    Are you actually setting the vertices and indices? I see this:
    Code (CSharp):
    1. outputMesh.SetVertexBufferParams(vertexDataArray.Length, vertexAttrDescriptor);
    2. outputMesh.SetIndexBufferParams(vertexIndexDataArray.Length, IndexFormat.UInt32);
    But not:
    Code (CSharp):
    1. outputMesh.SetVertexBufferData(vertexDataArray);
    2. outputMesh.SetIndexBufferData(vertexIndexDataArray);
    Regardless, if you can't get it to work with the funky
    AllocateWritableMeshData
    thing, just use
    Mesh
    directly.

    Also, the
    vertexAttrDescriptor
    can be made static instead of allocating it every time you want to set it.
     
  16. RoughSpaghetti3211

    RoughSpaghetti3211

    Joined:
    Aug 11, 2015
    Posts:
    1,708
    So this is what is confusing me , previously before using the AllocateWritableMeshData I set both Parm and Data but Mesh.MeshData doesn't contain a SetVertexBufferData and SetIndexBufferData ?


    Ths was what worked before and what im trying to test for speed using AllocateWritableMeshData

    Code (CSharp):
    1.  
    2. _markerSetMesh_2.Begin();
    3. m.mesh.SetVertexBufferParams(vertexDataArray.Length, vertexAttrDescriptor);
    4. m.mesh.SetVertexBufferData(vertexDataArray, 0, 0, vertexDataArray.Length);
    5. m.mesh.SetIndexBufferParams(vertexIndexDataArray.Length, IndexFormat.UInt32);
    6. m.mesh.SetIndexBufferData(vertexIndexDataArray, 0, 0, vertexIndexDataArray.Length);
    7. m.mesh.subMeshCount = 1;
    8. _markerSetMesh_2.End();
    9. [/ICODE]
     
    Last edited: Sep 6, 2020
  17. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    According to: https://docs.unity3d.com/2020.1/Documentation/ScriptReference/Mesh.MeshData.html , you need to get then copy in. If you know the counts ahead of time, you could pass the mesh data arrays directly into the job, and skip allocating the intermediate arrays. Otherwise, you'd get the NativeArrays and copy the values in, eg:

    Code (CSharp):
    1. outputMesh.GetVertexData<PlanetVertexData>().CopyFrom(vertexDataArray);
    2. outputMesh.GetIndexData<PlanetVertexIndexData>().CopyFrom(vertexIndexDataArray);
    EDIT: if it actually is any faster, post here! I'm curious if it makes a difference.
     
    Last edited: Sep 6, 2020
    MNNoxMortem likes this.
  18. RoughSpaghetti3211

    RoughSpaghetti3211

    Joined:
    Aug 11, 2015
    Posts:
    1,708
    So far it is not faster, ill do some cleanup to make sure it a better comparison.
    I also want to test passing the mesh data arrays directly into the job but I don't understand how this is supposed to work? Will that avoid the .CopyFrom()? Is GetVertexData suppose to return a pointer or something . this API is very confusing to me

    The only real mesh API Iev used is mfMesh in maya
     
  19. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    Yes, GetVertexData returns a pointer (and so will avoid
    CopyFrom
    ). So if you know the number of vertices and indices ahead of time, you could do like:

    Code (CSharp):
    1.  
    2. outputMesh.SetVertexBufferParams(3000, vertexAttrDescriptor);
    3. outputMesh.SetIndexBufferParams(5000, IndexFormat.UInt32);
    4. FactoryJob = new PlanetGraphUtils.GetTriangleMeshBuildData
    5. {
    6.     GraphInput = g.PlanetGraph,
    7.     Input = triangleBuildArray,
    8.     MapInput = vertexColorMap,
    9.     OutputVertexData = outputMesh.GetVertexData<PlanetVertexData>,
    10.     OutputVertexIndexData = outputMesh.GetVertexData<uint>,
    11. }
    12. // schedule job and wait for it to complete
    13. Mesh.ApplyAndDisposeWritableMeshData(outputMeshData,new[]{m.mesh},
    14.   MeshUpdateFlags.DontRecalculateBounds | MeshUpdateFlags.DontValidateIndices);
    EDIT: I agree the MeshData API is quite confusing (although Mesh is also pretty confusing I guess with all the states)
     
    MNNoxMortem likes this.
  20. RoughSpaghetti3211

    RoughSpaghetti3211

    Joined:
    Aug 11, 2015
    Posts:
    1,708
    Ok its getting late but so far passing directly is the fastest by far, In this test I build a hex tile planet per frame ( camera is orbiting and building faces facing camera) at around 19ms. Is a good start to where I was especially considering nothing is caches and all color, 2 UV projections, position tangents and normal are calculated per frame. Thank you so much for the help I really appreciate it

    Screen Shot 2020-09-06 at 12.29.30 AM.png
     
  21. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    Glad I could help. So the flavors are coconut, melon, lychee, banana, watermelon, lemon, broccoli, lime, licorice, and blue raspberry?
    Do you mean passing the NativeArrays into the job, or just setting them on the mesh?
     
  22. RoughSpaghetti3211

    RoughSpaghetti3211

    Joined:
    Aug 11, 2015
    Posts:
    1,708
    Yes, it looks very delicious lol

    So in my cast this was the slowest
    1. outputMesh.GetVertexData<PlanetVertexData>().CopyFrom(vertexDataArray);
    2. outputMesh.GetIndexData<PlanetVertexIndexData>().CopyFrom(vertexIndexDataArray);

    Then setting the mesh directory was faster
    1. m.mesh.SetVertexBufferParams(vertexDataArray.Length, vertexAttrDescriptor);
    2. m.mesh.SetVertexBufferData(vertexDataArray, 0, 0, vertexDataArray.Length);
    3. m.mesh.SetIndexBufferParams(vertexIndexDataArray.Length, IndexFormat.UInt32);
    4. m.mesh.SetIndexBufferData(vertexIndexDataArray, 0, 0, vertexIndexDataArray.Length);
    and passing this the mesh data directy to a job and using ApplyAndDisposeWritableMeshData was the fasest.

    One issue I ran into is I can't seem to pass both OutputVertexIndexData & OutputVertexData to the same job.

    InvalidOperationException: The writeable UNKNOWN_OBJECT_TYPE JobParallelForFactoryGraph`2.FactoryJob.OutputVertexData is the same UNKNOWN_OBJECT_TYPE as JobParallelForFactoryGraph`2.FactoryJob.OutputVertexIndexData, two containers may not be the same (aliasing).

    Code (CSharp):
    1.  
    2. FactoryJob = new PlanetGraphUtils.GetTriangleMeshBuildDataTest1
    3. {
    4.     Input = triangleBuildArray,
    5.     OutputVertexIndexData = outputMesh.GetIndexData<uint>(),
    6.     OutputVertexData = outputMesh.GetVertexData<PlanetVertexData>()
    7. }
    8.  

    Splitting this into 2 jobs worked but now trying to combining the dependencies is a bit of a headache
     
    bb8_1 likes this.
  23. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    You can try applying
    [NoAlias]
    https://docs.unity3d.com/Packages/com.unity.burst@latest/api/Unity.Burst.NoAliasAttribute.html to the fields. If that doesn't work, you could also try:

    Code (CSharp):
    1. public static unsafe NativeArray<T> swapSafetyHandle(ref NativeArray<T> array1)
    2. {
    3.     void* ptr = NativeArrayUnsafeUtility.GetUnsafePtr(array1);
    4.     NativeArray<T> array2 = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<T>(ptr, array1.Length, Allocator.None);
    5.     #if ENABLE_UNITY_COLLECTIONS_CHECKS
    6.         // this is the part i don't fully understand so just try s**t until it works
    7.         AtomicSafetyHandle handle = AtomicSafetyHandle.Create();
    8.         NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref array2, handle);
    9.     #endif
    10.     return array2;
    11. }
    (sorry can't test these; unity is not installed on this PC). Those arrays would also need to be disposed, but disposing them won't affect the underlying data at all since they're just pointers to it.
     
  24. RoughSpaghetti3211

    RoughSpaghetti3211

    Joined:
    Aug 11, 2015
    Posts:
    1,708
    I could not get it to work, I might put a pin in this one a comeback to it when I grow smarter. I will try to pick the low hanging fruit first. But I have to say I feel good about the progress so far on my old 2016 mac book, never going to need this many tiles but fun to test

     
    Antypodish likes this.
  25. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,778
    @francois85
    Are your tiles individual entities?
    It feels kind of slow generation.
    I ma just curious, as I am also working on hex tile generation sphere.
     
  26. RoughSpaghetti3211

    RoughSpaghetti3211

    Joined:
    Aug 11, 2015
    Posts:
    1,708
    no, its one mesh rebuild per frame ~200000 tiles as a test. a quick profile shows that the actual mesh part is around 7ms. and the other 7m is calculating the color base on temperature and precipitation. Looks like the building and looking up the native hashmap is my bottleneck behind RenderPiplenManager.DoRenderLoop_internals() from hdrp

    O and the purple wire is just OnDrawGizmo() a quick debug which is also killing performance

    Can I ask what number are you getting, It would be good to know what is possible.
     
    Last edited: Sep 8, 2020
  27. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,778
    I haven't done stress test yet, nor really timed it. But me, I instead build around 10k individual entities tiles in a frame. However, I build it once.
    Sorry for not being more specific. When I get to the point that I am ready with it, I may post some info.
     
    RoughSpaghetti3211 likes this.
  28. RoughSpaghetti3211

    RoughSpaghetti3211

    Joined:
    Aug 11, 2015
    Posts:
    1,708
    In my case, I found that keeping tiles as entity was better than IBuffer array, but still keeping the mesh as one. Now I don't know what will bit me in the long run but for now, this gives me what I need
     
  29. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,778
    I think keeping tiles as entities is good approach. You can have more control over it and even easier to filter groups of tiles.

    Similar could apply for voxels, or chunks, as per OP.
     
  30. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,778
    @francois85, @burningmime
    hi guys.

    I am actually highly glad this thread exists.
    I keep working on my hex planet, looking into different approaches of rendering.
    Now I am testing planet with over +160k 3D tiles. I use in latest approach tiles meshes, combined into bigger meshes.

    upload_2020-10-3_3-14-12.png

    I found, that setting vertices and colors directly to mesh, cause quite GC.
    So I looked into SetVertexBufferData approach, discussed above.

    I managed to render meshes (smaller planet example below). Only that, I lost info about UV and vertexColors.

    upload_2020-10-3_3-10-52.png

    And this what I should see, using discussed approach

    upload_2020-10-3_4-2-20.png

    above example uses

    Code (CSharp):
    1. var layout = new[]
    2.             {
    3.                 new VertexAttributeDescriptor ( VertexAttribute.Position, VertexAttributeFormat.Float32, 3 ),
    4.             };
    Code (CSharp):
    1.  
    2. meshBatchData.mesh.SetVertexBufferParams ( meshBatchData.na_vertices.Length, layout ) ; // new VertexAttributeDescriptor () { attribute = VertexAttribute.Position } ) ;
    3.                 meshBatchData.mesh.SetVertexBufferData ( meshBatchData.na_vertices, 0, 0, meshBatchData.na_vertices.Length ) ;
    4.  
    And then I use
    Code (CSharp):
    1. mesh.SetColors ( vertexColors ) ;

    But whatever I am trying else, fails me.
    I tried modify and put color and texture0 arrays in following. In different combinations.

    Code (CSharp):
    1. var layout = new[]
    2. {
    3.     new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3),
    4.     new VertexAttributeDescriptor(VertexAttribute.Normal, VertexAttributeFormat.Float16, 2),
    5.     new VertexAttributeDescriptor(VertexAttribute.Tangent, VertexAttributeFormat.UNorm8, 4),
    6. };
    But seems I am doing something wrong.
    I printed out attributes and I set them as follow:

    Code (CSharp):
    1. new VertexAttributeDescriptor ( VertexAttribute.Position, VertexAttributeFormat.Float32, 3 ),
    2.                 new VertexAttributeDescriptor ( VertexAttribute.Normal, VertexAttributeFormat.Float32, 3 ),
    3.                 new VertexAttributeDescriptor ( VertexAttribute.Color, VertexAttributeFormat.Float32, 4 ),
    4.                 new VertexAttributeDescriptor ( VertexAttribute.TexCoord0, VertexAttributeFormat.Float32, 2 )
    Any suggestions, or tips for UV / colors setup?

    My results are rather bizarre (part of the mesh is inside larger hell of hex tiles. :confused:

    upload_2020-10-3_4-19-2.png

    Ideally, I just want update vertex colors and vertices.
    Equivalent to mesh.SetColors and mesh.SetVertices, without loosing other properties of the mesh.
     

    Attached Files:

    Last edited: Oct 3, 2020
    Egad_McDad and bb8_1 like this.
  31. RoughSpaghetti3211

    RoughSpaghetti3211

    Joined:
    Aug 11, 2015
    Posts:
    1,708
    When I get home I can post that snippet of my code ( probably not the best way of doing it). I have no GC but I’m setting all the streams every time.
     
    Antypodish likes this.
  32. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,778
    @francois85 no probs.
    In meantime I did small change, which resulted quite a difference.

    I removed ToArray () and Reinterpret () bits
    Code (CSharp):
    1. meshBatchData.mesh.SetVertices ( meshBatchData.na_vertices.Reinterpret <Vector3> ().ToArray () ) ;
    2. meshBatchData.mesh.SetColors ( meshBatchData.na_vertexColors.ToArray () ) ;
    Ending with
    Code (CSharp):
    1.  
    2. meshBatchData.mesh.SetVertices ( meshBatchData.na_vertices ) ;
    3. meshBatchData.mesh.SetColors ( meshBatchData.na_vertexColors ) ;  
    As the result, no GC anymore. This lead to cut out time by over 60%.
    I din't know, that meshBatchData.mesh.Set can accept NativeArray <float3>.
    But I need admit, this was derived from earlier tries of my code.
    Code (CSharp):
    1. meshBatchData.mesh.vertices = meshBatchData.na_vertices.Reinterpret <Vector3> ().ToArray () ;
    At the current, vertices manipulation jobs in my case, takes 73ms for hex planet of 163k tiles (stress test).
    Color changes takes around 40 ms.

    I just quickly profiled and checked, other thing.
    If I use mesh.SetVertexBufferParam and mesh.SetVertexBufferData, (however they don't work correctly for me atm.), that could potentially reduce times by another 50%. From 73 ms to around 35ms.:eek:

    This is potentially further massive improvement factor.
     
    Last edited: Oct 3, 2020
    bb8_1 likes this.
  33. RoughSpaghetti3211

    RoughSpaghetti3211

    Joined:
    Aug 11, 2015
    Posts:
    1,708
    Not sure if this is helpful but here is what I have


    Code (CSharp):
    1.  
    2. /// <summary>
    3. ///     Mesh vertex data container eg Tiles
    4. /// </summary>
    5. public struct PlanetVertexData
    6. {
    7.     public float3 Position;
    8.     public float3 Normal;
    9.     public float4 Tangent;
    10.     public float4 Color;
    11.     public float2 UVs;
    12.     public float2 UvBarycentric;
    13. }
    14.  
    15. /// <summary>
    16. ///     Mesh vertex index data container eg Triangles
    17. /// </summary>
    18. public struct PlanetVertexIndexData
    19. {
    20.     private int _value;
    21.  
    22.     public static implicit operator PlanetVertexIndexData(int v)
    23.     {
    24.         return new PlanetVertexIndexData {_value = v};
    25.     }
    26.  
    27.     public static implicit operator int(PlanetVertexIndexData v)
    28.     {
    29.         return v._value;
    30.     }
    31. }
    32.  
    33. struct PlanetVertexAttributeDescriptorData
    34. {
    35.     public static readonly VertexAttributeDescriptor[] vertexAttrDescriptor =
    36.     {
    37.         new VertexAttributeDescriptor(VertexAttribute.Position),
    38.         new VertexAttributeDescriptor(VertexAttribute.Normal),
    39.         new VertexAttributeDescriptor(VertexAttribute.Tangent, VertexAttributeFormat.Float32, 4),
    40.         new VertexAttributeDescriptor(VertexAttribute.Color, VertexAttributeFormat.Float32, 4),
    41.         new VertexAttributeDescriptor(VertexAttribute.TexCoord0, VertexAttributeFormat.Float32, 2),
    42.         new VertexAttributeDescriptor(VertexAttribute.TexCoord7, VertexAttributeFormat.Float32, 2)
    43.     };
    44.  
    45. }
    46.  
    Code (CSharp):
    1.  
    2. var vertexDataArray = new NativeArray<PlanetVertexData>(triangleBuildArray.Length * 3, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
    3. var vertexIndexDataArray = new NativeArray<PlanetVertexIndexData>(triangleBuildArray.Length * 3, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
    4.  
    5. _markerSetMeshAllocateWritableMeshData.Begin();
    6. Mesh.MeshDataArray planetMeshData = Mesh.AllocateWritableMeshData(1); // TODO : Only do this once
    7. Mesh.MeshData planetMesh = planetMeshData[0];
    8. _markerSetMeshAllocateWritableMeshData.End();
    9.  
    10. _markerSetVertexBuffer.Begin();
    11. planetMesh.SetVertexBufferParams(vertexDataArray.Length, PlanetVertexAttributeDescriptorData.vertexAttrDescriptor);
    12. planetMesh.SetIndexBufferParams(vertexIndexDataArray.Length, IndexFormat.UInt32);
    13. _markerSetVertexBuffer.End();
    14.  
    15. // Get  vertexDataArray & vertexIndexDataArray in Job
    16. // ..
    17. // ..
    18.  
    19.  
    20. _markerSetMesDescriptor.Begin();
    21. var sm = new SubMeshDescriptor
    22. {
    23.     baseVertex = 0,
    24.     bounds = default,
    25.     indexCount = vertexIndexDataArray.Length,
    26.     indexStart = 0,
    27.     topology = MeshTopology.Triangles,
    28.     vertexCount = vertexDataArray.Length
    29. };
    30. _markerSetMesDescriptor.End();
    31.  
    32. _markerSetSubMesh.Begin();
    33. planetMesh.subMeshCount = 1;
    34. planetMesh.SetSubMesh(0, sm, MeshUpdateFlags.DontRecalculateBounds | MeshUpdateFlags.DontValidateIndices | MeshUpdateFlags.DontNotifyMeshUsers);
    35. _markerSetSubMesh.End();
    36.  
    37. _markerApplyAndDisposeWritableMeshData.Begin();
    38. Mesh.ApplyAndDisposeWritableMeshData(planetMeshData, m.mesh, MeshUpdateFlags.DontRecalculateBounds | MeshUpdateFlags.DontValidateIndices);
    39. _markerApplyAndDisposeWritableMeshData.End();
    40.  
    41. _markerSetComponentRenderBounds.Begin();
    42. EntityManager.SetComponentData(e, new RenderBounds
    43. {
    44.     Value = new AABB
    45.     {
    46.         Center = new float3(m.mesh.bounds.center.x, m.mesh.bounds.center.y, m.mesh.bounds.center.z),
    47.         Extents = new float3(m.mesh.bounds.extents.x, m.mesh.bounds.extents.y, m.mesh.bounds.extents.z)
    48.     }
    49. });
    50. _markerSetComponentRenderBounds.End();
    51.  
    52.  
    Sorry it a bit messy but im happy with the performance compared t where I was, I think Im at around 200k tiles at ~14ms
     
    Last edited: Oct 4, 2020
    Egad_McDad and Antypodish like this.
  34. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,778
    I am looking into it now, many thx.
    I understand, you use
    Code (CSharp):
    1. SetVertexBufferData ( vertices, 0, 0, vertices.Length ) ;
    somewhere, as you wrote earlier
     
  35. RoughSpaghetti3211

    RoughSpaghetti3211

    Joined:
    Aug 11, 2015
    Posts:
    1,708
    No I think you don’t need it, I use Mesh.AllocateWritableMeshData. I’m very new to the unity mesh API so this could all be wrong
     
  36. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    Maybe you have the wrong vertex order or size? Can you post your vertex struct (the thing that looks like:
    Code (CSharp):
    1. public struct PlanetVertexData
    2. {
    3.     public float3 Position;
    4.     public float3 Normal;
    5.     public float4 Tangent;
    6.     public float4 Color;
    7.     public float2 UVs;
    8.     public float2 UvBarycentric;
    9. }

    And also your definition (thing that looks like:
    Code (CSharp):
    1. public static readonly VertexAttributeDescriptor[] vertexAttrDescriptor =
    2. {
    3.     new VertexAttributeDescriptor(VertexAttribute.Position),
    4.     new VertexAttributeDescriptor(VertexAttribute.Normal),
    5.     new VertexAttributeDescriptor(VertexAttribute.Tangent, VertexAttributeFormat.Float32, 4),
    6.     new VertexAttributeDescriptor(VertexAttribute.Color, VertexAttributeFormat.Float32, 4),
    7.     new VertexAttributeDescriptor(VertexAttribute.TexCoord0, VertexAttributeFormat.Float32, 2),
    8.     new VertexAttributeDescriptor(VertexAttribute.TexCoord7, VertexAttributeFormat.Float32, 2)
    9. };
    To see if they match?
     
    Antypodish likes this.
  37. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,778
    @burningmime yeah that occurred to me last night. But I was too tired to try this.
    So I will give it try another go.

    Would I be correct, if I assume that I don't need tangent (that for bump map?) and text coord7 (that is uv7?).
     

    Attached Files:

  38. RoughSpaghetti3211

    RoughSpaghetti3211

    Joined:
    Aug 11, 2015
    Posts:
    1,708
    I had a ton of issues matching the two, that’s why I set all the streams all at once just to get it working. All the orders seemed very particular to me.
     
    Antypodish likes this.
  39. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,778
    @francois85 I will keep that in mind thx. So I will try use all of them.
     
  40. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    If you post 'em here, I'll take a look.

    The order *shouldn't* matter, but who knows what Unity is doing. I do know that if you use RealculateTangents (and presumably normals and bounds), Unity expects the vertex to be in a (AFAICT, undocumented) layout.
     
    Antypodish likes this.
  41. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,778
    Thx. I shall, when I come back to that part. I am refactoring other part of the code. So once I got that working, I will definitely look back. One thing I know I did wrong, is incorrect triangles indices assignment.
    I need to comply accordingly, with this part of the code.
    Code (CSharp):
    1. planetMesh.SetIndexBufferParams(vertexIndexDataArray.Length, IndexFormat.UInt32);
    However, I dispose them at mesh generation. But I need to bring them to my mesh updater system.
    I will do that in next few days, then hopefully I will post some results.
     
  42. tteneder

    tteneder

    Unity Technologies

    Joined:
    Feb 22, 2011
    Posts:
    175
    fyi: I found this thread because stumbled over this exception as well:
    Code (csharp):
    1. InvalidOperationException: The writeable UNKNOWN_OBJECT_TYPE ...
    In my case I tried to access a NativeArray I accidentally already disposed, so you might want to check for that. I can imagine this could also show up if you didn't properly allocate the NativeArray.

    hth
     
  43. jasons-novaleaf

    jasons-novaleaf

    Joined:
    Sep 13, 2012
    Posts:
    181
    Riding on this thread's resurrection, to any readers:

    Do you have any links you could share to a newbie looking to get into DOTS based procedural mesh generation? The generating and efficient batched rendering of meshes is I think the first step I need to take.

    Also I was reading a bit on SharedComponents, so like they could share a bounding box. Any ideas on how to use that for culling/querying those entities? I bet it's not so hard to find out, but still, any pointers on where to start would be awesome.
     
  44. Nyanpas

    Nyanpas

    Joined:
    Dec 29, 2016
    Posts:
    406
    This is a huge struct. How will it not incur memcpy?
     
  45. DragonCoder

    DragonCoder

    Joined:
    Jul 3, 2015
    Posts:
    1,698
    Trying to edit a fairly small mesh every frame (or every fixed update at least), I wonder why is there only a method:
    ApplyAndDisposeWritableMeshData()

    and not
    ApplyWritableMeshData()


    Would it not be even better performance to reuse the data structures (MeshData) and send them to the GPU (assuming that's what the "Apply" part does) every time they have been modified, instead of disposing and realocating?
    Or is there some mechanism in the background that ensures that the disposed memory is rapidly re-allocated without hickups on the next call of AllocateWritableMeshData()?
     
    Last edited: Apr 21, 2022
  46. tteneder

    tteneder

    Unity Technologies

    Joined:
    Feb 22, 2011
    Posts:
    175
    Have a look at the MeshApiExamples example project, in particular the Procedural Wave example. I'd assume this is how it's done properly.
     
    MNNoxMortem likes this.
  47. DragonCoder

    DragonCoder

    Joined:
    Jul 3, 2015
    Posts:
    1,698
    Had seen that and the CPU Variant there does not use the more recent MeshApi at all but the old SetVertices and SetTriangles methods. Cannot use the compute shader variant in my case because I need in some frames more and in others triangles, not only displacing vertices.

    Well, guess I'll implement both (MeshData vs SetTriangles) and see what yields more fps.
     
    tteneder likes this.
  48. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    5,975
    Coming back to the aliasing error regarding vertexes & triangles array from MeshData: does this really mean I have to create TWO IJobParallelFor, one to set the vertexes and another to set the triangle indexes?

    Note that my goal is to create a mesh from scratch, not modify an existing one.

    In my case I get this error:
    Code (CSharp):
    1. InvalidOperationException: The writeable UNKNOWN_OBJECT_TYPE CreateVoxelQuadsJob.vertexes is the same UNKNOWN_OBJECT_TYPE as CreateVoxelQuadsJob.triangles, two containers may not be the same (aliasing).
    Here's the beginning of the job's code:
    Code (CSharp):
    1.     internal struct CreateVoxelQuadsJob : IJobParallelFor
    2.     {
    3.         [ReadOnly] private readonly float3 chunkPosition;
    4.         [ReadOnly] private NativeArray<VoxelInfo> voxelInfos;
    5.         [ReadOnly] private readonly CubeVertexPositions vertexPositions;
    6.         private NativeArray<VoxelMeshData> voxelMeshDatas;
    7.  
    8.         [WriteOnly] [NativeDisableParallelForRestriction] private NativeArray<VoxelChunk.VertexData> vertexes;
    9.         [WriteOnly] [NativeDisableParallelForRestriction] private NativeArray<ushort> triangles;
    10.  
    11.         public CreateVoxelQuadsJob(NativeArray<VoxelInfo> voxelInfos, NativeArray<VoxelMeshData> voxelMeshDatas,
    12.             Mesh.MeshData meshData, float3 chunkPosition, CubeVertexPositions vertexPositions)
    13.         {
    14.             this.chunkPosition = chunkPosition;
    15.             this.voxelInfos = voxelInfos;
    16.             this.voxelMeshDatas = voxelMeshDatas;
    17.             this.vertexes = meshData.GetVertexData<VoxelChunk.VertexData>();
    18.             this.triangles = meshData.GetIndexData<ushort>();
    19.             this.vertexPositions = vertexPositions;
    20.         }
    21.  
    I tried the [NoAlias] and [NativeDisableContainerSafetyRestriction] attributes in all combinations with the above attributes on vertexes and triangles fields to no avail. Interestingly, when safety restrictions are disabled, the parallel for restrictions attribute seems to be ignored since in that case I get the Index out of restricted IJobParallelFor range error.

    I'm hoping there may be a trick of using the combined vertexes & triangles array, and then setting triangles indexes with an offset. Is that possible?
     
  49. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    5,975
    FYI I ended up splitting the job, one for vertices and another for indices. I think this is generally the way to go, one (parallel) job per array rather than trying to stuff everything into a single job. Those smaller Jobs are easier to read, debug, and tend to be more reusable anyway.
     
    laurentlavigne and apkdev like this.
  50. ScottJame

    ScottJame

    Joined:
    Dec 11, 2017
    Posts:
    5