Memory Leak Problems and Countermeasures When Using Models from AssetBundle

Updated: 10/21/2020

Loading and unloading Prefab from AssetBundle for models using clipping may cause memory leaks depending on how CubismMaskTexture is handled.

This section describes the reasons for the lack of support, as well as countermeasures to address the issue.

Summary

To handle models with clipping set in Unity, use CubismMaskController.
CubismMaskController.MaskTexture (instance of CubismMaskTexture), if not set, refers to GlobalMaskTexture in the application at initialization.
However, depending on the project specifications, an instance of CubismMaskTexture may be included in the AssetBundle instead of on the application side.
In this case, loading and unloading models from AssetBundle may cause memory leaks.

The cause of memory leaks is that the RenderTexture inside CubismMaskTexture remains after it is generated.
There are two ways to deal with this problem, depending on how CubismMaskTexture is used.

Case 1: If you are using one CubismMaskTexture for all models
1-1. Delete EditorGUILayout.ObjectField in CustomMaskControllerInspector.cs
1-2. Change _maskTexture serialized in CubismMaskController.cs to a non-serialized process
1-3. Re-create AssetBundle

Case 2: If you have an instance of CubismMaskTexture for each model
2-1. Create a method to delete _renderTexture in CubismMasktexture.cs
2-2. Call the method created in 2-1 at the timing of destroying the model
2-3. Re-create AssetBundle

Either of the above measures will eliminate the memory leak.

Background/Causes

After making an AssetBundle of a Live2D model with clipping set, loading and unloading the model from the AssetBundle causes a memory leak.

The reason is that a RenderTexture for the mask is generated each time the model is loaded, and the RenderTexture remains even if the model is deleted.
Objects set in a component are made into an AssetBundle with a copy of the object, not a reference to the object.
As a result, if a model is generated through AssetBundle conversion and CubismMaskController.MaskTexture is set to GlobalMaskTexture, an instance of CubismMaskTexture, a copy of GlobalMaskTexture will be generated and a RenderTexture will also be created, each time the model is loaded.
However, even if the model is deleted, the RenderTexture remains undeleted. This results in memory leaks.

The fix for this bug has not been reflected in the Cubism SDK for the following three reasons.

  1. Because the method of reflecting this fix differs depending on how RenderTexture for masks is handled
  2. Because CubismComponents, which focuses on clarity and structural simplicity, becomes more complex
  3. Because there is no problem if you do not use AssetBundle

Countermeasures

Points to note in countermeasures

This section presents two measures.

As a reminder, the two measures are independent measures, so do not use both measures in one project at the same time.

Countermeasures vary depending on how CubismMaskTexture is used. There are two cases: when only one CubismMaskTexture is used, and when a CubismMaskTexture is instantiated and used for each model.
Here is a description of a countermeasure for each of the two uses.

Case 1: To display a model using a single CubismMaskTexture

If you are using a single CubismMaskTexture to display a model, use this measure.

First, delete the EditorGUILayout.ObjectField contained within the CustomMaskControllerInspector.cs.

            ・・・
            // Draw mask texture.
            EditorGUI.BeginChangeCheck();


            // The relevant part(s). Delete this line.
            controller.MaskTexture = EditorGUILayout.ObjectField("Mask Texture", controller.MaskTexture, typeof(CubismMaskTexture), true) as CubismMaskTexture;


            // Apply changes.
            if (EditorGUI.EndChangeCheck())
            {
                EditorUtility.SetDirty(controller);
            }
            ・・・

Next, change the _maskTexture serialized in CubismMaskController.cs to a non-serialized process.

    ・・・
    public sealed class CubismMaskController : MonoBehaviour, ICubismMaskTextureCommandSource
    {
        /// <summary>
        /// <see cref="MaskTexture"/> backing field.
        /// </summary>
        [SerializeField, HideInInspector] // The relevant part(s). Delete this line.
        private CubismMaskTexture _maskTexture;

        /// <summary>
        /// Mask texture.
        /// </summary>
        public CubismMaskTexture MaskTexture
        {
        ・・・

Countermeasures have now been taken.
After this, re-create the AssetBundle.

Case 2: If you have an instance of CubismMaskTexture for each model

If you have instances of CubismMaskTexture for each model, here are the measures to take.

First, create a public method in CubismMaskTexture.cs to Destroy _renderTexture.
The following is sample code.

        public void DeleteRenderTexture()
        {
            DestroyImmediate(_renderTexture);
        }

The aforementioned method is called when the model is to be destroyed.
As a result, the RenderTexture can be deleted along with the model.

After the above measures have been taken, re-create the AssetBundle.

Please let us know what you think about this article.