Unity 6.3
0 онлайн 85 гостей 3 в системе
Вход

NativeArrays

NativeArrays и другие типы в пространстве имен Unity.Collections хранят данные в памяти, которая не управляется временем выполнения скрипта. Когда вы передаете указатель на эти данные в основной код, вам не нужно "привязывать" буфер в памяти. Однако, вам все равно нужно учитывать срок жизни памяти данных, который определяется используемым Распределитель:

Распределитель Пожизненный срок Типичный размер пул памяти
Allocator.Temp Память автоматически очищается в конце каждого кадра. 4–16 MB основной поток 256 KB рабочих потоков
Allocator.Persistent Память сохраняется до тех пор, пока объект, являющийся владельцем выделения, не будет удален.
Allocator.Domain Память сохраняется до тех пор, пока домен C# не будет разгружен.
Allocator.TempJob Память сохраняется до тех пор, пока объект, которому принадлежит выделение, не будет уничтожен. TempJob выделения не должны сохраняться более чем на четыре кадра. Если этот тип памяти исчерпан, то Unity возвращается к более медленному методу выделения памяти. 16–64 MB
Allocator.None Распределитель None используется, когда экземпляр не владеет памятью в буфере, на который он ссылается.

Allocator.Temp обычно является самым быстрым способом выделения памяти, но вы можете использовать его только до конца кадра. Если вы выделяете больше памяти, чем имеется в пуле памяти, Unity переходит обратно к более медленному типу выделения. Дополнительную информацию см. в Неуправляемая память C#.

Раскладка структуры NativeArray — внутренняя деталь реализации, поэтому управлять маршалингом её полей нельзя. Однако указатель на буфер внутри массива можно передать, воспользовавшись явным указателем в небезопасном контексте.

Используйте unsafe для получения указателя массива

Используйте NativeArray.GetUnsafePtr() метод или NativeArrayUnsafeUtility класс, чтобы получить указатель на буфер внутри NativeArray. Передав такой указатель неуправляемой функции, вы можете работать с ним как с массивом C.

Следующий пример представляет собой неуправляемую функцию, которая принимает массив Color (32-битные целые числа без знака) в качестве параметра:

typedef int32_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, staying within the bounds of the colors array
                const int colorIndex = ((y / squareSize) + (x / squareSize)) % numColors;
                texture[y * width + x] = colors[colorIndex];
            }
        }
    }
}

Примечание: См. Вызов неуправляемых функций из управляемого кода для получения информации об аннотациях, необходимых для компиляции и вызова таких функций, как эта, в качестве части динамически загружаемой библиотеки.

Вы можете передать этой функции указатель на буфер NativeArray. Использование указателей в C# требует небезопасного контекста. См. Компиляция небезопасного C# кода для информации о включении компиляции unsafe кода в Unity.

В следующем коде показано, как вызвать неуправляемую функцию CheckerFill(). В примере выполняются следующие действия:

  1. Получает NativeArray, содержащий пиксельные данные объекта Texture2D.
  2. Получает указатель на буфер NativeArray с GetUnsafePtr().
  3. Вызывает неуправляемую функцию CheckerFill()для заполнения текстуры шаблоном проверки.
  4. Применяет результат к объекту Texture2D.
using UnityEngine;
using Unity.Collections;
using System.Runtime.InteropServices;
using Unity.Collections.LowLevel.Unsafe;

public class NativeArrayExamples
{
    // Change DllImport to use library name for
    // precompiled, dynamically linked libraries.
    [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's buffer
            void* pixelBuffer = pixelData.GetUnsafePtr();
            // The color palette to choose from
            var palette = new Color32[] { one, two };
            // Call the unmanaged function, passing 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);
        }
    }
}

Примечание: для безопасного доступа к массиву из неуправляемого кода, вы должны передать число элементов, которые он содержит. В этом примере код передает numColors (длину цветовой палитры), так что функция никогда не читает за конец массива colors. Длина массива текстуры может быть рассчитана из параметров width и height, так что длина не передается явно в этом примере.

Демонстрация

Чтобы вызвать метод NativeArrayExamples FillTextureWithCheckerboard, можно воспользоваться следующим классом MonoBehaviour. В примере в сцену добавляются два примитива GameObject с текстурами, сформированными неуправляемой функцией CheckerFill.

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.");
    }
}

Примечания:

  • Пример требует IL2CPP и не работает в редакторе режима воспроизведения, когда вы используете [DllImport("__Internal")]. Чтобы запустить пример в режиме воспроизведения, вы можете компилировать неуправляемый примерный код в качестве библиотеки и изменить DLLImport для использования имени библиотеки. См. DllImport атрибут для больше информации.
  • Демонстрация загружает шейдер URP Lit во время выполнения. Процесс сборки удаляет неиспользуемые шейдеры и варианты из сборки, и не может обнаружить использование во время выполнения, как это. Самым простым обходом для запуска этого примера является добавление примитивного объекта в сцену перед сборкой.
  • Texture2D.GetPixelData<T> возвращает NativeArray, который указывает на существующую память текстуры. Он не выделяет памяти, так что вам не нужно удалять NativeArray. (Удалить его не делает ничего.)