[NUI][API10] Fix some memory leak codes what already fixed at API12
authorEunki Hong <eunkiki.hong@samsung.com>
Fri, 19 Jan 2024 04:04:36 +0000 (13:04 +0900)
committerEunki Hong <h.pichulia@gmail.com>
Mon, 18 Mar 2024 08:02:29 +0000 (17:02 +0900)
Let we backport some minor memory leak resolve PR what we already merged.

This PR squash 3 different patches.

---

Add some remarks at BaseHandle class about Dispose

(Follow PR #5893)

Resolve memory leak for DisposeTest sample

There was leak issue since we don't call FreeHGlobal after AllocHGlobal.

And also, let we Dispose Renderer / Geometry / Shader / TextureSet what NUI create.
Those classes memory might give confuse when we test the memory.

(Follow PR #5895)

Call Mashal.FreeHGlobal() what we allocated

Let we free allocated heap memory by Mashal.

It might cause memory leak issue if user try to use VertexBuffer.

And also, let we also fix FrameBrokerBase implementation
that might be the guideline who want to use custom Geometry class.

(Follow PR #5902)

src/Tizen.NUI.Wearable/src/public/Title.cs
src/Tizen.NUI/src/internal/FrameBroker/FrameBrokerBase.cs
src/Tizen.NUI/src/public/BaseComponents/DirectRenderingGLView.cs
src/Tizen.NUI/src/public/BaseComponents/ViewAccessibilityWrappers.cs
src/Tizen.NUI/src/public/Common/BaseHandle.cs
src/Tizen.NUI/src/public/Rendering/VertexBuffer.cs
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/DisposeTest.cs

index 5d3fb1e..0b53846 100755 (executable)
@@ -326,17 +326,24 @@ namespace Tizen.NUI.Components
             vertex3.position = new Vec2(0.5f, -0.5f);
             vertex4.position = new Vec2(0.5f, 0.5f);
 
-
             TexturedQuadVertex[] texturedQuadVertexData = new TexturedQuadVertex[4] { vertex1, vertex2, vertex3, vertex4 };
 
-            int lenght = Marshal.SizeOf(vertex1);
-            IntPtr pA = Marshal.AllocHGlobal(lenght * 4);
+            int length = Marshal.SizeOf(vertex1);
+            IntPtr pA = Marshal.AllocHGlobal(checked(length * 4));
 
-            for (int i = 0; i < 4; i++)
+            try
+            {
+                for (int i = 0; i < 4; i++)
+                {
+                    Marshal.StructureToPtr(texturedQuadVertexData[i], pA + i * length, true);
+                }
+                vertexData.SetData(pA, 4);
+            }
+            finally
             {
-                Marshal.StructureToPtr(texturedQuadVertexData[i], pA + i * lenght, true);
+                // Free AllocHGlobal memory after call PropertyBuffer.SetData()
+                Marshal.FreeHGlobal(pA);
             }
-            vertexData.SetData(pA, 4);
 
             Geometry geometry = new Geometry();
             geometry.AddVertexBuffer(vertexData);
index 1c5afd6..ca250f8 100755 (executable)
@@ -290,8 +290,15 @@ namespace Tizen.NUI
             public Vec2 position;
         };
 
-        private IntPtr RectangleDataPtr()
+        private PropertyBuffer CreateQuadPropertyBuffer()
         {
+            /* Create Property buffer */
+            PropertyValue value = new PropertyValue((int)PropertyType.Vector2);
+            PropertyMap vertexFormat = new PropertyMap();
+            vertexFormat.Add("aPosition", value);
+
+            PropertyBuffer vertexBuffer = new PropertyBuffer(vertexFormat);
+            
             TexturedQuadVertex vertex1 = new TexturedQuadVertex();
             TexturedQuadVertex vertex2 = new TexturedQuadVertex();
             TexturedQuadVertex vertex3 = new TexturedQuadVertex();
@@ -303,33 +310,38 @@ namespace Tizen.NUI
 
             TexturedQuadVertex[] texturedQuadVertexData = new TexturedQuadVertex[4] { vertex1, vertex2, vertex3, vertex4 };
 
-            int lenght = Marshal.SizeOf(vertex1);
-            IntPtr pA = Marshal.AllocHGlobal(lenght * 4);
+            int length = Marshal.SizeOf(vertex1);
+            IntPtr pA = Marshal.AllocHGlobal(checked(length * 4));
 
-            for (int i = 0; i < 4; i++)
+            try
             {
-                Marshal.StructureToPtr(texturedQuadVertexData[i], pA + i * lenght, true);
+                for (int i = 0; i < 4; i++)
+                {
+                    Marshal.StructureToPtr(texturedQuadVertexData[i], pA + i * length, true);
+                }
+
+                vertexBuffer.SetData(pA, 4);
+            }
+            finally
+            {
+                // Free AllocHGlobal memory after call PropertyBuffer.SetData()
+                Marshal.FreeHGlobal(pA);
             }
 
-            return pA;
+            value.Dispose();
+            vertexFormat.Dispose();
+
+            return vertexBuffer;
         }
 
         private Geometry CreateQuadGeometry()
         {
-            /* Create Property buffer */
-            PropertyValue value = new PropertyValue((int)PropertyType.Vector2);
-            PropertyMap vertexFormat = new PropertyMap();
-            vertexFormat.Add("aPosition", value);
-
-            PropertyBuffer vertexBuffer = new PropertyBuffer(vertexFormat);
-            vertexBuffer.SetData(RectangleDataPtr(), 4);
+            PropertyBuffer vertexBuffer = CreateQuadPropertyBuffer();
 
             Geometry geometry = new Geometry();
             geometry.AddVertexBuffer(vertexBuffer);
             geometry.SetType(Geometry.Type.TRIANGLE_STRIP);
 
-            value.Dispose();
-            vertexFormat.Dispose();
             vertexBuffer.Dispose();
 
             return geometry;
index 79196fa..c7b6a00 100644 (file)
@@ -184,16 +184,26 @@ namespace Tizen.NUI.BaseComponents
             {
                 if (textures != null)
                 {
-                    IntPtr unmanagedPointer = Marshal.AllocHGlobal(sizeof(IntPtr) * textures.Count);
-                    IntPtr[] texturesArray = new IntPtr[textures.Count];
-                    for (int i = 0; i < textures.Count; i++)
+                    int count = textures.Count;
+                    int intptrBytes = checked(Marshal.SizeOf(typeof(IntPtr)) * count);
+                    if (intptrBytes > 0)
                     {
-                        texturesArray[i] = HandleRef.ToIntPtr(Texture.getCPtr(textures[i]));
+                        IntPtr[] texturesArray = new IntPtr[count];
+                        for (int i = 0; i < count; i++)
+                        {
+                            texturesArray[i] = HandleRef.ToIntPtr(Texture.getCPtr(textures[i]));
+                        }
+                        IntPtr unmanagedPointer = Marshal.AllocHGlobal(intptrBytes);
+                        try
+                        {
+                            Marshal.Copy(texturesArray, 0, unmanagedPointer, texturesArray.Length);
+                            Interop.GLView.GlViewBindTextureResources(SwigCPtr, unmanagedPointer, texturesArray.Length);
+                        }
+                        finally
+                        {
+                            Marshal.FreeHGlobal(unmanagedPointer);
+                        }
                     }
-                    System.Runtime.InteropServices.Marshal.Copy(texturesArray, 0, unmanagedPointer, textures.Count);
-
-                    Interop.GLView.GlViewBindTextureResources(SwigCPtr, unmanagedPointer, textures.Count);
-                    Marshal.FreeHGlobal(unmanagedPointer);
                 }
             }
         }
index 6b48d1c..817aea5 100644 (file)
@@ -45,6 +45,9 @@ namespace Tizen.NUI.BaseComponents
 
             Marshal.StructureToPtr(ad, ptr, false);
             Interop.ControlDevel.DaliAccessibilitySetAccessibilityDelegate(ptr, Convert.ToUInt32(size));
+
+            // Do not free AllocHGlobal memory, for performance. It will be used for native side very frequencly.
+            // Note that Marshal.AllocHGlobal memory will be freed after process terminated.
         }
 
         private static View GetViewFromRefObject(IntPtr refObjectPtr)
index 9399569..d01802c 100755 (executable)
@@ -25,6 +25,11 @@ namespace Tizen.NUI
     /// <summary>
     /// BaseHandle is a handle to an internal Dali resource.
     /// </summary>
+    /// <remarks>
+    /// Internal Dali resources with BaseHandle has reference count internally.<br/>
+    /// And Dali resources will release the object only if reference count become zero.<br/>
+    /// It mean, even we call <see cref="Dispose()"/>, the reousrce will not be released if some native has reference count.
+    /// </remarks>
     /// <since_tizen> 3 </since_tizen>
     public class BaseHandle : Element, global::System.IDisposable
     {
@@ -302,6 +307,10 @@ namespace Tizen.NUI
         /// <summary>
         /// Dispose.
         /// </summary>
+        /// <remarks>
+        /// This method release only C# side objects. If someone hold BaseHandle at Native side<br/>
+        /// the object will not be removed until native side reset the handle.
+        /// </remarks>
         /// <since_tizen> 3 </since_tizen>
         public void Dispose()
         {
index ff52fae..e2963a4 100755 (executable)
@@ -62,12 +62,21 @@ namespace Tizen.NUI
             int structSize = Marshal.SizeOf<VertexType>();
             global::System.IntPtr buffer = Marshal.AllocHGlobal(structSize * vertices.Length);
 
-            for (int i = 0; i < vertices.Length; i++)
+            try
             {
-                Marshal.StructureToPtr(vertices[i], buffer + i * structSize, true);
+                for (int i = 0; i < vertices.Length; i++)
+                {
+                    Marshal.StructureToPtr(vertices[i], buffer + i * structSize, true);
+                }
+
+                Interop.VertexBuffer.SetData(SwigCPtr, buffer, (uint)vertices.Length);
+            }
+            finally
+            {
+                // Free AllocHGlobal memory after call Interop.VertexBuffer.SetData()
+                Marshal.FreeHGlobal(buffer);
             }
 
-            Interop.VertexBuffer.SetData(SwigCPtr, buffer, (uint)vertices.Length);
             if (NDalicPINVOKE.SWIGPendingException.Pending)
                 throw NDalicPINVOKE.SWIGPendingException.Retrieve();
         }
index a248264..7ffd4d8 100644 (file)
@@ -92,7 +92,7 @@ namespace Tizen.NUI.Samples
 
         // Copy from dali-toolkit/internal/visuals/primitive/primitive-visual.cpp
         // NOTE. I add one more slices for texture coordinate
-        private global::System.IntPtr SphereVertexDataPtr()
+        private TexturedQuadVertex[] SphereVertexData()
         {
             TexturedQuadVertex[] vertices = new TexturedQuadVertex[SPHERE_VERTEX_NUMBER];
 
@@ -140,15 +140,7 @@ namespace Tizen.NUI.Samples
             }
             // 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;
+            return vertices;
         }
 
         private ushort[] SphereIndexData()
@@ -204,7 +196,6 @@ namespace Tizen.NUI.Samples
         const int SPHERE_VERTEX_NUMBER = (SPHERE_SLICES + 1) * (SPHERE_STACKS - 1) + 2;
         const int SPHERE_INDEX_NUMBER = 6 * SPHERE_SLICES * (SPHERE_STACKS - 1);
 
-
         private const int AutoDisposedObjectCount = 10;
         private const int ManualDisposedObjectCount = 10;
         private Window win;
@@ -213,8 +204,11 @@ namespace Tizen.NUI.Samples
         private bool toggle = false;
         private string resource;
         private List<Custom3DView> views;
+        private List<Renderer> renderers;
         private Animation rotateAnimation;
 
+        private Dictionary<string, Texture> textureDictionary = new Dictionary<string, Texture>();
+
         public void Activate()
         {
             win = NUIApplication.GetDefaultWindow();
@@ -230,6 +224,7 @@ namespace Tizen.NUI.Samples
             win.Add(root);
 
             views = new List<Custom3DView>();
+            renderers = new List<Renderer>();
             rotateAnimation = new Animation(1500); //1.5s
 
             AddManyViews();
@@ -238,7 +233,6 @@ namespace Tizen.NUI.Samples
             timer = new Timer(3000); //3s
             timer.Tick += OnTimerTick;
             timer.Start();
-
         }
 
         private bool OnTimerTick(object source, Timer.TickEventArgs e)
@@ -258,13 +252,31 @@ namespace Tizen.NUI.Samples
 
         private Geometry GenerateGeometry()
         {
-            PropertyMap vertexFormat = new PropertyMap();
+            using 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);
+            using PropertyBuffer vertexBuffer = new PropertyBuffer(vertexFormat);
+
+            TexturedQuadVertex[] vertices = SphereVertexData();
 
-            vertexBuffer.SetData(SphereVertexDataPtr(), SPHERE_VERTEX_NUMBER);
+            int length = Marshal.SizeOf(vertices[0]);
+            global::System.IntPtr pA = Marshal.AllocHGlobal(checked(length * SPHERE_VERTEX_NUMBER));
+
+            try
+            {
+                for (int i = 0; i < SPHERE_VERTEX_NUMBER; i++)
+                {
+                    Marshal.StructureToPtr(vertices[i], pA + i * length, true);
+                }
+
+                vertexBuffer.SetData(pA, SPHERE_VERTEX_NUMBER);
+            }
+            finally
+            {
+                // We can free raw data after SetData call finished.
+                Marshal.FreeHGlobal(pA);
+            }
 
             ushort[] indexBuffer = SphereIndexData();
 
@@ -272,9 +284,51 @@ namespace Tizen.NUI.Samples
             geometry.AddVertexBuffer(vertexBuffer);
             geometry.SetIndexBuffer(indexBuffer, SPHERE_INDEX_NUMBER);
             geometry.SetType(Geometry.Type.TRIANGLES);
+
             return geometry;
         }
 
+        private Shader GenerateShader()
+        {
+            Shader shader = new Shader(VERTEX_SHADER, FRAGMENT_SHADER);
+            return shader;
+        }
+
+        private Renderer GenerateRenderer(string textureUrl)
+        {
+            Texture texture;
+            if (!textureDictionary.TryGetValue(textureUrl, out texture))
+            {
+                // Let we load image only 1 times per each objects
+                using PixelData pixelData = PixelBuffer.Convert(ImageLoader.LoadImageFromFile(
+                    textureUrl,
+                    new Size2D(),
+                    FittingModeType.ScaleToFill
+                ));
+
+                texture = new Texture(
+                    TextureType.TEXTURE_2D,
+                    pixelData.GetPixelFormat(),
+                    pixelData.GetWidth(),
+                    pixelData.GetHeight()
+                );
+                texture.Upload(pixelData);
+                if (!textureDictionary.TryAdd(textureUrl, texture))
+                {
+                    Tizen.Log.Error("NUI", "Something wrong when we try to add Texture into dictionary\n");
+                }
+            }
+            TextureSet textureSet = new TextureSet();
+            textureSet.SetTexture(0u, texture);
+
+            Renderer renderer = new Renderer(GenerateGeometry(), GenerateShader());
+            renderer.SetTextures(textureSet);
+
+            renderers.Add(renderer);
+
+            return renderer;
+        }
+
         private void AddManyViews()
         {
             Random rand = new Random();
@@ -292,24 +346,7 @@ namespace Tizen.NUI.Samples
                     ),
                 };
                 root.Add(view);
-
-                PixelData pixelData = PixelBuffer.Convert(ImageLoader.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);
+                view.AddRenderer(GenerateRenderer(resource + "/images/PopupTest/circle.jpg"));
 
                 rotateAnimation.AnimateBy(view, "Orientation", new Rotation(new Radian(new Degree(360.0f)), Vector3.YAxis));
             }
@@ -329,23 +366,7 @@ namespace Tizen.NUI.Samples
                 root.Add(view);
                 views.Add(view);
 
-                PixelData pixelData = PixelBuffer.Convert(ImageLoader.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);
+                view.AddRenderer(GenerateRenderer(resource + "/images/PaletteTest/red2.jpg"));
 
                 rotateAnimation.AnimateBy(view, "Orientation", new Rotation(new Radian(new Degree(-360.0f)), Vector3.YAxis));
             }
@@ -359,13 +380,21 @@ namespace Tizen.NUI.Samples
             {
                 root.Remove(root.GetChildAt((uint)i));
             }
-            foreach(var view in views)
+            foreach (var view in views)
+            {
+                view?.Dispose();
+            }
+            foreach (var renderer in renderers)
             {
-                var renderer = view.GetRendererAt(0);
-                renderer.Dispose();
-                view.Dispose();
+                renderer?.GetGeometry()?.Dispose();
+                renderer?.GetShader()?.Dispose();
+                renderer?.GetTextures()?.Dispose();
+                renderer?.Dispose();
             }
+
             views.Clear();
+            renderers.Clear();
+
             rotateAnimation.Clear();
         }
 
@@ -383,6 +412,11 @@ namespace Tizen.NUI.Samples
             rotateAnimation?.Dispose();
             root.Unparent();
             root.Dispose();
+
+            textureDictionary.Clear();
+
+            // Revert default layer behavior as LayerUI
+            win.GetDefaultLayer().Behavior = Layer.LayerBehavior.LayerUI;
         }
     }
 }