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つの理由によって行っておりません。
- マスク用RenderTextureの扱い方によって対応方法が異なるため
- わかりやすさ、構造のシンプルさに重点を置いているCubismComponentsが複雑化してしまうため
- 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を作り直してください。