181 lines
7.1 KiB
C#
181 lines
7.1 KiB
C#
using UnityEngine;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System;
|
|
using Object = UnityEngine.Object;
|
|
|
|
namespace UnityEditor.Rendering.Universal
|
|
{
|
|
internal static class MaterialReferenceBuilder
|
|
{
|
|
public static readonly Dictionary<Type, List<MethodInfo>> MaterialReferenceLookup;
|
|
|
|
static MaterialReferenceBuilder()
|
|
{
|
|
MaterialReferenceLookup = GetMaterialReferenceLookup();
|
|
}
|
|
|
|
private static Dictionary<Type, List<MethodInfo>> GetMaterialReferenceLookup()
|
|
{
|
|
var result = new Dictionary<Type, List<MethodInfo>>();
|
|
|
|
var allObjectsWithMaterialProperties = TypeCache.GetTypesDerivedFrom<Object>()
|
|
.Where(type => type.GetProperties().Any(HasMaterialProperty));
|
|
|
|
foreach (var property in allObjectsWithMaterialProperties)
|
|
{
|
|
if (!result.ContainsKey(property))
|
|
{
|
|
result.Add(property, new List<MethodInfo>());
|
|
}
|
|
|
|
var materialProps = GetMaterialPropertiesWithoutLeaking(property);
|
|
foreach (var prop in materialProps)
|
|
{
|
|
result[property].Add(prop.GetGetMethod());
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static bool HasMaterialProperty(PropertyInfo prop)
|
|
{
|
|
return prop.PropertyType == typeof(Material) || prop.PropertyType == typeof(Material[]);
|
|
}
|
|
|
|
private static List<Material> GetMaterials(Object obj)
|
|
{
|
|
var result = new List<Material>();
|
|
|
|
var allMaterialProperties = obj.GetType().GetMaterialPropertiesWithoutLeaking();
|
|
foreach (var property in allMaterialProperties)
|
|
{
|
|
var value = property.GetGetMethod().GetMaterialFromMethod(obj, (methodName, objectName) =>
|
|
$"The method {methodName} was not found on {objectName}. This property will not be indexed.");
|
|
|
|
if (value is Material materialResult)
|
|
{
|
|
result.Add(materialResult);
|
|
}
|
|
else if (value is Material[] materialList)
|
|
{
|
|
result.AddRange(materialList);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets all of the types in the Material Reference lookup that are components. Used to determine whether to run the
|
|
/// method directly or on the component
|
|
/// </summary>
|
|
/// <returns>List of types that are components</returns>
|
|
public static List<Type> GetComponentTypes()
|
|
{
|
|
return MaterialReferenceLookup.Keys.Where(key => typeof(Component).IsAssignableFrom(key)).ToList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets all material properties from an object or a component of an object
|
|
/// </summary>
|
|
/// <param name="obj">The GameObject or Scriptable Object</param>
|
|
/// <returns>List of Materials</returns>
|
|
public static List<Material> GetMaterialsFromObject(Object obj)
|
|
{
|
|
var result = new List<Material>();
|
|
|
|
if (obj is GameObject go)
|
|
{
|
|
foreach (var key in GetComponentTypes())
|
|
{
|
|
var components = go.GetComponentsInChildren(key);
|
|
foreach (var component in components)
|
|
{
|
|
result.AddRange(GetMaterials(component));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result.AddRange(GetMaterials(obj));
|
|
}
|
|
|
|
return result.Distinct().ToList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Text Mesh pro will sometimes be missing the GetFontSharedMaterials method, even though the property is supposed
|
|
/// to have that method. This gracefully handles that case.
|
|
/// </summary>
|
|
/// <param name="method">The Method being invoked</param>
|
|
/// <param name="obj">The Unity Object the method is invoked upon</param>
|
|
/// <param name="generateErrorString">The function that takes the method name and object name and produces an error string</param>
|
|
/// <returns>The resulting object from invoking the method on the Object</returns>
|
|
/// <exception cref="Exception">Any exception that is not the missing method exception</exception>
|
|
public static object GetMaterialFromMethod(this MethodInfo method,
|
|
Object obj,
|
|
Func<string, string, string> generateErrorString)
|
|
{
|
|
object result = null;
|
|
try
|
|
{
|
|
result = method.Invoke(obj, null);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
// swallow the missing method exception, there's nothing we can do about it at this point
|
|
// and we've already checked for other possible null exceptions here
|
|
if ((e.InnerException is NullReferenceException))
|
|
{
|
|
Debug.LogWarning(generateErrorString(method.Name, obj.name));
|
|
}
|
|
else
|
|
{
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the SharedMaterial(s) properties when there are shared materials so that we don't leak material instances into the scene
|
|
/// </summary>
|
|
/// <param name="property">The property Type that we are getting the SharedMaterial(s) properties from</param>
|
|
/// <returns>List of shared material properties and other material properties that won't leak material instances</returns>
|
|
public static IEnumerable<PropertyInfo> GetMaterialPropertiesWithoutLeaking(this Type property)
|
|
{
|
|
var materialProps = property.GetProperties().Where(HasMaterialProperty).ToList();
|
|
|
|
// if there is a sharedMaterial property or sharedMaterials property, remove the property that will leak materials
|
|
var sharedMaterialProps =
|
|
materialProps.Where(prop => prop.Name.ToLowerInvariant().Contains("shared")).ToList();
|
|
|
|
var propsToRemove = sharedMaterialProps
|
|
.Select(prop => prop.Name.ToLowerInvariant().Replace("shared", string.Empty))
|
|
.ToList();
|
|
materialProps.RemoveAll(prop => propsToRemove.Contains(prop.Name.ToLowerInvariant()));
|
|
|
|
// also remove any property which has no setter
|
|
materialProps.RemoveAll(prop => prop.SetMethod == null);
|
|
|
|
return materialProps;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get whether or not a Material is considered readonly (Built In Resource)
|
|
/// </summary>
|
|
/// <param name="material">The Material to test</param>
|
|
/// <returns>Boolean of whether or not that Material is considered readonly</returns>
|
|
public static bool GetIsReadonlyMaterial(Material material)
|
|
{
|
|
var assetPath = AssetDatabase.GetAssetPath(material);
|
|
|
|
return string.IsNullOrEmpty(assetPath) || assetPath.Equals(@"Resources/unity_builtin_extra", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
}
|
|
}
|