[NUI] fix for a owner object to maintain the reference of owned objects
authordongsug.song <dongsug.song@samsung.com>
Wed, 18 Aug 2021 00:29:30 +0000 (09:29 +0900)
committerJunsuChoi <junduru019@gmail.com>
Tue, 24 Aug 2021 05:09:56 +0000 (14:09 +0900)
- Renderer maintains the references of Geometry and Shader
- The references are automatically released when Renderer object is disposed by DisposeQueue
- Tested by test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/DisposeTest.cs

src/Tizen.NUI/src/public/Rendering/Renderer.cs
test/Tizen.NUI.Samples/Tizen.NUI.Samples.sln
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/DisposeTest.cs [new file with mode: 0644]

index 551d6e3..ad0d278 100755 (executable)
@@ -27,14 +27,19 @@ namespace Tizen.NUI
     /// <since_tizen> 3 </since_tizen>
     public class Renderer : Animatable
     {
+        private Geometry CurrentGeometry = null;
+        private Shader CurrentShader = null;
+
         /// <summary>
         /// Create an instance of Renderer.
         /// </summary>
         /// <since_tizen> 3 </since_tizen>
         public Renderer(Geometry geometry, Shader shader) : this(Interop.Renderer.New(Geometry.getCPtr(geometry), Shader.getCPtr(shader)), true)
         {
-            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+            CurrentGeometry = geometry;
+            CurrentShader = shader;
 
+            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
         }
 
         /// <summary>
@@ -572,7 +577,7 @@ namespace Tizen.NUI
         /// <since_tizen> 3 </since_tizen>
         public void SetGeometry(Geometry geometry)
         {
-            Interop.Renderer.SetGeometry(SwigCPtr, Geometry.getCPtr(geometry));
+            Interop.Renderer.SetGeometry(SwigCPtr, Geometry.getCPtr(CurrentGeometry = geometry));
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
         }
 
@@ -583,11 +588,7 @@ namespace Tizen.NUI
         /// <since_tizen> 3 </since_tizen>
         public Geometry GetGeometry()
         {
-            //to fix memory leak issue, match the handle count with native side.
-            System.IntPtr cPtr = Interop.Renderer.GetGeometry(SwigCPtr);
-            Geometry ret = this.GetInstanceSafely<Geometry>(cPtr);
-            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
-            return ret;
+            return CurrentGeometry;
         }
 
         /// <summary>
@@ -644,7 +645,7 @@ namespace Tizen.NUI
         /// <since_tizen> 3 </since_tizen>
         public void SetShader(Shader shader)
         {
-            Interop.Renderer.SetShader(SwigCPtr, Shader.getCPtr(shader));
+            Interop.Renderer.SetShader(SwigCPtr, Shader.getCPtr(CurrentShader = shader));
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
         }
 
@@ -655,11 +656,7 @@ namespace Tizen.NUI
         /// <since_tizen> 3 </since_tizen>
         public Shader GetShader()
         {
-            //to fix memory leak issue, match the handle count with native side.
-            System.IntPtr cPtr = Interop.Renderer.GetShader(SwigCPtr);
-            Shader ret = this.GetInstanceSafely<Shader>(cPtr);
-            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
-            return ret;
+            return CurrentShader;
         }
 
         internal static global::System.Runtime.InteropServices.HandleRef getCPtr(Renderer obj)
index 56f9326..fff9ac0 100755 (executable)
@@ -33,6 +33,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tizen.System.Feedback", "..
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tizen.Multimedia.Camera", "..\..\src\Tizen.Multimedia.Camera\Tizen.Multimedia.Camera.csproj", "{210C3F38-BD17-4583-816D-550C319FC6CF}"
 EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tizen.Applications.ThemeManager", "..\..\src\Tizen.Applications.ThemeManager\Tizen.Applications.ThemeManager.csproj", "{FB8B42D6-76CC-4836-8A80-58A816C6A17F}"
+EndProject
 Global
        GlobalSection(SolutionConfigurationPlatforms) = preSolution
                Debug|Any CPU = Debug|Any CPU
@@ -99,6 +101,10 @@ Global
                {210C3F38-BD17-4583-816D-550C319FC6CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
                {210C3F38-BD17-4583-816D-550C319FC6CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
                {210C3F38-BD17-4583-816D-550C319FC6CF}.Release|Any CPU.Build.0 = Release|Any CPU
+               {FB8B42D6-76CC-4836-8A80-58A816C6A17F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {FB8B42D6-76CC-4836-8A80-58A816C6A17F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {FB8B42D6-76CC-4836-8A80-58A816C6A17F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {FB8B42D6-76CC-4836-8A80-58A816C6A17F}.Release|Any CPU.Build.0 = Release|Any CPU
        EndGlobalSection
        GlobalSection(SolutionProperties) = preSolution
                HideSolutionNode = FALSE
diff --git a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/DisposeTest.cs b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/DisposeTest.cs
new file mode 100644 (file)
index 0000000..3c73248
--- /dev/null
@@ -0,0 +1,387 @@
+
+using System;
+using System.Runtime.InteropServices;
+using Tizen.NUI.BaseComponents;
+using System.Collections.Generic;
+
+namespace Tizen.NUI.Samples
+{
+    // copied from https://github.com/hinohie/nui-demo/blob/geotest/Mesh/Mesh.cs
+    public class DisposeTest : IExample
+    {
+
+        public struct Vec2
+        {
+            float x;
+            float y;
+            public Vec2(float xIn, float yIn)
+            {
+                x = xIn;
+                y = yIn;
+            }
+        }
+        public struct Vec3
+        {
+            float x;
+            float y;
+            float z;
+            public Vec3(float xIn, float yIn, float zIn)
+            {
+                x = xIn;
+                y = yIn;
+                z = zIn;
+            }
+        }
+
+        struct TexturedQuadVertex
+        {
+            public Vec3 position;
+            public Vec3 normal;
+            public Vec2 texcoord;
+        }
+
+        static readonly string VERTEX_SHADER =
+        "attribute mediump vec3 aPosition;\n" +
+        "attribute mediump vec3 aNormal;\n" +
+        "attribute mediump vec2 aTexCoord;\n" +
+        "uniform mediump mat4 uMvpMatrix;\n" +
+        "uniform mediump mat3 uNormalMatrix;\n" +
+        "uniform mediump vec3 uSize;\n" +
+        "varying mediump vec3 vNormal;\n" +
+        "varying mediump vec2 vTexCoord;\n" +
+        "varying mediump vec3 vPosition;\n" +
+        "void main()\n" +
+        "{\n" +
+        //"    vec4 pos = vec4(aPosition, 1.0)*vec4(uSize,1.0);\n"+
+        "    vec4 pos = vec4(aPosition, 1.0)*vec4(uSize.xy, 400.0 ,1.0);\n" +
+        "    gl_Position = uMvpMatrix*pos;\n" +
+        "    vPosition = aPosition;\n" +
+        "    vNormal   = normalize(uNormalMatrix * aNormal);\n" +
+        "    vTexCoord = aTexCoord;\n" +
+        "}\n";
+
+        static readonly string FRAGMENT_SHADER =
+        "uniform lowp vec4 uColor;\n" +
+        "uniform sampler2D sTexture;\n" +
+        "varying mediump vec3 vNormal;\n" +
+        "varying mediump vec2 vTexCoord;\n" +
+        "varying mediump vec3 vPosition;\n" +
+        "mediump vec3 uLightDir = vec3(2.0, 0.5, 1.0);\n" +
+        "mediump vec3 uViewDir  = vec3(0.0, 0.0, 1.0);\n" +
+        "mediump vec3 uAmbientColor = vec3(0.2, 0.2, 0.2);\n" +
+        "mediump vec3 uDiffuseColor = vec3(0.8, 0.8, 0.8);\n" +
+        "mediump vec3 uSpecularColor = vec3(0.5, 0.5, 0.5);\n" +
+        "void main()\n" +
+        "{\n" +
+        "    mediump vec3 lightdir = normalize(uLightDir);\n" +
+        "    mediump vec3 eyedir   = normalize(uViewDir);\n" +
+        "    mediump vec4 texColor = texture2D( sTexture, vTexCoord ) * uColor;\n" +
+        "    mediump float diffuse = min(max(-dot(vNormal, lightdir) + 0.1, 0.0), 1.0);\n" +
+        "    mediump vec3 reflectdir = reflect(-lightdir, vNormal);\n" +
+        "    mediump float specular = pow(max(0.0, dot(reflectdir, eyedir)), 50.0);\n" +
+        "    mediump vec4 color = texColor * vec4(uAmbientColor + uDiffuseColor * diffuse, 1.0) + vec4(uSpecularColor, 0.0) * specular;\n" +
+        "    gl_FragColor = color;\n" +
+        "}\n";
+
+        // Copy from dali-toolkit/internal/visuals/primitive/primitive-visual.cpp
+        private global::System.IntPtr SphereVertexDataPtr()
+        {
+            TexturedQuadVertex[] vertices = new TexturedQuadVertex[SPHERE_VERTEX_NUMBER];
+
+            const int slices = SPHERE_SLICES;
+            const int stacks = SPHERE_STACKS;
+            // Build start.
+            {
+
+                int vertexIndex = 0; //Track progress through vertices.
+                float x;
+                float y;
+                float z;
+
+                //Top stack.
+                vertices[vertexIndex].position = new Vec3(0.0f, 0.5f, 0.0f);
+                vertices[vertexIndex].normal = new Vec3(0.0f, 1.0f, 0.0f);
+                vertices[vertexIndex].texcoord = new Vec2(0.5f, 1.0f);
+                vertexIndex++;
+
+                //Middle stacks.
+                for (int i = 1; i < stacks; i++)
+                {
+                    for (int j = 0; j < slices; j++, vertexIndex++)
+                    {
+                        float cos_j = (float)Math.Cos(2.0f * (float)Math.PI * j / (float)slices);
+                        float sin_j = (float)Math.Sin(2.0f * (float)Math.PI * j / (float)slices);
+                        float cos_i = (float)Math.Cos((float)Math.PI * i / (float)stacks);
+                        float sin_i = (float)Math.Sin((float)Math.PI * i / (float)stacks);
+                        x = cos_j * sin_i;
+                        y = cos_i;
+                        z = sin_j * sin_i;
+
+                        vertices[vertexIndex].position = new Vec3(x / 2.0f, y / 2.0f, z / 2.0f);
+                        vertices[vertexIndex].normal = new Vec3(x, y, z);
+                        vertices[vertexIndex].texcoord = new Vec2((float)j / (float)slices, 1.0f - (float)i / (float)stacks);
+                    }
+                }
+
+                //Bottom stack.
+                vertices[vertexIndex].position = new Vec3(0.0f, -0.5f, 0.0f);
+                vertices[vertexIndex].normal = new Vec3(0.0f, -1.0f, 0.0f);
+                vertices[vertexIndex].texcoord = new Vec2(0.5f, 0.0f);
+            }
+            // Build done.
+
+            int length = Marshal.SizeOf(vertices[0]);
+            global::System.IntPtr pA = Marshal.AllocHGlobal(length * SPHERE_VERTEX_NUMBER);
+
+            for (int i = 0; i < SPHERE_VERTEX_NUMBER; i++)
+            {
+                Marshal.StructureToPtr(vertices[i], pA + i * length, true);
+            }
+
+            return pA;
+        }
+
+        private ushort[] SphereIndexData()
+        {
+            ushort[] indices = new ushort[SPHERE_INDEX_NUMBER];
+            const int slices = SPHERE_SLICES;
+            const int stacks = SPHERE_STACKS;
+
+            // Build start.
+            {
+                int indiceIndex = 0; //Used to keep track of progress through indices.
+                int previousCycleBeginning = 1; //Stores the index of the vertex that started the cycle of the previous stack.
+                int currentCycleBeginning = 1 + slices;
+
+                //Top stack. Loop from index 1 to index slices, as not counting the very first vertex.
+                for (int i = 1; i <= slices; i++, indiceIndex += 3)
+                {
+                    indices[indiceIndex] = 0;
+                    if (i == slices)
+                    {
+                        //End, so loop around.
+                        indices[indiceIndex + 1] = 1;
+                    }
+                    else
+                    {
+                        indices[indiceIndex + 1] = (ushort)(i + 1);
+                    }
+                    indices[indiceIndex + 2] = (ushort)i;
+                }
+
+                //Middle Stacks. Want to form triangles between the top and bottom stacks, so loop up to the number of stacks - 2.
+                for (int i = 0; i < stacks - 2; i++, previousCycleBeginning += slices, currentCycleBeginning += slices)
+                {
+                    for (int j = 0; j < slices; j++, indiceIndex += 6)
+                    {
+                        if (j == slices - 1)
+                        {
+                            //End, so loop around.
+                            indices[indiceIndex] = (ushort)(previousCycleBeginning + j);
+                            indices[indiceIndex + 1] = (ushort)previousCycleBeginning;
+                            indices[indiceIndex + 2] = (ushort)(currentCycleBeginning + j);
+                            indices[indiceIndex + 3] = (ushort)(currentCycleBeginning + j);
+                            indices[indiceIndex + 4] = (ushort)previousCycleBeginning;
+                            indices[indiceIndex + 5] = (ushort)currentCycleBeginning;
+                        }
+                        else
+                        {
+                            indices[indiceIndex] = (ushort)(previousCycleBeginning + j);
+                            indices[indiceIndex + 1] = (ushort)(previousCycleBeginning + 1 + j);
+                            indices[indiceIndex + 2] = (ushort)(currentCycleBeginning + j);
+                            indices[indiceIndex + 3] = (ushort)(currentCycleBeginning + j);
+                            indices[indiceIndex + 4] = (ushort)(previousCycleBeginning + 1 + j);
+                            indices[indiceIndex + 5] = (ushort)(currentCycleBeginning + 1 + j);
+                        }
+                    }
+                }
+
+                //Bottom stack. Loop around the last stack from the previous loop, and go up to the penultimate vertex.
+                for (int i = 0; i < slices; i++, indiceIndex += 3)
+                {
+                    indices[indiceIndex] = (ushort)(previousCycleBeginning + slices);
+                    indices[indiceIndex + 1] = (ushort)(previousCycleBeginning + i);
+                    if (i == slices - 1)
+                    {
+                        //End, so loop around.
+                        indices[indiceIndex + 2] = (ushort)previousCycleBeginning;
+                    }
+                    else
+                    {
+                        indices[indiceIndex + 2] = (ushort)(previousCycleBeginning + i + 1);
+                    }
+                }
+            }
+            // Build done.
+
+            return indices;
+        }
+        const int NUMBER_OF_SPHERES_ROW = 3;
+        const int NUMBER_OF_SPHERES_COLUMN = 2;
+        const int NUMBER_OF_SPHERES = NUMBER_OF_SPHERES_ROW * NUMBER_OF_SPHERES_COLUMN;
+        const int SPHERE_SLICES = 150; // >= 3
+        const int SPHERE_STACKS = 90; // >= 1
+        const int SPHERE_VERTEX_NUMBER = SPHERE_SLICES * (SPHERE_STACKS - 1) + 2;
+        const int SPHERE_INDEX_NUMBER = 6 * SPHERE_SLICES * (SPHERE_STACKS - 1);
+
+
+        private const int MaxObject = 5;
+        private Window win;
+        private View root;
+        private Timer timer;
+        private bool toggle = false;
+        private string resource;
+        private List<View> views;
+
+        public void Activate()
+        {
+            win = NUIApplication.GetDefaultWindow();
+            resource = Tizen.Applications.Application.Current.DirectoryInfo.Resource;
+            root = new View()
+            {
+                Name = "root",
+                Size = new Size(10, 10),
+                BackgroundColor = Color.Blue,
+            };
+            win.Add(root);
+
+            views = new List<View>();
+
+            AddManyViews();
+            toggle = true;
+
+            timer = new Timer(3000); //3s
+            timer.Tick += OnTimerTick;
+            timer.Start();
+        }
+
+        private bool OnTimerTick(object source, Timer.TickEventArgs e)
+        {
+            toggle = !toggle;
+            if (toggle)
+            {
+                AddManyViews();
+            }
+            else
+            {
+                RemoveAllViews();
+                FullGC();
+            }
+            return true;
+        }
+
+        private Geometry GenerateGeometry()
+        {
+            PropertyMap vertexFormat = new PropertyMap();
+            vertexFormat.Add("aPosition", new PropertyValue((int)PropertyType.Vector3));
+            vertexFormat.Add("aNormal", new PropertyValue((int)PropertyType.Vector3));
+            vertexFormat.Add("aTexCoord", new PropertyValue((int)PropertyType.Vector2));
+            PropertyBuffer vertexBuffer = new PropertyBuffer(vertexFormat);
+
+            vertexBuffer.SetData(SphereVertexDataPtr(), SPHERE_VERTEX_NUMBER);
+
+            ushort[] indexBuffer = SphereIndexData();
+
+            Geometry geometry = new Geometry();
+            geometry.AddVertexBuffer(vertexBuffer);
+            geometry.SetIndexBuffer(indexBuffer, SPHERE_INDEX_NUMBER);
+            geometry.SetType(Geometry.Type.TRIANGLES);
+            return geometry;
+        }
+
+        private void AddManyViews()
+        {
+            Random rand = new Random();
+
+            for (int i = 0; i < MaxObject; i++)
+            {
+                int viewSize = 150;
+                var view = new View()
+                {
+                    Size = new Size(viewSize, viewSize, viewSize),
+                    Position = new Position(rand.Next(10, 600), rand.Next(10, 600)),
+                };
+                root.Add(view);
+
+                PixelData pixelData = PixelBuffer.Convert(ImageLoading.LoadImageFromFile(
+                    resource + "/images/PopupTest/circle.jpg",
+                    new Size2D(),
+                    FittingModeType.ScaleToFill
+                ));
+                Texture texture = new Texture(
+                    TextureType.TEXTURE_2D,
+                    pixelData.GetPixelFormat(),
+                    pixelData.GetWidth(),
+                    pixelData.GetHeight()
+                );
+                texture.Upload(pixelData);
+                TextureSet textureSet = new TextureSet();
+                textureSet.SetTexture(0u, texture);
+                Renderer renderer = new Renderer(GenerateGeometry(), new Shader(VERTEX_SHADER, FRAGMENT_SHADER));
+                renderer.SetTextures(textureSet);
+                view.AddRenderer(renderer);
+            }
+            
+            for (int i = 0; i < MaxObject; i++)
+            {
+                int viewSize = 150;
+                var view = new View()
+                {
+                    Size = new Size(viewSize, viewSize, viewSize),
+                    Position = new Position(rand.Next(10, 600), rand.Next(10, 600)),
+                };
+                root.Add(view);
+                views.Add(view);
+
+                PixelData pixelData = PixelBuffer.Convert(ImageLoading.LoadImageFromFile(
+                    resource + "/images/PaletteTest/red2.jpg",
+                    new Size2D(),
+                    FittingModeType.ScaleToFill
+                ));
+                Texture texture = new Texture(
+                    TextureType.TEXTURE_2D,
+                    pixelData.GetPixelFormat(),
+                    pixelData.GetWidth(),
+                    pixelData.GetHeight()
+                );
+                texture.Upload(pixelData);
+                TextureSet textureSet = new TextureSet();
+                textureSet.SetTexture(0u, texture);
+                Renderer renderer = new Renderer(GenerateGeometry(), new Shader(VERTEX_SHADER, FRAGMENT_SHADER));
+                renderer.SetTextures(textureSet);
+                view.AddRenderer(renderer);
+            }
+
+        }
+        private void RemoveAllViews()
+        {
+            uint cnt = root.ChildCount;
+            for (int i = (int)(cnt - 1); i >= 0; i--)
+            {
+                root.Remove(root.GetChildAt((uint)i));
+            }
+            foreach(var view in views)
+            {
+                var renderer = view.GetRendererAt(0);
+                renderer.Dispose();
+                view.Dispose();
+            }
+            views.Clear();
+        }
+
+        private void FullGC()
+        {
+            global::System.GC.Collect();
+            global::System.GC.WaitForPendingFinalizers();
+            global::System.GC.Collect();
+        }
+
+        public void Deactivate()
+        {
+            timer.Stop();
+            RemoveAllViews();
+            root.Unparent();
+            root.Dispose();
+        }
+    }
+}