Allow setting mesh arrays with NativeArrays.

Hey folks,

I have a feature request for the Unity dev team. It would be very nice if you could add some overloads to the Mesh class so that we could set triangles, vertices and uvs using references to NativeArrays. I’m currently building my geometry in a job, but I have to convert the NativeArray data to an array to give to the Mesh class (which no doubt marshalls the data back to native) which is horribly inefficient.

Any plans to add this functionality? Also please give me a heads up if there is an equivalent approach I’m not aware of.

Cheers, -gormulent

1 Like

They will do that. You can find the answer in one of threads about the job system.

You can actually modify arrays (int[ ] and Vector3[ ] etc) within jobs using pointers. This is not great but at the moment it’s the best work around to avoid the slow copy (a magnitude of performance increase.)

keijiro has an example of github: GitHub - keijiro/Firefly: Unity ECS example for special effects

The general idea is this

        [BurstCompile]
        private struct GenerateMeshJob : IJob
        {
            // ...

            // basically a wrapper for a int pointer, from https://github.com/keijiro/Firefly
            public NativeCounter.Concurrent Counter;

            // Pointers to the start of an array
            [NativeDisableUnsafePtrRestriction] public void* Vertices;

            public void Execute()
            {
                // ...
                UnsafeUtility.WriteArrayElement(Vertices, vertCount + n, vertices);
                // ...
            }
        }
 
        // Job creation
        var generateMeshJob = new GenerateMeshJob
        {
            Vertices = UnsafeUtility.AddressOf(ref vertices[0]),
        }
        .Schedule(getVisibleFacesJob);

From this I figured out you can actually do it for Lists as well (using the internal array lists hold) which I needed because I wanted to use the SetUV(List).

This is a little more complicated as it’s either super slow or creates garbage if you’re not careful. I was intending to write a blog post / tutorial on doing all this later this week.

I’ve managed to get a nice voxel engine running generating meshes for 400k voxels in 2.8ms on an older 3570k. It takes 4x longer to upload the changed mesh to the GPU than it does to generate it.

6 Likes

That seems pretty unsafe unless you’re damn certain that you’re adding the right job dependencies …

At any rate, I’m hoping they add overloads for this, it’s silly to prepare this data in C# arrays, the marshalling cost is just too high.

It is called unsafe for a reason!

That said, as much as I hate the idea of having to use pointers in c#, you got to do what you got to do.
As far as I’m aware, it’s the best (and only) way to do this at the moment.
They will eventually add support for meshes (for example Texture2D has support now) but until we have to suck it up or wait.

I was considering writing a wrapper to hide this process in the background, a bit like the concurrent counter.

I’m not talking about the use of pointers, I’m a veteran C++ engine programmer haha - I’m talking about threading issues.

Oh sure, but it’s not really much worse than writing to a NativeArray with [NativeDisableParallelForRestriction] which is pretty common behavior.

1 Like

Based on this example I modified the CopyToFast extention to do a single Memory copy call.
This way you can create the NativeArrays in jobs as normal and then just do a fast copy to the managed array before adding. You still have the overhead of the extra memory copy but is faster than the current CopyTo implementation.

    public static class NativeArrayExtensions
    {
        public static unsafe void CopyToFast<T>(
            this NativeArray<T> nativeArray,
            T[] array)
            where T : struct
        {
            if (array == null)
            {
                throw new NullReferenceException(nameof(array) + " is null");
            }

            int nativeArrayLength = nativeArray.Length;
            if (array.Length < nativeArrayLength)
            {
                throw new IndexOutOfRangeException(
                    nameof(array) + " is shorter than " + nameof(nativeArray));
            }

            int byteLength = nativeArray.Length * Marshal.SizeOf(default(T));
            void* managedBuffer = UnsafeUtility.AddressOf(ref array[0]);
            void* nativeBuffer = nativeArray.GetUnsafePtr();
            Buffer.MemoryCopy(nativeBuffer, managedBuffer, byteLength, byteLength);
        }
}

EDIT:

I did some speed testing and replaced the buffer.MemoryCopy with this on windows platform

#if PLATFORM_STANDALONE_WIN
        [DllImport("msvcrt.dll", EntryPoint = "memcpy")]
        public static extern void CopyMemory(IntPtr pDest, IntPtr pSrc, int length);
#endif

I did something similar except I extended it and created my own UnsafeNativeArray to completely avoid having to do any copy.

Here are some performance comparisons

Default NativeArray.CopyTo (2.40ms)

Array.CopyTo - very similar to Marshal performance (0.15ms)
Similar approach to @LennartJohansen , much much faster compared to default implementation

My custom UnsafeNativeArray - directly editing the array, no copying (0.07ms)

So relative performance between Array.Copy and directly editing array isn’t huge, however it does halve your memory requirement as you don’t require 2 copies of the array - not a big deal if you only need a single array but if you need a lot it’s signficant.

If anyone wants to try my UnsafeNativeArray, I’ve uploaded it here: https://github.com/tertle/UnsafeNativeArray

It works basically the same as NativeArray (90% of the code is identical) except you pass it an array in the constructor. It includes the same checks. It’s not safe because there are no checks on using the array you pass in. You must complete the job before using this array otherwise you’ll run into trouble.

var array = new int[30];
var unsafeNativeArray = new UnsafeNativeArray<int>(array);

var job = new Job
{
    UnsafeNativeArray = unsafeNativeArray; // use UnsafeNativeArray just like a NativeArray
}.Schedule();

job.Complete(); // make sure job is complete before using array

// Use array how you like, no need to copy result as UnsafeNativeArray just directly modifies the array

-edit-
one thing to note is you can’t use a 0 length array with UnsafeNativeArray, will throw an exception.

4 Likes

Loo

Looks good.

I did not try it yet but I guess you could also get a pointer to the managed arrays first element and create the array from this.

 void* managedBuffer = UnsafeUtility.AddressOf(ref array[0]);
NativeArray<T> newArray = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<T>(managedBuffer,array.length,Allocator.None)

My guess is allocator.None since the managed array keeps the memory reference and will free it.

Oh interesting. Did not know that existed. Going to go exploring.

-edit-

seems to mostly do what I did so I don’t need a separate class for it then

that’s awesome, cheers for that

-edit2-

can’t seem to get it to work. job complains it’s not assigned or constructed, even though I can see it fully allocated. will keep exploring

did it work with just the address of the array? or do you need to use

GCHandle.Alloc(managedArray, GCHandleType.Pinned);
and get the pointer from that?

Ok. Got it (kind of) working. You need to setup the safety as well.

            AtomicSafetyHandle safety;
            DisposeSentinel disposeSentinel;
            DisposeSentinel.Create(out safety, out disposeSentinel, 1);
            NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref nativeArray, safety);

Just using UnsafeUtility.AddressOf(ref array[0]); worked fine

The issue is, there does not seem to be a way to also pass the disposeSentinel in - I’m not sure why there is only a method for the safety. So I don’t think it’s going to dispose correctly.

I will give it a test tomorrow also. This could be a good way to serialize data. Unity can manage the saving to a scriptable object and the job system can work on data with the nativearray you create from the array.

Hey Tertle - Any chance you’ve written that blog post? Thanks for this post

Nah never got around to it. Happy to answer any questions though.

I don’t use this approach anymore as DynamicBuffers now exist that removes most of the need for this.

So now I use buffers and wrote an extension method for List.AddRange(DynamicBuffer)

namespace BovineLabs.Common.Native
{
    using System;
    using System.Collections.Generic;
    using BovineLabs.Common.Utility;
    using Unity.Collections;
    using Unity.Collections.LowLevel.Unsafe;
    using Unity.Entities;

    /// <summary>
    /// Extensions for Native Containers.
    /// </summary>
    public static class Extensions
    {
        /// <summary>
        /// Adds a native version of <see cref="List{T}.AddRange(IEnumerable{T})"/>.
        /// </summary>
        /// <typeparam name="T">The type.</typeparam>
        /// <param name="list">The <see cref="List{T}"/> to add to.</param>
        /// <param name="array">The native array to add to the list.</param>
        public static unsafe void AddRange<T>(this List<T> list, NativeArray<T> array)
            where T : struct
        {
            AddRange(list, array, array.Length);
        }

        /// <summary>
        /// Adds a native version of <see cref="List{T}.AddRange(IEnumerable{T})"/>.
        /// </summary>
        /// <typeparam name="T">The type.</typeparam>
        /// <param name="list">The <see cref="List{T}"/> to add to.</param>
        /// <param name="array">The array to add to the list.</param>
        /// <param name="length">The length of the array to add to the list.</param>
        public static unsafe void AddRange<T>(this List<T> list, NativeArray<T> array, int length)
            where T : struct
        {
            list.AddRange(array.GetUnsafeReadOnlyPtr(), length);
        }

        /// <summary>
        /// Adds a native version of <see cref="List{T}.AddRange(IEnumerable{T})"/>.
        /// </summary>
        /// <typeparam name="T">The type.</typeparam>
        /// <param name="list">The <see cref="List{T}"/> to add to.</param>
        /// <param name="nativeList">The native list to add to the list.</param>
        public static unsafe void AddRange<T>(this List<T> list, NativeList<T> nativeList)
            where T : struct
        {
            list.AddRange(nativeList.GetUnsafePtr(), nativeList.Length);
        }

        /// <summary>
        /// Adds a native version of <see cref="List{T}.AddRange(IEnumerable{T})"/>.
        /// </summary>
        /// <typeparam name="T">The type.</typeparam>
        /// <param name="list">The <see cref="List{T}"/> to add to.</param>
        /// <param name="nativeSlice">The array to add to the list.</param>
        public static unsafe void AddRange<T>(this List<T> list, NativeSlice<T> nativeSlice)
            where T : struct
        {
            list.AddRange(nativeSlice.GetUnsafeReadOnlyPtr(), nativeSlice.Length);
        }

        /// <summary>
        /// Adds a native version of <see cref="List{T}.AddRange(IEnumerable{T})"/>.
        /// </summary>
        /// <typeparam name="T">The type.</typeparam>
        /// <param name="list">The <see cref="List{T}"/> to add to.</param>
        /// <param name="dynamicBuffer">The dynamic buffer to add to the list.</param>
        public static unsafe void AddRange<T>(this List<T> list, DynamicBuffer<T> dynamicBuffer)
            where T : struct
        {
            list.AddRange(dynamicBuffer.GetUnsafePtr(), dynamicBuffer.Length);
        }

        /// <summary>
        /// Adds a range of values to a list using a buffer;
        /// </summary>
        /// <typeparam name="T">The type.</typeparam>
        /// <param name="list">The list to add the values to.</param>
        /// <param name="arrayBuffer">The buffer to add from.</param>
        /// <param name="length">The length of the buffer.</param>
        public static unsafe void AddRange<T>(this 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);
        }
    }
}

And my mesh setter just becomes this

        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.AddRange(vertices);
            this.uvsList.AddRange(uvs);
            this.normalsList.AddRange(normals);
            this.trianglesList.AddRange(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();
        }

Much cleaner.

6 Likes

An excellent solution. How do you access the NoAllocHelpers class btw? It’s internal if I’m not mistaken.

Have a wrapper that caches a delegate generated using reflection.

Can post code after Christmas .

-edit-

Posted it here before: NativeArray and Mesh

1 Like

This is brilliant, thank you.