Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Creating a new Texture2D for EditorWindow

Discussion in 'Immediate Mode GUI (IMGUI)' started by fadden, Nov 4, 2015.

  1. fadden

    fadden

    Joined:
    Feb 11, 2015
    Posts:
    13
    I'm attempting to create a 1x1 texture for use in an EditorWindow. For some reason the Texture2D is being destroyed before I have a chance to use it.

    My code looks vaguely like this:

    public class Blah : EditorWindow {
    private Texture2D RED;

    void OnEnable() {
    RED = Make1x1Texture(Color.red);
    Debug.Log("Created RED: " + RED);
    }

    void OnDisable() { ... Debug.Log(), destroy-immediate it ... }

    private static Texture2D Make1x1Texture(Color color) {
    Texture2D result = new Texture2D(1, 1);
    ... set pixels, apply, return ...
    }

    void OnGUI() {
    Debug.Log("OnGUI RED=" + RED);
    ...use...
    }
    }

    Pretty straightforward. But in the editor console, I see:

    Created RED: (UnityEngine.Texture2D)
    OnGUI RED=null

    Note RED is not a null reference; rather, it's a valid reference to a Texture2D that has been destroyed, so the Texture2D ToString() method prints "null" (which sent me in the wrong direction for several minutes).

    For some reason, Unity is destroying the Texture2D between the time it's created and when it's first used in OnGUI. This results in messages like `null texture passed to GUI.DrawTexture` when I try to draw with it.

    At first I was creating the Texture2D with a field initializer, and I thought the scene GC was eating the texture, but now I'm creating it in OnEnable(). I've also tried creating it in Awake(). I've tried making the field public in case the serializer needs to do something clever with it. None of these have helped.

    The strange part is, sometimes it works, and will continue to work for a while. Then, when I start Unity the next day, it starts failing again. I'm clearly doing something the editor doesn't like, and either racing or getting into a situation where my code and something in the editor are fighting each other and everybody loses.

    I have a workaround -- simply create and destroy the Texture2D in every OnGUI() call -- but that seems like a waste. And this seems like the sort of problem that could bite me again in the future, so I'd like to understand what's going on. What is the correct way to do this?

    Using Unity 5.1.3f1 Personal.
     
  2. liortal

    liortal

    Joined:
    Oct 17, 2012
    Posts:
    3,562
  3. fadden

    fadden

    Joined:
    Feb 11, 2015
    Posts:
    13
    I added [SerializeField] before the Texture2D fields (and, in the past, have marked them as public). As before, it worked while I was fiddling with the editor, but broke when I restarted Unity. Before I wasn't going in and out of play mode, because there was nothing to play yet, but I can do that now.

    Steps taken and observed behavior:
    1. Start with the EditorWindow in a mode where it creates and destroys the textures in OnGUI. Go in and out of play mode to ensure all is working.
    2. In Visual Studio, change the EditorWindow source to use OnEnable / OnDestroy (using an #ifdef).
    3. Back in Unity, note the "OnEnable creating" console message that indicates OnEnable just created the textures (the OnGUI approach destroys them and sets them to null each time, so OnEnable creates them the first time). Fiddle with the editor to make sure everything is working.
    4. Go into play mode. Use the editor. All still working fine.
    5. Leave play mode. Immediate flood of "null texture passed to GUI.DrawTexture".
    So the Texture2D objects are being reset when I leave play mode. The EditorWindow is not being disabled, so it doesn't have an opportunity to create/destroy the objects.

    Can Texture2D be serialized?

    The issue when Unity first launches is similar but possibly worse. Somehow, after the initial Texture2D creation in OnEnable, Unity wipes out the textures. I don't understand why Unity would do a serialize / deserialize sequence on initial startup *after* calling OnEnable on the EditorWindow object. (I'm not 100% convinced that it is, but I don't know what else to blame this weird behavior on.)
     
  4. liortal

    liortal

    Joined:
    Oct 17, 2012
    Posts:
    3,562
    It's an interesting scenario, although from what i know it should work.

    According to Unity's serialization rules, anything that is "serializable" should be serialized. That includes built-in types (derived from UnityEngine.Object) as well as primitve types, or other custom types (e.g: classes) that are marked as [Serializable] and have [SerializeField] on them.

    I tried 2 extra scenarios:

    1. Return Texture2D.whiteTexture from the method that generates the texture.
    2. Return a texture that is loaded from assets (e.g: a .PNG image that i stored in the project).

    In both cases, the texture is persisted in the editor, throughout play mode and when exiting it.
    so, it seems that the issue is with the texture that is generated dynamically in code.

    For now, i think you can fix it by using the built-in Texture2D.whiteTexture (and tint it by multiplying it with whatever color you want).

    Still not sure what is the root cause for the different behaviour with textures that are created in code vs. ones that are loaded from assets. Maybe whiteTexture also exists as some asset, and so that is the way Unity is able to deserialize it back after playmode and make it available?

    Perhaps @LightStriker could elaborate further on this matter :)
     
  5. fadden

    fadden

    Joined:
    Feb 11, 2015
    Posts:
    13
    Here's what I tried:

    void OnEnable() {
    Debug.Log("Creating mats in OnEnable");
    m_wallTexture = Texture2D.whiteTexture;
    m_wallMat = new Material(Shader.Find("Unlit/Color"));
    m_wallMat.SetColor("_Color", Color.cyan);​
    }​

    I changed my GUI.DrawTexture() call to Graphics.DrawTexture(), and specified the new Material.

    This worked great... until I entered and exited play, at which point the Material suffered the same fate as the Texture2D. With an internally-null material, the whiteTexture just got rendered as a white texture. Logging the value of the Material yields the expected (and arguably misleading) "m_wallMat is null".
     
  6. liortal

    liortal

    Joined:
    Oct 17, 2012
    Posts:
    3,562
    Instead of using a Material, you can use GUI.color = .... to set a color that is used for tinting.
    This will tint your texture to whatever color you like.
     
    fadden likes this.
  7. fadden

    fadden

    Joined:
    Feb 11, 2015
    Posts:
    13
    Oh, nice. That works very well. Thanks!
     
    liortal likes this.
  8. fadden

    fadden

    Joined:
    Feb 11, 2015
    Posts:
    13
    About a week before posting here, I posted the question in the Answers area. I recently got a very interesting response from @Bunny83, who pointed out the HideFlags class.

    The earlier solution with Texture2D.whiteTexture is better for my current project, since it means I don't have to allocate texture objects at all, but if I were doing something more involved then the correct way would be to set HideFlags. I haven't found a whole lot of documentation, and there appear to be some pitfalls, but a simple test appeared to work correctly. At the least, the behavior I was seeing makes some sense now.