Using cloned material causes a crash

EsaKEsaK SEMember ✭✭

I load a material from the ResourceCache, then I Clone() it and modify a shader parameter in it.
I keep references to both instances in my class and use them on my models depending on some state.
I can use the original material (loaded from the ResourceCache), but the cloned instance I can only use one time.
When I try to set it on my model a second time my app crashes completely (giving me a stack trace).
What can the problem be? Isn't Clone() supposed to work?
I'm using version 1.1.120.

Posts

  • EgorBoEgorBo BYXamarin Team ✭✭✭

    @EsaKylli.5920 can you please provide a small repro code snippet\project?

  • EsaKEsaK SEMember ✭✭

    This is part of my code (in a subclass to Component):

        private Material boxMaterialDefault;
        private Material boxMaterialSelected;
    
        private void CreateMaterials()
        {
            boxMaterialDefault = Application.ResourceCache.GetMaterial("Materials/GameBox.xml");
    
            boxMaterialSelected = boxMaterialDefault.Clone("GameBoxSelected");
            boxMaterialSelected.SetShaderParameter("MatDiffColor", new Color(0.0f, 0.75f, 0.0f, 1.0f));
    
            //boxMaterialSelected = Application.ResourceCache.GetMaterial("Materials/GameBoxSelected.xml");
        }
    

    CreateMaterials() is called when the Application starts. The material is set on my StaticModel via SetMaterial().
    If I comment out the initialization of "boxMaterialSelected", and instead use the commented out line at the end (loading from ResourceCache) it works.

  • EgorBoEgorBo BYXamarin Team ✭✭✭

    @EsaKylli.5920
    I modified StaticScene sample with this code:

    var mat = cache.GetMaterial("Materials/Mushroom.xml");
    var matCloned = mat.Clone("Clonned");
    matCloned.SetShaderParameter("MatDiffColor", new Color(0.0f, 0.75f, 0.0f, 1.0f));
    
    for (int i = 0; i < 200; i++)
    {
        var mushroom = scene.CreateChild ("Mushroom");
        mushroom.Position = new Vector3 (rand.Next (90)-45, 0, rand.Next (90)-45);
        mushroom.Rotation = new Quaternion (0, rand.Next (360), 0);
        mushroom.SetScale (0.5f+rand.Next (20000)/10000.0f);
        var mushroomObject = mushroom.CreateComponent<StaticModel>();
        mushroomObject.Model = cache.GetModel ("Models/Mushroom.mdl");
        mushroomObject.SetMaterial (i % 2 == 0 ? matCloned : mat);
    }
    

    and it works fine for me (Windows). Can you please create a small repro project?

  • EsaKEsaK SEMember ✭✭

    Your above code works fine for me too (on Mac).
    I have isolated the problem now to this specific use-case (modified StaticScene).
    If you left-click once, and then another time you should get a crash.
    The funny thing here is that if you don't use a cloned material (but two different ones loaded from the ResourceCache) it works.
    I have no clue what's going on....

    public class StaticScene : Sample
    {
        Camera camera;
        Scene scene;
    
        public StaticScene(ApplicationOptions options = null) : base(options) { }
    
        protected override void Start()
        {
            base.Start();
            // ** Added
            CreateMaterials();
            //
            CreateScene ();
            SimpleCreateInstructionsWithWasd ();
            SetupViewport ();
        }
    
        void HandleKeyDown(KeyDownEventArgs e)
        {
            switch (e.Key)
            {
                case Key.Esc:
                    Exit();
                    return;
            }
        }
    
        void CreateScene ()
        {
            var cache = ResourceCache;
            scene = new Scene ();
    
            // Create the Octree component to the scene. This is required before adding any drawable components, or else nothing will
            // show up. The default octree volume will be from (-1000, -1000, -1000) to (1000, 1000, 1000) in world coordinates; it
            // is also legal to place objects outside the volume but their visibility can then not be checked in a hierarchically
            // optimizing manner
            scene.CreateComponent<Octree> ();
    
            // Create a child scene node (at world origin) and a StaticModel component into it. Set the StaticModel to show a simple
            // plane mesh with a "stone" material. Note that naming the scene nodes is optional. Scale the scene node larger
            // (100 x 100 world units)
            var planeNode = scene.CreateChild("Plane");
            planeNode.Scale = new Vector3 (100, 1, 100);
            var planeObject = planeNode.CreateComponent<StaticModel> ();
            planeObject.Model = cache.GetModel ("Models/Plane.mdl");
            planeObject.SetMaterial (cache.GetMaterial ("Materials/StoneTiled.xml"));
    
            // Create a directional light to the world so that we can see something. The light scene node's orientation controls the
            // light direction; we will use the SetDirection() function which calculates the orientation from a forward direction vector.
            // The light will use default settings (white light, no shadows)
            var lightNode = scene.CreateChild("DirectionalLight");
            lightNode.SetDirection (new Vector3(0.6f, -1.0f, 0.8f)); // The direction vector does not need to be normalized
            var light = lightNode.CreateComponent<Light>();
            light.LightType = LightType.Directional;
    
            // ** Removed
            /*
            var rand = new Random();
            for (int i = 0; i < 200; i++)
            {
                var mushroom = scene.CreateChild ("Mushroom");
                mushroom.Position = new Vector3 (rand.Next (90)-45, 0, rand.Next (90)-45);
                mushroom.Rotation = new Quaternion (0, rand.Next (360), 0);
                mushroom.SetScale (0.5f+rand.Next (20000)/10000.0f);
                var mushroomObject = mushroom.CreateComponent<StaticModel>();
                mushroomObject.Model = cache.GetModel ("Models/Mushroom.mdl");
                mushroomObject.SetMaterial (cache.GetMaterial ("Materials/Mushroom.xml"));
            }
            */
            //
            CameraNode = scene.CreateChild ("camera");
            camera = CameraNode.CreateComponent<Camera>();
            CameraNode.Position = new Vector3 (0, 5, 0);
        }
    
        void SetupViewport ()
        {
            var renderer = Renderer;
            renderer.SetViewport (0, new Viewport (Context, scene, camera, null));
        }
    
        protected override void OnUpdate(float timeStep)
        {
            base.OnUpdate(timeStep);
            SimpleMoveCamera3D(timeStep);
    
            // ** Added
            var input = Application.Current.Input;
            if (input.GetMouseButtonDown(MouseButton.Left))
            {
                if (selectedNode != null)
                {
                    selectedNodeModel.SetMaterial(originalMaterial);
                    selectedNode = null;
                    selectedNodeModel = null;
                }
                else
                {
                    CreateMushroom(new Vector3(x, 0, 20));
                    x += 1;
                }
            }
            //
        }
    
        // ** Added
        private float x = 0;
        private Node selectedNode;
        private StaticModel selectedNodeModel;
    
        private Material originalMaterial;
        private Material clonedMaterial;
    
        private void CreateMaterials()
        {
            var cache = ResourceCache;
    
            originalMaterial = cache.GetMaterial("Materials/Mushroom.xml");
            clonedMaterial = originalMaterial.Clone("Clonned");
            clonedMaterial.SetShaderParameter("MatDiffColor", new Color(0.0f, 0.0f, 0.75f, 1.0f));
        }
    
        private void CreateMushroom(Vector3 pos)
        {
            var mushroom = scene.CreateChild("Mushroom");
            mushroom.Position = pos;
            mushroom.SetScale(2);
            var mushroomObject = mushroom.CreateComponent<StaticModel>();
            mushroomObject.Model = ResourceCache.GetModel("Models/Mushroom.mdl");
            mushroomObject.SetMaterial(clonedMaterial);
    
            selectedNode = mushroom;
            selectedNodeModel = mushroomObject;
        }
        //
    
    }
    
  • EgorBoEgorBo BYXamarin Team ✭✭✭
    edited August 2016

    @EsaKylli.5920
    The problem is:
    when you replace cloned material with the original one via this code:
    selectedNodeModel.SetMaterial(originalMaterial);
    the inner Urho3D will delete cloned material as nobody uses it anymore (for example, if the scene would have some long-live StaticModel with this cloned material - the bug wouldn't happen).
    Initial SetMaterial(clonedMaterial) incremented inner RefsCount to 1 from 0 and the following operation
    selectedNodeModel.SetMaterial(originalMaterial);
    did ReleasRef over clonedMaterial and since RefCount became 0 - it triggered "delete".

    So as a workaround you can do:
    clonedMaterial = originalMaterial.Clone("Clonned");
    clonedMaterial.AddRef(); //surrogate reference in order to keep it alive

  • EsaKEsaK SEMember ✭✭

    That makes sense. I guess it's a consequence when mixing managed (C#) and unmanaged (C++) code?
    (When working with Urho3D in C++ I remember I had to encapsulate the material reference in my class in a SharedPtr.)

    Do I have to add ReleseRef() to it also? When should I do that? Not when exiting the game, isn't it deallocated anyway in that case?

  • EgorBoEgorBo BYXamarin Team ✭✭✭

    @EsaKylli.5920
    It's a temporary workaround, will be fixed in the next version. As for now, yes, you'll have to call ReleaseRef once you don't need "clonedMaterial" anymore (otherwise it will leak).

  • EsaKEsaK SEMember ✭✭

    So what you are saying is that the reference counting will work automatically between managed and unmanaged code (in a coming version)?

    Do I have to call ReleaseRef() even when exiting the game?

Sign In or Register to comment.