// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using UnityEngine;
using Object = UnityEngine.Object;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Microsoft.MixedReality.Toolkit.Rendering
{
///
/// The MaterialInstance behavior aides in tracking instance material lifetime and automatically destroys instanced materials for the user.
/// This utility component can be used as a replacement to Renderer.material or
/// Renderer.materials. When invoking Unity's Renderer.material(s), Unity
/// automatically instantiates new materials. It is the caller's responsibility to destroy the materials when a material is no longer needed or the game object is
/// destroyed. The MaterialInstance behavior helps avoid material leaks and keeps material allocation paths consistent during edit and run time.
///
[HelpURL("https://microsoft.github.io/MixedRealityToolkit-Unity/Documentation/Rendering/MaterialInstance.html")]
[ExecuteAlways, RequireComponent(typeof(Renderer))]
[AddComponentMenu("Scripts/MRTK/Core/MaterialInstance")]
public class MaterialInstance : MonoBehaviour
{
///
/// Returns the first instantiated Material assigned to the renderer, similar to Renderer.material.
/// If any owner is specified the instanced material(s) will not be released until all owners are released. When a material
/// is no longer needed ReleaseMaterial should be called with the matching owner.
///
/// An optional owner to track instance ownership.
/// The first instantiated Material.
public Material AcquireMaterial(Object owner = null, bool instance = true)
{
if (owner != null)
{
materialOwners.Add(owner);
}
if (instance)
{
AcquireInstances();
}
if (instanceMaterials?.Length > 0)
{
return instanceMaterials[0];
}
return null;
}
///
/// Returns all the instantiated materials of this object, similar to Renderer.materials.
/// If any owner is specified the instanced material(s) will not be released until all owners are released. When a material
/// is no longer needed ReleaseMaterial should be called with the matching owner.
///
/// An optional owner to track instance ownership.
/// Should this acquisition attempt to instance materials?
/// All the instantiated materials.
public Material[] AcquireMaterials(Object owner = null, bool instance = true)
{
if (owner != null)
{
materialOwners.Add(owner);
}
if (instance)
{
AcquireInstances();
}
return instanceMaterials;
}
///
/// Relinquishes ownership of a material instance. This should be called when a material is no longer needed
/// after acquire ownership with AcquireMaterial(s).
///
/// The same owner which originally acquire ownership via AcquireMaterial(s).
/// Should this acquisition attempt to instance materials?
/// When ownership count hits zero should the MaterialInstance component be destroyed?
public void ReleaseMaterial(Object owner, bool autoDestroy = true)
{
materialOwners.Remove(owner);
if (autoDestroy && materialOwners.Count == 0)
{
DestorySafe(this);
}
}
///
/// Returns the first instantiated Material assigned to the renderer, similar to Renderer.material.
///
public Material Material
{
get { return AcquireMaterial(); }
}
///
/// Returns all the instantiated materials of this object, similar to Renderer.materials.
///
public Material[] Materials
{
get { return AcquireMaterials(); }
}
private Renderer CachedRenderer
{
get
{
if (cachedRenderer == null)
{
cachedRenderer = GetComponent();
}
return cachedRenderer;
}
}
private Renderer cachedRenderer = null;
[SerializeField, HideInInspector]
private Material[] defaultMaterials = null;
private Material[] instanceMaterials = null;
private bool initialized = false;
private bool materialsInstanced = false;
private readonly HashSet