You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
696 lines
36 KiB
696 lines
36 KiB
using System; |
|
using System.Collections.Generic; |
|
using System.Linq; |
|
using System.Text.RegularExpressions; |
|
using UnityEditor.Animations; |
|
using UnityEditor.SceneManagement; |
|
using UnityEngine; |
|
using UnityEngine.Playables; |
|
using UnityEngine.Rendering; |
|
using IMaterial = UnityEditor.Rendering.UpgradeUtility.IMaterial; |
|
using UID = UnityEditor.Rendering.UpgradeUtility.UID; |
|
|
|
namespace UnityEditor.Rendering |
|
{ |
|
/// <summary> |
|
/// A class containing static methods for updating <see cref="AnimationClip"/> assets with bindings for <see cref="Material"/> properties. |
|
/// </summary> |
|
/// <remarks> |
|
/// Animation clips store bindings for material properties by path name, but don't know whether those properties exist on their dependents. |
|
/// Because property names did not change uniformly in the material/shader upgrade process, it is not possible to patch path names indiscriminately. |
|
/// This class provides utilities for discovering how clips are used, so users can make decisions about whether or not to then update them. |
|
/// It has the limitation that it only knows about: |
|
/// - Clips that are directly referenced by an <see cref="Animation"/> component |
|
/// - Clips referenced by an <see cref="AnimatorController"/> used by an <see cref="Animator"/> component |
|
/// - Clips that are sub-assets of a <see cref="PlayableAsset"/> used by a <see cref="PlayableDirector"/> component with a single <see cref="Animator"/> binding |
|
/// It does not know about clips that might be referenced in other ways for run-time reassignment. |
|
/// Recommended usage is to call <see cref="DoUpgradeAllClipsMenuItem"/> from a menu item callback. |
|
/// The utility can also provide faster, more reliable results if it knows what <see cref="MaterialUpgrader"/> was used to upgrade specific materials. |
|
/// </remarks> |
|
static partial class AnimationClipUpgrader |
|
{ |
|
static readonly Regex k_MatchMaterialPropertyName = new Regex(@"material.(\w+)(\.\w+)?", RegexOptions.Compiled); |
|
|
|
/// <summary> |
|
/// Determines whether the specified<see cref="EditorCurveBinding"/> is for a material property. |
|
/// </summary> |
|
/// <remarks>Internal only for testability.</remarks> |
|
/// <param name="b">An <see cref="EditorCurveBinding"/>.</param> |
|
/// <returns><c>true</c> if the binding is for a material property; <c>false</c> otherwise.</returns> |
|
internal static bool IsMaterialBinding(EditorCurveBinding b) => |
|
(b.type?.IsSubclassOf(typeof(Renderer)) ?? false) |
|
&& !string.IsNullOrEmpty(b.propertyName) |
|
&& k_MatchMaterialPropertyName.IsMatch(b.propertyName); |
|
|
|
static readonly IReadOnlyCollection<string> k_ColorAttributeSuffixes = |
|
new HashSet<string>(new[] { ".r", ".g", ".b", ".a" }); |
|
|
|
/// <summary> |
|
/// Infer a shader property name and type from an <see cref="EditorCurveBinding"/>. |
|
/// </summary> |
|
/// <remarks>Internal only for testability.</remarks> |
|
/// <param name="binding">A binding presumed to map to a material property. (See also <seealso cref="IsMaterialBinding"/>.)</param> |
|
/// <returns>A shader property name, and a guess of what type of shader property it targets.</returns> |
|
internal static (string Name, ShaderPropertyType Type) InferShaderProperty(EditorCurveBinding binding) |
|
{ |
|
var match = k_MatchMaterialPropertyName.Match(binding.propertyName); |
|
var propertyName = match.Groups[1].Value; |
|
var propertyType = match.Groups[2].Value; |
|
return (propertyName, |
|
k_ColorAttributeSuffixes.Contains(propertyType) ? ShaderPropertyType.Color : ShaderPropertyType.Float); |
|
} |
|
|
|
/// <summary> |
|
/// Gets asset data for all clip assets at the specified paths, which contain bindings for material properties. |
|
/// (See also <seealso cref="GatherClipsUsageInDependentPrefabs"/> and <seealso cref="GatherClipsUsageInDependentScenes"/>.) |
|
/// </summary> |
|
/// <param name="clipPaths">Paths to assets containing <see cref="AnimationClip"/>.</param> |
|
/// <returns> |
|
/// Lookup table mapping <see cref="AnimationClip"/> to its asset path, bindings, property rename table, and usage. |
|
/// (Use <see cref="GatherClipsUsageInDependentPrefabs"/> to initialize rename table and usage.) |
|
/// </returns> |
|
internal static IDictionary< |
|
IAnimationClip, |
|
(ClipPath Path, EditorCurveBinding[] Bindings, SerializedShaderPropertyUsage Usage, IDictionary<EditorCurveBinding, string> PropertyRenames) |
|
> GetAssetDataForClipsFiltered( |
|
IEnumerable<ClipPath> clipPaths |
|
) |
|
{ |
|
var result = new Dictionary< |
|
IAnimationClip, |
|
(ClipPath Path, EditorCurveBinding[] Bindings, SerializedShaderPropertyUsage Usage, IDictionary<EditorCurveBinding, string> PropertyRenames) |
|
>(); |
|
foreach (var clipPath in clipPaths) |
|
{ |
|
foreach (var asset in AssetDatabase.LoadAllAssetsAtPath(clipPath)) |
|
{ |
|
if (!(asset is AnimationClip clip)) |
|
continue; |
|
|
|
var bindings = AnimationUtility.GetCurveBindings(clip).Where(IsMaterialBinding).ToArray(); |
|
if (bindings.Length == 0) |
|
continue; |
|
|
|
result[(AnimationClipProxy)clip] = |
|
(clipPath, bindings, SerializedShaderPropertyUsage.Unknown, new Dictionary<EditorCurveBinding, string>()); |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
/// <summary> |
|
/// Get dependency mappings between <see cref="AnimationClip"/> and their dependents. |
|
/// </summary> |
|
/// <param name="clips"> Paths to clips to consider. (See also <seealso cref="GetAssetDataForClipsFiltered"/>.)</param> |
|
/// <param name="assets">Paths to assets to consider.</param> |
|
/// <param name="clipToDependentAssets">Mapping of clip paths to paths of their dependents.</param> |
|
/// <param name="assetToClipDependencies">Mapping of asset paths to their clip dependencies.</param> |
|
/// <typeparam name="T">The type of asset path.</typeparam> |
|
internal static void GetClipDependencyMappings<T>( |
|
IEnumerable<ClipPath> clips, |
|
IEnumerable<T> assets, |
|
out IReadOnlyDictionary<ClipPath, IReadOnlyCollection<T>> clipToDependentAssets, |
|
out IReadOnlyDictionary<T, IReadOnlyCollection<ClipPath>> assetToClipDependencies |
|
) where T : struct, IAssetPath |
|
{ |
|
// ensure there are no duplicate keys |
|
clips = new HashSet<ClipPath>(clips); |
|
assets = new HashSet<T>(assets); |
|
|
|
// create mutable builders |
|
var clipsDependentsBuilder = clips.ToDictionary(c => c, c => new HashSet<T>()); |
|
var assetsBuilder = new Dictionary<T, HashSet<ClipPath>>(); |
|
|
|
// build dependency tables |
|
foreach (var asset in assets) |
|
{ |
|
assetsBuilder[asset] = new HashSet<ClipPath>(); |
|
|
|
foreach (var dependencyPath in AssetDatabase.GetDependencies(asset.Path)) |
|
{ |
|
if (!clipsDependentsBuilder.TryGetValue(dependencyPath, out var dependents)) |
|
continue; |
|
|
|
dependents.Add(asset); |
|
assetsBuilder[asset].Add(dependencyPath); |
|
} |
|
} |
|
|
|
// return readonly results |
|
clipToDependentAssets = |
|
clipsDependentsBuilder.ToDictionary(kv => kv.Key, kv => kv.Value as IReadOnlyCollection<T>); |
|
assetToClipDependencies = |
|
assetsBuilder.ToDictionary(kv => kv.Key, kv => kv.Value as IReadOnlyCollection<ClipPath>); |
|
} |
|
|
|
// reusable buffers |
|
static readonly List<Animation> s_AnimationBuffer = new List<Animation>(8); |
|
static readonly List<Animator> s_AnimatorBuffer = new List<Animator>(8); |
|
static readonly List<IAnimationClipSource> s_CustomAnimationBuffer = new List<IAnimationClipSource>(8); |
|
static readonly List<PlayableDirector> s_PlayableDirectorBuffer = new List<PlayableDirector>(8); |
|
|
|
/// <summary> |
|
/// Get information about a clip's usage among its dependent scenes to determine whether or not it should be upgraded. |
|
/// </summary> |
|
/// <param name="clipDependents"> |
|
/// A table mapping clip asset paths, to asset paths of their dependent prefabs. |
|
/// (See <seealso cref="GetClipDependencyMappings{T}"/>.) |
|
/// </param> |
|
/// <param name="assetDependencies"> |
|
/// A table mapping prefab asset paths, to asset paths of their clip dependencies. |
|
/// (See <seealso cref="GetClipDependencyMappings{T}"/>.) |
|
/// </param> |
|
/// <param name="clipData"> |
|
/// A table mapping clips to other data about them. (See also <seealso cref="GetAssetDataForClipsFiltered"/>.) |
|
/// </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="progressFunctor"> |
|
/// Optional functor to display a progress bar. |
|
/// </param> |
|
internal static void GatherClipsUsageInDependentPrefabs( |
|
IReadOnlyDictionary<ClipPath, IReadOnlyCollection<PrefabPath>> clipDependents, |
|
// TODO: right now, clip dependencies are gathered in Animation/Animator, so this may not be needed... |
|
IReadOnlyDictionary<PrefabPath, IReadOnlyCollection<ClipPath>> assetDependencies, |
|
IDictionary< |
|
IAnimationClip, |
|
(ClipPath Path, EditorCurveBinding[] Bindings, SerializedShaderPropertyUsage Usage, IDictionary<EditorCurveBinding, string> PropertyRenames) |
|
> clipData, |
|
IReadOnlyDictionary<string, IReadOnlyList<MaterialUpgrader>> allUpgradePathsToNewShaders, |
|
IReadOnlyDictionary<UID, MaterialUpgrader> upgradePathsUsedByMaterials = default, |
|
Func<string, float, bool> progressFunctor = null |
|
) |
|
{ |
|
int clipIndex = 0; |
|
int totalNumberOfClips = clipDependents.Count; |
|
|
|
// check all dependents for usage |
|
foreach (var kv in clipDependents) |
|
{ |
|
float currentProgress = (float)++clipIndex / totalNumberOfClips; |
|
if (progressFunctor != null && progressFunctor($"({clipIndex} of {totalNumberOfClips}) {kv.Key.Path}", currentProgress)) |
|
break; |
|
|
|
foreach (var prefabPath in kv.Value) |
|
{ |
|
var go = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath); |
|
GatherClipsUsageForGameObject(go, clipData, allUpgradePathsToNewShaders, upgradePathsUsedByMaterials); |
|
} |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Get information about a clip's usage among its dependent scenes to determine whether or not it should be upgraded. |
|
/// </summary> |
|
/// <remarks> |
|
/// Because this method will open scenes to search for usages, it is recommended you first prompt for user input. |
|
/// It is also a good idea to first call <see cref="GatherClipsUsageInDependentPrefabs"/> to generate usage data. |
|
/// Clips that are already known to be unsafe for upgrading based on their prefab usage can be skipped here. |
|
/// </remarks> |
|
/// <param name="clipDependents"> |
|
/// A table mapping clip asset paths, to asset paths of their dependent scenes. |
|
/// (See <seealso cref="GetClipDependencyMappings{T}"/>.) |
|
/// </param> |
|
/// <param name="assetDependencies"> |
|
/// A table mapping scene asset paths, to asset paths of their clip dependencies. |
|
/// (See <seealso cref="GetClipDependencyMappings{T}"/>.) |
|
/// </param> |
|
/// <param name="clipData"> |
|
/// A table mapping clips to other data about them. (See also <seealso cref="GetAssetDataForClipsFiltered"/>.) |
|
/// </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="progressFunctor"> |
|
/// Optional functor to display a progress bar. |
|
/// </param> |
|
internal static void GatherClipsUsageInDependentScenes( |
|
IReadOnlyDictionary<ClipPath, IReadOnlyCollection<ScenePath>> clipDependents, |
|
// TODO: right now, clip dependencies are gathered in Animation/Animator, so this may not be needed... |
|
IReadOnlyDictionary<ScenePath, IReadOnlyCollection<ClipPath>> assetDependencies, |
|
IDictionary< |
|
IAnimationClip, |
|
(ClipPath Path, EditorCurveBinding[] Bindings, SerializedShaderPropertyUsage Usage, IDictionary<EditorCurveBinding, string> |
|
PropertyRenames) |
|
> clipData, |
|
IReadOnlyDictionary<string, IReadOnlyList<MaterialUpgrader>> allUpgradePathsToNewShaders, |
|
IReadOnlyDictionary<UID, MaterialUpgrader> upgradePathsUsedByMaterials = default, |
|
Func<string, float, bool> progressFunctor = null |
|
) |
|
{ |
|
int clipIndex = 0; |
|
int totalNumberOfClips = clipDependents.Count; |
|
|
|
// check all dependents for usage |
|
foreach (var kv in clipDependents) |
|
{ |
|
float currentProgress = (float)++clipIndex / totalNumberOfClips; |
|
if (progressFunctor != null && progressFunctor($"({clipIndex} of {totalNumberOfClips}) {kv.Key.Path}", currentProgress)) |
|
break; |
|
|
|
foreach (var scenePath in kv.Value) |
|
{ |
|
var scene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single); |
|
foreach (var go in scene.GetRootGameObjects()) |
|
GatherClipsUsageForGameObject(go, clipData, allUpgradePathsToNewShaders, upgradePathsUsedByMaterials); |
|
} |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Update usage information about the specified clips in the clip data table. |
|
/// </summary> |
|
/// <param name="go">A prefab, or a <see cref="GameObject"/> in a scene.</param> |
|
/// <param name="clipData"> |
|
/// A table mapping clips to other data about them. (See also <seealso cref="GetAssetDataForClipsFiltered"/>.) |
|
/// </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> |
|
static void GatherClipsUsageForGameObject( |
|
GameObject go, |
|
IDictionary< |
|
IAnimationClip, |
|
(ClipPath Path, EditorCurveBinding[] Bindings, SerializedShaderPropertyUsage Usage, IDictionary<EditorCurveBinding, string> PropertyRenames) |
|
> clipData, |
|
IReadOnlyDictionary<string, IReadOnlyList<MaterialUpgrader>> allUpgradePathsToNewShaders, |
|
IReadOnlyDictionary<UID, MaterialUpgrader> upgradePathsUsedByMaterials = default |
|
) |
|
{ |
|
go.GetComponentsInChildren(true, s_AnimationBuffer); |
|
go.GetComponentsInChildren(true, s_AnimatorBuffer); |
|
go.GetComponentsInChildren(true, s_CustomAnimationBuffer); |
|
|
|
// first check clip usage among GameObjects with legacy Animation |
|
var gameObjects = new HashSet<GameObject>(s_AnimationBuffer.Select(a => a.gameObject) |
|
.Union(s_AnimatorBuffer.Select(a => a.gameObject)) |
|
.Union(s_CustomAnimationBuffer.Where(a => a is Component).Select(a => ((Component)a).gameObject))); |
|
|
|
foreach (var gameObject in gameObjects) |
|
{ |
|
var clips = AnimationUtility.GetAnimationClips(gameObject).Select(clip => (IAnimationClip)(AnimationClipProxy)clip); |
|
|
|
GatherClipsUsageForAnimatedHierarchy( |
|
gameObject.transform, clips, clipData, allUpgradePathsToNewShaders, upgradePathsUsedByMaterials |
|
); |
|
} |
|
|
|
// next check clip usage among GameObjects with PlayableDirector |
|
go.GetComponentsInChildren(true, s_PlayableDirectorBuffer); |
|
foreach (var playableDirector in s_PlayableDirectorBuffer) |
|
{ |
|
var playableAsset = playableDirector.playableAsset; |
|
if (playableAsset == null) |
|
continue; |
|
|
|
var assetPath = AssetDatabase.GetAssetPath(playableAsset); |
|
|
|
// get all clip sub-assets |
|
var clips = new HashSet<IAnimationClip>( |
|
AssetDatabase.LoadAllAssetsAtPath(assetPath) |
|
.Where(asset => asset is AnimationClip) |
|
.Select(asset => (IAnimationClip)(AnimationClipProxy)(asset as AnimationClip)) |
|
); |
|
|
|
// get all clip dependency-assets |
|
// this will not handle nested clips in FBX like assets, but these are less likely to be editable |
|
clips.UnionWith(AssetDatabase.GetDependencies(assetPath) |
|
.Select(AssetDatabase.LoadAssetAtPath<AnimationClip>) |
|
.Where(asset => asset is AnimationClip) |
|
.Select(asset => (IAnimationClip)(AnimationClipProxy)asset)); |
|
|
|
// check if the value of a binding is an animator, and examines clip usage relative to it |
|
// this is imprecise, but is suitable to catch the majority of cases (i.e., a single animator binding) |
|
using (var so = new SerializedObject(playableDirector)) |
|
{ |
|
var clipsProp = so.FindProperty("m_SceneBindings"); |
|
for (int i = 0, count = clipsProp.arraySize; i < count; ++i) |
|
{ |
|
var elementProp = clipsProp.GetArrayElementAtIndex(i); |
|
var value = elementProp.FindPropertyRelative("value"); |
|
if (value.objectReferenceValue is Animator animator) |
|
{ |
|
GatherClipsUsageForAnimatedHierarchy( |
|
animator.transform, clips, clipData, allUpgradePathsToNewShaders, upgradePathsUsedByMaterials |
|
); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// release UnityObject references |
|
s_AnimationBuffer.Clear(); |
|
s_AnimatorBuffer.Clear(); |
|
s_CustomAnimationBuffer.Clear(); |
|
s_PlayableDirectorBuffer.Clear(); |
|
} |
|
|
|
// reusable buffers |
|
static readonly List<Renderer> s_RendererBuffer = new List<Renderer>(8); |
|
|
|
static readonly Dictionary<string, (IRenderer Renderer, List<IMaterial> Materials)> s_RenderersByPath = |
|
new Dictionary<string, (IRenderer Renderer, List<IMaterial> Materials)>(); |
|
|
|
/// <summary> |
|
/// Update usage information about the specified clips in the clip data table. |
|
/// </summary> |
|
/// <param name="root">The root of the animated hierarchy (i.e., object with Animation or Animator).</param> |
|
/// <param name="clips">Collection of animation clips</param> |
|
/// <param name="clipData"> |
|
/// A table mapping clips to other data about them. (See also <seealso cref="GetAssetDataForClipsFiltered"/>.) |
|
/// </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> |
|
static void GatherClipsUsageForAnimatedHierarchy( |
|
Transform root, |
|
IEnumerable<IAnimationClip> clips, |
|
IDictionary< |
|
IAnimationClip, |
|
(ClipPath Path, EditorCurveBinding[] Bindings, SerializedShaderPropertyUsage Usage, IDictionary<EditorCurveBinding, string> PropertyRenames) |
|
> clipData, |
|
IReadOnlyDictionary<string, IReadOnlyList<MaterialUpgrader>> allUpgradePathsToNewShaders, |
|
IReadOnlyDictionary<UID, MaterialUpgrader> upgradePathsUsedByMaterials |
|
) |
|
{ |
|
// TODO: report paths of specific assets that contribute to problematic results? |
|
|
|
// find all renderers in the animated hierarchy |
|
root.GetComponentsInChildren(true, s_RendererBuffer); |
|
foreach (var renderer in s_RendererBuffer) |
|
{ |
|
var path = AnimationUtility.CalculateTransformPath(renderer.transform, root); |
|
var m = ListPool<IMaterial>.Get(); |
|
var r = (RendererProxy)renderer; |
|
r.GetSharedMaterials(m); |
|
s_RenderersByPath[path] = (r, m); |
|
} |
|
|
|
// if there are any renderers, check all clips for usage |
|
if (s_RendererBuffer.Count > 0) |
|
{ |
|
foreach (var clip in clips) |
|
GatherClipUsage(clip, clipData, s_RenderersByPath, allUpgradePathsToNewShaders, upgradePathsUsedByMaterials); |
|
} |
|
|
|
// release UnityObject references |
|
s_RendererBuffer.Clear(); |
|
foreach (var (_, materials) in s_RenderersByPath.Values) |
|
ListPool<IMaterial>.Release(materials); |
|
s_RenderersByPath.Clear(); |
|
} |
|
|
|
/// <summary> |
|
/// Update usage information about the specified clip in the clip data table. |
|
/// </summary> |
|
/// <remarks> |
|
/// This method works by looking at shaders used by materials assigned to the specified renderers. |
|
/// Usage and property renames for the clip are updated, if a binding in the clip matches an upgrader. |
|
/// Internal only for testability. |
|
/// </remarks> |
|
/// <param name="clip">An animation clip.</param> |
|
/// <param name="clipData"> |
|
/// A table mapping clips to other data about them. (See also <seealso cref="GetAssetDataForClipsFiltered"/>.) |
|
/// </param> |
|
/// <param name="renderersByPath"> |
|
/// A table mapping transform paths of renderers to lists of the materials they use. |
|
/// </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> |
|
internal static void GatherClipUsage( |
|
IAnimationClip clip, |
|
IDictionary< |
|
IAnimationClip, |
|
(ClipPath Path, EditorCurveBinding[] Bindings, SerializedShaderPropertyUsage Usage, IDictionary<EditorCurveBinding, string> PropertyRenames) |
|
> clipData, |
|
IReadOnlyDictionary<string, (IRenderer Renderer, List<IMaterial> Materials)> renderersByPath, |
|
IReadOnlyDictionary<string, IReadOnlyList<MaterialUpgrader>> allUpgradePathsToNewShaders, |
|
IReadOnlyDictionary<UID, MaterialUpgrader> upgradePathsUsedByMaterials |
|
) |
|
{ |
|
// exit if clip is unknown; it may have been filtered at an earlier stage |
|
if (!clipData.TryGetValue(clip, out var data)) |
|
return; |
|
|
|
// see if any animated material bindings in the clip refer to renderers in animated hierarchy |
|
foreach (var binding in data.Bindings) |
|
{ |
|
// skip if binding is not for material, or refers to a nonexistent renderer |
|
if ( |
|
!IsMaterialBinding(binding) |
|
|| !renderersByPath.TryGetValue(binding.path, out var rendererData) |
|
) continue; |
|
|
|
// determine the shader property name and type from the binding |
|
var shaderProperty = InferShaderProperty(binding); |
|
var renameType = shaderProperty.Type == ShaderPropertyType.Color |
|
? MaterialUpgrader.MaterialPropertyType.Color |
|
: MaterialUpgrader.MaterialPropertyType.Float; |
|
|
|
// material property animations apply to all materials, so check shader usage in all of them |
|
foreach (var material in rendererData.Materials) |
|
{ |
|
var usage = UpgradeUtility.GetNewPropertyName( |
|
shaderProperty.Name, |
|
material, |
|
renameType, |
|
allUpgradePathsToNewShaders, |
|
upgradePathsUsedByMaterials, |
|
out var newPropertyName |
|
); |
|
|
|
// if the property has already been upgraded with a different name, mark the upgrade as ambiguous |
|
if ( |
|
usage == SerializedShaderPropertyUsage.UsedByUpgraded |
|
&& data.PropertyRenames.TryGetValue(binding, out var propertyRename) |
|
&& propertyRename != newPropertyName |
|
) |
|
usage |= SerializedShaderPropertyUsage.UsedByAmbiguouslyUpgraded; |
|
|
|
data.Usage |= usage; |
|
data.PropertyRenames[binding] = newPropertyName; |
|
} |
|
} |
|
|
|
clipData[clip] = data; |
|
} |
|
|
|
/// <summary> |
|
/// Upgrade the specified clips using the associated property rename table. |
|
/// </summary> |
|
/// <param name="clipsToUpgrade"> |
|
/// A table mapping clips to property renaming tables that can be safely applied to their bindings. |
|
/// </param> |
|
/// <param name="excludeFlags">Do not upgrade clips that have any of these flags set.</param> |
|
/// <param name="upgraded">Collector for all clips that are upgraded.</param> |
|
/// <param name="notUpgraded">Collector for all clips that are not upgraded.</param> |
|
/// <param name="progressFunctor">Optional functor to display a progress bar.</param> |
|
internal static void UpgradeClips( |
|
IDictionary<IAnimationClip, (ClipPath Path, EditorCurveBinding[] Bindings, SerializedShaderPropertyUsage Usage, IDictionary<EditorCurveBinding, string> PropertyRenames)> clipsToUpgrade, |
|
SerializedShaderPropertyUsage excludeFlags, |
|
HashSet<(IAnimationClip Clip, ClipPath Path, SerializedShaderPropertyUsage Usage)> upgraded, |
|
HashSet<(IAnimationClip Clip, ClipPath Path, SerializedShaderPropertyUsage Usage)> notUpgraded, |
|
Func<string, float, bool> progressFunctor = null |
|
) |
|
{ |
|
upgraded.Clear(); |
|
notUpgraded.Clear(); |
|
|
|
int clipIndex = 0; |
|
int totalNumberOfClips = clipsToUpgrade.Count; |
|
|
|
foreach (var kv in clipsToUpgrade) |
|
{ |
|
float currentProgress = (float)++clipIndex / totalNumberOfClips; |
|
if (progressFunctor != null && progressFunctor($"({clipIndex} of {totalNumberOfClips}) {kv.Value.Path.Path}", currentProgress)) |
|
break; |
|
|
|
if (kv.Value.Usage == SerializedShaderPropertyUsage.Unknown || (kv.Value.Usage & excludeFlags) != 0) |
|
{ |
|
notUpgraded.Add((kv.Key, kv.Value.Path, kv.Value.Usage)); |
|
continue; |
|
} |
|
|
|
var renames = kv.Value.PropertyRenames; |
|
var bindings = kv.Key.GetCurveBindings().Where(IsMaterialBinding).ToArray(); |
|
if (bindings.Length > 0) |
|
{ |
|
var newBindings = new EditorCurveBinding[bindings.Length]; |
|
|
|
for (int i = 0; i < bindings.Length; ++i) |
|
{ |
|
var binding = bindings[i]; |
|
|
|
newBindings[i] = binding; |
|
if (renames.TryGetValue(binding, out var newName)) |
|
newBindings[i].propertyName = k_MatchMaterialPropertyName.Replace(newBindings[i].propertyName, $"material.{newName}$2"); |
|
} |
|
|
|
kv.Key.ReplaceBindings(bindings, newBindings); |
|
} |
|
|
|
upgraded.Add((kv.Key, kv.Value.Path, kv.Value.Usage)); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// A function to call from a menu item callback, which will upgrade all <see cref="AnimationClip"/> in the project. |
|
/// </summary> |
|
/// <param name="allUpgraders">All <see cref="MaterialUpgrader"/> for the current render pipeline.</param> |
|
/// <param name="knownUpgradePaths"> |
|
/// Optional mapping of materials to upgraders they are known to have used. |
|
/// Without this mapping, the method makes inferences about how a material might have been upgraded. |
|
/// Making these inferences is slower and possibly not sensitive to ambiguous upgrade paths. |
|
/// </param> |
|
/// <param name="filterFlags"> |
|
/// Optional flags to filter out clips that are used in ways that are not safe for upgrading. |
|
/// </param> |
|
public static void DoUpgradeAllClipsMenuItem( |
|
IEnumerable<MaterialUpgrader> allUpgraders, |
|
string progressBarName, |
|
IReadOnlyDictionary<UID, MaterialUpgrader> knownUpgradePaths = default, |
|
SerializedShaderPropertyUsage filterFlags = ~(SerializedShaderPropertyUsage.UsedByUpgraded | SerializedShaderPropertyUsage.UsedByNonUpgraded) |
|
) |
|
{ |
|
var clipPaths = AssetDatabase.FindAssets("t:AnimationClip") |
|
.Select(p => (ClipPath)AssetDatabase.GUIDToAssetPath(p)) |
|
.ToArray(); |
|
DoUpgradeClipsMenuItem(clipPaths, allUpgraders, progressBarName, knownUpgradePaths, filterFlags); |
|
} |
|
|
|
static void DoUpgradeClipsMenuItem( |
|
ClipPath[] clipPaths, |
|
IEnumerable<MaterialUpgrader> allUpgraders, |
|
string progressBarName, |
|
IReadOnlyDictionary<UID, MaterialUpgrader> upgradePathsUsedByMaterials, |
|
SerializedShaderPropertyUsage filterFlags |
|
) |
|
{ |
|
// exit early if no clips |
|
if (clipPaths?.Length == 0) |
|
return; |
|
|
|
// display dialog box |
|
var dialogMessage = L10n.Tr( |
|
"Upgrading Material curves in AnimationClips assumes you have already upgraded Materials and shaders as needed. " + |
|
"It also requires loading assets that use clips to inspect their usage, which can be a slow process. " + |
|
"Do you want to proceed?" |
|
); |
|
|
|
if (!EditorUtility.DisplayDialog( |
|
L10n.Tr("Upgrade AnimationClips"), |
|
dialogMessage, |
|
DialogText.proceed, |
|
DialogText.cancel)) |
|
return; |
|
|
|
// only include scene paths if user requested it |
|
var prefabPaths = AssetDatabase.FindAssets("t:Prefab") |
|
.Select(p => (PrefabPath)AssetDatabase.GUIDToAssetPath(p)) |
|
.ToArray(); |
|
var scenePaths = AssetDatabase.FindAssets("t:Scene") |
|
.Select(p => (ScenePath)AssetDatabase.GUIDToAssetPath(p)) |
|
.ToArray(); |
|
|
|
// retrieve clip assets with material animation |
|
var clipData = GetAssetDataForClipsFiltered(clipPaths); |
|
|
|
const float kGatherInPrefabsTotalProgress = 0.33f; |
|
const float kGatherInScenesTotalProgress = 0.66f; |
|
const float kUpgradeClipsTotalProgress = 1f; |
|
|
|
// create table mapping all upgrade paths to new shaders |
|
var allUpgradePathsToNewShaders = UpgradeUtility.GetAllUpgradePathsToShaders(allUpgraders); |
|
|
|
// retrieve interdependencies with prefabs to figure out which clips can be safely upgraded |
|
GetClipDependencyMappings(clipPaths, prefabPaths, out var clipPrefabDependents, out var prefabDependencies); |
|
|
|
GatherClipsUsageInDependentPrefabs( |
|
clipPrefabDependents, prefabDependencies, clipData, allUpgradePathsToNewShaders, upgradePathsUsedByMaterials, |
|
(info, progress) => EditorUtility.DisplayCancelableProgressBar(progressBarName, $"Gathering from prefabs {info}", Mathf.Lerp(0f, kGatherInPrefabsTotalProgress, progress)) |
|
); |
|
|
|
if (EditorUtility.DisplayCancelableProgressBar(progressBarName, "", kGatherInPrefabsTotalProgress)) |
|
{ |
|
EditorUtility.ClearProgressBar(); |
|
return; |
|
} |
|
|
|
// if any scenes should be considered, do the same for clips used by scenes |
|
if (scenePaths.Any()) |
|
{ |
|
GetClipDependencyMappings(clipPaths, scenePaths, out var clipSceneDependents, out var sceneDependencies); |
|
GatherClipsUsageInDependentScenes( |
|
clipSceneDependents, sceneDependencies, clipData, allUpgradePathsToNewShaders, upgradePathsUsedByMaterials, |
|
(info, progress) => EditorUtility.DisplayCancelableProgressBar(progressBarName, $"Gathering from scenes {info}", Mathf.Lerp(kGatherInPrefabsTotalProgress, kGatherInScenesTotalProgress, progress)) |
|
); |
|
} |
|
|
|
if (EditorUtility.DisplayCancelableProgressBar(progressBarName, "", kGatherInScenesTotalProgress)) |
|
{ |
|
EditorUtility.ClearProgressBar(); |
|
return; |
|
} |
|
|
|
// patch clips that should be upgraded |
|
var upgraded = new HashSet<(IAnimationClip Clip, ClipPath Path, SerializedShaderPropertyUsage Usage)>(); |
|
var notUpgraded = new HashSet<(IAnimationClip Clip, ClipPath Path, SerializedShaderPropertyUsage Usage)>(); |
|
|
|
AssetDatabase.StartAssetEditing(); |
|
UpgradeClips( |
|
clipData, filterFlags, upgraded, notUpgraded, |
|
(info, progress) => EditorUtility.DisplayCancelableProgressBar(progressBarName, $"Upgrading clips {info}", Mathf.Lerp(kGatherInScenesTotalProgress, kUpgradeClipsTotalProgress, progress)) |
|
); |
|
AssetDatabase.SaveAssets(); |
|
AssetDatabase.StopAssetEditing(); |
|
|
|
EditorUtility.ClearProgressBar(); |
|
|
|
// report results |
|
if (upgraded.Count > 0) |
|
{ |
|
var successes = upgraded.Select(data => $"- {data.Path}: ({data.Usage})"); |
|
Debug.Log( |
|
"Upgraded the following clips:\n" + |
|
$"{string.Join("\n", successes)}" |
|
); |
|
} |
|
|
|
if (notUpgraded.Count == clipData.Count) |
|
{ |
|
Debug.LogWarning("No clips were upgraded. Did you remember to upgrade materials first?"); |
|
} |
|
else if (notUpgraded.Count > 0) |
|
{ |
|
var errors = notUpgraded.Select(data => $"- {data.Path}: ({data.Usage})"); |
|
Debug.LogWarning( |
|
$"Did not modify following clips because they they were used in ways other than {~filterFlags}:\n" + |
|
$"{string.Join("\n", errors)}" |
|
); |
|
} |
|
} |
|
} |
|
}
|
|
|