Search Unity

Question Help me!!! Load texture by folder ( asynchronously )

Discussion in 'Scripting' started by bebo77, Jun 5, 2023.

  1. bebo77

    bebo77

    Joined:
    Aug 3, 2012
    Posts:
    84
    Hi everyone,

    I'm loading some textures from an external folder, but they are 4k in size, so they make my game stutter while loading.

    Is there a way to load them asynchronously?

    this is the code i'm using:

    Code (CSharp):
    1.         void LoadTextureSkin(string m_path, string m_reference ,int materialIndex)
    2.         {
    3.             Debug.Log(m_path);
    4.  
    5.             if (m_path == "") return;
    6.  
    7.             string filename = m_path;
    8.             var rawData = System.IO.File.ReadAllBytes(filename);
    9.             Texture2D tex = new Texture2D(2, 2); // Create an empty Texture; size doesn't matter (she said)
    10.             tex.LoadImage(rawData);
    11.  
    12.             characterCustomizzation.m_skinnedMeshRenderer.materials[materialIndex].SetTexture(m_reference,tex);
    13.         }
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,745
    The profiler (Window -> Analysis -> Profiler) can tell you for sure but the bulk of your hitch is likely line 8 and line 10.

    Some fraction of this COULD be multi-threaded, but not all of it.

    Line 8 above , the streaming in of the data could go in a thread, then marshal the data over to the Unity main thread, where you must be in order to execute lines 9, 10 and 12.

    I have no idea how much that might help you but it's not a heavy lift to try.

    As for getting the data over to the main thread, Thread Ninja can help here (free asset store product), or you can use something like this:

    Delegate queue to marshal tasks from other threads to the main thread:

    https://forum.unity.com/threads/how...-everytime-it-is-called.1148354/#post-7370375
     
    orionsyndrome and Bunny83 like this.
  3. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,005
    Note that depending on the actual file format the actual loading and decoding process may be quite expensive. Decoding pngs or jpg is not that trivial. So if you really need to load a lot of images in the background, you may want to use an external image parsing library. That way you can load and decode the images on a separate thread and prepare a Color32[] inside the thread. So you only have to create the Texture2D object on the main thread and assign the pixel data.

    Even better performance wise than using SetPixels32 is the relatively new LoadRawTextureData method. However that requires you to actually setup the image format the exact way that the GPU expects it in memory. This depends on the used TextureFormat and on the mipmap count. A bit easier would be SetPixelData which allows you to set the different mipmap levels separately.
     
    orionsyndrome likes this.
  4. tsukimi

    tsukimi

    Joined:
    Dec 10, 2014
    Posts:
    80
    I haven't actually test the code, but this uses Task to run asynchronusly, which would hopefully run on another thread (if the hardware supports).
    Code (CSharp):
    1.  
    2.     private IEnumerator LoadCoroutine(string m_path)
    3.     {
    4.         var task = Task.Run(() => System.IO.File.ReadAllBytesAsync(m_path));
    5.         while (!task.IsCompleted) {
    6.             yield return null;
    7.         }
    8.         Texture2D tex = new Texture2D(2, 2); // Create an empty Texture; size doesn't matter (she said)
    9.         tex.LoadImage(task.Result);
    10.         // and the rest works...
    11.     }
    according to this post, you can't load bytes into Texture2D outside Main thread, so you can only run the file-reading part asynchronusly.
    If you have UniTask in your project, or your project is 2023.1 above, than you can directly use async function and then
    await ReadAllBytesAsync()
    .
     
  5. bebo77

    bebo77

    Joined:
    Aug 3, 2012
    Posts:
    84
    Thanks everyone for the replies,

    At the moment I'm still studying it, but it seems that the problem is executing tex.LoadImage(rawData);

    I tried with LoadRawTextureData but for some reason I just get completely white images.

    Using the corutine described by tsukimi however texture loading continues to affect project performance by crashing the game during loading time.
     
    Yuchen_Chang likes this.
  6. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,005
    Do you even understand what "LoadRawTextureData" does? It can not load any file format. It expects raw texture data as a series of color values that make up the image. That means, as I said in my answer, that you have to load and decode the image yourself, probably with a third party library to actually get and setup the raw data yourself. The advantage is that you could do all that in a separate thread and prepare those raw data asynchronously. Once done you would use LoadRawTextureData on the main thread to actually apply the data to the texture object and upload the data to the GPU. This should be faster as Unity doesn't have to decode the image anymore (since you would have done that yourself already). You can not just pass for example a jpeg or png file to LoadRawTextureData.
     
  7. bebo77

    bebo77

    Joined:
    Aug 3, 2012
    Posts:
    84
    I followed the Unity LoadRawTextureData documentation
    https://docs.unity3d.com/ScriptReference/Texture2D.LoadRawTextureData.html

    And I tryed to replace the manually written bytes[] with those loaded via System.IO.File.ReadAllBytesAsync(filename);


    It seemed like a working concept to me but at this point I think I'm not quite understanding how it works.
    I'm trying to study this... I'm not very knowledgeable on the subject.

    In particular I don't understand how to generate the correct Bytes from the image.
     
    Last edited: Jun 6, 2023
  8. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,745
    Read again what @Bunny83 wrote above, specifically:

    LoadImage() supports certain file formats by turning them into the correct image bytes.

    The Raw loader is for when you already have the bytes converted.
     
  9. bebo77

    bebo77

    Joined:
    Aug 3, 2012
    Posts:
    84
    So,
    I'm currently loading images more efficiently than the previous way.

    Code (CSharp):
    1.         IEnumerator GetTexture(string m_path, string m_reference, int materialIndex)
    2.         {
    3.             if (m_path != "")
    4.             {
    5.  
    6.                 string m_DataPath = Application.dataPath.Replace("Assets", "/");
    7.  
    8.                 using (UnityWebRequest uwr = UnityWebRequestTexture.GetTexture(m_DataPath + m_path))
    9.                 {
    10.                     yield return uwr.SendWebRequest();
    11.  
    12.                     if (uwr.result != UnityWebRequest.Result.Success)
    13.                     {
    14.                         Debug.Log(uwr.error);
    15.                     }
    16.                     else
    17.                     {
    18.                         // Get downloaded asset bundle
    19.                         var texture = DownloadHandlerTexture.GetContent(uwr);
    20.  
    21.                         characterCustomizzation.m_skinnedMeshRenderer.materials[materialIndex].SetTexture(m_reference, texture);
    22.                     }
    23.                 }
    24.             }
    25.         }

    But as much as they load faster the game keeps stuck for the few milliseconds of loading.
     
    Last edited: Jun 6, 2023
  10. bebo77

    bebo77

    Joined:
    Aug 3, 2012
    Posts:
    84

    yes, I tried to figure out how to decode by myself but with poor results... at the moment I'm also exploring other possibilities.

    The goal is always to be able to load the images without blocking the game during the loading phase, In order to eventually insert a loadBar etc...
     
  11. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,745
    You're doing a lot of weird engineering here.

    Is this for runtime? Your first post would imply that.

    If so your entire directory structure is GONE. You don't have Assets/ or any of the other files.

    If you MUST have actual raw image data then you can look into StreamingAssets but it won't necessarily address your original concerns about hiccups.

    Here's some more readings:

    https://docs.unity3d.com/Manual/ScriptingAssets.html
     
  12. bebo77

    bebo77

    Joined:
    Aug 3, 2012
    Posts:
    84
  13. Magnilum

    Magnilum

    Joined:
    Jul 1, 2019
    Posts:
    241
    Hi everyone, I know that is been 7 months after the last post on this forum but I am facing a problem and don't know how to fix it so maybe you could help me.

    I tried the simple way to load an image from a external folder of the project at runtime:

    Code (CSharp):
    1. byte[] bytes = File.ReadAllBytes(imagePath);
    2. Texture2D texture= new Texture2D(1, 1);
    3. texture.LoadImage(bytes);
    This works fine but the only problem is, my images are 16kx16k and it takes almost 2 minutes and 30 seconds for only one image to be loaded.

    I heard it is possible to save the cache of this loading and reuse it for the next loading of the same image. But I have not found any resources of this process.

    Do you have any idea on how to do it?
     
  14. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,932
    Just use the async version of the method instead.

    And then just hold onto the data so you don't have to read it again.
     
  15. DragonCoder

    DragonCoder

    Joined:
    Jul 3, 2015
    Posts:
    1,700
  16. uoitaliareborn

    uoitaliareborn

    Joined:
    Sep 5, 2020
    Posts:
    1