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(). В примере выполняются следующие действия:
- Получает NativeArray, содержащий пиксельные данные объекта Texture2D.
- Получает указатель на буфер NativeArray с
GetUnsafePtr(). - Вызывает неуправляемую функцию
CheckerFill()для заполнения текстуры шаблоном проверки. - Применяет результат к объекту 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. (Удалить его не делает ничего.)