AssetBundleからモデルを使用するとメモリリークする問題と対策

最終更新: 2020年10月21日

クリッピングを使用したモデルのPrefabをAssetBundleからロード・アンロードした場合、CubismMaskTextureの扱い方によってはメモリリークが発生することがあります。

ここでは、その原因と未対応の理由、及びその対策について説明します。

概要

クリッピングが設定されたモデルをUnityで扱う場合、CubismMaskControllerを使用します。
CubismMaskController.MaskTexture にセットするマスク用テクスチャ(CubismMaskTextureのインスタンス)は、セットされていない場合、初期化時にアプリケーション内のGlobalMaskTextureを参照します。
しかしプロジェクトの仕様によっては、CubismMaskTextureのインスタンスをアプリケーション側ではなくAssetBundleに含めることがあります。
この場合、AssetBundleからモデルをロード・アンロードを行うとメモリリークが発生することがあります。

メモリがリークする原因は、CubismMaskTexture内部のRenderTextureが生成されたあと残り続けてしまうことです。
対策はCubismMaskTextureの使用方法によって2通りあります。

case 1: すべてのモデルで一つのCubismMaskTextureを使用している場合
1-1, CubismMaskControllerInspector.csのEditorGUILayout.ObjectFieldを削除します
1-2, CubismMaskController.csでシリアライズされている_maskTextureをシリアライズをしない処理に変更します
1-3, AssetBundleを作り直します

case 2: モデルごとにCubismMaskTextureをインスタンスしている場合
2-1, CubismMasktexture.csに、_renderTextureを削除するメソッドを作成します
2-2, モデルを破棄するタイミングで2-1で作成したメソッドを呼び出します
2-3, AssetBundleを作り直します

上記の対策どちらかを行うことでメモリリークは解消されます。

背景・原因

Live2Dモデルでクリッピングが設定されたモデルをAssetBundle化した後、そこからモデルをロード・アンロードを行うとメモリリークが発生します。

原因としては、モデルをロードするたびにマスク用のRenderTextureが生成され、モデルが削除されてもRenderTextureは残ってしまうためです。
コンポーネントに設定されているオブジェクトは、オブジェクトの参照ではなくオブジェクトのコピーでAssetBundle化されます。
その結果、AssetBundle化を通してモデルを生成するとCubismMaskController.MaskTextureにCubismMaskTextureのインスタンスであるGlobalMaskTextureがセットされている場合、モデルをロードするたび同時にGlobalMaskTextureのコピーも生成され、RenderTextureも作られます。
しかしモデルを削除してもRenderTextureは削除されずに残ってしまいます。その結果、メモリリークが発生します。

Cubism SDKへの、本不具合の修正反映は、以下の3つの理由によって行っておりません。

  1. マスク用RenderTextureの扱い方によって対応方法が異なるため
  2. わかりやすさ、構造のシンプルさに重点を置いているCubismComponentsが複雑化してしまうため
  3. AssetBundleを利用しない場合は問題がないため

対策

対策における注意点

本項では2つの対策を提示します。

注意点として、2つの対策は独立した対策方法のため、同時に2つの対策を1つのプロジェクトで行わないようにしてください。

対策はCubismMaskTextureの使い方によって対策が異なります。一つのCubismMaskTextureだけを使用している場合と、モデルごとにCubismMaskTextureをインスタンスして使用している場合です。
ここでは2つの使用方法に対して、それぞれ1つずつ対策の説明を行います。

Case 1: 一つのCubismMaskTextureを用いてモデルを表示する場合

一つのCubismMaskTextureを使いまわして、モデルを表示している場合はこちらの対策を行います。

まず最初に、CubismMaskControllerInspector.cs内に含まれているEditorGUILayout.ObjectFieldを削除します。

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


            //当該部分。この行を削除する。
            controller.MaskTexture = EditorGUILayout.ObjectField("Mask Texture", controller.MaskTexture, typeof(CubismMaskTexture), true) as CubismMaskTexture;


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

次にCubismMaskController.csでシリアライズされている_maskTextureをシリアライズしない処理に変更します。

    ・・・
    public sealed class CubismMaskController : MonoBehaviour, ICubismMaskTextureCommandSource
    {
        /// <summary>
        /// <see cref="MaskTexture"/> backing field.
        /// </summary>
        [SerializeField, HideInInspector] //当該部分。この行を削除する。
        private CubismMaskTexture _maskTexture;

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

これで対策ができました。
この後、AssetBundleを作り直してください。

case 2: モデルごとにCubismMaskTextureをインスタンスしている場合

モデルごとにCubismMaskTextureをインスタンスしている場合の対策はこちらになります。

最初にCubismMaskTexture.csに_randerTextureをDestroyするpublicメソッドを作成します。
以下はコード例です。

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

先述したメソッドをモデルを破棄するタイミングで呼び出します。
結果、モデルと同様にRenderTextureを削除することができます。

以上の対策が終わった後、AssetBundleを作り直してください。

この記事はお役に立ちましたか?
はいいいえ
この記事に関するご意見・
ご要望をお聞かせください。