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

197 lines
9.0 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityObject = UnityEngine.Object;
namespace UnityEditor.Rendering
{
/// <summary>
/// Flags describing usage of an asset by its dependents, when that asset might have serialized shader property names.
/// </summary>
[Flags]
enum SerializedShaderPropertyUsage : byte
{
/// <summary>
/// Asset's usage is unknown.
/// </summary>
Unknown = 0,
/// <summary>
/// Asset contains no serialized shader properties.
/// </summary>
NoShaderProperties = 1,
/// <summary>
/// Asset is used by objects that have materials which have been upgraded.
/// </summary>
UsedByUpgraded = 2,
/// <summary>
/// Asset is used by objects that have materials which were not upgraded.
/// </summary>
UsedByNonUpgraded = 4,
/// <summary>
/// Asset is used by objects that have materials which may have been upgraded, but there is no unambiguous upgrade path.
/// </summary>
UsedByAmbiguouslyUpgraded = 8,
}
/// <summary>
/// Class containing utility methods for upgrading assets affected by render pipeline migration.
/// </summary>
static class UpgradeUtility
{
/// <summary>
/// Stable, unique identifier for some asset.
/// </summary>
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();
}
/// <summary>
/// Create A table of new shader names and all known upgrade paths to them in the target pipeline.
/// </summary>
/// <param name="upgraders">The set of <see cref="MaterialUpgrader"/> from which to build the table.</param>
/// <returns>A table of new shader names and all known upgrade paths to them in the target pipeline.</returns>
public static Dictionary<string, IReadOnlyList<MaterialUpgrader>> GetAllUpgradePathsToShaders(
IEnumerable<MaterialUpgrader> upgraders
)
{
var upgradePathBuilder = new Dictionary<string, List<MaterialUpgrader>>();
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<MaterialUpgrader>();
allPaths.Add(upgrader);
}
return upgradePathBuilder.ToDictionary(kv => kv.Key, kv => kv.Value as IReadOnlyList<MaterialUpgrader>);
}
/// <summary>
/// Gets the new name for a serialized shader property, which needs to be applied to a material that has been upgraded.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="shaderPropertyName">A shader property name serialized on a host object.</param>
/// <param name="material">
/// The target material to which some shader property modification will be applied.
/// It is presumed to have already been upgraded.
/// </param>
/// <param name="materialPropertyType">What type of property <paramref name="shaderPropertyName"/> is.</param>
/// <param name="allUpgradePathsToNewShaders">
/// A table of new shader names and all known upgrade paths to them in the target pipeline.
/// (See also <seealso cref="UpgradeUtility.GetAllUpgradePathsToShaders"/>.)
/// </param>
/// <param name="upgradePathsUsedByMaterials">
/// Optional table of materials known to have gone through a specific upgrade path.
/// </param>
/// <param name="newPropertyName">
/// The new name for <paramref name="shaderPropertyName"/>.
/// Its value is only guaranteed to be unambiguous if the method returns
/// <see cref="SerializedShaderPropertyUsage.UsedByUpgraded"/>.
/// </param>
/// <returns>
/// Usage flags indicating how <paramref name="shaderPropertyName"/> relates to <paramref name="material"/>.
/// </returns>
public static SerializedShaderPropertyUsage GetNewPropertyName(
string shaderPropertyName,
IMaterial material,
MaterialUpgrader.MaterialPropertyType materialPropertyType,
IReadOnlyDictionary<string, IReadOnlyList<MaterialUpgrader>> allUpgradePathsToNewShaders,
IReadOnlyDictionary<UID, MaterialUpgrader> 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;
}
}
}