Marcel Lorenz
Marcel Lorenz

Reputation: 453

Texture2D.LoadImage async

Simple and short question,

I download a texture from a server (185MB texture atlas). But creating the texture out of the bytes using Texture2D.LoadImage blocks the application for about 4 seconds. How can I create this texture asynchronously?

// Load texture atlas texture data
HttpWebRequest atlasTextureRequest = WebRequest.CreateHttp(EntityDatabase.ATLAS_TEXTURE_PATH);
WebResponse atlasTextureResponse = await atlasTextureRequest.GetResponseAsync();

byte[] atlasTextureData = await EntityDatabase.ReadStreamAsync(
    atlasTextureResponse.GetResponseStream(),
    (int)atlasTextureResponse.ContentLength
);

EntityDatabase.TextureAtlasTexture = new Texture2D(1, 1);
EntityDatabase.TextureAtlasTexture.LoadImage(atlasTextureData);

Upvotes: 5

Views: 7731

Answers (2)

derHugo
derHugo

Reputation: 90639

Extending on Micky's answer

Currently the only built-in (without the need of additional native code) non-blocking way to load a texture would be using

UnityWebRequestTexture.GetTexture in a Coroutine

Example from the API

void Start()
{
    StartCoroutine(GetText(SOME_URL));
}

IEnumerator DownloadTexture(string url)
{
    using (UnityWebRequest uwr = UnityWebRequestTexture.GetTexture(url))
    {
        yield return uwr.SendWebRequest();

        if (uwr.result != UnityWebRequest.Result.Success)
        {
            Debug.Log(uwr.error);
        }
        else
        {
            // Get downloaded asset bundle
            var texture = DownloadHandlerTexture.GetContent(uwr);
        }
    }
}

This also works for local file paths on your device.

If this directly is not an option you could still download and store the file temporary on your device asynchronously and then use the UnityWebRequestTexture.GetTexture in order to load that locally stored file and then delete the local file again.

Upvotes: 1

user585968
user585968

Reputation:

OP:

How can I create this texture asynchronously?

Short answer

You can't, "The C# API's for Texture creation (E.g. new Texture2D) must always be called on the main thread".

Longer answer

The async/await support brought to us via .NET 4.x allows for asynchronous I/O and asynchronous compute. Generally there are no barriers to their use but in Unity 3D there is, particularly compute.

Your code below is I/O bound and is an example of asynchronous I/O and Unity should not have any qualms with it. The bit that will pose a problem will be your texture creation. Read on.

WebResponse atlasTextureResponse = await atlasTextureRequest.GetResponseAsync();

byte[] atlasTextureData = await EntityDatabase.ReadStreamAsync(
    atlasTextureResponse.GetResponseStream(),
    (int)atlasTextureResponse.ContentLength
);

Creating a texture in Unity is CPU-bound and is an example of a compute operation. To perform compute operations in the background you would normally do something like:

var stuff = 
    await Task.Run (() => ConstructSomethingThatTakesALongTimeSynchonosouly ();

...which is fine but we want to create a Texture2D so we could try to do something like:

public class LoadTextureAsyncExample : MonoBehaviour
{
    // Start runs in the main thread
    private async void Start()
    {
        var textureBytes = await DownloadTextureAsync();
        _texture = await Task.Run(() => CreateTexture2D(textureBytes));
    }
 
    private async Task<byte[]> DownloadTextureAsync()
    {
        // essentially your code above.  
        // This method will be a mixture of main/pool thread
    }

    private Texture2D CreateTexture2D(byte[] textureBytes)
    {
        // this method is running in a thread pool thread
        var texture = new Texture2D(1, 1);
        texture.LoadRawTextureData(textureBytes);
        return texture;
    }

...now whilst CreateTexture2D will run in a thread-pool thread and won't block the main thread, the code will fail at the point of creating a Texture2D due to it running in a thread different from the main thread.

Textures created by C# must be created in the main thread. You could try setting Texture.allowThreadedTextureCreation = true but that won't work either. Unity says it best below.

Unity (my emphasis):

Texture.allowThreadedTextureCreation

Description

Allow Unity internals to perform Texture creation on any thread (rather than the dedicated render thread).

Note: The C# API's for Texture creation (E.g. new Texture2D) must always be called on the main thread. This setting does not alter that requirement.

Upvotes: 3

Related Questions