Texture2D.LoadImage异步加载

4

简短的问题,

我从服务器下载了一张纹理(185MB的合图)。但是使用Texture2D.LoadImage将字节创建为纹理会导致应用程序阻塞约4秒钟。如何异步创建此纹理?

// 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);
2个回答

2

OP:

如何异步创建纹理?

简短回答

你不能,“使用C# API创建纹理(如new Texture2D)必须始终在主线程上调用”

详细回答

.NET 4.x 带来了通过 async/await 实现的异步 I/O 和异步计算支持。它们通常没有任何使用障碍,但在 Unity 3D 中存在一些问题,尤其是在计算方面。

您下面的代码是一个I/O绑定的示例,并且是异步 I/O 的一个例子,Unity不应该有任何问题。 会造成问题的部分将是纹理的创建。请继续阅读。

WebResponse atlasTextureResponse = await atlasTextureRequest.GetResponseAsync();

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

在Unity中创建纹理是一个计算密集型的操作,它是计算操作的一个示例。要在后台执行计算操作,通常需要执行以下操作:

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

虽然这样也可以,但我们想要创建一个Texture2D,因此我们可以尝试做如下操作:

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;
    }

现在,虽然CreateTexture2D将在线程池线程中运行并且不会阻塞主线程,但由于它在与主线程不同的线程中运行,代码在创建Texture2D时会失败

C#创建的纹理必须在主线程中创建。您可以尝试设置Texture.allowThreadedTextureCreation = true,但这也不起作用。Unity在下面最好地解释了这一点。

Unity(我强调):

Texture.allowThreadedTextureCreation

描述

允许Unity内部在任何线程上执行纹理创建(而不是专用渲染线程)。

注意:C# API用于纹理创建(例如new Texture2D)必须始终在主线程上调用。该设置不改变此要求。


最后一部分让我想知道allowThreadedTextureCreation到底是用来做什么的。对我来说,它读起来像是它所要做的唯一的事情并没有起作用。 - pete

1

Micky的回答的基础上进行扩展

目前,加载纹理的唯一内置(无需额外本地代码)非阻塞方式是使用

UnityWebRequestTexture.GetTextureCoroutine

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);
        }
    }
}

这也适用于设备上的本地文件路径。

如果直接使用不是一个选项,您仍然可以异步下载并临时存储文件到您的设备上,然后使用UnityWebRequestTexture.GetTexture来加载本地存储的文件,然后再删除本地文件。


1
它真的不会阻塞吗,甚至在DownloadHandlerTexture.GetContent中也不会?那么我们需要使用一个专为在线访问而设计的方法来加载本地纹理而不会阻塞吗?如果Unity能够做到这一点,为什么他们没有为常规的纹理加载方法制作呢? - pete
2
@pete,因为如果已经有一个方法可以实现这个功能,为什么要创建第二个呢? ;) 以 Android 设备为例,在流媒体资产文件夹中访问文本文件时也是这样使用的。因为它被压缩到应用程序包中,所以您不能使用普通文件 IO ... 这并不意味着它是后台的 Web 请求,那只是顶级 API 的名称。 - derHugo
1
我刚刚检查了一下,这种方法确实允许您异步执行Texture2D.LoadImage任务,在加载单个图像时将主线程阻塞的时间从约600毫秒降至约0毫秒(在我的测试设备上)。如果您已经在内存中拥有图像数据,则将图像保存到文件存储并重新加载比使用Texture2D.LoadImage()更为理想(除非阻塞主线程不是问题,例如在静态加载屏幕期间)。 - Sven Viking
(或者图片大小足够小,不会影响帧率的阻塞时间。) - Sven Viking

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接