Search Unity

Build Meshes with the Job System?

Discussion in 'C# Job System' started by z000z, Feb 13, 2018.

  1. z000z

    z000z

    Joined:
    Dec 31, 2014
    Posts:
    96
    Is it on the radar possibly to use the job system for mesh building? Currently one of the biggest slowdowns I have at the moment is creating meshes, I've already got the actual vertices/normals/etc being created in background threads but just simply assigning the vertices to a mesh is a pretty intensive act and currently has to happen on the main thread.

    Any chance we might see some way of doing this in jobs?
     
    Deleted User and kayb14 like this.
  2. MadeFromPolygons

    MadeFromPolygons

    Joined:
    Oct 5, 2013
    Posts:
    3,983
    Sure but you could do it right now using compute shader
     
  3. z000z

    z000z

    Joined:
    Dec 31, 2014
    Posts:
    96
    The problem with the compute shader, is my target ATM includes iOS/Android devices that don't support the compute shader.
     
  4. LeonhardP

    LeonhardP

    Unity Technologies

    Joined:
    Jul 4, 2016
    Posts:
    3,136
  5. z000z

    z000z

    Joined:
    Dec 31, 2014
    Posts:
    96
    @LeonhardP That thread is about drawing meshes, this is for building a mesh.
     
  6. MartinGram

    MartinGram

    Administrator

    Joined:
    Feb 24, 2017
    Posts:
    72
    Hello z000z,

    This is a feature that is on our list to do, but it will not be ready for the 2018.1 release.
     
  7. z000z

    z000z

    Joined:
    Dec 31, 2014
    Posts:
    96
  8. MadeFromPolygons

    MadeFromPolygons

    Joined:
    Oct 5, 2013
    Posts:
    3,983
    iOS supports compute, only android does not ;)
     
  9. supron

    supron

    Joined:
    Aug 24, 2013
    Posts:
    67
    What about animations? Making AnimationClip is even slower than creating meshes. I hope this is also on your list.
     
  10. kayb14

    kayb14

    Joined:
    Jun 12, 2016
    Posts:
    10
    What about the 2019 release? Please make a list of currently available job types.
    It's really hard to find them scattered around forums, github and the official api.

    anyways so much thanks for the year 2018 with unity!!!
     
    Bas-Smit likes this.
  11. Bas-Smit

    Bas-Smit

    Joined:
    Dec 23, 2012
    Posts:
    274
    @MartinGram any update on this? Should I be able to use GetNativeVertexBufferPtr? I tried for a while but only got editor crashes for my troubles.
     
    learc83 likes this.
  12. LiamMitchell

    LiamMitchell

    Joined:
    Oct 30, 2015
    Posts:
    5
    Interested in this, any update?
     
    JesOb likes this.
  13. Vanamerax

    Vanamerax

    Joined:
    Jan 12, 2012
    Posts:
    938
    Mesh building via Jobs would also be at the top of my wishlist regarding ECS API.

    Any ETA?
     
    JesOb likes this.
  14. mohydineName

    mohydineName

    Joined:
    Aug 30, 2009
    Posts:
    301
    Yes, ETA?
     
    Ivan-Pestrikov, Bas-Smit and JesOb like this.
  15. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,168
    Will we have a way to write mesh vertex data directly without copying whole array? Especially writing just some parts of vertices, not all of it
     
  16. Piefayth

    Piefayth

    Joined:
    Feb 7, 2017
    Posts:
    61
    Huh, I was experimenting with this a while ago. I don't really know if this approach is valid, but you can certainly write to a managed array in a bursted job if you're willing to give up all the safety restrictions.

    Code (CSharp):
    1. /* system */
    2. Vector3[] vertices = mesh.vertices;
    3. void* pVertices = UnsafeUtility.AddressOf(ref vertices[0]);
    4. int length = vertices.Length;
    5.  
    6. new SomeJob { pVertices = pVertices }.Schedule(length, 1, inputDeps);
    7.  
    8. /* job */
    9. [BurstCompile]
    10. unsafe struct SomeJob {
    11.     [NativeDisableUnsafePtrRestriction] public void* pVertices;
    12.  
    13.     public void Execute(int index) {
    14.         UnsafeUtility.WriteArrayElement(pVertices, index, new float3(1, 1, 1));
    15.     }
    16. }
    The only weakness of this is that after the job completes you need to reassign the vertices array you wrote to to the mesh. I can't figure out a way to write directly. This appears to duck the copy, but it weirds me out that this approach requires the reassignment, cause that gives me the impression there are two instances of the underlying array... I am posting with very incomplete knowledge, so discussion and corrections are welcome!
     
  17. Bas-Smit

    Bas-Smit

    Joined:
    Dec 23, 2012
    Posts:
    274
    the first line copies the native array to managed, which is why you need to write it back, we want to write the native copy directly
     
    Thaina and Piefayth like this.
  18. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,168
    There would be no point to use unsafe pointer if you use
    mesh.vertices


    There was
    mesh.GetNativeVertexBufferPtr
    that could return IntPtr to vertex stream. But it was not safe and we hardly know the layout of that struct

    What I wish is that unity should have a function that could partially update the vertex buffer. Maybe like this

    Code (CSharp):
    1. mesh.SetVertexPositionAt(0,new Vector3(1,0,0));
    2. mesh.SetVertexPositionAt(1,new Vector3(0,1,0));
    3. mesh.SetVertexPositionAt(2,new Vector3(0,0,1));
    4.  
    5. Or maybe
    6.  
    7. // IEnumerable<ValueTuple<int,Vector3>>
    8. mesh.SetVertexPositions(new (int,Vector3)[]{
    9.     (0,new Vector3(1,0,0)),
    10.     (1,new Vector3(0,1,0)),
    11.     (2,new Vector3(0,0,1)),
    12. });
    13.  
    14. Or maybe
    15.  
    16. mesh.SetVertexPositions(1,new Vector3[]{ // apply from offset 1 of buffer with all 3 elements of array
    17.     new Vector3(1,0,0),
    18.     new Vector3(0,1,0),
    19.     new Vector3(0,0,1),
    20. });
    21.  
    22. and maybe
    23.  
    24. mesh.SetVertexPositions(1,0,2,new Vector3[]{ // apply from offset 1 of buffer since index 0 of array by 2 element
    25.     new Vector3(1,0,0),
    26.     new Vector3(0,1,0),
    27.     new Vector3(0,0,1),
    28. });
    29.  
    30.  
     
    Last edited: Apr 30, 2019
    JesOb likes this.
  19. TheJavierD

    TheJavierD

    Joined:
    Jan 6, 2017
    Posts:
    51
    Yes please, we need this as well.
     
  20. s3vv4

    s3vv4

    Joined:
    Nov 22, 2017
    Posts:
    1
    Was there any progress towards this feature in the recent Unity versions?
     
  21. RoughSpaghetti3211

    RoughSpaghetti3211

    Joined:
    Aug 11, 2015
    Posts:
    1,709
    What’s the current workarounds people are using. Do we just used ToArray() on native arrays?
     
  22. Riccsson

    Riccsson

    Joined:
    Feb 10, 2014
    Posts:
    1
    Hi.
    I am in the same situation, I need to create huge meshes in background thread.
    I have tried to use CopyPtrToStructure() but the layout seems to be a mess and cant find out how it is build.
    (I have readed the https://docs.unity3d.com/ScriptReference/Mesh.GetNativeVertexBufferPtr.html)

    Here is the code I use to try to find out the layout:

    Code (CSharp):
    1.         struct Layout
    2.         {
    3.             Vector3 position;
    4.             Vector3 normal;
    5.             Color color;
    6.             Vector2 uv;
    7.         }
    8.  
    Code (CSharp):
    1.             nativeVertices = mesh.GetNativeVertexBufferPtr(0);
    2.             Layout viewLayout;
    3.             UnsafeUtility.CopyPtrToStructure(nativeVertices.ToPointer(), out viewLayout);
    But the viewLayout shows only random strange values, and I only set the position, normal, color and uv with no mesh compression settings.
    Right now I am totally stuck.
     
    GeorgeAdamon likes this.
  23. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,168
    https://twitter.com/_kzr/status/1115422397518106624?lang=en

    Also maybe?

    https://github.com/ousttrue/UniShar...iSharpDX/Scripts/RenderAPI_D3D11.cs#L212-L233
     
    Last edited: Aug 23, 2019
  24. MartinGram

    MartinGram

    Administrator

    Joined:
    Feb 24, 2017
    Posts:
    72
    @Aras has posted a thread here regarding updating the mesh API
     
  25. GeorgeAdamon

    GeorgeAdamon

    Joined:
    May 31, 2017
    Posts:
    48
    Same problem for me.
     
  26. HDProDesignTeam

    HDProDesignTeam

    Joined:
    Mar 16, 2019
    Posts:
    8
    Hi, is it possible in unity 2020.3.3 LTS? If yes, could you please provide some information or links that we can use.

    Thanks in advance
     
    bb8_1 likes this.
  27. elliotc-unity

    elliotc-unity

    Unity Technologies

    Joined:
    Nov 5, 2015
    Posts:
    230
  28. runner78

    runner78

    Joined:
    Mar 14, 2015
    Posts:
    792
  29. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    6,001
    @demirel04 Here's my Cube Mesh generator that uses the MeshData API which is a bit lower-level than the Mesh API. It's not in a job but will be. I hope this provides enough of an example. The Neighbour parameter is an enum (with extension methods) that tells me which of the sides I should not generate (those with a "neighbour"). I only use position & normal attributes but the others work in a similar way.

    Code (CSharp):
    1.         private static Mesh Create(Neighbour neighbours = Neighbour.None)
    2.         {
    3.             var neighbourCount = neighbours.SideCount();
    4.             if (neighbourCount == 6)
    5.                 return null;
    6.  
    7.             var facesCount = 6 - neighbourCount;
    8.             var vertexCount = 4 * facesCount;
    9.             var triangleIndexCount = 6 * facesCount;
    10.  
    11.             var dataArray = Mesh.AllocateWritableMeshData(1);
    12.             var meshData = dataArray[0];
    13.             var i = 0;
    14.  
    15.             {
    16.                 var vertexAttributeCount = 2;
    17.                 var attributes = new NativeArray<VertexAttributeDescriptor>(vertexAttributeCount, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
    18.                 var index = -1;
    19.                 attributes[++index] = new VertexAttributeDescriptor(VertexAttribute.Position, dimension: 3, stream: index);
    20.                 attributes[++index] = new VertexAttributeDescriptor(VertexAttribute.Normal, dimension: 3, stream: index);
    21.                 //attributes[++index] = new VertexAttributeDescriptor(VertexAttribute.Color, dimension: 4, stream: index);
    22.                 //attributes[++index] = new VertexAttributeDescriptor(VertexAttribute.Tangent, dimension: 4, stream: index, format:VertexAttributeFormat.Float16);
    23.                 //attributes[++index] = new VertexAttributeDescriptor(VertexAttribute.TexCoord0, dimension: 2, stream: index, format:VertexAttributeFormat.Float16);
    24.                 meshData.SetVertexBufferParams(vertexCount, attributes);
    25.                 attributes.Dispose();
    26.             }
    27.  
    28.             meshData.SetIndexBufferParams(triangleIndexCount, IndexFormat.UInt32);
    29.  
    30.             var noFront = NeighbourCheck.HasFlag(neighbours, Neighbour.Front);
    31.             var noRight = NeighbourCheck.HasFlag(neighbours, Neighbour.Right);
    32.             var noBack = NeighbourCheck.HasFlag(neighbours, Neighbour.Back);
    33.             var noLeft = NeighbourCheck.HasFlag(neighbours, Neighbour.Left);
    34.             var noTop = NeighbourCheck.HasFlag(neighbours, Neighbour.Top);
    35.             var noBottom = NeighbourCheck.HasFlag(neighbours, Neighbour.Bottom);
    36.  
    37.             var positions = meshData.GetVertexData<float3>();
    38.             i = 0;
    39.             // front
    40.             if (noFront == false)
    41.             {
    42.                 positions[i++] = new float3(-.5f, -.5f, -.5f);
    43.                 positions[i++] = new float3(-.5f, .5f, -.5f);
    44.                 positions[i++] = new float3(.5f, .5f, -.5f);
    45.                 positions[i++] = new float3(.5f, -.5f, -.5f);
    46.             }
    47.             // right
    48.             if (noRight == false)
    49.             {
    50.                 positions[i++] = new float3(.5f, -.5f, -.5f);
    51.                 positions[i++] = new float3(.5f, .5f, -.5f);
    52.                 positions[i++] = new float3(.5f, .5f, .5f);
    53.                 positions[i++] = new float3(.5f, -.5f, .5f);
    54.             }
    55.             // back
    56.             if (noBack == false)
    57.             {
    58.                 positions[i++] = new float3(.5f, -.5f, .5f);
    59.                 positions[i++] = new float3(.5f, .5f, .5f);
    60.                 positions[i++] = new float3(-.5f, .5f, .5f);
    61.                 positions[i++] = new float3(-.5f, -.5f, .5f);
    62.             }
    63.             // left
    64.             if (noLeft == false)
    65.             {
    66.                 positions[i++] = new float3(-.5f, -.5f, .5f);
    67.                 positions[i++] = new float3(-.5f, .5f, .5f);
    68.                 positions[i++] = new float3(-.5f, .5f, -.5f);
    69.                 positions[i++] = new float3(-.5f, -.5f, -.5f);
    70.             }
    71.             // top
    72.             if (noTop == false)
    73.             {
    74.                 positions[i++] = new float3(-.5f, .5f, -.5f);
    75.                 positions[i++] = new float3(-.5f, .5f, .5f);
    76.                 positions[i++] = new float3(.5f, .5f, .5f);
    77.                 positions[i++] = new float3(.5f, .5f, -.5f);
    78.             }
    79.             // bottom
    80.             if (noBottom == false)
    81.             {
    82.                 positions[i++] = new float3(.5f, -.5f, .5f);
    83.                 positions[i++] = new float3(-.5f, -.5f, .5f);
    84.                 positions[i++] = new float3(-.5f, -.5f, -.5f);
    85.                 positions[i++] = new float3(.5f, -.5f, -.5f);
    86.             }
    87.  
    88.             var normals = meshData.GetVertexData<float3>(1);
    89.             i = 0;
    90.             // front
    91.             if (noFront == false)
    92.             {
    93.                 normals[i++] = new float3(0f, 0f, -1f);
    94.                 normals[i++] = new float3(0f, 0f, -1f);
    95.                 normals[i++] = new float3(0f, 0f, -1f);
    96.                 normals[i++] = new float3(0f, 0f, -1f);
    97.             }
    98.             // right
    99.             if (noRight == false)
    100.             {
    101.                 normals[i++] = new float3(1f, 0f, 0f);
    102.                 normals[i++] = new float3(1f, 0f, 0f);
    103.                 normals[i++] = new float3(1f, 0f, 0f);
    104.                 normals[i++] = new float3(1f, 0f, 0f);
    105.             }
    106.             // back
    107.             if (noBack == false)
    108.             {
    109.                 normals[i++] = new float3(0f, 0f, 1f);
    110.                 normals[i++] = new float3(0f, 0f, 1f);
    111.                 normals[i++] = new float3(0f, 0f, 1f);
    112.                 normals[i++] = new float3(0f, 0f, 1f);
    113.             }
    114.             // left
    115.             if (noLeft == false)
    116.             {
    117.                 normals[i++] = new float3(-1f, 0f, 0f);
    118.                 normals[i++] = new float3(-1f, 0f, 0f);
    119.                 normals[i++] = new float3(-1f, 0f, 0f);
    120.                 normals[i++] = new float3(-1f, 0f, 0f);
    121.             }
    122.             // top
    123.             if (noTop == false)
    124.             {
    125.                 normals[i++] = new float3(0f, 1f, 0f);
    126.                 normals[i++] = new float3(0f, 1f, 0f);
    127.                 normals[i++] = new float3(0f, 1f, 0f);
    128.                 normals[i++] = new float3(0f, 1f, 0f);
    129.             }
    130.             // bottom
    131.             if (noBottom == false)
    132.             {
    133.                 normals[i++] = new float3(0f, -1f, 0f);
    134.                 normals[i++] = new float3(0f, -1f, 0f);
    135.                 normals[i++] = new float3(0f, -1f, 0f);
    136.                 normals[i++] = new float3(0f, -1f, 0f);
    137.             }
    138.  
    139.             meshData.SetIndexBufferParams(triangleIndexCount, IndexFormat.UInt16);
    140.             var triangleIndices = meshData.GetIndexData<ushort>();
    141.             ushort currentIndex = 0;
    142.             ushort firstIndex = 0;
    143.             i = 0;
    144.             for (var face = 0; face < facesCount; face++)
    145.             {
    146.                 firstIndex = currentIndex;
    147.                 triangleIndices[i++] = currentIndex;
    148.                 triangleIndices[i++] = ++currentIndex;
    149.                 triangleIndices[i++] = ++currentIndex;
    150.                 triangleIndices[i++] = currentIndex;
    151.                 triangleIndices[i++] = ++currentIndex;
    152.                 triangleIndices[i++] = firstIndex;
    153.                 currentIndex++;
    154.             }
    155.  
    156.             var bounds = new Bounds(Vector3.zero, new Vector3(1f, 1f, 1f));
    157.             meshData.subMeshCount = 1;
    158.             meshData.SetSubMesh(0,
    159.                 new SubMeshDescriptor(0, triangleIndexCount)
    160.                 {
    161.                     bounds = bounds,
    162.                     vertexCount = vertexCount,
    163.                 },
    164.                 MeshUpdateFlags.DontRecalculateBounds);
    165.  
    166.             var mesh = new Mesh
    167.             {
    168.                 bounds = bounds,
    169.                 name = "Generated Cube",
    170.             };
    171.             mesh.Optimize();
    172.             Mesh.ApplyAndDisposeWritableMeshData(dataArray, mesh);
    173.  
    174.             return mesh;
    175.         }
     
    bb8_1 and apkdev like this.
  30. MUGIK

    MUGIK

    Joined:
    Jul 2, 2015
    Posts:
    481
    But can we somehow schedule SetVertexBufferData calls?
    Red is a mesh creation(which is insanely fast thanks to Burst and Jobs). There are about 20-30 meshes.
    Orange is SetVertexBufferData calls.
    Gaps between calls are most likely due to some thread sync idk.
    upload_2022-8-6_23-25-23.png

    I disable all possible checks when calling SetVertexBufferData.
    upload_2022-8-6_23-28-33.png

    It just feels wrong that mesh creation is faster than copying data to the vertex buffer.
     
  31. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    6,001
    That is what the Mesh.MeshData API is for. It can be used in jobs.