x


WWW Texture access takes a long time

Hi,

I'm having issues with the "WWW.texture" property and wondering if anyone has a suggestion. The WWW class asynchronously loads it's data from a remote source, which is a good thing. The problem I'm having, however, is that accessing the "WWW.texture" property after that load takes an extremely long time to process. I'm trying to unwrap that texture from within the Update() method, so the extensive delay is causing a significant decrease in frame rate. Further, I have to load a large set of image textures in parallel, so the compounded delays is dragging my frame rate to a crawl temporarily.

That's the problem... Now I'm trying to determine a good solution. I've tried managing the unwrapping of the texture from a secondary thread, but the "WWW.isDone", "WWW.error" and "WWW.texture" method all throw errors indicating that they MUST be called on the main thread. That takes a multi-threaded solution around WWW out of the mix. Anyone have any other thoughts about how to resolve this issue?

Thanks in advance...

more ▼

asked Dec 15, 2010 at 05:10 AM

BillyK gravatar image

BillyK
33 9 6 9

(comments are locked)
10|3000 characters needed characters left

3 answers: sort voted first

I'm not sure what you mean by "unwrapping the texture", nor am I sure why you'd want to do that in Update, which runs once every frame. Uploading textures to the GPU takes time no matter what, but it's not something I'd call an "extremely long time", and is generally a small fraction of a second (enough to cause a visible framerate hiccup unless the texture is tiny, but that's not something you can avoid). You should probably post code.

The example from the docs for WWW.texture works with no issues, but an alternate technique is to use WWW.LoadImageIntoTexture, which is recommended if you're downloading textures more than once, since WWW.texture stays in memory unless destroyed. Not sure if that has any effect on speed though.

more ▼

answered Dec 15, 2010 at 06:21 AM

Eric5h5 gravatar image

Eric5h5
114k 59 218 715

(comments are locked)
10|3000 characters needed characters left

I finally came up with a way to handle this situation. The heart of the problem is that processing "WWW.texture" several times within the same "Update()" call adds up and chokes the frames per second. One or two would be fine, but 96 (which is the number of images I'm queuing) is not. The trick I used in order to resolve this issue was to create a GameObject which acts as a queue for the textures being unwrapped. That GameObject works through a queue during each "Update()" call, processing a single texture each call. This effectively spreads out the texture calls, rather than allowing them to stack up and choke the frame rate.

The code for the class used to act as the queue is as follows:

using UnityEngine; using System.Collections;

public class TextureLoadManager : MonoBehaviour { private static TextureLoadManager _instance = null;

 public static TextureLoadManager FindTextureLoadManager()
 {
     return _instance;
 }

 private Queue _waitingTextureQueue = null;

 void Awake()
 {
     _instance = this;

     Queue queue = new Queue();
     _waitingTextureQueue = Queue.Synchronized(queue);
 }

 public TextureUnwrapper CreateTextureUnwrapper(WWW loadingTexture)
 {
     TextureUnwrapper textureUnwrapper = new TextureUnwrapper(loadingTexture);
     _waitingTextureQueue.Enqueue(textureUnwrapper);
     return textureUnwrapper;
 }

 void Update()
 {
     if (_waitingTextureQueue.Count > 0)
     {
         ((TextureUnwrapper)_waitingTextureQueue.Dequeue()).Unwrap();
     }
 }

 public class TextureUnwrapper
 {
     private WWW _loadingTexture;
     private Texture2D _texture;

     private bool _isUnwrapped = false;

     public TextureUnwrapper(WWW loadingTexture)
     {
         _loadingTexture = loadingTexture;
     }

     public Texture2D Texture
     {
         get { return _texture; }
     }

     public IEnumerator WaitForUnwrapCompletion()
     {
         while (!_isUnwrapped)
         {
             yield return 0;
         }
     }

     public void Unwrap()
     {
         _texture = _loadingTexture.texture;
         _loadingTexture = null;
         _isUnwrapped = true;
     }
 }

}

An example of the code I'm using in order to process textures through that queue would be the following:

void Update() { if (!_initCoverMaterial) { _initCoverMaterial = true;

         StartCoroutine(UpdateCoverMaterial());
     }
 }

 private IEnumerator UpdateCoverMaterial()
 {
     if (_coverMaterial != null && _iconImageSource != null && _iconImageSource.Trim().Length > 0)
     {
         WWW loadingImage = null;

         try
         {
             loadingImage = new WWW(_iconImageSource);
         }
         catch (Exception genErr)
         {                
         }

         if (loadingImage != null)
         {
             yield return loadingImage;

             if (loadingImage.error == null)
             {
                 TextureLoadManager.TextureUnwrapper textureUnwrapper = _textureLoadManager.CreateTextureUnwrapper(loadingImage);
                 yield return StartCoroutine(textureUnwrapper.WaitForUnwrapCompletion());
                 _coverImage = textureUnwrapper.Texture;

                 _coverMaterial.mainTexture = _coverImage;
             }

             loadingImage.Dispose();
             loadingImage = null;
         }
     }

     yield return 0;
 }

more ▼

answered Dec 16, 2010 at 01:06 AM

BillyK gravatar image

BillyK
33 9 6 9

(comments are locked)
10|3000 characters needed characters left

"Unwrapping the texture" was referring to the operation that occurs when "WWW.texture" is called, considering the fact that it does not return immediately, I can only assume that the data is turned into a Texture2D object at that point, which makes sense, and that process takes some time. Looking at a deep profile of the operation, a small sampling of the cases when that property is called for chew up times from 77 - 83ms a piece. I'm loading several images in parallel (i.e. a wall of images, essentially), and if several of the WWW instances complete the download within the same approximate time, the calls for textures adds up, dropping the frame rate dramatically (i.e. < 6fps).

With regards to the reason for why the "Update()" method is used, rather than the "Start()" or "Awake()" method is that the list of images to load is dynamically obtained from a back-end service and the GameObject is present and visible prior to the point at which that call returns. That being said, the access of the "WWW.texture" property occurs only once, since I use a lock in order to ensure that fact. The following is an example of the code I'm currently using:

void Update() { if (!_initCoverMaterial) { // This lock disallows multiple processing attempts _initCoverMaterial = true;

     StartCoroutine(UpdateCoverMaterial());
 }

}

private IEnumerator UpdateCoverMaterial() { if (_coverMaterial != null && _iconImageSource != null && _iconImageSource.Trim().Length > 0) { WWW loadingImage = null;

     try
     {
         loadingImage = new WWW(_iconImageSource);
     }
     catch (Exception genErr)
     {
     }

     if (loadingImage != null)
     {
         yield return loadingImage;

         if (loadingImage.error == null)
         {
             yield return (_coverImage = loadingImage.texture);
             _coverMaterial.mainTexture = _coverImage;
         }

         loadingImage.Dispose();
         loadingImage = null;
     }
 }

 yield return 0;

}

The code works just fine, but the problem is that I have several of these running in parallel to ensure that all images appear as quickly as possible.

Any thoughts about an alternate approach? As I mentioned, all Texture2D and WWW methods appear to require that they occur on the main thread, so a multi-threaded solution doesn't appear to work (unless someone knows a work-around). Is there some way to cause the WWW instance to pre-generate the Texture during it's asynchronous operation (probably not, due to the "run on the main thread" restriction in the related classes, but maybe someone knows a trick)?

more ▼

answered Dec 15, 2010 at 07:44 AM

BillyK gravatar image

BillyK
33 9 6 9

I edited my answer with a mention of LoadImageIntoTexture. The Update function here still seems entirely superfluous; instead of constantly polling _initCoverMaterial I'd just call UpdateCoverMaterial directly when needed.

Dec 15, 2010 at 08:23 AM Eric5h5

I can't, actually. The callback used to obtain the list of images is in a separate thread. As a result, I'm not able to process the WWW methods and properties, since they have to be on the main thread. I'm polling from the "Update()" method to make sure that the processing occurs on the main thread whereas the callback does not.

Dec 16, 2010 at 12:54 AM BillyK

That being said... I actually figured out a way to resolve the issue (see below).

Dec 16, 2010 at 12:55 AM BillyK

I managed to move part of the image decoding and resizing to a separate thread by using a third-party jpeg library:

http://answers.unity3d.com/questions/249993/typeloadexception-trying-to-use-fjcore-fluxjpeg.html

Unfortunately the resulting code is very slow, so I posted a suggestion here:

http://feedback.unity3d.com/forums/15792-unity/suggestions/2840580-async-image-decoding-and-resizing-for-www-texture

May 10, 2012 at 09:52 AM stijn
(comments are locked)
10|3000 characters needed characters left
Your answer
toggle preview:

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this question

By Email:

Once you sign in you will be able to subscribe for any updates here

By RSS:

Answers

Answers and Comments

Topics:

x3693
x858
x667
x298

asked: Dec 15, 2010 at 05:10 AM

Seen: 5028 times

Last Updated: May 10, 2012 at 09:52 AM