using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityObject = UnityEngine.Object; namespace UnityEditor.Rendering { /// /// Flags describing usage of an asset by its dependents, when that asset might have serialized shader property names. /// [Flags] enum SerializedShaderPropertyUsage : byte { /// /// Asset's usage is unknown. /// Unknown = 0, /// /// Asset contains no serialized shader properties. /// NoShaderProperties = 1, /// /// Asset is used by objects that have materials which have been upgraded. /// UsedByUpgraded = 2, /// /// Asset is used by objects that have materials which were not upgraded. /// UsedByNonUpgraded = 4, /// /// Asset is used by objects that have materials which may have been upgraded, but there is no unambiguous upgrade path. /// UsedByAmbiguouslyUpgraded = 8, } /// /// Class containing utility methods for upgrading assets affected by render pipeline migration. /// static class UpgradeUtility { /// /// Stable, unique identifier for some asset. /// internal struct UID { public string Value; public static implicit operator string(UID uid) => uid.Value; public static implicit operator UID(string id) => new UID { Value = id }; } internal interface IMaterial { UID ID { get; } string ShaderName { get; } } internal struct MaterialProxy : IMaterial { public MaterialProxy(Material material, UnityObject[] allAssetsAtPath) { m_ID = $"{allAssetsAtPath}{Array.IndexOf(allAssetsAtPath, material)}"; m_Material = material; } UID m_ID; Material m_Material; public UID ID => m_ID; public string ShaderName => m_Material.shader.name; public static implicit operator Material(MaterialProxy proxy) => proxy.m_Material; public static implicit operator MaterialProxy(Material material) => new MaterialProxy(material, AssetDatabase.LoadAllAssetsAtPath(AssetDatabase.GetAssetPath(material))); public override string ToString() => m_Material.ToString(); } /// /// Create A table of new shader names and all known upgrade paths to them in the target pipeline. /// /// The set of from which to build the table. /// A table of new shader names and all known upgrade paths to them in the target pipeline. public static Dictionary> GetAllUpgradePathsToShaders( IEnumerable upgraders ) { var upgradePathBuilder = new Dictionary>(); foreach (var upgrader in upgraders) { // skip over upgraders that do not rename shaders or have not been initialized if (upgrader.NewShaderPath == null) continue; if (!upgradePathBuilder.TryGetValue(upgrader.NewShaderPath, out var allPaths)) upgradePathBuilder[upgrader.NewShaderPath] = allPaths = new List(); allPaths.Add(upgrader); } return upgradePathBuilder.ToDictionary(kv => kv.Key, kv => kv.Value as IReadOnlyList); } /// /// Gets the new name for a serialized shader property, which needs to be applied to a material that has been upgraded. /// /// /// Some assets serialize shader property names, in order to apply modifications to a material at run-time. /// Use this method's return value to determine whether the serialized property name can be safely substituted, /// based on a material the host object intends to apply it to. /// /// A shader property name serialized on a host object. /// /// The target material to which some shader property modification will be applied. /// It is presumed to have already been upgraded. /// /// What type of property is. /// /// A table of new shader names and all known upgrade paths to them in the target pipeline. /// (See also .) /// /// /// Optional table of materials known to have gone through a specific upgrade path. /// /// /// The new name for . /// Its value is only guaranteed to be unambiguous if the method returns /// . /// /// /// Usage flags indicating how relates to . /// public static SerializedShaderPropertyUsage GetNewPropertyName( string shaderPropertyName, IMaterial material, MaterialUpgrader.MaterialPropertyType materialPropertyType, IReadOnlyDictionary> allUpgradePathsToNewShaders, IReadOnlyDictionary upgradePathsUsedByMaterials, out string newPropertyName ) { var result = SerializedShaderPropertyUsage.Unknown; // we want to find out if we should rename the property newPropertyName = shaderPropertyName; // first check if we already know how this material was upgraded if (upgradePathsUsedByMaterials != null && upgradePathsUsedByMaterials.TryGetValue(material.ID, out var upgrader)) { result |= SerializedShaderPropertyUsage.UsedByUpgraded; var propertyRenameTable = upgrader.GetPropertyRenameMap(materialPropertyType); propertyRenameTable.TryGetValue(shaderPropertyName, out newPropertyName); } // otherwise, try to guess whether it might have been upgraded if (newPropertyName == shaderPropertyName) { // get possible known upgrade paths material might have taken allUpgradePathsToNewShaders.TryGetValue(material.ShaderName, out var possibleUpgraders); // if there are none, then assume this material was not upgraded if ((possibleUpgraders?.Count ?? 0) == 0) { result |= SerializedShaderPropertyUsage.UsedByNonUpgraded; } // otherwise, see if there are any possible upgrade paths else { // narrow possible upgraders to those which specify a rename for the bound property var matchingUpgraders = possibleUpgraders.Where( u => u.GetPropertyRenameMap(materialPropertyType).ContainsKey(shaderPropertyName) ).ToList(); // if there are any, assume the material has been upgraded if (matchingUpgraders.Any()) { result |= SerializedShaderPropertyUsage.UsedByUpgraded; // if there are many possible upgrade paths to take, mark the upgrade as ambiguous newPropertyName = matchingUpgraders[0].GetPropertyRenameMap(materialPropertyType)[shaderPropertyName]; var name = newPropertyName; // cannot use out param inside lambda if (matchingUpgraders.Any(u => u.GetPropertyRenameMap(materialPropertyType)[shaderPropertyName] != name)) result |= SerializedShaderPropertyUsage.UsedByAmbiguouslyUpgraded; } else { var alreadyUpgraded = possibleUpgraders.Any(u => u.GetPropertyRenameMap(materialPropertyType).Values.Contains(shaderPropertyName)); if (alreadyUpgraded) { result |= SerializedShaderPropertyUsage.UsedByUpgraded; } } } } return result; } } }