Search Unity

Question Draw Generated Meshes (Custom Hybrid Renderer)

Discussion in 'Graphics for ECS' started by Opeth001, Jul 25, 2020.

  1. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,116
    Hello Everyone,

    I am optimizing my custom hybrid renderer by minimizing 'Draw Calls' combining visible entities Meshes each frame.

    I have no experience with Unity's Mesh class and I need help creating / modifying a mesh on each frame to draw it in an optimized way.


    Generated MeshData:
    Code (CSharp):
    1.    
    2. public struct BlobMeshData
    3.     {
    4.         public BlobArray<float3> Vertices;
    5.         public BlobArray<float3> Normals;
    6.         public BlobArray<int> Triangles;
    7.         public BlobArray<float2> Uvs;
    8.     }
    9.  
    note: all Meshes are referencing the same Material.
    Textures are Packed and Uvs are updated when subscene is closed using the GameObjectConversionSystem workflow.


    My Questions:

    1) is there a way to directly draw a mesh (+ material, layer ...) without having to create / update a new / old mesh? (GC)
    * I have seen the advanced methods of Mesh Class before but I don't understand how it works.

    2) does the limit of 65536 vert... per Mesh still exist?
    * I have seen this limit warning on some random websites but not in the Unity documentation.


    Thank you in Advance!
     
  2. Fribur

    Fribur

    Joined:
    Jan 5, 2019
    Posts:
    136
    Here an old post addressing how to use the advanced Mesh API to generate Meshes from scratch: https://forum.unity.com/posts/5672914/

    Unity has here an example how to get meshes, and combine them all into one (btw the vert limit is configurable via index parameter): https://github.com/Unity-Technologies/MeshApiExamples-2020.1

    Hope the references help you to figure out how to get meshes, change them, store them back. All without GC. I found the most difficult part to figure out the vertex struct format...spend hours on that.
     
    Opeth001 likes this.
  3. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,116
    Thanks @Fribur !
    do you have an idea why with the Mesh Advanced API we dont have to set Triangles ?
     
  4. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,759
    So I believe SetIndices with MeshTopology.Triangles
    https://docs.unity3d.com/ScriptReference/Mesh.SetIndices.html
    https://docs.unity3d.com/ScriptReference/MeshTopology.Triangles.html

    is the same as SetTriangles. It just lets you set other types of mesh types as well.

    When you use the advanced API, you still have your SetIndices method but I'm not 100% certain how you specify topology
     
    Opeth001 likes this.
  5. Fribur

    Fribur

    Joined:
    Jan 5, 2019
    Posts:
    136
    Mesh Topology (line, triangle etc) which determines basically only how the index buffer is interpreted is specified when setting the SubMeshDescriptor (see both links above). I guess SetTriangles is a convenience method that Ara deemed unnecessary when extending the advanced API.

    Here a couple more links that might help:
    https://docs.unity3d.com/ScriptReference/Rendering.SubMeshDescriptor.html

    https://docs.unity3d.com/2020.1/Documentation/ScriptReference/Mesh.SetVertexBufferParams.html

    https://docs.unity3d.com/ScriptReference/Mesh.SetIndexBufferParams.html
     
    Opeth001 likes this.
  6. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    I posted about something similar here with the whole workflow: https://forum.unity.com/threads/wha...eating-mesh-via-meshdata.937625/#post-6127703 .

    Basically, you don't want to have separate arrays for position, UV, normal, etc. Instead you want one array of vertices, which is how the graphics card sees it (IMO, this also leads to cleaner code). So you want to make a struct like this:

    Code (CSharp):
    1.     internal struct BuildingVertex
    2.     {
    3.         public float3 pos;
    4.         public float3 normal;
    5.         public float2 uv;
    6.    
    7.         public static readonly VertexAttributeDescriptor[] LAYOUT =
    8.         {
    9.             new VertexAttributeDescriptor { attribute = VertexAttribute.Position, format = VertexAttributeFormat.Float32, dimension = 3 },
    10.             new VertexAttributeDescriptor { attribute = VertexAttribute.Normal, format = VertexAttributeFormat.Float32, dimension = 3 },
    11.             new VertexAttributeDescriptor { attribute = VertexAttribute.TexCoord0, format = VertexAttributeFormat.Float32, dimension = 2 },
    12.         };
    13.     }
    14.  
    Now, Unity Mesh APIs don't take BlobArrays, but they do take NativeArrays. You can convert them with an ugly hack:

    Code (CSharp):
    1.         public static unsafe NativeArray<T> ToNativeArray<T>(ref this BlobArray<T> ba) where T : struct
    2.         {
    3.             NativeArray<T> arr = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<T>(ba.GetUnsafePtr(), ba.Length, Allocator.Invalid);
    4. #if ENABLE_UNITY_COLLECTIONS_CHECKS
    5.             NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref arr, AtomicSafetyHandle.GetTempUnsafePtrSliceHandle());
    6. #endif
    7.             return arr;
    8.         }
    So once you have a NativeArray<int> of indices, and a NativeArray<MyVertex> of vertices, you call these 5 functions:
    https://docs.unity3d.com/ScriptReference/Mesh.SetVertexBufferParams.html
    https://docs.unity3d.com/ScriptReference/Mesh.SetVertexBufferData.html
    https://docs.unity3d.com/ScriptReference/Mesh.SetIndexBufferParams.html
    https://docs.unity3d.com/ScriptReference/Mesh.SetIndexBufferData.html
    https://docs.unity3d.com/ScriptReference/Mesh.SetSubMesh.html

    (and possibly some others to recalculate things eg if you need tangents for bump mapping).

    Here's a sample I wrote for that other post that should get you started:

    Code (CSharp):
    1. struct MyVertex {
    2.     float3 position;
    3.     float3 normal;
    4.     float2 uv; }
    5.  
    6. static readonly VertexAttributeDescriptor[] vertexLayout =  {
    7.     new VertexAttributeDescriptor { attribute = VertexAttribute.Position, format = VertexAttributeFormat.Float32, dimension = 3 },
    8.     new VertexAttributeDescriptor { attribute = VertexAttribute.Normal, format = VertexAttributeFormat.Float32, dimension = 3 },
    9.     new VertexAttributeDescriptor { attribute = VertexAttribute.TexCoord0, format = VertexAttributeFormat.Float32, dimension = 2 } };
    10.  
    11. struct MyJob : IJob {
    12.     NativeList<MyVertex> vertices;
    13.     NativeList<int> indices;
    14.     void Execute() { /* ... */ } }
    15.  
    16. // Initialization code (run once)
    17. Mesh mesh = new Mesh();
    18. mesh.hideFlags = HideFlags.DontSaveInBuild | HideFlags.DontSaveInEditor;
    19. mesh.MarkDynamic();
    20.  
    21. // Start job (beginning of frame)
    22. MyJob job = new MyJob();
    23. job.vertices = new NativeList<MyVertex>(512, Allocator.TempJob);
    24. job.indices = new NativeList<int>(512, Allocator.TempJob);
    25. JobHandle handle = job.Schedule();
    26.  
    27. // Complete job and upload to GPU (end of frame)
    28. handle.Complete();
    29. mesh.Clear(true);
    30. mesh.SetVertexBufferParams(job.vertices.Length, vertexLayout);
    31. mesh.SetVertexBufferData<MyVertex>(job.vertices, 0, 0, job.vertices.Length, 0, 0);
    32. mesh.SetIndexBufferParams(job.indices.Length, IndexFormat.UInt32 /* or 16, and use ushort instead of int */);
    33. mesh.SetIndexBufferData<int>(job.indices, 0, 0, job.indices.Length, 0);
    34. mesh.subMeshCount = 1;
    35. SubMeshDescriptor smd = new SubMeshDescriptor {
    36.     topology = MeshTopology.Triangles,
    37.     vertexCount = job.vertices.Length,
    38.     indexCount = job.indices.Length };
    39. mesh.SetSubMesh(0, smd, 0);
    40. mesh.UploadMeshData(false);
    41. job.vertices.Dispose();
    42. job.indices.Dispose();
    You probably want to pass MeshUpdateFlags.DontValidateIndices and such to speed it up, and to calculate the bounds in the job. See my code here: https://gitlab.com/burningmime/easybuilding/-/blob/master/Packages/building/src/jobs/MeshBuilder.cs . When I disabled all the CPU-side validation and recalculating bounds, applying the data to the mesh averages at 0.08ms per frame (eg almost nothing at all). There's no GC involved after the initial allocation.

    Also, if you're not going to be changing the number of vertices/indices every frame, you can call SetVertexBufferParams, SetIndexBufferParams and SetSubMesh just one time, and only use SetVertexBufferData and SetIndexBufferData each frame.

    The idea is basically to have it in memory in your code as close to how it's represented on the GPU. If you're curious about what the GPU is doing, this is always a good read (but totally unnecessary, and a little old): https://fgiesen.wordpress.com/2011/07/09/a-trip-through-the-graphics-pipeline-2011-index/
     
    Last edited: Jul 27, 2020
  7. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,116

    Thank you very much i got it :)
    i still have 2 questions:
    1) what is the indices list and how do you calculate it ?
    2) is the limit of 65536 Vertex per Mesh still exist ?

    Thanks again!
     
    MNNoxMortem likes this.
  8. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,759
    Indices is likely your triangles

    The default index buffer is 16bit which is 65535 but you can set it to 32 bit 4billion verts if you want
    https://docs.unity3d.com/ScriptReference/Mesh-indexFormat.html

    GPU support for 32bit varies between platform but these days that's only a concern for really old mobile devices though
     
    MNNoxMortem, RedEarth and Opeth001 like this.
  9. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,116
    thanks!
     
  10. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,116
    can anyone help me to find the issue with my Parallel Mesh Combining Job ?


    the steps im following are:

    1)
    Frustum Culling on all chunks to collect per chunk Matrices and MeshData of all visible Entities.
    * note: in the InitializationSystemGroup i disable all chunks that are too far away to be taken in consideration by frustum culling. (TOP-DOWN Optimization)

    2)
    a Job will collect all included Batches from the Frustum Culling job and set them to NativeArrays for easy access from the MeshCombinerJob:
    EG:
    The job will get an input of [A,A,A,B,B,C,C,A,B] ( chunks Batchs with Combining Compatibility Sorting Index )
    * Compatibility depends on Layers, Receive/Cast Shadows and Material

    Resulting Arrays:
    [4,3,2] : Meshes counts that can be Merged Together.
    [0,1,2,7,3,4,8,5,6] : Ordred Indices by Mergeable Groups

    let's pretend that every chunk batch contains a 10 vertices and 15 Tris.
    Verts: [0, 10, 20, 0, 10, 0, 10, 30, 20 ] :
    Tris: [0, 15, 30, 0, 15, 0, 15, 45, 30 ] :
    the arrays above are the key to parallelize the combining process, so the IJobParallelFor Index can start writing the Resulting Mesh without calculating all previous Indices.

    the Issue:
    when setting the SetIndexBufferData i get an error:
    Code (CSharp):
    1. ArgumentException: Index buffer element #94176 (value 38976) is out of bounds; mesh only has 38976 vertices
    At first I thought the job might be linked to bad indexes when combining meshes, but after debugging each index and its start / end offset. I have found that the parallel job correctly accesses MeshDatas and the resulting mesh indices.

    later I found out that the combining process was miscalculating the triangles and creating values greater than the maximum length of the vertices.


    The Combining Job:


    Code (CSharp):
    1.  [BurstCompile]
    2.         struct CombineMeshesJob : IJobParallelFor
    3.         {
    4.             [ReadOnly] public NativeArray<int> SortedIndices;// [A,A,A,B,B,C,C,A,B] Sorted indices: [0,1,2,7,3,4,8,5,6]
    5.            
    6.             [ReadOnly] public NativeArray<MeshDataPrevousIndex> PreviousMeshDataIndicesCount; //contains all previous Vertices/Triangles Count by Index
    7.            
    8.             [ReadOnly] public NativeArray<MeshData> MeshDatas;
    9.            
    10.             [ReadOnly] public NativeArray<ChunkMatrices> ChunkMatrices; // Contains the Count and World Prositions of all visible entities within a chunk
    11.  
    12.             // this StartIndex is used to identify the initial index that this Job will start in SortedIndices Array
    13.             [ReadOnly] public int StartIndex;
    14.  
    15.             [ReadOnly] public int ResultMeshIndex; // this index is related to SharedIndexCount
    16.  
    17.             [NativeDisableParallelForRestriction]
    18.             [NativeDisableContainerSafetyRestriction]
    19.             public NativeArray<MergedMeshData> CombinedMeshes;
    20.            
    21.  
    22.             public void Execute(int index)
    23.             {
    24.  
    25.  
    26.                 // Relative to MergeableChunksIndex
    27.                 var meshBatchIndex = SortedIndices[index + StartIndex];
    28.  
    29.                 var mergedMeshDataVertices = CombinedMeshes[ResultMeshIndex];
    30.  
    31.  
    32.                 NativeArray<VertexMeshData> finalMeshVertices;
    33.                 NativeArray<int> finalMeshTris;
    34.                 unsafe
    35.                 {
    36.                     finalMeshVertices = NativeUtility.PtrToNativeArray(mergedMeshDataVertices.VertexMeshDataPtr, mergedMeshDataVertices.VertexCount);
    37.                     finalMeshTris = NativeUtility.PtrToNativeArray(mergedMeshDataVertices.TrianglesMeshDataPtr, mergedMeshDataVertices.TrianglesCount);
    38.                 }
    39.                  
    40.  
    41.                 ref var meshDataBlob =  ref MeshDatas[meshBatchIndex].BlobMeshData.Value;
    42.                 var meshVerticesCount = meshDataBlob.Vertices.Length;
    43.                 var meshTrisCount = meshDataBlob.Triangles.Length;
    44.  
    45.                 //this represents the Vertex Index that this job should start from in the final Combined Mesh
    46.                 var previousMeshDataIndicesCount = PreviousMeshDataIndicesCount[meshBatchIndex];
    47.                 var vertOffset = previousMeshDataIndicesCount.VertexIndex;
    48.                
    49.  
    50.                 var trisOffset = previousMeshDataIndicesCount.TrianglesIndex;
    51.  
    52.  
    53.                 Debug.Log($"FinalMeshIndex: {ResultMeshIndex} _ index: {meshBatchIndex},  vertOffset: {vertOffset},  trisOffset: {trisOffset} ,   Before");
    54.  
    55.                 var chunkMatrices = ChunkMatrices[meshBatchIndex];
    56.                 NativeArray<Matrix4x4> chunkMatricesArray;
    57.  
    58.                 unsafe
    59.                 {
    60.                     chunkMatricesArray = NativeUtility.PtrToNativeArray((Matrix4x4*)chunkMatrices.MatricesPtr, chunkMatrices.BatchCount);
    61.                 }
    62.  
    63.                 for (var i = 0; i < chunkMatrices.BatchCount; i++)
    64.                 {
    65.  
    66.                     var matrix = chunkMatricesArray[i];
    67.  
    68.  
    69.                     //Triangles
    70.                     for (var j = 0; j < meshTrisCount; j++)
    71.                     {
    72.                         finalMeshTris[trisOffset++] = j + vertOffset;
    73.                     }
    74.  
    75.                     //Vertices
    76.                     for (var j = 0; j < meshVerticesCount; j++)
    77.                     {
    78.                         var vertexMeshData = new VertexMeshData
    79.                         {
    80.                             Position = matrix.MultiplyPoint3x4(meshDataBlob.Vertices[j]),
    81.                             Normal = matrix.MultiplyVector(meshDataBlob.Normals[j]).normalized,
    82.                             Uv = meshDataBlob.Uvs[j]
    83.                         };
    84.  
    85.                         finalMeshVertices[vertOffset++] = vertexMeshData;
    86.                     }
    87.                 }
    88.  
    89.  
    90.                 Debug.Log($"FinalMeshIndex: {ResultMeshIndex} _ index: {meshBatchIndex},  vertOffset: {vertOffset},  trisOffset: {trisOffset},   After");
    91.  
    92.             }
    93.  
    94.  
    95.         }
     
  11. Fribur

    Fribur

    Joined:
    Jan 5, 2019
    Posts:
    136
    Not sure I can fully follow...maybe the issue is related to the Pointer? Possibly I can offer an alternative approach I am using (prompted also by the observation you use a NativeArray of Meshdata, instead of MeshDataArray:
    (1) Identify all Entitites having Meshes that should be combined, and while doing so store the Entitites, the Vertex Count and Index Count in NativeArray's
    (2) Add Meshes to a Managed List
    Code (CSharp):
    1.             foreach (var e in MeshEntities)
    2.                 meshList.Add(entityManager.GetSharedComponentData<RenderMesh>(e).mesh);
    (3) Using the advanced Mesh API, get an MeshDataArray from the MeshList (allocation free)
    Code (CSharp):
    1. MeshDataArray inputMeshDataArray = AcquireReadOnlyMeshData(meshList);
    (4) Get the total Vertex and Index count of the CombinedMesh, allocate the output MeshData (how to define Vertex Layout was described above in the thread):
    Code (CSharp):
    1.                 int combinedVertexDataLength = 0;
    2.                 int combinedIndexDataLength = 0;
    3.                 for (int i = 0; i < filteredNumberOfEntities; i++)
    4.                 {
    5.                     combinedVertexDataLength += vertexCount[i];
    6.                     combinedIndexDataLength += indexCount[i];
    7.                 }
    8.                 MeshDataArray outputMeshDataArray = AllocateWritableMeshData(1);
    9.                outputMeshDataArray[0].SetVertexBufferParams(combinedVertexDataLength, layout);
    10.                 outputMeshDataArray[0].SetIndexBufferParams(combinedIndexDataLength, IndexFormat.UInt32);
    11.  
    (5) Manually Schedule IJobs (NOT IJobParallelFor), passing in the current VertextStart and the IndexStart
    Code (CSharp):
    1.                 for (int i = 0; i < filteredNumberOfEntities; i++)
    2.                 {
    3.                     var job = new SetCombinedMeshDataJob
    4.                     {
    5.                         ...
    6.                         tStart = tStart,
    7.                         vStart = vStart,
    8.                         index = i,
    9.                     };
    10.                     allJobs[i] = job.Schedule();
    11.                     vStart += vertexCount[i];
    12.                     tStart += indexCount[i];
    13.                 }
    14.                 JobHandle.CompleteAll(allJobs);
    (6) Inside the job, use those known Vertex and Index start positions, to write into the correct slot of the CombinedMeshData:
    Code (CSharp):
    1.  for (var i = 0; i < vCount; ++i)
    2.                 outputVerts[vStart + i] = new PolygonVertex() { pos = new float3(vertices[i].x, -depth * jobGlobalSettings.DepthScale, vertices[i].y), normal = new float3(0, 1, 0), color = new Color(S52color.x, S52color.y, S52color.z, S52color.w) };
    3.  
    4.             ref BlobArray<int> blobIndices = ref earcutBlob[entity].blobAssetReference.Value.MapEarcutArrays[jobBlobIndex[index]].EarcutIndices;
    5.             int tCount = blobIndices.Length;
    6.             var outputTris = jobOutputMeshData.GetIndexData<int>();
    7.             for (int j = 0; j < tCount; j++)
    8.                 outputTris[tStart + j] = (vStart + blobIndices[j]);
     
    Last edited: Jul 30, 2020
    Opeth001 likes this.
  12. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,116
    sorry i didn't mention it before, but im not able to upgrade my project to Unity 2020.1.
    my project infinity throws errors until editor crashes

    The MeshData is a BlobAsset containing Mesh Verts,Tris,Normals.. and added to chunks as a ChunkComponentData so it is easily accessible from Jobs
     
  13. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    > ArgumentException: Index buffer element #94176 (value 38976) is out of bounds; mesh only has 38976 vertices

    Are you using submeshes? Those have an offset that's added to every index, which can lead to things being out of range. I usually just leave the offset as 0 and manually add the index offsets in code. Otherwise, just triple-check your indices (triangles) array that everything is pointing to a valid vertex. The error says exactly what it thinks is wrong (note vertex array is 0-based, so 38976 is actually the 38977th vertex)
     
    Last edited: Jul 30, 2020
    Opeth001 likes this.
  14. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,116
    afaik im not using SubMeshes,
    the MeshData im combining are BlobAssets created at runtime copying meshes matrices from the Mesh field within the RenderMesh.
    Code (CSharp):
    1.  
    2.     [UpdateInGroup(typeof(InitializationSystemGroup))]
    3.     /// <summary>
    4.     /// adds MeshData CCD to compatible Chunks when Missing.
    5.     /// </summary>
    6.     public class MeshDataManagerSystem : SystemBase
    7.     {
    8.  
    9.         private EntityQuery m_MissingMeshData;
    10.         public NativeHashMap<int,MeshData> cachedMeshesData;
    11.  
    12.         protected override void OnCreate()
    13.         {
    14.             cachedMeshesData = new NativeHashMap<int, MeshData>(1024, Allocator.Persistent);
    15.             m_MissingMeshData = GetEntityQuery(ComponentType.ChunkComponentExclude<MeshData>(), ComponentType.ReadOnly<RenderMesh>(), ComponentType.ReadOnly<RenderMeshMergeableTag>());
    16.         }
    17.        
    18.  
    19.         protected override void OnDestroy()
    20.         {
    21.             cachedMeshesData.Dispose();
    22.         }
    23.  
    24.        
    25.         protected override void OnUpdate()
    26.         {
    27.  
    28.             var getRenderMesh = GetArchetypeChunkSharedComponentType<RenderMesh>();
    29.             //Getting chunks before invalidating the Query
    30.             var chunks = m_MissingMeshData.CreateArchetypeChunkArray(Allocator.TempJob);
    31.  
    32.             EntityManager.AddComponent(m_MissingMeshData, ComponentType.ChunkComponent<MeshData>());
    33.             for (var i = 0; i < chunks.Length; i++)
    34.             {
    35.                 var chunk = chunks[i];
    36.                 var renderMesh = chunk.GetSharedComponentData(getRenderMesh, EntityManager);
    37.  
    38.                 var meshId = renderMesh.mesh.GetHashCode();
    39.  
    40.                 if (cachedMeshesData.TryGetValue(meshId, out var blobMeshData))
    41.                 {
    42.                     EntityManager.SetChunkComponentData(chunk, blobMeshData);
    43.                 }
    44.                 else
    45.                 {
    46.                     //Generate BlobMeshData from Mesh
    47.                     blobMeshData = new MeshData(renderMesh.mesh);
    48.                     EntityManager.SetChunkComponentData(chunk, blobMeshData);
    49.                     cachedMeshesData.Add(meshId, blobMeshData);
    50.                 }
    51.             }
    52.             chunks.Dispose();
    53.  
    54.         }
    55.     }

    Code (CSharp):
    1.  
    2.     /// <summary>
    3.     /// used as ChunkComponent Data Referencing a MeshData
    4.     /// </summary>
    5.     [Serializable]
    6.     public struct MeshData : IComponentData
    7.     {
    8.         public BlobAssetReference<BlobMeshData> BlobMeshData;
    9.      
    10.         public MeshData(Mesh mesh)
    11.         {
    12.             BlobBuilder blobBuilder = new BlobBuilder(Allocator.Temp);
    13.  
    14.             ref var blobMeshData = ref blobBuilder.ConstructRoot<BlobMeshData>();
    15.            
    16.             var vertices = blobBuilder.Allocate(ref blobMeshData.Vertices, mesh.vertices.Length);
    17.             var normals = blobBuilder.Allocate(ref blobMeshData.Normals, mesh.normals.Length);
    18.             var triangles = blobBuilder.Allocate(ref blobMeshData.Triangles, mesh.triangles.Length);
    19.             var uvs = blobBuilder.Allocate(ref blobMeshData.Uvs, mesh.uv.Length);
    20.          
    21.  
    22.             unsafe
    23.             {
    24.                 fixed (void* verticesPtr = mesh.vertices)
    25.                 {
    26.                     UnsafeUtility.MemCpy(vertices.GetUnsafePtr(), verticesPtr, UnsafeUtility.SizeOf<float3>() * mesh.vertices.Length);
    27.                 }
    28.  
    29.                 fixed (void* normalsPtr = mesh.normals)
    30.                 {
    31.                     UnsafeUtility.MemCpy(normals.GetUnsafePtr(), normalsPtr, UnsafeUtility.SizeOf<float3>() * mesh.normals.Length);
    32.                 }
    33.  
    34.                 fixed (void* trianglesPtr = mesh.triangles)
    35.                 {
    36.                     UnsafeUtility.MemCpy(triangles.GetUnsafePtr(), trianglesPtr, UnsafeUtility.SizeOf<int>() * mesh.triangles.Length);
    37.                 }
    38.  
    39.                 fixed (void* uvsPtr = mesh.uv)
    40.                 {
    41.                     UnsafeUtility.MemCpy(uvs.GetUnsafePtr(), uvsPtr, UnsafeUtility.SizeOf<float2>() * mesh.uv.Length);
    42.                 }
    43.             }
    44.  
    45.             BlobMeshData = blobBuilder.CreateBlobAssetReference<BlobMeshData>(Allocator.Persistent);
    46.  
    47.             blobBuilder.Dispose();
    48.         }
    49.  

    yes debugged the last 1000 tris of the resulting mesh and the values are greater than the number of vertices but I can't explain it.
     
    Last edited: Jul 30, 2020
  15. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,116
    I finally found the problem.
    for some reason, I was adding VerticesCount to the j iterator instead of the tris value from BlobMeshData.

    Code (CSharp):
    1.  
    2. //Tris
    3.   for (var j = 0; j < meshTrisCount; j++)
    4.    {
    5.           finalMeshTris[trisOffset++] = j + vertOffset; // Old line
    6.           finalMeshTris[trisOffset++] = meshDataBlob.Triangles[j] + verticesCount;
    7.     }
    8.  
    the only thin i need to fix now, is how can stop the official Hybrid Renderer from rendering so i can try my custom Hybrid Renderer ?
    for now it looks like they are rendered on top of each other

    Note:
    My custom hybrid renderer is based on the official package, I'm just replacing the RendererMeshSystemV2 using my own Frustum Culling and Rendering stuff.

    on older versions i just had to turn off rendering by replacing RendererMeshSystemV2 by running this line in OnStartRunning:

    Code (CSharp):
    1.  
    2. World.DefaultGameObjectInjectionWorld.GetOrCreateSystem<RenderMeshSystemV2>().Enabled = false;
    3.  
    but now even disabling RendererMeshSystemV2 the editor is still Rendering normally am i missing something ?

    Thanks!
     
  16. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,116
    Also:
    Why is the Mesh Advanced API so expensive?
    Commenting out this piece of code makes the entire system from goes from 5ms to 1.2ms.

    Code (CSharp):
    1.  
    2.  mesh.MarkDynamic();
    3.  mesh.Clear(true);
    4.  mesh.SetVertexBufferParams(finalMeshDataVertices.VertexCount, VertexBufferLayout);
    5.  mesh.SetVertexBufferData(vertexMeshDataArray, 0, 0, finalMeshDataVertices.VertexCount, 0, 0);
    6.  
    7.  mesh.SetIndexBufferParams(trisMeshDataArray.Length, IndexFormat.UInt32);
    8.  mesh.SetIndexBufferData(trisMeshDataArray, 0, 0, trisMeshDataArray.Length);
    9.              
    10.  mesh.subMeshCount = 1;
    11.  SubMeshDescriptor smd = new SubMeshDescriptor
    12.  {
    13.   topology = MeshTopology.Triangles,
    14.   vertexCount = finalMeshDataVertices.VertexCount,
    15.   indexCount = finalMeshDataVertices.TrianglesCount,
    16.   };
    17.   mesh.SetSubMesh(0, smd);
    18.   mesh.UploadMeshData(false);
     
  17. Fribur

    Fribur

    Joined:
    Jan 5, 2019
    Posts:
    136
    Opeth001 and burningmime like this.
  18. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    If you don't pass any MeshUpdateFlags it does a bunch of validation on the CPU side. First step, try this:

    Code (CSharp):
    1.  
    2. // do mesh.MarkDynamic() once, when creating the mesh for the first time
    3. // also mesh.subMeshCount = 1 during initialization, and skip it after that (if you're not changing it ever)
    4.  
    5. const MeshUpdateFlags MINIMAL_UPDATE = MeshUpdateFlags.DontRecalculateBounds
    6.     | MeshUpdateFlags.DontValidateIndices | MeshUpdateFlags.DontResetBoneBounds;
    7.  
    8.  mesh.Clear(true);
    9.  mesh.SetVertexBufferParams(finalMeshDataVertices.VertexCount, VertexBufferLayout);
    10.  mesh.SetVertexBufferData(vertexMeshDataArray, 0, 0, finalMeshDataVertices.VertexCount, 0, MINIMAL_UPDATE);
    11.  
    12.  mesh.SetIndexBufferParams(trisMeshDataArray.Length, IndexFormat.UInt32);
    13.  mesh.SetIndexBufferData(trisMeshDataArray, 0, 0, trisMeshDataArray.Length, MINIMAL_UPDATE);
    14.      
    15.  SubMeshDescriptor smd = new SubMeshDescriptor
    16.  {
    17.   topology = MeshTopology.Triangles,
    18.   vertexCount = finalMeshDataVertices.VertexCount,
    19.   indexCount = finalMeshDataVertices.TrianglesCount,
    20.   };
    21.   mesh.SetSubMesh(0, smd, MINIMAL_UPDATE);
    22. mesh.RecalculateBounds();
    23.   mesh.UploadMeshData(false);
    Idea is to do the bounds calculation only once, and never to do a CPU pass for validation. However, it still needs to pass through all the vertices to calculate bounds.

    You can also calculate bounds in the job. That way, it doesn't need to look at the data you pass at all, just upload it to the GPU. Calculating bounds in burst is really easy. Just:

    Code (csharp):
    1.  
    2. // somewhere in your burst job (might need a length-1 nativearray to pass it back to main thread)
    3. Bounds bounds = new Bounds(vertices[0], float3.zero);
    4. for(int i = 1; i < vertices.Length; ++i)
    5.     bounds.Encapsulate(vertices[i]);
    6.  
    And then pass bounds to BOTH the SubMeshDescriptor (in the constructor where you're setting MeshTopology) and calling
    mesh.bounds = bounds
    . And of course no need for
    RecalculateBounds()
    .

    EDIT: Oops, this is exactly what Fribur said. Go upvote his post.
     
    Last edited: Jul 31, 2020
    Opeth001 likes this.
  19. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,116
    yes im trying to make it work with a minimum performance for the moment, later ill try to optimize it.
     
  20. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,116
    I just changed my code to calculate Bounds by Chunk,
    knowing that my custom hybrid renderer runs on top of the official hybrid renderer, so Bounds are already calculated for me.
    1) if the Chunk is fully visible, i directly use the ChunkWorldRenderBounds without any calculation.
    2) if the chunk is partially visible, I will Encapsulate WorldRenderBounds for each visible entity.
    later in the system, while Chunks Meshes are combined in Worker threads, I encapsulate all the Chunks Bounds received from Frustum Culling.

    Good News :
    1) Calculating Bounds does not add any overhead to existing logic.

    Bad News :
    1) SetIndexBufferData & SetVertexBufferData still add too much overhead to the system, even when using MeshUpdateFlags.

    Code (CSharp):
    1. const MeshUpdateFlags MINIMAL_UPDATE = MeshUpdateFlags.DontRecalculateBounds
    2.     | MeshUpdateFlags.DontValidateIndices | MeshUpdateFlags.DontResetBoneBounds;
    I think maybe this is due to the high number of vertices and Indices.

    note: each frame i set approximately 2~3 meshes with 154295 vertices and 208458 triangles each using
    SetVertexBufferData and SetIndexBufferData API.


    2) im Scheduling an IJobParallelFor for each List of Compatible Chunks (That can be Merged Together) each Job Index has a metadata containing visible Entities Matrices, a BlobMeshData and Indexes so the job can correctly write to the resultMesh slots.

    Scheduling 2 IJobParallelFor in parallel with around 90 Chunks each and waiting them takes too long (~ 1ms).

    Do you guys have a better approach to combine hundreds of meshes per frame which can be Faster than the solution above ?

    I'm starting to think maybe it will be better if I only combine small meshes with low count (like dynamic batching does)
    * but how to do this on top to the Official Hybrid Renderer

    Suggestions are greatly appreciated.
    Thanks!
     
  21. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    Typically you want to schedule the jobs, do something else, and complete at end of frame (or even the next frame if you can tolerate a 1 frame delay). You should be spending that 1ms updating other systems/MonoBehaviours.
     
    Opeth001 likes this.
  22. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    Also, you can also just do this part a single time, during initialization, and never clear the mesh:
    Code (csharp):
    1. Mesh mesh = new Mesh();
    2. mesh.hideFlags = HideFlags.DontSaveInBuild | HideFlags.DontSaveInEditor;
    3. mesh.MarkDynamic();
    4. mesh.SetVertexBufferParams(SOME_ABSURDLY_HIGH_NUMBER, VertexBufferLayout);
    5. mesh.SetIndexBufferParams(SOME_EVEN_HIGHER_NUMBER, IndexFormat.UInt32);
    6. mesh.subMeshCount = 1;
    Your updates will then look like this:
    Code (csharp):
    1. mesh.SetVertexBufferData(vertexMeshDataArray, 0, 0, finalMeshDataVertices.VertexCount, 0, MINIMAL_UPDATE | MeshUpdateFlags.DontNotifyMeshUsers);
    2. mesh.SetIndexBufferData(trisMeshDataArray, 0, 0, trisMeshDataArray.Length, MINIMAL_UPDATE | MeshUpdateFlags.DontNotifyMeshUsers);
    3. mesh.SetSubMesh(0, new SubMeshDescriptor {
    4.     topology = MeshTopology.Triangles,
    5.     vertexCount = finalMeshDataVertices.VertexCount,
    6.     indexCount = finalMeshDataVertices.TrianglesCount,
    7.     bounds = bounds,
    8. }, MINIMAL_UPDATE /* and do notify mesh users here */);
    9. mesh.bounds = bounds;
    10. mesh.UploadMeshData(false);
    The downside is you're always using a lot of memory on the GPU, but the upside is that it's never reallocated or cleared.
     
    Last edited: Jul 31, 2020
    NikiWalker and Opeth001 like this.
  23. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,116
    I think i'll:
    1) Do Frustum Culling using last frame calculated RenderBounds in initializationSystemGroup. (for the moment everything is Static)
    2) Schedule MeshCombiningJobs in initializationSystemGroup.
    3) Wait for Combined Meshes in the PresentationSystemGroup.

    this should help to prevent unnecessary waiting for Jobs to be complete.

    It's a good idea :)
    in my case to prevent GC im pooling Meshes and link a SubMeshDescriptor class at their creation, then each frame i get the Submesh using mesh.GetSubMesh(0) and change vertexCount/indexCount.