NativeArray and Mesh

It would be great if we could pass NativeArray’s to the Mesh API.

Methods like Mesh.SetVertices(), Mesh.SetNormals(), etc. currently require a list and can therefore not be jobified properly.

Are there any plans to support this in the forseeable future?

8 Likes

More then that, Mesh wants Vector3s and can only be used from the main thread. I do hope we get a way to create and manage meshes from inside the Jobs system (and thus inside ECS as well).

6 Likes

It would be great to alter meshes directly from a job, but for the time being i would be satisfied when i could just pass the data i have calculate in the job system to the mesh on the main thread.
Let’s not get too greedy :wink:

1 Like

:smile: “The point is, ladies and gentleman, that greed, for lack of a better word, is good.” - Gordon Gekko in Wall Street

I’m not sure there would be huge amount of difference between modify Mesh and creating a new way to create a mesh then manage getting it to the main thread (when needed, DirectX 12, Vulkan and Metal don’t have this limit).

Can’t tell exactly when it is planned, but this is definitely part of the plan. There is an ultimate goal to make the whole rendering pipeline leveraging the C# ECS/job infrastructure, it will take time but once we will be there, it will be quite amazing.

12 Likes

Thanks for confirming that.
Not that i’m in a hurry, but i think i speak for all users here when i say “We want it NOW” :slight_smile:

1 Like

In the future we will also want to have overloads that would accept the types from the new math library, e.g. NativeArray etc

better to not be outdated when that time comes :stuck_out_tongue:

Absolutely. When the new math library will be out of experimental, we will make sure the new types can be used on any Unity API where it makes sense.

7 Likes

This makes me gitty. I know good things take time, but i cant WAIT for all of this and will be watching the progress on all of this very closely.

Any update on this or is it another case where we’ll be waiting years for a few lines of code that everyone agrees are needed?

I’m glad I’m not the only one. I have requested this in an older thread. :slight_smile:

I realize I sound petty above, but there is a lot of history on trying to get the Mesh setters improved.
https://feedback.unity3d.com/suggestions/allow-mesh-data-to-have-a-length

4 Likes

My RAM would appreciate this feature.
This is one of the only (and biggest) sources of garbage I am forced to deal with as of now.

But it’s probably more likely that Unity is focusing on reworking the Mesh Component from the ground up for ECS.
Just a guess.

How I convert DynamicBuffers to Mesh, no garbage, super quick Single memcpy per buffer.

I actually had an even faster version that I’ve talked about a bit before where I could manipulate a List within a job so it didn’t even require a memcopy but I feel the very minor performance cost of a memcopy is worth the safety of using buffers instead. Also means I don’t need SharedComponentData.

    public class MeshSystem : ComponentSystem
    {
        private readonly List<Vector3> verticesList = new List<Vector3>();
        private readonly List<Vector3> normalsList = new List<Vector3>();
        private readonly List<Vector3> uvsList = new List<Vector3>();
        private readonly List<int> trianglesList = new List<int>();

        // ...

        private void SetMesh(
            Mesh mesh,
            DynamicBuffer<Vector3> vertices,
            DynamicBuffer<Vector3> uvs,
            DynamicBuffer<Vector3> normals,
            DynamicBuffer<int> triangles)
        {
            mesh.Clear();

            if (vertices.Length == 0)
            {
                return;
            }

            this.verticesList.NativeAddRange(vertices);
            this.uvsList.NativeAddRange(uvs);
            this.normalsList.NativeAddRange(normals);
            this.trianglesList.NativeAddRange(triangles);

            mesh.SetVertices(this.verticesList);
            mesh.SetNormals(this.normalsList);
            mesh.SetUVs(0, this.uvsList);
            mesh.SetTriangles(this.trianglesList, 0);

            this.verticesList.Clear();
            this.normalsList.Clear();
            this.uvsList.Clear();
            this.trianglesList.Clear();
        }
public static unsafe void NativeAddRange<T>(this List<T> list, DynamicBuffer<T> dynamicBuffer)
            where T : struct
        {
            NativeAddRange(list, dynamicBuffer.GetBasePointer(), dynamicBuffer.Length);
        }

        private static unsafe void NativeAddRange<T>(List<T> list, void* arrayBuffer, int length)
            where T : struct
        {
            var index = list.Count;
            var newLength = index + length;

            // Resize our list if we require
            if (list.Capacity < newLength)
            {
                list.Capacity = newLength;
            }

            var items = NoAllocHelpers.ExtractArrayFromListT(list);
            var size = UnsafeUtility.SizeOf<T>();

            // Get the pointer to the end of the list
            var bufferStart = (IntPtr) UnsafeUtility.AddressOf(ref items[0]);
            var buffer = (byte*)(bufferStart + (size * index));

            UnsafeUtility.MemCpy(buffer, arrayBuffer, length * (long) size);

            NoAllocHelpers.ResizeList(list, newLength);
        }
11 Likes

How did you get access to the internal NoAllocHelpers class? It doesn’t appear that you’re using reflection.

Originally I wrote methods to mimic the behaviour but it required System.Reflection.Emit which is a no go so I just ended up calling it with reflection. The reflection is cached so except for the first time, it’s nearly just as fast as calling it normally.

    using System;
    using System.Collections.Generic;
    using System.Reflection;

    using UnityEngine;

    /// <summary>
    /// Provides access to the internal UnityEngine.NoAllocHelpers methods.
    /// </summary>
    public static class NoAllocHelpers
    {
        private static readonly Dictionary<Type, Delegate> ExtractArrayFromListTDelegates = new Dictionary<Type, Delegate>();
        private static readonly Dictionary<Type, Delegate> ResizeListDelegates = new Dictionary<Type, Delegate>();

        /// <summary>
        /// Extract the internal array from a list.
        /// </summary>
        /// <typeparam name="T"><see cref="List{T}"/>.</typeparam>
        /// <param name="list">The <see cref="List{T}"/> to extract from.</param>
        /// <returns>The internal array of the list.</returns>
        public static T[] ExtractArrayFromListT<T>(List<T> list)
        {
            if (!ExtractArrayFromListTDelegates.TryGetValue(typeof(T), out var obj))
            {
                var ass = Assembly.GetAssembly(typeof(Mesh)); // any class in UnityEngine
                var type = ass.GetType("UnityEngine.NoAllocHelpers");
                var methodInfo = type.GetMethod("ExtractArrayFromListT", BindingFlags.Static | BindingFlags.Public)
                    .MakeGenericMethod(typeof(T));

                obj = ExtractArrayFromListTDelegates[typeof(T)] = Delegate.CreateDelegate(typeof(Func<List<T>, T[]>), methodInfo);
            }

            var func = (Func<List<T>, T[]>)obj;
            return func.Invoke(list);
        }

        /// <summary>
        /// Resize a list.
        /// </summary>
        /// <typeparam name="T"><see cref="List{T}"/>.</typeparam>
        /// <param name="list">The <see cref="List{T}"/> to resize.</param>
        /// <param name="size">The new length of the <see cref="List{T}"/>.</param>
        public static void ResizeList<T>(List<T> list, int size)
        {
            if (!ResizeListDelegates.TryGetValue(typeof(T), out var obj))
            {
                var ass = Assembly.GetAssembly(typeof(Mesh)); // any class in UnityEngine
                var type = ass.GetType("UnityEngine.NoAllocHelpers");
                var methodInfo = type.GetMethod("ResizeList", BindingFlags.Static | BindingFlags.Public)
                    .MakeGenericMethod(typeof(T));
                obj = ResizeListDelegates[typeof(T)] =
                    Delegate.CreateDelegate(typeof(Action<List<T>, int>), methodInfo);
            }

            var action = (Action<List<T>, int>)obj;
            action.Invoke(list, size);
        }
    }
7 Likes

Sweet. I was previously using reflection to manually adjust my managed list’s array and size (using FieldInfo.Get/SetValue), but this is a bit faster and way easier to work with. Also doesn’t have any recurring GC allocations. Thanks! :smile:

@GabrieleUnity Ideally we could use it on anything that is enumerable and of type Vector3 or float3. That way we dont need overloads for Lists, and all other types work too.

The mathematics library has been out of preview for a few months. Is there a timeline now for supporting it throughout the other libraries (but like OP I’m particularly interested in meshes)?