Search Unity

Async Texture2D creation with Jobs

Discussion in 'Entity Component System' started by twfarro, May 8, 2018.

  1. twfarro

    twfarro

    Joined:
    Nov 23, 2013
    Posts:
    23
    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?
     
    tonytopper likes this.
  2. bdovaz

    bdovaz

    Joined:
    Dec 10, 2011
    Posts:
    1,052
    I'm also interested in this.
     
  3. amarcolina

    amarcolina

    Joined:
    Jun 19, 2014
    Posts:
    65
    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 https://github.com/reignstudios/Reign-Unity-Plugin-OpenSource 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.
     
  4. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    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<T> GetRawTextureData();
    https://docs.unity3d.com/2018.2/Documentation/ScriptReference/Texture2D.GetRawTextureData.html

    Just call Texture2D.Apply() on main thread when you're done manipulating to push it to the GPU.

    also has public void LoadRawTextureData(NativeArray<T> data);
    https://docs.unity3d.com/2018.2/Documentation/ScriptReference/Texture2D.LoadRawTextureData.html
     
  5. Przemyslaw_Zaworski

    Przemyslaw_Zaworski

    Joined:
    Jun 9, 2017
    Posts:
    328
    Basic template for creating procedural textures with new Job system (require Unity 2018.2):

    Code (CSharp):
    1. //  https://github.com/przemyslawzaworski/Unity3D-C-programming
    2.  
    3. using UnityEngine;
    4. using Unity.Collections;
    5. using Unity.Jobs;
    6. public class image : MonoBehaviour
    7. {
    8.     public int Resolution = 256;
    9.     public GameObject Target;
    10.     JobHandle HandleJob;
    11.     NativeArray<Color32> PixelArray;
    12.     Texture2D Map;
    13.  
    14.     struct CalculateJob : IJob
    15.     {
    16.         public NativeArray<Color32> Pixels;
    17.         public float Width;
    18.         public float Height;
    19.         public int Dimension;
    20.         public float Timer;
    21.         public void Execute()
    22.         {
    23.             for (int i = 0; i < Pixels.Length; i++)
    24.             {
    25.                 Vector2 uv = new Vector2(Width/Dimension,Height/Dimension);
    26.                 Pixels[i] = new Color32 ((byte)(uv.x*255),(byte)(uv.y*255),(byte)(Timer*255),255) ;
    27.                 Width++;
    28.                 if (Width>=Dimension) Width=0.0f;
    29.                 if ((i+1)%Dimension==0) Height++;
    30.             }
    31.         }
    32.     }
    33.     void Start()
    34.     {
    35.         if (QualitySettings.activeColorSpace==ColorSpace.Gamma)
    36.             Map = new Texture2D(Resolution,Resolution, TextureFormat.RGBA32, false,false);
    37.         else
    38.             Map = new Texture2D(Resolution,Resolution, TextureFormat.RGBA32, false,true);
    39.         Map.wrapMode = TextureWrapMode.Clamp;
    40.         Target.GetComponent<Renderer>().material = new Material(Shader.Find("Unlit/Texture"));
    41.         Target.GetComponent<Renderer>().material.mainTexture = Map;
    42.     }
    43.  
    44.     void Update()
    45.     {
    46.         PixelArray = Map.GetRawTextureData<Color32>();
    47.         int InitHeight = 0;
    48.         int InitWidth = 0;
    49.         float SetTime = Mathf.Sin(Time.time)*0.5f+0.5f;
    50.         CalculateJob calculate_job = new CalculateJob()
    51.         {
    52.             Pixels = PixelArray,
    53.             Width = InitWidth,
    54.             Height = InitHeight,
    55.             Dimension = Resolution,
    56.             Timer = SetTime
    57.         };
    58.         HandleJob = calculate_job.Schedule();
    59.         Map.Apply(false);
    60.     }
    61.  
    62.     public void LateUpdate()
    63.     {
    64.         HandleJob.Complete();
    65.     }
    66. }
     
    oen3, tonytopper, deus0 and 4 others like this.
  6. tteneder

    tteneder

    Unity Technologies

    Joined:
    Feb 22, 2011
    Posts:
    175
    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:
    https://gitlab.com/atteneder/ktxunity

    It's jobified and transcodes off the main thread. The actual GPU upload has to be on the main thread though (Unity constraint)
     
    tonytopper, deus0, FracEdd and 2 others like this.