Hello,
I got really excited about the new Jobs system, hoping it would finally let me take UnityEngine objects and methods off the main thread. Specifically, I wanted to load JPG files into Texture2D objects asynchronously, so as not to cause frame halting.
However, much to my dismay, since Unity Objects are non-blittable, they can’t be used in Jobs.
Currently I am using Texture2D.LoadImage(bytes[ ]) to create my Texture2Ds from JPG files that are on-disk. I have found no appreciable difference between this and the WWW method in terms of performance.
Through profiling and holistic debugging, I have determined that it is the LoadImage method that is hogging resources and the main thread, so simply getting the byte array in a different thread won’t resolve the performance issue.
Has anyone found a suitable way to load Texture2D objects off the main thread?
2 Likes
I’m also interested in this.
1 Like
Since Texture2D.LoadImage performs Jpeg decoding, it might be in your best interest to locate a suitable jpeg decoding library so you can decode the jpeg data on another thread (not within a job). A quick search suggests that GitHub - reignstudios/Reign-Unity-Plugin-OpenSource: Open Source components of the Reign Unity3D Plugins might be a good place to start for something you could use to turn that jpeg data into raw pixel data (although there might be a simpler library floating around that would be better). Once you have that raw pixel data, I’ve found that Texture2D.LoadRawTextureData is an extremely fast way to get that raw pixel data actually loaded into a unity texture.
1 Like
I’m not sure if this will help loading textures off disk, it may, but 2018.2 texture2D has a new overload which lets you manipulate textures within jobs.
public NativeArray GetRawTextureData();
Just call Texture2D.Apply() on main thread when you’re done manipulating to push it to the GPU.
also has public void LoadRawTextureData(NativeArray data);
4 Likes
Basic template for creating procedural textures with new Job system (require Unity 2018.2):
// https://github.com/przemyslawzaworski/Unity3D-C-programming
using UnityEngine;
using Unity.Collections;
using Unity.Jobs;
public class image : MonoBehaviour
{
public int Resolution = 256;
public GameObject Target;
JobHandle HandleJob;
NativeArray<Color32> PixelArray;
Texture2D Map;
struct CalculateJob : IJob
{
public NativeArray<Color32> Pixels;
public float Width;
public float Height;
public int Dimension;
public float Timer;
public void Execute()
{
for (int i = 0; i < Pixels.Length; i++)
{
Vector2 uv = new Vector2(Width/Dimension,Height/Dimension);
Pixels[i] = new Color32 ((byte)(uv.x*255),(byte)(uv.y*255),(byte)(Timer*255),255) ;
Width++;
if (Width>=Dimension) Width=0.0f;
if ((i+1)%Dimension==0) Height++;
}
}
}
void Start()
{
if (QualitySettings.activeColorSpace==ColorSpace.Gamma)
Map = new Texture2D(Resolution,Resolution, TextureFormat.RGBA32, false,false);
else
Map = new Texture2D(Resolution,Resolution, TextureFormat.RGBA32, false,true);
Map.wrapMode = TextureWrapMode.Clamp;
Target.GetComponent<Renderer>().material = new Material(Shader.Find("Unlit/Texture"));
Target.GetComponent<Renderer>().material.mainTexture = Map;
}
void Update()
{
PixelArray = Map.GetRawTextureData<Color32>();
int InitHeight = 0;
int InitWidth = 0;
float SetTime = Mathf.Sin(Time.time)*0.5f+0.5f;
CalculateJob calculate_job = new CalculateJob()
{
Pixels = PixelArray,
Width = InitWidth,
Height = InitHeight,
Dimension = Resolution,
Timer = SetTime
};
HandleJob = calculate_job.Schedule();
Map.Apply(false);
}
public void LateUpdate()
{
HandleJob.Complete();
}
}
7 Likes
If you have control over the content/images, I’d recommend using Basis Universal compressed texture files.
It’s a format that is superiour to Jpeg/PNG because:
- similar small in size (fast download)
- transcodes to GPU friendly format with good performance and up 8x less memory use
I’ve written a Unity package for it:
It’s jobified and transcodes off the main thread. The actual GPU upload has to be on the main thread though (Unity constraint)
6 Likes