Version: Unity 6.0 (6000.0)
Language : English
Structures, classes, and unions
Pass strings between C# and native code

NativeArrays

NativeArray<T> and other types in the Unity.Collections namespace store data in memory that isn’t managed by the scripting runtime. When you pass a pointer to this data to native code, you don’t have to pin the buffer in memory. However, you still need to consider the lifetime of the data, which depends on the allocator you use:

Allocator Lifetime Typical memory pool size
Allocator.Temp Unity automatically cleans up the memory at the end of each frame. 4–16 MB main thread, 256 KB worker threads
Allocator.Persistent The memory persists until the object that owns the allocation is disposed of.
Allocator.Domain The memory persists until the C# domain is unloaded.
Allocator.TempJob The memory persists until the object that owns the allocation is disposed of. Don’t keep TempJob allocations for more than four frames. If this type of memory is exhausted, Unity falls back to a slower memory allocation method. 16–64 MB
Allocator.None Use the None allocator when the instance doesn’t own the memory in the buffer it references.

Allocator.Temp is generally the fastest way to allocate memory, but you can only use it until the end of the frame. If you allocate more memory than the amount available in the memory pool, Unity falls back to a slower type of allocation. For more information, refer to C# unmanaged memory.

The layout of a NativeArray struct is an internal detail, so you can’t control how its fields are marshalled. However, you can pass a pointer to the buffer inside the array in an unsafe context.

Get a pointer to the NativeArray buffer

To get a pointer to the buffer of a NativeArray, use the GetUnsafePtr extension method in the NativeArrayUnsafeUtility class. When you pass the pointer to an unmanaged function, you can treat it like a C array.

The following example is an unmanaged function that takes an array of Color values, which are unsigned 32-bit integers, as a parameter:

#include <stdint.h>

typedef uint32_t Color;

extern "C" {
    void CheckerFill(Color* texture, int width, int height, int numSquares, const Color colors[], int numColors) {

        const int squareSize = width / numSquares;

        for (int y = 0; y < height; ++y) {
            for (int x = 0; x < width; ++x) {
                // Select a palette entry within the bounds of the colors array.
                const int colorIndex = ((y / squareSize) + (x / squareSize)) % numColors;
                texture[y * width + x] = colors[colorIndex];
            }
        }
    }
}

Note: Refer to Call unmanaged functions from managed code for information about the annotations needed to compile and call functions such as this as part of a dynamically loaded library.

You can pass a pointer to the buffer of a NativeArray to this function. Using pointers in C# requires an unsafe context. For information about enabling unsafe code compilation in Unity, refer to Compiling unsafe C# code.

The following code calls the unmanaged CheckerFill function. The example performs the following steps:

  1. Gets a NativeArray that contains the pixel data of a Texture2D object.
  2. Gets a pointer to the NativeArray buffer with GetUnsafePtr.
  3. Calls the unmanaged CheckerFill function to fill the texture with a checker pattern.
  4. Applies the result to the Texture2D object.
using UnityEngine;
using Unity.Collections;
using System.Runtime.InteropServices;
using Unity.Collections.LowLevel.Unsafe;

public class NativeArrayExamples
{
    // For precompiled, dynamically linked libraries, change the DllImport value to the library name.
    [DllImport("__Internal")]
    static extern unsafe void CheckerFill(Color32* pixelDataPtr, int width, int height, int squares, [In] Color32[] colors, int numColors);

    public static void FillTextureWithCheckerboard(Texture2D texture, Color32 one, Color32 two, int squaresPerSide)
    {
        unsafe
        {
            // Get the pixel data as a NativeArray.
            NativeArray<Color32> pixelData = texture.GetPixelData<Color32>(0);
            // Get a pointer to the NativeArray buffer.
            void* pixelBuffer = pixelData.GetUnsafePtr();
            // Define the color palette to choose from.
            var palette = new Color32[] { one, two };
            // Call the unmanaged function. Pass the palette length so it stays within the colors array.
            CheckerFill((Color32*)pixelBuffer, texture.width, texture.height, squaresPerSide, palette, palette.Length);
            // Apply the changes to the texture.
            texture.Apply(false);
        }
    }
}

Note: To safely access an array from C++ code, you must pass the number of elements it contains. In this example, the code passes numColors, the length of the color palette, so the function never reads past the end of the colors array. The function calculates the length of the texture array from the width and height parameters, so the example doesn’t pass that length explicitly.

Display the generated textures in a scene

You can use the following MonoBehaviour class to call the NativeArrayExamples FillTextureWithCheckerboard method. The example adds two GameObject primitives to the scene with textures generated using the unmanaged CheckerFill function.

using UnityEngine;

public class DemoNativeArray : MonoBehaviour
{
    void Start()
    {
        AddCheckeredObject(PrimitiveType.Sphere, Color.blue,  Color.yellow, Vector3.zero);
        AddCheckeredObject(PrimitiveType.Cube, Color.red,  Color.white, Vector3.one);
    }

    void AddCheckeredObject(PrimitiveType primitive, Color colorOne, Color colorTwo, Vector3 position)
    {
        // Create a new texture.
        Texture2D checkerboard = new Texture2D(256, 256, TextureFormat.RGBA32, false);
        // Fill the texture with a checkerboard pattern using the NativeArrayExamples class.
        NativeArrayExamples.FillTextureWithCheckerboard(checkerboard, colorOne, colorTwo, 8);

        var go = GameObject.CreatePrimitive(primitive);
        go.transform.position = position;

        var litShader = Shader.Find("Universal Render Pipeline/Lit");
        if (litShader != null)
        {
            // Create a new material using the URP Lit shader.
            Material material = new Material(litShader);
            // Assign the texture to the material.
            material.SetTexture("_BaseMap", checkerboard);
            go.GetComponent<MeshRenderer>().material = material;
        }
        else
            Debug.LogError("Unable to load the Universal Render Pipeline/Lit shader.");
    }
}

Notes:

  • When you use [DllImport("__Internal")], the example requires the IL2CPP scripting backend and doesn’t work in Play mode. To run the example in Play mode, compile the unmanaged example code as a library and change the DllImport statements to use the library name. For more information, refer to DllImport attribute.
  • The example loads the URP Lit shader at runtime. The build process strips unused shaders and variants from a build, and can’t detect runtime usage such as this. To make sure the build includes the shader, add a primitive GameObject that uses the URP Lit shader to the scene before you build.
  • Texture2D.GetPixelData<T> returns a NativeArray that points to the existing memory of the texture. It doesn’t allocate memory, so you don’t need to dispose of the NativeArray. Disposing of it has no effect.

Additional resources

Structures, classes, and unions
Pass strings between C# and native code