View Issue Details

IDProjectCategoryView StatusLast Update
0002360NoesisGUIUnitypublic2022-06-17 17:46
Reporterttermeer-rcg Assigned Tosfernandez  
PrioritynormalSeveritymajor 
Status resolvedResolutionno change required 
Product Version3.1.4 
Target Version3.1.5Fixed in Version3.1.5 
Summary0002360: Texture Memory Leak
Description

Using 2D Texture with a converter is never freed. I did not check if this is the only scenario where this happens. It feels it is related to Bindings. It may also comes from loading the texture from files outside the Unity project (this is mandatory for our use case)
It is unclear if UnregisterTextureCallback from the Noesis Texture is supposed to handle it or there should be an other way to handled it which is not documented.
This is a major issue as we load a huge amount of images and this leads to frequent game crash.

Steps To Reproduce
  • Run the Attached Project
  • Use Unity Memory Profiler to get the Memory Tree Map
  • Click the button "Select Random Training" multiple times
  • Observe Unity Memory Profiler and see that Texture2D is growing in size
Attached Files
BindingMemoryleak.zip (2,851,538 bytes)
PlatformWindows

Activities

sfernandez

sfernandez

2022-06-16 11:01

manager   ~0007966

Last edited: 2022-06-16 11:02

Hi, looking at the code I see you are creating Texture2D objects dynamically but you are not destroying them. Noesis cannot be in charge of the lifecycle of the texture because we don't own that resource (it could come from an asset handled by Unity).

I just made a simple scene without Noesis where I create Texture2D objects and immediately release its C# reference (even forcing a GC collect) and as expected the texture memory is not released. If I take a look at the Memory Profiler and select any of those dynamically created textures it indicates the following:

This is a dynamically created Asset Type object, that was either Instantiated, implicitly duplicated or explicitly constructed via 'new UnityEngine.Texture2D()'.
This object is not referenced anymore. It is therefore leaked!
Remember to unload these objects by explicitly calling 'Destroy()' on them, or via the more costly and broad sweeping indirect method of triggering 'Resources.UnloadUnusedAssets()

It happens the same when I check in your test the textures created in the converter, so Noesis is correctly releasing all its references.

You have to manage the lifecycle of the created textures and call Destroy when they are not used anymore.

ttermeer-rcg

ttermeer-rcg

2022-06-16 11:50

reporter   ~0007967

It makes sense however, how can we get a notification it is no longer in use ? There is no accessible callback when the image is no longer used in the Binding/Converter.
Maybe we should have access to Noesis.Texture.UnregisterTextureCallback notification ?

sfernandez

sfernandez

2022-06-16 12:57

manager   ~0007968

You can handle the lifecycle of the texture by using a custom Image that creates and destroys the asset when the image is loaded/unloaded. For example you can have the following in the xaml:

<local:SessionImage Height="648" Width="1296" SessionId="{Binding TrainingId}"/>

And that SessionImage will be just a custom Image element like the attached code.

I just test it in your project and it works as expected, freeing texure memory everytime you change the image and when the element is removed from the UI tree.

sfernandez

sfernandez

2022-06-16 12:58

manager   ~0007969

SessionImage.cs (2,204 bytes)   
#if UNITY_5_3_OR_NEWER
#define NOESIS
using Noesis;
using UnityEngine;
#else
using System.Windows;
using System.Windows.Threading;
#endif
using System.ComponentModel;
using System.Windows.Input;
using System;

namespace BindingMemoryLeak
{
    public class SessionImage : Image
    {
        public SessionImage()
        {
            this.Loaded += (s, e) => { ((SessionImage)s)?.LoadAsset(); };
            this.Unloaded += (s, e) => { ((SessionImage)s)?.UnloadAsset(); };
        }

        public int SessionId
        {
            get { return (int)GetValue(SessionIdProperty); }
            set { SetValue(SessionIdProperty, value); }
        }

        public static readonly DependencyProperty SessionIdProperty = DependencyProperty.Register(
            "SessionId", typeof(int), typeof(SessionImage), new PropertyMetadata(0, OnLoadAsset));

        public bool UseMipMaps
        {
            get { return (bool)GetValue(UseMipMapsProperty); }
            set { SetValue(UseMipMapsProperty, value); }
        }

        public static readonly DependencyProperty UseMipMapsProperty = DependencyProperty.Register(
            "UseMipMaps", typeof(bool), typeof(SessionImage), new PropertyMetadata(true, OnLoadAsset));


        private static void OnLoadAsset(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is SessionImage sessionImage)
            {
                sessionImage.LoadAsset();
            }
        }

#if NOESIS
        public void LoadAsset()
        {
            UnloadAsset();

            var sessionName = SessionId < 0 ? "SESSION_REST" : $"SESSION_{SessionId}";

            Source = LoadImage.GetImageSource("Trainings", $"{sessionName}.png", UseMipMaps, out _currentTexture);
        }

        public void UnloadAsset()
        {
            if (_currentTexture != null)
            {
                UnityEngine.Object.Destroy(_currentTexture);
                _currentTexture = null;
            }
        }

        private Texture2D _currentTexture;
#else
        public void LoadAsset() { }
        public void UnloadAsset() { }
#endif

    }
}
SessionImage.cs (2,204 bytes)   
ttermeer-rcg

ttermeer-rcg

2022-06-17 16:27

reporter   ~0007972

Thanks this does the trick. It completely slipped my mind to extend the image control.

sfernandez

sfernandez

2022-06-17 17:46

manager   ~0007975

Great, closing this as I think there is nothing wrong in Noesis code about it.

Issue History

Date Modified Username Field Change
2022-06-13 12:08 ttermeer-rcg New Issue
2022-06-13 12:08 ttermeer-rcg Tag Attached: Memory
2022-06-13 12:08 ttermeer-rcg Tag Attached: Unity
2022-06-13 12:08 ttermeer-rcg File Added: BindingMemoryleak.zip
2022-06-13 12:16 ttermeer-rcg Description Updated
2022-06-15 11:12 sfernandez Assigned To => sfernandez
2022-06-15 11:12 sfernandez Status new => assigned
2022-06-15 11:12 sfernandez Target Version => 3.1.5
2022-06-16 11:01 sfernandez Status assigned => feedback
2022-06-16 11:01 sfernandez Note Added: 0007966
2022-06-16 11:02 sfernandez Note Edited: 0007966
2022-06-16 11:02 sfernandez Note Edited: 0007966
2022-06-16 11:50 ttermeer-rcg Note Added: 0007967
2022-06-16 11:50 ttermeer-rcg Status feedback => assigned
2022-06-16 12:57 sfernandez Status assigned => feedback
2022-06-16 12:57 sfernandez Note Added: 0007968
2022-06-16 12:58 sfernandez Note Added: 0007969
2022-06-16 12:58 sfernandez File Added: SessionImage.cs
2022-06-17 16:27 ttermeer-rcg Note Added: 0007972
2022-06-17 16:27 ttermeer-rcg Status feedback => assigned
2022-06-17 17:46 sfernandez Status assigned => resolved
2022-06-17 17:46 sfernandez Resolution open => no change required
2022-06-17 17:46 sfernandez Fixed in Version => 3.1.5
2022-06-17 17:46 sfernandez Note Added: 0007975
2025-10-10 13:29 jsantos Category Unity3D => Unity