2D Toolkit Forum

2D Toolkit => Support => Topic started by: jmcguirk on July 12, 2013, 11:16:52 pm

Title: Unload Textures
Post by: jmcguirk on July 12, 2013, 11:16:52 pm
Howdy!

Just upgraded our code base to 2.0 and wanted to get a bit of clarity on best practices around using UnloadTextures

From what I'm seeing, UnloadTextures for a sprite collection loops over the Texture2Ds associated with the collection and calls unload on them. Which is awesome, and I've confirmed in the memory profiler that the memory associated with that atlas is returned.

However, when I later attempt to create a new Tk2DSprite (one from a prefab) that references a sprite in the atlas, I'm seeing the sprite rendered as corrupted.

I think this is because Unity will not Automatically reload the Texture2D for you, and requires you to do another RSC.Load() call to fetch the texture again. I've made some slight tweaks to the SpriteCollection export, process - wondered if you'd agree they're necessary to support Reuse of texture atlases that have been unloaded.


Does this make sense? is there another way to reuse a texture atlas that has been unloaded in this way?

Second question - more broadly, do you think it makes sense to invest in a ref counter autounload with tk2dsprite? E.g. (based on configuration) Automagically eject the texture atlas when the last tk2dsprite referencing that atlas is destroyed()? Then I think we'd wind up with a more intuitive system where the atlas would load/unload only when being used.
Title: Re: Unload Textures
Post by: unikronsoftware on July 13, 2013, 02:43:08 pm
1. This won't actually be necessary. If you can reproduce this issue in a minimal project - i.e. unload and then Unity not rebuilding it, send it to support at unikronsoftware dot com and I'll look into this for you. Referencing the asset again should be enough to trigger Unity to load it in again, but perhaps I've missed something.

2. Refcounting, while possible, is really tricky with Unity. It will mean having to put everything into resources, and that can lead to build issues - Unity builds everything in Resources in one go without freeing memory between assets. Automatically loading as well, could be tricky. A large atlas (2048 / 4096) can take a fair bit of time to load in, and the last thing you want is a random hitch whenever. There are often better ways to partition things in Unity (eg. scenes and LoadLevelAdditiveAsync) to get better performance with this. I agree with refcounting in principle for something like this, but doing this in Unity is tricky and will lead to unexpected results.
Title: Re: Unload Textures
Post by: jmcguirk on July 13, 2013, 07:32:06 pm
On Item 1 - Reloading Textures

Went ahead and produced a minimal example, attached project file to zip.

Here's roughly how it's setup

- Two PNG textures (A and B)
- A Tk2DSpriteCollection that is created containing A and B
- Two GameObjects, each with a Tk2dSprite (pointing at A and B respectively) that are saved off into Prefab objects.
- A scene containing a test controller and a tk2dcamera
- Hitting "1" on the keyboard causes a Resources.Load + Instantiate of PrefabA
- Hitting "2" on the keyboard destroys our copy of PrefabA and calls UnloadTextures on the underlying spritecollection
- Hitting "3" on they keyboard attempts to Resources.Load + Instantiate of PrefabB.

Expected:

- Prefab A is rendered on step 1
- Prefab A is destroyed on step 2
- Prefab B is rendered on step 3

Observed:

- Prefab A is rendered on step 1
- Prefab A is destroyed on step 2
- Prefab B appears in the scene hierarchy, but no texture is rendered (I believe this is because the underlying texture has not been reloaded).

Project file attached.

On Item 2 -

A fair point that automatically unloading a texture atlas whenever a refcount hits 0 will lead to hitches - especially if the game has a better view into the life cycle of that texture atlas. I think maybe RefCounting and a call to UnloadUnusedSpriteCollectionTextures() may be a good balance.
Title: Re: Unload Textures
Post by: unikronsoftware on July 13, 2013, 09:07:03 pm
Thanks for the repro. I've got a fix, but I'm not confident enough to release that publicly just yet. Drop me an email at support at unikronsoftware.com and I'll give you a tiny patch to try out.
Title: Re: Unload Textures
Post by: radu on July 19, 2013, 01:23:22 am
Hi there,

I'm experiencing the same thing where unloading the textures and then trying to use sprites with those texures results in black blocks instead of properly reloading texures. Can you send me the patch as well? I just to the latest tk2d version from the asset store 10 minutes ago.

Thanks,
Radu
Title: Re: Unload Textures
Post by: unikronsoftware on July 19, 2013, 10:17:05 am
I will once we've established the patch works properly. It looks very much like a bug in Unity - so I'd like to check in the latest beta version of Unity before releasing this as a solution.
Title: Re: Unload Textures
Post by: radu on July 19, 2013, 05:23:21 pm
Do you have an ETA for that?

Thanks,
Radu
Title: Re: Unload Textures
Post by: unikronsoftware on July 19, 2013, 06:39:43 pm
in tk2dBaseSprite.cs, add this function:
   public void ReloadTextures() {
      renderer.sharedMaterial.mainTexture = renderer.sharedMaterial.mainTexture as Texture2D;
   }

And in your game, when you need to reload a previously unloaded sprite texture, call ReloadTextures(). Calling this function repeatedly is fine.
Title: Re: Unload Textures
Post by: int64 on August 15, 2013, 08:22:02 am
1) put all spriteCollectionData prefabs inside Resources folder;
2) add to tk2dSpriteCollectionData.cs:

Code: [Select]
public void ReloadTextures() {
foreach (Material mat in inst.materialInsts) {
mat.mainTexture = mat.mainTexture as Texture2D;
};
}

3) use this:

Code: [Select]
tk2dSpriteCollectionData scdToUnload = Resources.Load ("Unload_collection_name", typeof(tk2dSpriteCollectionData)) as tk2dSpriteCollectionData;
tk2dSpriteCollectionData scdToReload = Resources.Load ("Reload_collection_name", typeof(tk2dSpriteCollectionData)) as tk2dSpriteCollectionData;
scdToUnload.UnloadTextures();
scdToReload.ReloadTextures();

UPD:
I renamed all tk2dSpriteCollectionData prefabs from default "data.prefab" to collection name (as in attached image)
Title: Re: Unload Textures
Post by: fattie on September 15, 2013, 02:55:47 pm
Hey Radu and Int64,

"I'm experiencing the same thing where unloading the textures and then trying to use sprites with those texures results in black blocks instead of properly reloading texures. Can you send me the patch as well? I just to the latest tk2d version from the asset store 10 minutes ago."


I experienced EXACTLY this problem on a big project.  HOWEVER.  I recently found that the problem only exhibits IF you are running out of texture memory on iOS.

On the same project for unrelated reasons we massively reduced the memory usage on all iOS devices.  I then COULD NOT EXHIBIT the problem you describe no matter how hard I tried.

I want to know if you two guys project had a lot of memory use when you exhibited this problem?

What do you think?
Title: Re: Unload Textures
Post by: radu on September 16, 2013, 03:55:53 pm
Actually for me the behavior is different on iOS, it would just silently crash if I use the large textures. Using mipmaps and half res textures worked fine. So never got the black squares on iOS.

On Android, a couple of months ago when I wrote this I would get black squares. Haven't had any issues since I introduces the texture unloading as posted here. Also, only unikron's addition to tk2dBaseSprite worked (well, I added it as an extension method to ease upgrading to newer versions of tk2d). The ReloadTextures on the sprite collection as posted by int64 did not work for me.

 
Title: Re: Unload Textures
Post by: Velvety on November 06, 2013, 08:02:28 pm
in tk2dBaseSprite.cs, add this function:
   public void ReloadTextures() {
      renderer.sharedMaterial.mainTexture = renderer.sharedMaterial.mainTexture as Texture2D;
   }

And in your game, when you need to reload a previously unloaded sprite texture, call ReloadTextures(). Calling this function repeatedly is fine.

This seems to work except for sprites with animations that span multiple sprite collections?  If I call ReloadTextures() on a sprite that spans a few sprite collections then the sprite texture flickers to  incorrect textures as it animates.  If I call ReloadTextures() on a sprite that has animations fully contained in only 1 sprite collection it seems to work fine.

What can I do to ensure sprites that span multiple sprite collections reload correctly?

Thanks!
Title: Re: Unload Textures
Post by: unikronsoftware on November 06, 2013, 08:42:18 pm
To get it to work on a multi atlas spanning sprite, you'd need to set this on every frame of animation. What version of Unity are you on, I thought they'd fixed this issue in 4.2?
Title: Re: Unload Textures
Post by: Velvety on November 06, 2013, 09:04:58 pm
To get it to work on a multi atlas spanning sprite, you'd need to set this on every frame of animation. What version of Unity are you on, I thought they'd fixed this issue in 4.2?

I am on Unity v4.2.2f1 Pro.  To be clear, I am unloading all sprite collections between game transitions and then reloading everything I need for a specific area.  The pseudo code I am using looks something like this:

Code: [Select]
// first call DestroyImmediate() on all sprite gameObjects
...

// Now unload the sprite collection data textures
foreach (string key in spriteCollectionMap.Keys) {
spriteCollectionMap[key].spriteCollection.UnloadTextures();
spriteCollectionMap[key].spriteCollection.ResetPlatformData();
}

// clear the sprite collection map so there are no references left
spriteCollectionMap.Clear();

// now that everything is deleted ensure all the unused assets are removed, try to trigger the GC
Resources.UnloadUnusedAssets();

// now reload the needed sprite collection
tk2dSpriteCollection sc = (tk2dSpriteCollection)Resources.Load(collectionName, typeof(tk2dSpriteCollection));
if (sc != null) {
spriteCollectionMap.Add(new KeyValuePair<string, tk2dSpriteCollection>(collectionName, sc));
}

// load the unit (sprite) from the empty unit prefab and set its sprite collection
Transform unit = (Transform)Instantiate(emptyUnitPrefab, Vector3.zero, transform.rotation);

// get the sprite collection and set the sprite
tk2dSpriteCollection sc = spriteCollectionMap[0];
if (sc != null && sc.spriteCollection != null) {
        // get the tk2dsprite from our unit prefab
tk2dSprite sprite = (tk2dSprite)unit.gameObject.GetComponent<tk2dSprite>();
sprite.SetSprite(sc.spriteCollection, "spriteName");
// we must call ReloadTextures() to ensure the sprite texture is reloaded after any unloading has happened
sprite.ReloadTextures();
}


Am I missing anything in this process?  Thanks for your help.
Title: Re: Unload Textures
Post by: unikronsoftware on November 06, 2013, 09:15:33 pm
No its the reload thing that is causing the issue. Its a bug in Unity which I thought was fixed in 4.2 but clearly not. The reload textures function is only Reloading the first visible texture, thats all.

You could try this instead, its untested but it may work better...
Code: [Select]
void ReloadTextures() {
Texture tex = renderer.sharedMaterial.mainTexture;
foreach (Texture t in Collection.inst.textures) {
renderer.sharedMaterial.mainTexture = t as Texture2D;
}
renderer.sharedMaterial.mainTexture = tex as Texture2D;
}
Title: Re: Unload Textures
Post by: Velvety on November 06, 2013, 10:37:16 pm
No its the reload thing that is causing the issue. Its a bug in Unity which I thought was fixed in 4.2 but clearly not. The reload textures function is only Reloading the first visible texture, thats all.

You could try this instead, its untested but it may work better...
Code: [Select]
void ReloadTextures() {
Texture tex = renderer.sharedMaterial.mainTexture;
foreach (Texture t in Collection.inst.textures) {
renderer.sharedMaterial.mainTexture = t as Texture2D;
}
renderer.sharedMaterial.mainTexture = tex as Texture2D;
}

That change did not seem to fix the issue.  Do I need to loop through animation frames or something?
Title: Re: Unload Textures
Post by: unikronsoftware on November 06, 2013, 11:39:06 pm
No, if you call the old "ReloadTextures" every frame it is likely to fix it. It looks like Unity only reloads the texture after reassigning when drawing it? I'm not sure of performance implications, but its worth trying.
Title: Re: Unload Textures
Post by: undersan on April 16, 2014, 08:37:22 pm
I thought I'd revive this thread to talk about unloading textures from asset bundles.  I'm on Unity 4.3.4 and I test on Mac and iOS.

>Referencing the asset again should be enough to trigger Unity to load it in again
This is correct if the asset is from the Resources folder.

For a texture from an asset bundle, the behavior I see is that all references to that texture are set to null the instant you call Resources.UnloadAsset.  It's up to you to manually reload the texture and fix up references to the reloaded texture.  Here's what I'm doing:

Code: [Select]
Resources.UnloadAsset(myCollectionData.textures[0]);

// texture is now unloaded and related sprites can't be used (they will display as garbage on Mac and iOS)

// later...

// For now, I've hardcoded the texture I care about. In the future,
// I need a system to know the path for a collection's texture.
string path = "Units/1m_citizen/atlas0.png";

Texture reloadedTexture = assetBundle.Load(path, typeof(Texture)) as Texture;

// manually fix up collection's references to this texture
myCollectionData.materials[0].mainTexture = reloadedTexture;
myCollectionData.textures[0] = reloadedTexture;

So now, given that I need to manually reload collection textures, I find myself leaning towards a refcounting solution like jmcguirk proposed in his initial post.

Any thoughts on this?
Title: Re: Unload Textures
Post by: unikronsoftware on April 17, 2014, 02:49:47 am
Hi,

Yeah once you bring asset bundles into the equation you'll be adding a whole other layer of complexity. Refcounting is one option, but you will need some form of a custom solution here.
Title: Re: Unload Textures
Post by: TwisterK on May 06, 2014, 04:37:39 am
I'm trying to do this unload texture method, note that my atlas setting is set to PNG to save space, it did work and unload the texture successfully, but when I trying to reload it, it just show me white blob, is there any additional method I need to call?
Title: Re: Unload Textures
Post by: unikronsoftware on May 06, 2014, 11:32:41 am
Hi, this isn't officially supported on sprites if you already have sprites in the scene, but it can be done.

After you unload, call ResetPlatformData on the sprite collection.
When you need to reload, simply create a new sprite using this collection, or instantiate an existing one. That will force it to reload the texture.
Title: Re: Unload Textures
Post by: TwisterK on May 07, 2014, 08:34:50 am
Hi, this isn't officially supported on sprites if you already have sprites in the scene, but it can be done.

After you unload, call ResetPlatformData on the sprite collection.
When you need to reload, simply create a new sprite using this collection, or instantiate an existing one. That will force it to reload the texture.

Thanks for the reply, I've tried it, it still showing white sprite. I trying to debug and check what wrong with it. It seem like after I've unload the texture, it won't get init anymore because the variable, materialInsts at the Init method won't become null and it simply skip all the init function. Please advise.

Code: [Select]
void Init()
{
// check if already initialized
if ( materialInsts != null )
return;
}

Update : Found the solution for this, I was unload and reset the wrong collection data all along, thanks for the help!