References being deleted on stage change in latest releases

Hi so I have some code that is essentially:

  • Setup a new stage/preview scene
  • Create new Texture2D(width, height)
  • Render scene to Texture2D
  • Close and delete the scene/stage
  • Return the Texture2D

This used to work fine, but in 2023.3.0b10 and 6000.0.0b13 this doesn’t work anymore.
When I call StageUtility.GoToMainStage(), the Texture2D is deleted and so when I return it and then try to access it I just get an error that it has been deleted.

How can I move the Texture2D out of the preview scene/stage without it being deleted.

Here is my full function code

        public static Texture2D RenderIcon(Icon icon, int width = 128, int height = 128)
        {
            //---Fix width/height---//
            width = Mathf.Clamp(width, 8, 2048);
            height = Mathf.Clamp(height, 8, 2048);

            //---Create stage and scene---//
            var scene = EditorSceneManager.NewPreviewScene();
            if (scene == null)
            {
                Debug.LogError("Error creating RapidIcon preview scene");
                return Utils.CreateColourTexture(width, height, Color.clear);
            }

            var stage = ScriptableObject.CreateInstance<RapidIconStage>();
            if (stage == null)
            {
                Debug.LogError("Error creating RapidIcon stage");
                return Utils.CreateColourTexture(width, height, Color.clear);
            }

            stage.SetScene(scene);

            //---Go to stage---//
            StageUtility.GoToStage(stage, true);

            //---Setup scene---//
            stage.SetupScene(icon);

            //and render icon---//
            Texture2D render = stage.RenderIcon(width, height);

            //---Apply post-processing shaders---//
            Texture2D img = CreateColourTexture(width, height, Color.clear);

            foreach (Material m in icon.iconSettings.postProcessingMaterials)
            {
                if (icon.iconSettings.materialToggles != null)
                {
                    if (icon.iconSettings.materialToggles[m])
                    {
                        var rtd = new RenderTextureDescriptor(img.width, img.height) { depthBufferBits = 24, msaaSamples = 8, useMipMap = false, sRGB = true };
                        var rt = new RenderTexture(rtd);

                        if (m == null)
                            continue;

                        if (m.shader.name == "RapidIcon/ObjectRender")
                            m.SetTexture("_Render", render);

                        Graphics.Blit(img, rt, m);

                        RenderTexture.active = rt;
                        img = new Texture2D(img.width, img.height);
                        img.ReadPixels(new Rect(0, 0, img.width, img.height), 0, 0);
                        img.Apply();
                        RenderTexture.active = null;
                        rt.Release();
                    }
                }
            }

            //---Apply filter mode---//
            img.filterMode = icon.iconSettings.filterMode;

            //---Cleanup stage and scene---//
            //  !! img is valid here !!
            StageUtility.GoToMainStage();
            // !! img is null here !!
            EditorSceneManager.ClosePreviewScene(scene);
            ScriptableObject.DestroyImmediate(stage);

            return img;
        }

There’s always the option to create a texture asset and delete it afterwards.

But since your method is holding a reference to the texture, it shouldn’t be null’ed no matter what. How did you do the null check? Did you try ReferenceEquals check? I can only imagine that the underlying C++ texture may have been disposed.

Maybe it has something to do that you’re creating new textures within the loop, replacing the previous img:
img = [new](http://www.google.com/search?q=new+msdn.microsoft.com) Texture2D(img.width, img.height);

Or CreateColourTexture returns a shared texture that gets disposed. What does it do?

Other than that I’m stumped why img would be null. It’s clearly in scope.
But I’m also stumped as to what that code does. Could you elaborate?

Btw, I suggest moving the m==null check as the first line of the foreach, because none of the code up to where you do the null check needed to be run if m were null.

I was just doing Debug.Log(img) to see when it was becoming null.
Debug.Log(Texture2D.ReferenceEquals(img, null)); returns false both before and after GoToMainStage.

I tried removing the “new Texture2D” from the loop, no difference.
CreateColourTexture just creates a new texture2D of a width and height, and assigns all pixels the given colour.

        public static Texture2D CreateColourTexture(int width, int height, Color c)
        {
            //---Create new texture---//
            Texture2D tex = new Texture2D(width, height);

            //---Set pixel colours---//
            Color[] pixels = Enumerable.Repeat(c, width * height).ToArray();
            tex.SetPixels(pixels);

            //---Apply changes and set filter mode---//
            tex.Apply();
            tex.filterMode = FilterMode.Point;

            return tex;
        }

This code creates a new preview scene, adds some objects and then renders the camera to a Texture2D, which I want to return.
Seems to be some kind of automatic disposal as the Texture2D is getting created in the preview scene and then being deleted when the scene is closed. For example, if I create the Texture2D (e.g. CreateColourTexture) before the call to “StageUtility.GoToStage”, then the error comes up when I try and access the Texture2D from witin the preview scene, as I guesss in that scenario it is being created in the main scene and then deleted as the preview scene becomes active.

It shouldn’t matter where the texture is created. The texture has no relation to the scene at all unless you assign it to a scene object. But it may still be managed differently than other assets since it’s uploaded to the GPU.

What happens if you only create a basic new texture in the preview stage, and then leave the stage. Is this texture also null’ed?

Another way you can keep the texture alive is to return the pixel data rather than the textures, so you can create a new texture after the method returns.

Yeah it happens even with a simple scenario like this

            Texture2D textest = CreateColourTexture(1, 1, Color.clear);
            Debug.Log(textest); //prints texture
            StageUtility.GoToMainStage();
            Debug.Log(textest); //prints null

I think caching the pixel data and rebuilding the texture after changing scenes might be the best option,

Hmmm you may want to send a bug report with this simple test case, particularly since this behaviour has changed in Unity 6 beta.

Try using DontDestroyOnLoad on the texture object before you load the new scene. Note that currently you’re leaking all those textures that you create in your loop as you just throw away the references. Textures are UnityEngine.Object derived and as such are not directly garbage collected as they are tracked objects. However when UnloadUnusedAssets is called (which happens automatically during a scene load) any assets that aren’t referenced anymore would be destroyed.

You should be more careful about your object creation and also explicitly clean up the instances you create. You should not rely on UnloadUnusedAssets.