Non-blocking loading and copying of large Texture2D's in C# for Unity

梦想的初衷 提交于 2019-11-30 19:36:46

The www.texture is known to cause hiccups when large Texture is downloaded.

Things you should try:

1.Use the WWW's LoadImageIntoTexture function which replaces the contents of an existing Texture2D with an image from the downloaded data. Keep reading if problem is still not solved.

WWW www = new WWW("file://" + filePath);
yield return www;
///////m_myTexture = www.texture;  // Commenting this line removes frame out
www.LoadImageIntoTexture(m_myTexture);

2.Use the www.textureNonReadable variable

Using www.textureNonReadable instead of www.texture can also speed up your loading time. I'be seen instances of this happening from time to time.

3.Use the function Graphics.CopyTexture to copy from one Texture to another. This should be fast. Continue reading if problem is still not solved.

//Create new Empty texture with size that matches source info
m_myTexture = new Texture2D(www.texture.width, www.texture.height, www.texture.format, false);
Graphics.CopyTexture(www.texture, m_myTexture);

4.Use Unity's UnityWebRequest API. This replaced the WWW class. You must have Unity 5.2 and above in order to use this. It has GetTexture function that is optimized for downloading textures.

using (UnityWebRequest www = UnityWebRequest.GetTexture("http://www.my-server.com/image.png"))
{
    yield return www.Send();
    if (www.isError)
    {
        Debug.Log(www.error);
    }
    else
    {
        m_myTexture = DownloadHandlerTexture.GetContent(www);
    }
}

If the three options above did not solve the freezing problem, another solution is copying the pixels one by one in a coroutine function with the GetPixel and SetPixel functions. You add a counter and set when you want it to wait. It spaced the Texture copying over time.

5.Copy Texture2D pixels one by one with the GetPixel and SetPixel functions. The example code includes 8K texture from Nasa for testing purposes. It won't block while copying the Texture. If it does, decrease the value of the LOOP_TO_WAIT variable in the copyTextureAsync function. You also have option to provide a function that will be called when this is done copying the Texture.

public Texture2D m_myTexture;

void Start()
{
    //Application.runInBackground = true;
    StartCoroutine(downloadTexture());
}

IEnumerator downloadTexture()
{
    //http://visibleearth.nasa.gov/view.php?id=79793
    //http://eoimages.gsfc.nasa.gov/images/imagerecords/79000/79793/city_lights_africa_8k.jpg

    string url = "http://eoimages.gsfc.nasa.gov/images/imagerecords/79000/79793/city_lights_africa_8k.jpg";
    //WWW www = new WWW("file://" + filePath);
    WWW www = new WWW(url);
    yield return www;

    //m_myTexture = www.texture;  // Commenting this line removes frame out

    Debug.Log("Downloaded Texture. Now copying it");

    //Copy Texture to m_myTexture WITHOUT callback function
    //StartCoroutine(copyTextureAsync(www.texture));

    //Copy Texture to m_myTexture WITH callback function
    StartCoroutine(copyTextureAsync(www.texture, false, finishedCopying));
}


IEnumerator copyTextureAsync(Texture2D source, bool useMipMap = false, System.Action callBack = null)
{

    const int LOOP_TO_WAIT = 400000; //Waits every 400,000 loop, Reduce this if still freezing
    int loopCounter = 0;

    int heightSize = source.height;
    int widthSize = source.width;

    //Create new Empty texture with size that matches source info
    m_myTexture = new Texture2D(widthSize, heightSize, source.format, useMipMap);

    for (int y = 0; y < heightSize; y++)
    {
        for (int x = 0; x < widthSize; x++)
        {
            //Get color/pixel at x,y pixel from source Texture
            Color tempSourceColor = source.GetPixel(x, y);

            //Set color/pixel at x,y pixel to destintaion Texture
            m_myTexture.SetPixel(x, y, tempSourceColor);

            loopCounter++;

            if (loopCounter % LOOP_TO_WAIT == 0)
            {
                //Debug.Log("Copying");
                yield return null; //Wait after every LOOP_TO_WAIT 
            }
        }
    }
    //Apply changes to the Texture
    m_myTexture.Apply();

    //Let our optional callback function know that we've done copying Texture
    if (callBack != null)
    {
        callBack.Invoke();
    }
}

void finishedCopying()
{
    Debug.Log("Finished Copying Texture");
    //Do something else
}

Eventually this problem was solved by creating a C++ Plugin (built through Android Studio 2.2) that makes use of "stb_image.h" for loading the image, and OpenGL to generate textures and map a set of scanlines onto the texture over multiple frames. The texture is then handed over to Unity through Texture2D.CreateExternalTexture().

This method does not make the work asynchronous but spreads the loading cost over multiple frames removing the synchronous block and subsequent frame out.

I wasn't able to make the texture creation asynchronous because in order for the OpenGL functions to work you are required to be running the code from Unity's main Render Thread, so functions must be called through GL.IssuePluginEvent() - Unity's docs use the following project to explain how to make use of this functionality: https://bitbucket.org/Unity-Technologies/graphicsdemos/

I've cleaned up the test repo I was working on and written instructions in the README to make it as easy as possible to understand the final solution I came to. I hope that it will be of use to someone at some point and that they won't have to spend as long as I've done to solve this problem! https://github.com/NeoSouldier/Texture2DTest/

The problem is, Unity will always load the entire image in memory when you create a Texture2D, no matter the method used. This takes time, and there's no way to avoid it. It won't parse the file and get bits of the image data, or load slowly per frame. This happens with any instantiation of anything in Unity, be it images, terrain, objects created by Instantiate(), etc.

If you require only the image data for some processing, I would suggest using a library like libjpeg or libpng (in a C# rendition of it) to get the data in another thread (you can use another thread as long as you don't invoke Unity methods), but if you have to display it, I don't see a way you're going to stop the lag.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!