UnityGame/Library/PackageCache/com.unity.render-pipelines.universal/Editor/Converter/MaterialReferenceBuilder.cs
2024-10-27 10:53:47 +03:00

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);
}
}
}