[NUI.Sample] Resolve memory leak for DisposeTest sample
authorEunki Hong <eunkiki.hong@samsung.com>
Sun, 21 Jan 2024 04:38:43 +0000 (13:38 +0900)
committerEunki Hong <h.pichulia@gmail.com>
Tue, 20 Feb 2024 09:33:15 +0000 (18:33 +0900)
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.

Signed-off-by: Eunki Hong <eunkiki.hong@samsung.com>
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/DisposeTest.cs

index 2f24a7c..616adb0 100644 (file)
@@ -100,7 +100,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];
 
@@ -148,15 +148,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()
@@ -212,7 +204,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 const int RecuvelyDisposedObjectCount = 10;
@@ -223,8 +214,11 @@ namespace Tizen.NUI.Samples
         private string resource;
         private List<Custom3DView> views;
         private List<Custom3DView> depthViews; // List of tree-formed views. 0 indexes view is root.
+        private List<Renderer> renderers;
         private Animation rotateAnimation;
 
+        private Dictionary<string, Texture> textureDictionary = new();
+
         public void Activate()
         {
             win = NUIApplication.GetDefaultWindow();
@@ -241,6 +235,7 @@ namespace Tizen.NUI.Samples
 
             views = new List<Custom3DView>();
             depthViews = new List<Custom3DView>();
+            renderers = new List<Renderer>();
             rotateAnimation = new Animation(1500); //1.5s
 
             AddManyViews();
@@ -249,7 +244,6 @@ namespace Tizen.NUI.Samples
             timer = new Timer(3000); //3s
             timer.Tick += OnTimerTick;
             timer.Start();
-
         }
 
         private bool OnTimerTick(object source, Timer.TickEventArgs e)
@@ -273,13 +267,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);
 
-            vertexBuffer.SetData(SphereVertexDataPtr(), SPHERE_VERTEX_NUMBER);
+            TexturedQuadVertex[] vertices = SphereVertexData();
+
+            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();
 
@@ -287,9 +299,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();
@@ -308,24 +362,7 @@ namespace Tizen.NUI.Samples
                     Name = "Auto_" + i.ToString(),
                 };
                 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));
             }
@@ -346,23 +383,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));
             }
@@ -399,23 +420,7 @@ namespace Tizen.NUI.Samples
                 }
                 depthViews.Add(view);
 
-                PixelData pixelData = PixelBuffer.Convert(ImageLoader.LoadImageFromFile(
-                    resource + "/images/PaletteTest/rock.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/rock.jpg"));
 
                 //rotateAnimation.AnimateBy(view, "Orientation", new Rotation(new Radian(new Degree(360.0f)), Vector3.ZAxis));
             }
@@ -429,19 +434,26 @@ namespace Tizen.NUI.Samples
             {
                 root.Remove(root.GetChildAt((uint)i));
             }
-            foreach(var view in views)
+            foreach (var view in views)
             {
-                var renderer = view.GetRendererAt(0);
-                renderer.Dispose();
-                view.Dispose();
+                view?.Dispose();
             }
-            if(depthViews?.Count > 0)
+            foreach (var renderer in renderers)
             {
-                depthViews[0].DisposeRecursively();
+                renderer?.GetGeometry()?.Dispose();
+                renderer?.GetShader()?.Dispose();
+                renderer?.GetTextures()?.Dispose();
+                renderer?.Dispose();
+            }
+            if (depthViews?.Count > 0)
+            {
+                depthViews[0]?.DisposeRecursively();
             }
 
             views.Clear();
             depthViews.Clear();
+            renderers.Clear();
+
             rotateAnimation.Clear();
         }
 
@@ -460,6 +472,8 @@ namespace Tizen.NUI.Samples
             root.Unparent();
             root.Dispose();
 
+            textureDictionary.Clear();
+
             // Revert default layer behavior as LayerUI
             win.GetDefaultLayer().Behavior = Layer.LayerBehavior.LayerUI;
         }