Можно ли изменять текстуру из другого потока в Unity?
Что проверяют
Знаете ли вы, что почти весь API Unity привязан к главному потоку, и умеете ли при этом вынести тяжёлую работу с пикселями в фон. Следом обычно просят рассказать про Job System или про то, как вернуть результат из Task обратно в главный поток.
Как отвечать
Вызывать методы Texture2D из другого потока нельзя: SetPixels, Apply, LoadImage и даже создание текстуры бросят UnityException с сообщением, что вызов возможен только из главного потока. Но готовить данные текстуры в фоне можно, и сильный ответ как раз про это разделение:
- считать в фоне, применять в главном: в
Task.Runготовите массивColor32[]илиbyte[], потом в главном потоке вызываетеSetPixels32илиLoadRawTextureDataиApply; - Job System:
texture.GetPixelData<Color32>(0)илиGetRawTextureData<T>()отдаютNativeArray, который смотрит прямо в CPU-память текстуры. Его передают вIJobParallelForс Burst, ждутComplete()и вызываютApply(). Копирования нет, это самый быстрый путь; - возврат в главный поток: в Unity 6 есть
Awaitable.BackgroundThreadAsync()иAwaitable.MainThreadAsync(). Кроме того, у Unity свойSynchronizationContext, поэтомуawait Task.Run(...), вызванный из главного потока, продолжит выполнение в нём же.
Добавьте про цену Apply(): он отправляет данные в GPU, поэтому его вызывают один раз после всех изменений, а Apply(false) не пересчитывает мип-уровни. SetPixels работает только с несжатыми форматами, а у импортированной текстуры для доступа к пикселям нужен флаг Read/Write.
Где спотыкаются
Отвечают «нельзя» и на этом останавливаются. Вопрос как раз про то, как разделить вычисления и загрузку в GPU.
Пишут в NativeArray из джоба и вызывают Apply() до Complete(). Safety system выдаст ошибку, а без неё получится гонка и мусор на текстуре.
Вызывают Apply() после каждого SetPixel в цикле. Каждый вызов это загрузка всей текстуры в видеопамять, и кадр проседает сильнее, чем от самих вычислений.