MaterialReferenceBuilder.cs 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Reflection;
  5. using System;
  6. using Object = UnityEngine.Object;
  7. namespace UnityEditor.Rendering.Universal.Converters
  8. {
  9. internal static class MaterialReferenceBuilder
  10. {
  11. public static readonly Dictionary<Type, List<MethodInfo>> MaterialReferenceLookup;
  12. static MaterialReferenceBuilder()
  13. {
  14. MaterialReferenceLookup = GetMaterialReferenceLookup();
  15. }
  16. private static Dictionary<Type, List<MethodInfo>> GetMaterialReferenceLookup()
  17. {
  18. var result = new Dictionary<Type, List<MethodInfo>>();
  19. var allObjectsWithMaterialProperties = TypeCache.GetTypesDerivedFrom<Object>()
  20. .Where(type => type.GetProperties().Any(HasMaterialProperty));
  21. foreach (var property in allObjectsWithMaterialProperties)
  22. {
  23. if (!result.ContainsKey(property))
  24. {
  25. result.Add(property, new List<MethodInfo>());
  26. }
  27. var materialProps = GetMaterialPropertiesWithoutLeaking(property);
  28. foreach (var prop in materialProps)
  29. {
  30. result[property].Add(prop.GetGetMethod());
  31. }
  32. }
  33. return result;
  34. }
  35. private static bool HasMaterialProperty(PropertyInfo prop)
  36. {
  37. return prop.PropertyType == typeof(Material) || prop.PropertyType == typeof(Material[]);
  38. }
  39. private static List<Material> GetMaterials(Object obj)
  40. {
  41. var result = new List<Material>();
  42. var allMaterialProperties = obj.GetType().GetMaterialPropertiesWithoutLeaking();
  43. foreach (var property in allMaterialProperties)
  44. {
  45. var value = property.GetGetMethod().GetMaterialFromMethod(obj, (methodName, objectName) =>
  46. $"The method {methodName} was not found on {objectName}. This property will not be indexed.");
  47. if (value is Material materialResult)
  48. {
  49. result.Add(materialResult);
  50. }
  51. else if (value is Material[] materialList)
  52. {
  53. result.AddRange(materialList);
  54. }
  55. }
  56. return result;
  57. }
  58. /// <summary>
  59. /// Gets all of the types in the Material Reference lookup that are components. Used to determine whether to run the
  60. /// method directly or on the component
  61. /// </summary>
  62. /// <returns>List of types that are components</returns>
  63. public static List<Type> GetComponentTypes()
  64. {
  65. return MaterialReferenceLookup.Keys.Where(key => typeof(Component).IsAssignableFrom(key)).ToList();
  66. }
  67. /// <summary>
  68. /// Gets all material properties from an object or a component of an object
  69. /// </summary>
  70. /// <param name="obj">The GameObject or Scriptable Object</param>
  71. /// <returns>List of Materials</returns>
  72. public static List<Material> GetMaterialsFromObject(Object obj)
  73. {
  74. var result = new List<Material>();
  75. if (obj is GameObject go)
  76. {
  77. foreach (var key in GetComponentTypes())
  78. {
  79. var components = go.GetComponentsInChildren(key);
  80. foreach (var component in components)
  81. {
  82. result.AddRange(GetMaterials(component));
  83. }
  84. }
  85. }
  86. else
  87. {
  88. result.AddRange(GetMaterials(obj));
  89. }
  90. return result.Distinct().ToList();
  91. }
  92. /// <summary>
  93. /// Text Mesh pro will sometimes be missing the GetFontSharedMaterials method, even though the property is supposed
  94. /// to have that method. This gracefully handles that case.
  95. /// </summary>
  96. /// <param name="method">The Method being invoked</param>
  97. /// <param name="obj">The Unity Object the method is invoked upon</param>
  98. /// <param name="generateErrorString">The function that takes the method name and object name and produces an error string</param>
  99. /// <returns>The resulting object from invoking the method on the Object</returns>
  100. /// <exception cref="Exception">Any exception that is not the missing method exception</exception>
  101. public static object GetMaterialFromMethod(this MethodInfo method,
  102. Object obj,
  103. Func<string, string, string> generateErrorString)
  104. {
  105. object result = null;
  106. try
  107. {
  108. result = method.Invoke(obj, null);
  109. }
  110. catch (Exception e)
  111. {
  112. // swallow the missing method exception, there's nothing we can do about it at this point
  113. // and we've already checked for other possible null exceptions here
  114. if ((e.InnerException is NullReferenceException))
  115. {
  116. Debug.LogWarning(generateErrorString(method.Name, obj.name));
  117. }
  118. else
  119. {
  120. throw e;
  121. }
  122. }
  123. return result;
  124. }
  125. /// <summary>
  126. /// Gets the SharedMaterial(s) properties when there are shared materials so that we don't leak material instances into the scene
  127. /// </summary>
  128. /// <param name="property">The property Type that we are getting the SharedMaterial(s) properties from</param>
  129. /// <returns>List of shared material properties and other material properties that won't leak material instances</returns>
  130. public static IEnumerable<PropertyInfo> GetMaterialPropertiesWithoutLeaking(this Type property)
  131. {
  132. var materialProps = property.GetProperties().Where(HasMaterialProperty).ToList();
  133. // if there is a sharedMaterial property or sharedMaterials property, remove the property that will leak materials
  134. var sharedMaterialProps =
  135. materialProps.Where(prop => prop.Name.ToLowerInvariant().Contains("shared")).ToList();
  136. var propsToRemove = sharedMaterialProps
  137. .Select(prop => prop.Name.ToLowerInvariant().Replace("shared", string.Empty))
  138. .ToList();
  139. materialProps.RemoveAll(prop => propsToRemove.Contains(prop.Name.ToLowerInvariant()));
  140. // also remove any property which has no setter
  141. materialProps.RemoveAll(prop => prop.SetMethod == null);
  142. return materialProps;
  143. }
  144. /// <summary>
  145. /// Get whether or not a Material is considered readonly (Built In Resource)
  146. /// </summary>
  147. /// <param name="material">The Material to test</param>
  148. /// <returns>Boolean of whether or not that Material is considered readonly</returns>
  149. public static bool GetIsReadonlyMaterial(Material material)
  150. {
  151. var assetPath = AssetDatabase.GetAssetPath(material);
  152. return string.IsNullOrEmpty(assetPath) || assetPath.Equals(@"Resources/unity_builtin_extra", StringComparison.OrdinalIgnoreCase);
  153. }
  154. }
  155. }