Hello Guest

Author Topic: Unload Textures  (Read 21814 times)

jmcguirk

  • 2D Toolkit
  • Newbie
  • *
  • Posts: 10
    • View Profile
Unload Textures
« 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.

  • Each sprite collection data now has a new field, string textureGuids[]
  • As part of the atlas creation building in the editor, the textureGuids[] are populated with Guids that reference their asset path and a pointer is added to the tk2d index via MakeLoadableAsset.
  • When a Texture atlas has its textures Unloaded, the spriteCollection is marked as being needing to have its atlases reloaded via a static collection that lives in tk2dSpriteCollectionData, indexed by spriteCollectionGuid
  • When a tk2dSprite is awake()'ned, the sprite tells the spriteCollectionData to rebuild itself if its dirty
  • A spriteCollectionData rebuilds itself by looking up all its textures by guid, and populating them into the index-appropriate material main textures

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.

unikronsoftware

  • Administrator
  • Hero Member
  • *****
  • Posts: 9709
    • View Profile
Re: Unload Textures
« Reply #1 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.

jmcguirk

  • 2D Toolkit
  • Newbie
  • *
  • Posts: 10
    • View Profile
Re: Unload Textures
« Reply #2 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.
« Last Edit: July 13, 2013, 07:59:30 pm by unikronsoftware »

unikronsoftware

  • Administrator
  • Hero Member
  • *****
  • Posts: 9709
    • View Profile
Re: Unload Textures
« Reply #3 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.

radu

  • 2D Toolkit
  • Jr. Member
  • *
  • Posts: 67
    • View Profile
Re: Unload Textures
« Reply #4 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

unikronsoftware

  • Administrator
  • Hero Member
  • *****
  • Posts: 9709
    • View Profile
Re: Unload Textures
« Reply #5 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.

radu

  • 2D Toolkit
  • Jr. Member
  • *
  • Posts: 67
    • View Profile
Re: Unload Textures
« Reply #6 on: July 19, 2013, 05:23:21 pm »
Do you have an ETA for that?

Thanks,
Radu

unikronsoftware

  • Administrator
  • Hero Member
  • *****
  • Posts: 9709
    • View Profile
Re: Unload Textures
« Reply #7 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.

int64

  • 2D Toolkit
  • Newbie
  • *
  • Posts: 6
    • View Profile
Re: Unload Textures
« Reply #8 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)
« Last Edit: August 15, 2013, 08:38:35 am by int64 »

fattie

  • 2D Toolkit
  • Full Member
  • *
  • Posts: 106
    • View Profile
Re: Unload Textures
« Reply #9 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?

radu

  • 2D Toolkit
  • Jr. Member
  • *
  • Posts: 67
    • View Profile
Re: Unload Textures
« Reply #10 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.

 

Velvety

  • 2D Toolkit
  • Newbie
  • *
  • Posts: 19
    • View Profile
    • Velvety Couch Games
Re: Unload Textures
« Reply #11 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!

unikronsoftware

  • Administrator
  • Hero Member
  • *****
  • Posts: 9709
    • View Profile
Re: Unload Textures
« Reply #12 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?

Velvety

  • 2D Toolkit
  • Newbie
  • *
  • Posts: 19
    • View Profile
    • Velvety Couch Games
Re: Unload Textures
« Reply #13 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.

unikronsoftware

  • Administrator
  • Hero Member
  • *****
  • Posts: 9709
    • View Profile
Re: Unload Textures
« Reply #14 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;
}