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> MaterialReferenceLookup; static MaterialReferenceBuilder() { MaterialReferenceLookup = GetMaterialReferenceLookup(); } private static Dictionary> GetMaterialReferenceLookup() { var result = new Dictionary>(); var allObjectsWithMaterialProperties = TypeCache.GetTypesDerivedFrom() .Where(type => type.GetProperties().Any(HasMaterialProperty)); foreach (var property in allObjectsWithMaterialProperties) { if (!result.ContainsKey(property)) { result.Add(property, new List()); } 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 GetMaterials(Object obj) { var result = new List(); 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; } /// /// 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 /// /// List of types that are components public static List GetComponentTypes() { return MaterialReferenceLookup.Keys.Where(key => typeof(Component).IsAssignableFrom(key)).ToList(); } /// /// Gets all material properties from an object or a component of an object /// /// The GameObject or Scriptable Object /// List of Materials public static List GetMaterialsFromObject(Object obj) { var result = new List(); 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(); } /// /// 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. /// /// The Method being invoked /// The Unity Object the method is invoked upon /// The function that takes the method name and object name and produces an error string /// The resulting object from invoking the method on the Object /// Any exception that is not the missing method exception public static object GetMaterialFromMethod(this MethodInfo method, Object obj, Func 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; } /// /// Gets the SharedMaterial(s) properties when there are shared materials so that we don't leak material instances into the scene /// /// The property Type that we are getting the SharedMaterial(s) properties from /// List of shared material properties and other material properties that won't leak material instances public static IEnumerable 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; } /// /// Get whether or not a Material is considered readonly (Built In Resource) /// /// The Material to test /// Boolean of whether or not that Material is considered readonly public static bool GetIsReadonlyMaterial(Material material) { var assetPath = AssetDatabase.GetAssetPath(material); return string.IsNullOrEmpty(assetPath) || assetPath.Equals(@"Resources/unity_builtin_extra", StringComparison.OrdinalIgnoreCase); } } }