AssetBundle에서 모델을 사용할 때 메모리 누수 문제 및 해결

업데이트: 2020/10/21

클리핑을 사용한 모델의 Prefab을 AssetBundle에서 로드 및 언로드하면 CubismMaskTexture를 처리하는 방법에 따라서는 메모리 누수가 발생할 수 있습니다.

여기에서는 그 원인과 미대응의 이유 및 그 대책에 대해 설명합니다.

개요

클리핑이 설정된 모델을 Unity로 처리하는 경우 CubismMaskController를 사용합니다.
CubismMaskController.MaskTexture에 세팅하는 마스크용 텍스쳐(CubismMaskTexture의 인스턴스)는 세팅되어 있지 않은 경우 초기화 시에 애플리케이션 내의 GlobalMaskTexture를 참조합니다.
그러나 프로젝트 사양에 따라서는 CubismMaskTexture의 인스턴스를 애플리케이션 측이 아닌 AssetBundle에 포함시킬 수 있습니다.
이 경우 AssetBundle에서 모델을 로드 및 언로드하면 메모리 누수가 발생할 수 있습니다.

메모리가 누출되는 원인은 CubismMaskTexture 내부의 RenderTexture가 생성된 후 계속 남아 버리는 것입니다.
대책은 CubismMaskTexture를 사용하는 방법에 따라 두 가지가 있습니다.

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개의 대책을 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을 다시 작성하십시오.

이 기사가 도움이 되었나요?
아니요
이 기사에 관한 의견 및 요청사항을 보내 주시기 바랍니다.