Search Unity

Changing mesh data directly via native arrays isn't faster than converting to/setting managed arrays

Discussion in 'Entity Component System' started by keenanwoodall, Sep 24, 2019.

  1. keenanwoodall

    keenanwoodall

    Joined:
    May 30, 2014
    Posts:
    598
    I'm trying to update some of my code that was used for mesh modification. Before the new updates to the Mesh API in the 2019.3 beta, I had to memcpy my native arrays of float2/3/4s into managed arrays of Vector2/3/4s so that I could assign them to the mesh via the mesh properties. Now the Mesh class has methods that allow you to send native arrays to replace mesh data directly. I assumed this would increase speeds a lot as the mesh data is no longer getting copied twice; however when I changed my code I didn't notice any performance increase.

    Here's the original code that I wrote before the improved mesh API. The important part is the call to `CopyNativeDataToManagedData` at the top. It handles copying the data from the native arrays to the managed arrays so that I can copy the managed data to the actual mesh. You can ignore all the if statements, they're just there to prevent copying unchanged data.

    Code (CSharp):
    1. /// <summary>
    2. /// Applies the dynamic native data to the dynamic mesh.
    3. /// </summary>
    4. public void ApplyData (DataFlags dataFlags)
    5. {
    6.     // Copy the native data into the managed data for efficient transfer into the actual mesh.
    7.     DataUtils.CopyNativeDataToManagedData (dynamicManaged, DynamicNative, dataFlags);
    8.  
    9.     if (DynamicMesh == null)
    10.         return;
    11.     // Send managed data to mesh.
    12.     if ((dataFlags & DataFlags.Vertices) != 0)
    13.         DynamicMesh.vertices = dynamicManaged.Vertices;
    14.     if ((dataFlags & DataFlags.Normals) != 0)
    15.         DynamicMesh.normals = dynamicManaged.Normals;
    16.     if ((dataFlags & DataFlags.Tangents) != 0)
    17.         DynamicMesh.tangents = dynamicManaged.Tangents;
    18.     if ((dataFlags & DataFlags.UVs) != 0)
    19.         DynamicMesh.uv = dynamicManaged.UVs;
    20.     if ((dataFlags & DataFlags.Colors) != 0)
    21.         DynamicMesh.colors = dynamicManaged.Colors;
    22.     if ((dataFlags & DataFlags.Triangles) != 0)
    23.         DynamicMesh.triangles = dynamicManaged.Triangles;
    24.     if ((dataFlags & DataFlags.Bounds) != 0)
    25.         DynamicMesh.bounds = dynamicManaged.Bounds;
    26. }
    Here's the updated code that copies data directly from the native arrays to the mesh. As you can see, I'm no longer calling `CopyNativeDataToManagedData` and am assigning data directly from the native arrays. The only other difference is that I removed the part that assigns new triangle indices as there wasn't an overload that supported updating them via a native array.
    Code (CSharp):
    1. /// <summary>
    2. /// Applies the dynamic native data to the dynamic mesh.
    3. /// </summary>
    4. public void ApplyData (DataFlags dataFlags)
    5. {
    6.     if (DynamicMesh == null)
    7.         return;
    8.     // Send native data directly to mesh.
    9.     if ((dataFlags & DataFlags.Vertices) != 0)
    10.         DynamicMesh.SetVertices(DynamicNative.VertexBuffer);
    11.     if ((dataFlags & DataFlags.Normals) != 0)
    12.         DynamicMesh.SetNormals(DynamicNative.NormalBuffer);
    13.     if ((dataFlags & DataFlags.Tangents) != 0)
    14.         DynamicMesh.SetTangents(DynamicNative.TangentBuffer);
    15.     if ((dataFlags & DataFlags.UVs) != 0)
    16.         DynamicMesh.SetUVs(0, DynamicNative.UVBuffer);
    17.     if ((dataFlags & DataFlags.Colors) != 0)
    18.         DynamicMesh.SetColors(DynamicNative.ColorBuffer);
    19.     if ((dataFlags & DataFlags.Bounds) != 0)
    20.         DynamicMesh.bounds = DynamicNative.Bounds[0];
    21. }
    Here's a screensnip of the profiler before and after my changes:
    Performance.png

    I turned on the deep profiler and ApplyData was taking up 15% of the total ms so it's not an insignificant part of the frame.
    upload_2019-9-24_14-30-5.png

    I must be doing something incorrectly because the performance was exactly the same. I'd love to figure out how to speed up my code tho!
     
    Last edited: Sep 24, 2019
  2. owen_proto

    owen_proto

    Joined:
    Mar 18, 2018
    Posts:
    120
    You want to be using setvertexbufferparams/setvertexbufferdata and the equivalent for triangles. You will need to create a new struct to wrap all the vertex data (positions/normals/tangents/UVs) and then use the setbufferparams method to make sure the mesh’s data layout matches. I’m using the Unity.mathematics types since I do mesh gen in burst compiled jobs.

    Check this thread: https://forum.unity.com/threads/feedback-wanted-mesh-scripting-api-improvements.684670/

    Also! If you get setting tris to work please let me know how you did it. I couldn’t get it working and set it aside for the time being.
     
  3. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    676
    I'm working with the new mesh API and i have no problem setting the triangles. What problems do you see?
    I'm doing something like this to set the triangles:
    Code (CSharp):
    1.       mesh.SetVertexBufferData(vertexArray.AsNativeArray(), 0, 0, vertexCount, 0);
    2.       mesh.SetIndexBufferParams(vertexIndexArray.Length, IndexFormat.UInt32);
    3.       mesh.SetIndexBufferData(vertexIndexArray.AsNativeArray(), 0, 0, vertexIndexArray.Length);
    4.       mesh.subMeshCount = 1;
    5.       mesh.SetSubMesh(0, new SubMeshDescriptor() {
    6.         baseVertex = 0,
    7.         bounds = default,
    8.         indexStart = 0,
    9.         indexCount = vertexIndexArray.Length,
    10.         firstVertex = 0,
    11.         topology = MeshTopology.Triangles,
    12.         vertexCount = vertexArray.Length
    13.       });
    14.       mesh.UploadMeshData(false);
     
  4. keenanwoodall

    keenanwoodall

    Joined:
    May 30, 2014
    Posts:
    598
    Ah that's too bad. I'm not sure I like that each element in the native array is supposed to store all the vertex data. My whole system has mesh data stored in separate native arrays (for vertices, normals etc) so that jobs only operate on the parts of the mesh they need and unchanged data isn't copied to the mesh. Also the fact that the data has to be defined in a custom struct kinda makes it hard to handle cases where you wanna dynamically change what data is stored, right? You can't really create a custom vertex struct at runtime. I guess the only reason they'd do that is because the data isn't stored in separate buffers on the native side?
     
  5. owen_proto

    owen_proto

    Joined:
    Mar 18, 2018
    Posts:
    120
    @GilCat Thank you! I wasn't setting submesh data :D. I should read more carefully. Works now!

    @keenanwoodall I don't know for sure, but I would think the speed benefits are greater even if you are recalculating all the vertex data together if your code is multithreaded. You could try caching some of the data and only calculating what you need and see if that is faster. That is currently what I'm doing with triangles when I don't need them recalculated.