I’m wondering how to handle Texture2D resources right to avoid resource leaks. I’ve googled quite a lot and I think that:
you need to Destroy() Texture2D objects after use manually
you must not do that for Texture2D objects created with other methods.
Is that correct?
The example that I’m trying to solve is this one:
if (GUI.Button(new Rect(10, 70, 50, 30), “Click”))
{
Texture2D t = new Texture2D(width, height);
//…
myplane.GetComponent().sharedMaterial.mainTexture = t;
The old value of mainTexture might have been created with “new Texture2D” or with “Resources.Load” as I use both. (Or it might have been assigned in the editor.) Is it a good idea to simply Destroy() the old value of mainTexture in that case?
Texture2D should be destroyed if you created the instance yourself or you will get leaks, especially in editor scripts. If you loaded it with Resources.Load, you should not have to dispose of it as it will be disposed of when calling Resources.UnloadUnusedAssets or when Play is stopped. However, I don’t think there is a problem with destroying it anyway as long as it’s not being used by anything anymore. Just be sure not to call DestroyImmediate with allowDestroyingAssets set to True or it may delete the asset file.
When creating a temporary Texture2D, you should also set the hideFlags property to HideAndDontSave:
Texture2D tex = new Texture2D(200, 200);
tex.hideFlags = HideFlags.HideAndDontSave;
If you need to create a lot of temporary textures, it may be convenient to create some kind of manager to handle creation and destruction of the textures. Do something like call textureManager.CreateTempTexture(width, size) to get your new instance and let the manager handle the creation parameters and adding it to a list of texture instances. Once you’re done with the texture, check it out of your manager and let it destroy it and remove it from the list (if it’s in the list, otherwise you can opt to destroy it or just leave it). In the case of quitting the game in the editor, to avoid leaks, just have the manager destroy all instanced textures in OnDisable.
since I’m mixing the different types, I guess I’ll need such a textureManager. Do you know if such a script already exists somewhere? I tried google and asset store, but couldn’t find it.
No I don’t and I doubt one exists because it’s not really something that would be used very often. At its simplest it would only cover your particular needs. It shouldn’t take long at all to write something like that for basic uses. Most likely less than 10 minutes.
This is a very basic example. This uses a static class to store a list of UnityEngine.Objects. You can create a new temporary Texture2D using the CreateTexture2D method which will store a reference in the list. You can manually destroy the texture when you’re done with it by passing it to TempObjectManager.Destroy, or you can just let it destroy them all on exit when OnDisable is called. Note that this is not a MonoBehavior, so OnDisable will not be called by Unity. You will need to call TempObjectManager.OnDisable or Clear from a MonoBehaviour that always exists in your scene (your game manager?) from OnDisable in order for it to clear the list when you exit Play in the editor. (It would be very easy to change this into a MonoBehaviour singleton, but I prefer using as few MonoBehaviours as possible, especially for little tasks such as this.)
using UnityEngine;
using System.Collections.Generic;
namespace MyNamespace {
public static class TempObjectManager {
static List<Object> tempObjects;
static void TempObjectManager() {
tempObjects = new List<Object>();
}
/// <summary>
/// Call this from some MonoBehaviour in OnDisable to destroy all objects.
/// </summary>
public static void OnDisable() {
Clear();
}
public static void Clear() {
// Destroy all temp objects in the manager
for(int i = 0; i < tempObjects.Count; i++) {
if(tempObjects[i] == null) continue;
Object.Destroy(tempObjects[i]);
}
tempObjects.Clear(); // clear the list
}
/// <summary>
/// Adds a temp object to the manager.
/// </summary>
/// <param name="obj">Texture</param>
public static void Add(Object obj) {
if(obj == null) return;
if(tempObjects.Contains(obj)) return; // already in the list
tempObjects.Add(obj); // add to list
}
/// <summary>
/// Destroys the object and removes it from the manager.
/// </summary>
/// <param name="obj">Object</param>
public static void Destroy(Object obj) {
if(obj == null) return;
tempObjects.Remove(obj); // remove from list
Object.Destroy(obj); // destroy the object
}
/// <summary>
/// Creates a temporary texture and stores it in the manager.
/// </summary>
/// <param name="width">Width of the texture</param>
/// <param name="height">Height of the texture</param>
/// <returns>Texture2D</returns>
public static Texture2D CreateTexture2D(int width, int height) {
Texture2D tex = new Texture2D(width, height);
tex.hideFlags = HideFlags.HideAndDontSave;
tempObjects.Add(tex);
return tex;
}
}
}
In our case, we create Texture atlases for customized avatars and we have a cache that keeps them around for a while in hopes that they might be re-used by other avatars with the same settings.
It would be nice if Textures has a built-in reference count that could be incremented and decremented manually or automatically when referenced by materials and possibly if referenced temporarily by draw calls.
Unfortunately, if they do, it is not exposed so it looks like we need to implement our own handler that tracks the lifetime of materials to ultimately track the reference count of textures.