Texture2D.LoadImage too slow, anyway to use threading to speed it up ?

I have images coming in from the network, they are in jpg format(bytes), average 100-200 image per second. In order to display them on the screen i must convert them into texture first using Texture2D.LoadImage, but the problem is that function can only be called from the main thread, it takes a long time to convert even 1 image, around 20-50 ms per image, so each second the main thread can only do up to ~30 images.

Problem can be solved if i able to move most of the work to other thread, assuming the system has multiple cores, im wondering if there is a way i can convert the byte streams into raw texture data so it can help speed things up ? Or are there any other methods ?

thanks.

The following code works, it will take a long time in threading to process the image file, but will load almost instantly in the main thread. Just wandering if there are better alternatives ?

using UnityEngine;
using System.Collections;
using System.IO;
using System.Diagnostics;
using System.Drawing;
using System.Threading;

public class clsLoadImage : MonoBehaviour {
    MeshRenderer mr;
    Texture2D tx;
    Thread threadProcessImage;
    Image img;
    byte[] bytTx;

    void Awake() {
        img = Image.FromFile(@"c:/temp/3.jpg");
       
        mr = GetComponent<MeshRenderer>();
    }

    void OnGUI() {
        if(GUI.Button (new Rect(0,0,100,100), "Process Image")) {
            threadProcessImage = new Thread(fct_ProcessImage);
            threadProcessImage.Start();
        }
        if (GUI.Button(new Rect(100, 0, 100, 100), "Load Image")) {
            fct_LoadImage();
        }
    }

    void fct_ProcessImage() {
        Stopwatch sw = new Stopwatch();
        sw.Start();

        MemoryStream msBMP = new MemoryStream();
        img.Save(msBMP, System.Drawing.Imaging.ImageFormat.Bmp);
        UnityEngine.Debug.Log("save to bitmap = " + sw.ElapsedMilliseconds + " miliseconds");

        byte[] bytBMP = msBMP.ToArray();
        int intPointer = 54;
        MemoryStream msTx = new MemoryStream();
        for (int cnt = 0; cnt < img.Width * img.Height; cnt++) {
            msTx.WriteByte(0);
            msTx.Write(bytBMP, intPointer, 3);
            intPointer += 3;
        }
        msTx.Position = 0;
        bytTx = msTx.ToArray();

        UnityEngine.Debug.Log("Process " + img.Width + "x" + img.Height + " image = " + sw.ElapsedMilliseconds + " miliseconds");
    }

    void fct_LoadImage() {
        Stopwatch sw = new Stopwatch();
        sw.Start();

        tx = new Texture2D(img.Width, img.Height, TextureFormat.ARGB32, false);
        tx.LoadRawTextureData(bytTx);
        tx.Apply();
        mr.material.SetTexture("_MainTex", tx);

        UnityEngine.Debug.Log("Loading " + img.Width + "x" + img.Height + " image = " + sw.ElapsedMilliseconds + " miliseconds");
    }

}

save to bitmap = 32 miliseconds
Process 1216x1216 image = 717 miliseconds
Loading 1216x1216 image = 3 miliseconds

Were you able to find an asn

Were you able to an answer to your question? I am having a similar issue that I am tackling…

me too, whole thing freeze for 5 seconds while loading and processing images using WWW

1 Like

Hello, I’ve also been facing this issue. WWW. is ridiculously slow. Also I tried the new substitution for www, unity "
using UnityEngine.Networking;" namespace, same ressults, I’m sure there is a bug because it can take like 10 seconds and some other times a minute or so, or even almost “fast” for very same scene loading very same tank skin, its all pretty random, I’ve been reading, some people say is a network adapter issue in unity blablabla, dunno, whatever, I found the way to fast load textures.

In some other threads people suggest texture2d.LoadImage(yourByetArray), but while this is faster, it is still some some seconds freeze, the problem is not in the reading the
fileData = File.ReadAllBytes(fullpath);
the delay takes place in converting the byte array into a texture, so, the tex.LoadImage(byte[ ]).

Some peopla said that that .loadRawTextureData(byte[ ]) was the fastest so I had tro try, and yes it is VERY fast. I think, from hwat I did read that you can load even a non raw saved textures by striping parts of the byte[ ] but that I’ve not yet tested. I’ve an skin editor and hwat I do is that before saving the texture, witch is already a texture2D type

                if (rt)
                {
                    Texture2D tx = new Texture2D(rt.width, rt.height, TextureFormat.ARGB32, false);
                    RenderTexture.active = rt;
                    tx.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
                    tx.Apply();

                    //byte[] bytes = tx.EncodeToJPG(95);
                    //byte[] bytes = tx.EncodeToPNG();
                    byte[] bytes = tx.GetRawTextureData();

                //file
                string fullPath = dirPath + textName;
                //Debug.Log("fullPath" + fullPath);
                 System.IO.File.WriteAllBytes(fullPath, bytes);               
                }

you can see how I was encodign to jpg or png before, notice ARGB32 texture format, you need same when loading texture. false is I for mipchain, set it true if when loading you’re going to set true otherwise it will say it lacks info.

when loading do this:

    public static Texture2D LoadSkin(string fullpath)
    {
        Texture2D tex = null;
        byte[] fileData;

        if (File.Exists(fullpath))
        {
            fileData = File.ReadAllBytes(fullpath);
            tex = new Texture2D(4096, 4096,TextureFormat.ARGB32,false);
            //tex.LoadImage(fileData); //..this will auto-resize the texture dimensions.
            tex.LoadRawTextureData(fileData); //.no longer.this will auto-resize the texture dimensions.
            tex.Apply();
        }

        return tex;
    }

notice you need to feed the texture size, as theese are skins I’ve hardcoded but you could feed this as a param I guess, also mip generation in this example is false but when I’ve all running will set to true. In my first attemp I miss put texture format with RGBA32 and colors where glitched, naming so similar :smile:

hope this help. This aint ASYNC, but is fast enough. I found an async method posted in forums BUT it relies in too much thir party libs, good luck doing multyplatform with that.
Further, I was making a mistake, if you are applying theh skin (in my case a tank) to all objects, same texture is a different instance on each part because LoadSkin() is called for each material, this is poorly coded, I’ve checked and each have a texture with a different id, instead, you should load the skin in a sort of SkinDispatcher with something like a dictionary like <string SkinID, texture2D skin> and you feed the material with that single instance of the skin. I’ve yet to do that part.

Hope this helps to others.

side note, may be textureload now is async behind scenes now, how to read more about this
https://docs.unity3d.com/Manual/AsyncTextureUpload.html

I have similar problem, I have a player walking around the scene, and depending on where the player is at any given time, i have to DOWNLOAD maybe 30 JPG images from internet, convert them to textures, then apply them to cubes near the player.

So i have to do all this while the player is walking around without the walk animation getting jittery because of all this texture downloading and rendering. Right now its really horrible, it looks like an earthquake is happening in the scene when i start loading and rendering all these textures because of the extreme jitter.

So i thought of putting all the image downloading and rendering in a separate thread from the main thread.

Right now i wrote the thread so that the downloading is fairly fast and smooth without any jitter. The next thing is to avoid using LoadImage because that has to be done on the main thread and its really slow, this LoadImage is whats causing the jitter, i assume because its decoding the JPG in the main thread.

So i think the solution is to be able to decode the JPG from inside the separate thread, then send the decoded data back to the main thread do LoadRawImageData which is fast.

So im trying to find the source code for a JPG image decoder written in C# that i can use in a thread, there are several of the out there…

https://keyj.emphy.de/nanojpeg/

Does anybody think this approach will work? I havnt finished working on it yet, still trying to get one of these decoders to work…

Another way is probably to save the JPG as a raw bitmap before hand, and use that with the LoadRawImageData instead of the JPG, so that no decoding is necessary so it may eliminate the jitter, but i see that bitmap files can be maybe 6 times as large as a jpg file, which will cause a longer download time.

Why do we have to go through all this trouble? Why dont Unity just make an asyc version of LoadImage??? instead of constantly creating new features while ignoring this HUGE problem that still exists in all of their versions.

Thought they supposed to be image experts since the know how to make a GAME ENGINE which should be WAY MORE DIFFICULT than creating a async LoadImage.

it is crazy they do not offer something fast, async and multiplatform out of the box… but this is classic unity style.

So as a follow up I did what I commented above, the skin dispatcher and it works pretty fast.

  //SKIN DISPATCHER // llamado desde skinManager
    public void SkinDispatcher(string fullPath, Material m)
    {
        //la textura ya exsite
        if (customSkinDic.ContainsKey(fullPath))
        {
            m.SetTexture("_MainTex", customSkinDic[fullPath]);
        }

        //texture is not loaded
        else
        {
           Texture2D skin= Utils.LoadSkin(fullPath);
           customSkinDic.Add(fullPath, skin);
           m.SetTexture("_MainTex", customSkinDic[fullPath]);

        }
    }

This avoids same texture be loaded twice.

Still, it has minimal impact on load times but that hickup is breaking something in my gameplay, sure I’ve poorly implemented message system etc… so I may change the dispatcher to load all the tank skins BEFORE theese are instantiated.

ALSO I’ve noticed that textures you load with raw data ARE KEPT IN THE GPU unleess you unload them somehow something I’ve not yet done

After unity 2017, you can try UnityWebRequestTexture.GetTexture.

Old thread, but if anyone reading this needs an async texutre importer then I just made one: matiaslavik/unity-async-textureimport: Async texture import for Unity game engine. A faster alternative to Texture2D.Load. Want to support my projects? Donate some money to Red Cross and send me a screenshot/message, and I'll be greatly motivated! :) Any amount is welcome! - Codeberg.org

It uses FreeImage to load the texture and generates mipmaps on a separate thread. You can use it from a coroutine, and yield until loading is done, without freezing the main thread.

4 Likes

This is great! What platforms does it support? Looking to get this working on iOS/Android

HI Hello, I am a Unity VR developer from China. I use the code you wrote: https://codeberg.org/matiaslavik/unity-async-textureimport. It’s really great.

My code level is very basic, I hope to get your help. I now use freeimage to decode images, and your code uses the tex.LoadRawTextureData(texData.data); method when loading, so that when loading 4K images, there will be freezes.

I want to upload the GPU program loading directly, you can see the code of this friend: https://github.com/hecomi/UnityCustomTextureUpdate. The problem is that only PNG can be decoded, and many image formats can be decoded with freeimage.

Can you teach me how to get texture ID and texture binding code with Freeimage? I hope to get your reply, my email: gulanxiu13@126.com. thank you very much.

an alternative is to save the image, and load it directly with “UnityWebRequestTexture.GetTexture”, since this function loads images and does not block the main thread.

public void RecibirDatos(byte[ ] TexByte)
{
File.WriteAllBytes(Application.persistentDataPath + “/xd”, TexByte);
StartCoroutine(trabcou());
}
IEnumerator trabcou()
{
UnityWebRequest www = UnityWebRequestTexture.GetTexture(“file:///” + Application.persistentDataPath + “/xd”, true);
yield return www.SendWebRequest();

while ()
{
yield return www;
}
Texture2D x2 = new Texture2D(2, 2, TextureFormat.ARGB32, false);
if (www.isNetworkError || www.isHttpError)
{

}
else
{
x2 = ((DownloadHandlerTexture)www.downloadHandler).texture;.texture;)
x2.name = “uwu”;
}
yield return null;
File.Delete(Application.persistentDataPath + “/xd”);

//x2 load and not block main thread
//
}

1 Like

Just to update my own thread, i actually found a better way to solve this problem once and for all, with a paid asset

I am not advertising for the asset, im just sharing my solution.

The delay happen on the conversion, unity cannot use jpeg directly, so it need to convert jpeg to whatever format unity can use(depends on platform), once the conversion is done, loading it is extremely fast, so you will need to move the conversion to other thread(s) and only load the image in the main thread.

  1. Use the asset shown above to decode jpeg into byte array in thread.
  2. Use texture2D.LoadRawTextureData(decodedByteArray) and texture2D.Apply() in main thread.

I found another way to load async, its not ideal but works. Save theese textures in a resources folder, and then call a resources.loadasync

I have just encountered the problem, bought and downloaded that asset and its crashing. I have sent them a message, but until then - do not buy the asset.

Will this work in build though? I didn’t think builds had access to resources folder?

It works, but it is not recommended. You may have memory usage issues because using resources folder is a mess in runtime to memory manage. On Xbox I noticed it takes a lot of memory just on start, I don´t know if it just allocates this memory to match the resources folder or what exactl is going on, but I guiess this is why unity does not recommend to use resources folder, despite of how convenient it is.