You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

683 lines
24 KiB
C#

1 year ago
//#define TMP_DEBUG_MODE
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.UI;
namespace TMPro
{
public static class TMP_MaterialManager
{
private static List<MaskingMaterial> m_materialList = new List<MaskingMaterial>();
private static Dictionary<long, FallbackMaterial> m_fallbackMaterials = new Dictionary<long, FallbackMaterial>();
private static Dictionary<int, long> m_fallbackMaterialLookup = new Dictionary<int, long>();
private static List<FallbackMaterial> m_fallbackCleanupList = new List<FallbackMaterial>();
private static bool isFallbackListDirty;
static TMP_MaterialManager()
{
Canvas.willRenderCanvases += OnPreRender;
}
static void OnPreRender()
{
if (isFallbackListDirty)
{
//Debug.Log("2 - Cleaning up Fallback Materials.");
CleanupFallbackMaterials();
isFallbackListDirty = false;
}
}
/// <summary>
/// Create a Masking Material Instance for the given ID
/// </summary>
/// <param name="baseMaterial"></param>
/// <param name="stencilID"></param>
/// <returns></returns>
public static Material GetStencilMaterial(Material baseMaterial, int stencilID)
{
// Check if Material supports masking
if (!baseMaterial.HasProperty(ShaderUtilities.ID_StencilID))
{
Debug.LogWarning("Selected Shader does not support Stencil Masking. Please select the Distance Field or Mobile Distance Field Shader.");
return baseMaterial;
}
int baseMaterialID = baseMaterial.GetInstanceID();
// If baseMaterial already has a corresponding masking material, return it.
for (int i = 0; i < m_materialList.Count; i++)
{
if (m_materialList[i].baseMaterial.GetInstanceID() == baseMaterialID && m_materialList[i].stencilID == stencilID)
{
m_materialList[i].count += 1;
#if TMP_DEBUG_MODE
ListMaterials();
#endif
return m_materialList[i].stencilMaterial;
}
}
// No matching masking material found. Create and return a new one.
Material stencilMaterial;
//Create new Masking Material Instance for this Base Material
stencilMaterial = new Material(baseMaterial);
stencilMaterial.hideFlags = HideFlags.HideAndDontSave;
#if UNITY_EDITOR
stencilMaterial.name += " Masking ID:" + stencilID;
#endif
stencilMaterial.shaderKeywords = baseMaterial.shaderKeywords;
// Set Stencil Properties
ShaderUtilities.GetShaderPropertyIDs();
stencilMaterial.SetFloat(ShaderUtilities.ID_StencilID, stencilID);
//stencilMaterial.SetFloat(ShaderUtilities.ID_StencilOp, 0);
stencilMaterial.SetFloat(ShaderUtilities.ID_StencilComp, 4);
//stencilMaterial.SetFloat(ShaderUtilities.ID_StencilReadMask, stencilID);
//stencilMaterial.SetFloat(ShaderUtilities.ID_StencilWriteMask, 0);
MaskingMaterial temp = new MaskingMaterial();
temp.baseMaterial = baseMaterial;
temp.stencilMaterial = stencilMaterial;
temp.stencilID = stencilID;
temp.count = 1;
m_materialList.Add(temp);
#if TMP_DEBUG_MODE
ListMaterials();
#endif
return stencilMaterial;
}
/// <summary>
/// Function to release the stencil material.
/// </summary>
/// <param name="stencilMaterial"></param>
public static void ReleaseStencilMaterial(Material stencilMaterial)
{
int stencilMaterialID = stencilMaterial.GetInstanceID();
for (int i = 0; i < m_materialList.Count; i++)
{
if (m_materialList[i].stencilMaterial.GetInstanceID() == stencilMaterialID)
{
if (m_materialList[i].count > 1)
m_materialList[i].count -= 1;
else
{
Object.DestroyImmediate(m_materialList[i].stencilMaterial);
m_materialList.RemoveAt(i);
stencilMaterial = null;
}
break;
}
}
#if TMP_DEBUG_MODE
ListMaterials();
#endif
}
// Function which returns the base material associated with a Masking Material
public static Material GetBaseMaterial(Material stencilMaterial)
{
// Check if maskingMaterial already has a base material associated with it.
int index = m_materialList.FindIndex(item => item.stencilMaterial == stencilMaterial);
if (index == -1)
return null;
else
return m_materialList[index].baseMaterial;
}
/// <summary>
/// Function to set the Material Stencil ID
/// </summary>
/// <param name="material"></param>
/// <param name="stencilID"></param>
/// <returns></returns>
public static Material SetStencil(Material material, int stencilID)
{
material.SetFloat(ShaderUtilities.ID_StencilID, stencilID);
if (stencilID == 0)
material.SetFloat(ShaderUtilities.ID_StencilComp, 8);
else
material.SetFloat(ShaderUtilities.ID_StencilComp, 4);
return material;
}
public static void AddMaskingMaterial(Material baseMaterial, Material stencilMaterial, int stencilID)
{
// Check if maskingMaterial already has a base material associated with it.
int index = m_materialList.FindIndex(item => item.stencilMaterial == stencilMaterial);
if (index == -1)
{
MaskingMaterial temp = new MaskingMaterial();
temp.baseMaterial = baseMaterial;
temp.stencilMaterial = stencilMaterial;
temp.stencilID = stencilID;
temp.count = 1;
m_materialList.Add(temp);
}
else
{
stencilMaterial = m_materialList[index].stencilMaterial;
m_materialList[index].count += 1;
}
}
public static void RemoveStencilMaterial(Material stencilMaterial)
{
// Check if maskingMaterial is already on the list.
int index = m_materialList.FindIndex(item => item.stencilMaterial == stencilMaterial);
if (index != -1)
{
m_materialList.RemoveAt(index);
}
#if TMP_DEBUG_MODE
ListMaterials();
#endif
}
public static void ReleaseBaseMaterial(Material baseMaterial)
{
// Check if baseMaterial already has a masking material associated with it.
int index = m_materialList.FindIndex(item => item.baseMaterial == baseMaterial);
if (index == -1)
{
Debug.Log("No Masking Material exists for " + baseMaterial.name);
}
else
{
if (m_materialList[index].count > 1)
{
m_materialList[index].count -= 1;
Debug.Log("Removed (1) reference to " + m_materialList[index].stencilMaterial.name + ". There are " + m_materialList[index].count + " references left.");
}
else
{
Debug.Log("Removed last reference to " + m_materialList[index].stencilMaterial.name + " with ID " + m_materialList[index].stencilMaterial.GetInstanceID());
Object.DestroyImmediate(m_materialList[index].stencilMaterial);
m_materialList.RemoveAt(index);
}
}
#if TMP_DEBUG_MODE
ListMaterials();
#endif
}
public static void ClearMaterials()
{
if (m_materialList.Count == 0)
{
Debug.Log("Material List has already been cleared.");
return;
}
for (int i = 0; i < m_materialList.Count; i++)
{
//Material baseMaterial = m_materialList[i].baseMaterial;
Material stencilMaterial = m_materialList[i].stencilMaterial;
Object.DestroyImmediate(stencilMaterial);
}
m_materialList.Clear();
}
/// <summary>
/// Function to get the Stencil ID
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static int GetStencilID(GameObject obj)
{
// Implementation is almost copied from Unity UI
var count = 0;
var transform = obj.transform;
var stopAfter = FindRootSortOverrideCanvas(transform);
if (transform == stopAfter)
return count;
var t = transform.parent;
var components = TMP_ListPool<Mask>.Get();
while (t != null)
{
t.GetComponents<Mask>(components);
for (var i = 0; i < components.Count; ++i)
{
var mask = components[i];
if (mask != null && mask.MaskEnabled() && mask.graphic.IsActive())
{
++count;
break;
}
}
if (t == stopAfter)
break;
t = t.parent;
}
TMP_ListPool<Mask>.Release(components);
return Mathf.Min((1 << count) - 1, 255);
}
public static Material GetMaterialForRendering(MaskableGraphic graphic, Material baseMaterial)
{
if (baseMaterial == null)
return null;
var modifiers = TMP_ListPool<IMaterialModifier>.Get();
graphic.GetComponents(modifiers);
var result = baseMaterial;
for (int i = 0; i < modifiers.Count; i++)
result = modifiers[i].GetModifiedMaterial(result);
TMP_ListPool<IMaterialModifier>.Release(modifiers);
return result;
}
private static Transform FindRootSortOverrideCanvas(Transform start)
{
// Implementation is copied from Unity UI
var canvasList = TMP_ListPool<Canvas>.Get();
start.GetComponentsInParent(false, canvasList);
Canvas canvas = null;
for (int i = 0; i < canvasList.Count; ++i)
{
canvas = canvasList[i];
// We found the canvas we want to use break
if (canvas.overrideSorting)
break;
}
TMP_ListPool<Canvas>.Release(canvasList);
return canvas != null ? canvas.transform : null;
}
internal static Material GetFallbackMaterial(TMP_FontAsset fontAsset, Material sourceMaterial, int atlasIndex)
{
int sourceMaterialID = sourceMaterial.GetInstanceID();
Texture tex = fontAsset.atlasTextures[atlasIndex];
int texID = tex.GetInstanceID();
long key = (long)sourceMaterialID << 32 | (long)(uint)texID;
FallbackMaterial fallback;
if (m_fallbackMaterials.TryGetValue(key, out fallback))
{
// Check if source material properties have changed.
int sourceMaterialCRC = sourceMaterial.ComputeCRC();
if (sourceMaterialCRC == fallback.sourceMaterialCRC)
return fallback.fallbackMaterial;
CopyMaterialPresetProperties(sourceMaterial, fallback.fallbackMaterial);
fallback.sourceMaterialCRC = sourceMaterialCRC;
return fallback.fallbackMaterial;
}
// Create new material from the source material and assign relevant atlas texture
Material fallbackMaterial = new Material(sourceMaterial);
fallbackMaterial.SetTexture(ShaderUtilities.ID_MainTex, tex);
fallbackMaterial.hideFlags = HideFlags.HideAndDontSave;
#if UNITY_EDITOR
fallbackMaterial.name += " + " + tex.name;
#endif
fallback = new FallbackMaterial();
fallback.fallbackID = key;
fallback.sourceMaterial = fontAsset.material;
fallback.sourceMaterialCRC = sourceMaterial.ComputeCRC();
fallback.fallbackMaterial = fallbackMaterial;
fallback.count = 0;
m_fallbackMaterials.Add(key, fallback);
m_fallbackMaterialLookup.Add(fallbackMaterial.GetInstanceID(), key);
#if TMP_DEBUG_MODE
ListFallbackMaterials();
#endif
return fallbackMaterial;
}
/// <summary>
/// This function returns a material instance using the material properties of a previous material but using the font atlas texture of the new font asset.
/// </summary>
/// <param name="sourceMaterial">The material containing the source material properties to be copied to the new material.</param>
/// <param name="targetMaterial">The font atlas texture that should be assigned to the new material.</param>
/// <returns></returns>
public static Material GetFallbackMaterial (Material sourceMaterial, Material targetMaterial)
{
int sourceID = sourceMaterial.GetInstanceID();
Texture tex = targetMaterial.GetTexture(ShaderUtilities.ID_MainTex);
int texID = tex.GetInstanceID();
long key = (long)sourceID << 32 | (long)(uint)texID;
FallbackMaterial fallback;
if (m_fallbackMaterials.TryGetValue(key, out fallback))
{
// Check if source material properties have changed.
int sourceMaterialCRC = sourceMaterial.ComputeCRC();
if (sourceMaterialCRC == fallback.sourceMaterialCRC)
return fallback.fallbackMaterial;
CopyMaterialPresetProperties(sourceMaterial, fallback.fallbackMaterial);
fallback.sourceMaterialCRC = sourceMaterialCRC;
return fallback.fallbackMaterial;
}
// Create new material from the source material and copy properties if using distance field shaders.
Material fallbackMaterial;
if (sourceMaterial.HasProperty(ShaderUtilities.ID_GradientScale) && targetMaterial.HasProperty(ShaderUtilities.ID_GradientScale))
{
fallbackMaterial = new Material(sourceMaterial);
fallbackMaterial.hideFlags = HideFlags.HideAndDontSave;
#if UNITY_EDITOR
fallbackMaterial.name += " + " + tex.name;
//Debug.Log("Creating new fallback material for " + fallbackMaterial.name);
#endif
fallbackMaterial.SetTexture(ShaderUtilities.ID_MainTex, tex);
// Retain material properties unique to target material.
fallbackMaterial.SetFloat(ShaderUtilities.ID_GradientScale, targetMaterial.GetFloat(ShaderUtilities.ID_GradientScale));
fallbackMaterial.SetFloat(ShaderUtilities.ID_TextureWidth, targetMaterial.GetFloat(ShaderUtilities.ID_TextureWidth));
fallbackMaterial.SetFloat(ShaderUtilities.ID_TextureHeight, targetMaterial.GetFloat(ShaderUtilities.ID_TextureHeight));
fallbackMaterial.SetFloat(ShaderUtilities.ID_WeightNormal, targetMaterial.GetFloat(ShaderUtilities.ID_WeightNormal));
fallbackMaterial.SetFloat(ShaderUtilities.ID_WeightBold, targetMaterial.GetFloat(ShaderUtilities.ID_WeightBold));
}
else
{
fallbackMaterial = new Material(targetMaterial);
}
fallback = new FallbackMaterial();
fallback.fallbackID = key;
fallback.sourceMaterial = sourceMaterial;
fallback.sourceMaterialCRC = sourceMaterial.ComputeCRC();
fallback.fallbackMaterial = fallbackMaterial;
fallback.count = 0;
m_fallbackMaterials.Add(key, fallback);
m_fallbackMaterialLookup.Add(fallbackMaterial.GetInstanceID(), key);
#if TMP_DEBUG_MODE
ListFallbackMaterials();
#endif
return fallbackMaterial;
}
/// <summary>
///
/// </summary>
/// <param name="targetMaterial"></param>
public static void AddFallbackMaterialReference(Material targetMaterial)
{
if (targetMaterial == null) return;
int sourceID = targetMaterial.GetInstanceID();
long key;
// Lookup key to retrieve
if (m_fallbackMaterialLookup.TryGetValue(sourceID, out key))
{
FallbackMaterial fallback;
if (m_fallbackMaterials.TryGetValue(key, out fallback))
{
//Debug.Log("Adding Fallback material " + fallback.fallbackMaterial.name + " with reference count of " + (fallback.count + 1));
fallback.count += 1;
}
}
}
/// <summary>
///
/// </summary>
/// <param name="targetMaterial"></param>
public static void RemoveFallbackMaterialReference(Material targetMaterial)
{
if (targetMaterial == null) return;
int sourceID = targetMaterial.GetInstanceID();
long key;
// Lookup key to retrieve
if (m_fallbackMaterialLookup.TryGetValue(sourceID, out key))
{
FallbackMaterial fallback;
if (m_fallbackMaterials.TryGetValue(key, out fallback))
{
fallback.count -= 1;
if (fallback.count < 1)
m_fallbackCleanupList.Add(fallback);
}
}
}
/// <summary>
///
/// </summary>
public static void CleanupFallbackMaterials()
{
// Return if the list is empty.
if (m_fallbackCleanupList.Count == 0) return;
for (int i = 0; i < m_fallbackCleanupList.Count; i++)
{
FallbackMaterial fallback = m_fallbackCleanupList[i];
if (fallback.count < 1)
{
//Debug.Log("Cleaning up " + fallback.fallbackMaterial.name);
Material mat = fallback.fallbackMaterial;
m_fallbackMaterials.Remove(fallback.fallbackID);
m_fallbackMaterialLookup.Remove(mat.GetInstanceID());
Object.DestroyImmediate(mat);
mat = null;
}
}
m_fallbackCleanupList.Clear();
}
/// <summary>
/// Function to release the fallback material.
/// </summary>
/// <param name="fallbackMaterial">Material to be released.</param>
public static void ReleaseFallbackMaterial(Material fallbackMaterial)
{
if (fallbackMaterial == null) return;
int materialID = fallbackMaterial.GetInstanceID();
long key;
if (m_fallbackMaterialLookup.TryGetValue(materialID, out key))
{
FallbackMaterial fallback;
if (m_fallbackMaterials.TryGetValue(key, out fallback))
{
//Debug.Log("Releasing Fallback material " + fallback.fallbackMaterial.name + " with remaining reference count of " + (fallback.count - 1));
fallback.count -= 1;
if (fallback.count < 1)
m_fallbackCleanupList.Add(fallback);
}
}
isFallbackListDirty = true;
#if TMP_DEBUG_MODE
ListFallbackMaterials();
#endif
}
private class FallbackMaterial
{
public long fallbackID;
public Material sourceMaterial;
internal int sourceMaterialCRC;
public Material fallbackMaterial;
public int count;
}
private class MaskingMaterial
{
public Material baseMaterial;
public Material stencilMaterial;
public int count;
public int stencilID;
}
/// <summary>
/// Function to copy the properties of a source material preset to another while preserving the unique font asset properties of the destination material.
/// </summary>
/// <param name="source"></param>
/// <param name="destination"></param>
public static void CopyMaterialPresetProperties(Material source, Material destination)
{
if (!source.HasProperty(ShaderUtilities.ID_GradientScale) || !destination.HasProperty(ShaderUtilities.ID_GradientScale))
return;
// Save unique material properties
Texture dst_texture = destination.GetTexture(ShaderUtilities.ID_MainTex);
float dst_gradientScale = destination.GetFloat(ShaderUtilities.ID_GradientScale);
float dst_texWidth = destination.GetFloat(ShaderUtilities.ID_TextureWidth);
float dst_texHeight = destination.GetFloat(ShaderUtilities.ID_TextureHeight);
float dst_weightNormal = destination.GetFloat(ShaderUtilities.ID_WeightNormal);
float dst_weightBold = destination.GetFloat(ShaderUtilities.ID_WeightBold);
// Copy all material properties
destination.CopyPropertiesFromMaterial(source);
// Copy shader keywords
destination.shaderKeywords = source.shaderKeywords;
// Restore unique material properties
destination.SetTexture(ShaderUtilities.ID_MainTex, dst_texture);
destination.SetFloat(ShaderUtilities.ID_GradientScale, dst_gradientScale);
destination.SetFloat(ShaderUtilities.ID_TextureWidth, dst_texWidth);
destination.SetFloat(ShaderUtilities.ID_TextureHeight, dst_texHeight);
destination.SetFloat(ShaderUtilities.ID_WeightNormal, dst_weightNormal);
destination.SetFloat(ShaderUtilities.ID_WeightBold, dst_weightBold);
}
#if TMP_DEBUG_MODE
/// <summary>
///
/// </summary>
public static void ListMaterials()
{
if (m_materialList.Count == 0)
{
Debug.Log("Material List is empty.");
return;
}
//Debug.Log("List contains " + m_materialList.Count() + " items.");
for (int i = 0; i < m_materialList.Count; i++)
{
Material baseMaterial = m_materialList[i].baseMaterial;
Material stencilMaterial = m_materialList[i].stencilMaterial;
Debug.Log("Item #" + (i + 1) + " - Base Material is [" + baseMaterial.name + "] with ID " + baseMaterial.GetInstanceID() + " is associated with [" + (stencilMaterial != null ? stencilMaterial.name : "Null") + "] Stencil ID " + m_materialList[i].stencilID + " with ID " + (stencilMaterial != null ? stencilMaterial.GetInstanceID() : 0) + " and is referenced " + m_materialList[i].count + " time(s).");
}
}
/// <summary>
///
/// </summary>
public static void ListFallbackMaterials()
{
if (m_fallbackMaterials.Count == 0)
{
Debug.Log("Material List is empty.");
return;
}
Debug.Log("List contains " + m_fallbackMaterials.Count + " items.");
int count = 0;
foreach (var fallback in m_fallbackMaterials)
{
Material baseMaterial = fallback.Value.baseMaterial;
Material fallbackMaterial = fallback.Value.fallbackMaterial;
string output = "Item #" + (count++);
if (baseMaterial != null)
output += " - Base Material is [" + baseMaterial.name + "] with ID " + baseMaterial.GetInstanceID();
if (fallbackMaterial != null)
output += " is associated with [" + fallbackMaterial.name + "] with ID " + fallbackMaterial.GetInstanceID() + " and is referenced " + fallback.Value.count + " time(s).";
Debug.Log(output);
}
}
#endif
}
}