AnimationClipUpgrader.cs 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text.RegularExpressions;
  5. using UnityEditor.Animations;
  6. using UnityEditor.SceneManagement;
  7. using UnityEngine;
  8. using UnityEngine.Playables;
  9. using UnityEngine.Rendering;
  10. using IMaterial = UnityEditor.Rendering.UpgradeUtility.IMaterial;
  11. using UID = UnityEditor.Rendering.UpgradeUtility.UID;
  12. namespace UnityEditor.Rendering
  13. {
  14. /// <summary>
  15. /// A class containing static methods for updating <see cref="AnimationClip"/> assets with bindings for <see cref="Material"/> properties.
  16. /// </summary>
  17. /// <remarks>
  18. /// Animation clips store bindings for material properties by path name, but don't know whether those properties exist on their dependents.
  19. /// Because property names did not change uniformly in the material/shader upgrade process, it is not possible to patch path names indiscriminately.
  20. /// This class provides utilities for discovering how clips are used, so users can make decisions about whether or not to then update them.
  21. /// It has the limitation that it only knows about:
  22. /// - Clips that are directly referenced by an <see cref="Animation"/> component
  23. /// - Clips referenced by an <see cref="AnimatorController"/> used by an <see cref="Animator"/> component
  24. /// - Clips that are sub-assets of a <see cref="PlayableAsset"/> used by a <see cref="PlayableDirector"/> component with a single <see cref="Animator"/> binding
  25. /// It does not know about clips that might be referenced in other ways for run-time reassignment.
  26. /// Recommended usage is to call <see cref="DoUpgradeAllClipsMenuItem"/> from a menu item callback.
  27. /// The utility can also provide faster, more reliable results if it knows what <see cref="MaterialUpgrader"/> was used to upgrade specific materials.
  28. /// </remarks>
  29. static partial class AnimationClipUpgrader
  30. {
  31. static readonly Regex k_MatchMaterialPropertyName = new Regex(@"material.(\w+)(\.\w+)?", RegexOptions.Compiled);
  32. /// <summary>
  33. /// Determines whether the specified<see cref="EditorCurveBinding"/> is for a material property.
  34. /// </summary>
  35. /// <remarks>Internal only for testability.</remarks>
  36. /// <param name="b">An <see cref="EditorCurveBinding"/>.</param>
  37. /// <returns><c>true</c> if the binding is for a material property; <c>false</c> otherwise.</returns>
  38. internal static bool IsMaterialBinding(EditorCurveBinding b) =>
  39. (b.type?.IsSubclassOf(typeof(Renderer)) ?? false)
  40. && !string.IsNullOrEmpty(b.propertyName)
  41. && k_MatchMaterialPropertyName.IsMatch(b.propertyName);
  42. static readonly IReadOnlyCollection<string> k_ColorAttributeSuffixes =
  43. new HashSet<string>(new[] { ".r", ".g", ".b", ".a" });
  44. /// <summary>
  45. /// Infer a shader property name and type from an <see cref="EditorCurveBinding"/>.
  46. /// </summary>
  47. /// <remarks>Internal only for testability.</remarks>
  48. /// <param name="binding">A binding presumed to map to a material property. (See also <seealso cref="IsMaterialBinding"/>.)</param>
  49. /// <returns>A shader property name, and a guess of what type of shader property it targets.</returns>
  50. internal static (string Name, ShaderPropertyType Type) InferShaderProperty(EditorCurveBinding binding)
  51. {
  52. var match = k_MatchMaterialPropertyName.Match(binding.propertyName);
  53. var propertyName = match.Groups[1].Value;
  54. var propertyType = match.Groups[2].Value;
  55. return (propertyName,
  56. k_ColorAttributeSuffixes.Contains(propertyType) ? ShaderPropertyType.Color : ShaderPropertyType.Float);
  57. }
  58. /// <summary>
  59. /// Gets asset data for all clip assets at the specified paths, which contain bindings for material properties.
  60. /// (See also <seealso cref="GatherClipsUsageInDependentPrefabs"/> and <seealso cref="GatherClipsUsageInDependentScenes"/>.)
  61. /// </summary>
  62. /// <param name="clipPaths">Paths to assets containing <see cref="AnimationClip"/>.</param>
  63. /// <returns>
  64. /// Lookup table mapping <see cref="AnimationClip"/> to its asset path, bindings, property rename table, and usage.
  65. /// (Use <see cref="GatherClipsUsageInDependentPrefabs"/> to initialize rename table and usage.)
  66. /// </returns>
  67. internal static IDictionary<
  68. IAnimationClip,
  69. (ClipPath Path, EditorCurveBinding[] Bindings, SerializedShaderPropertyUsage Usage, IDictionary<EditorCurveBinding, string> PropertyRenames)
  70. > GetAssetDataForClipsFiltered(
  71. IEnumerable<ClipPath> clipPaths
  72. )
  73. {
  74. var result = new Dictionary<
  75. IAnimationClip,
  76. (ClipPath Path, EditorCurveBinding[] Bindings, SerializedShaderPropertyUsage Usage, IDictionary<EditorCurveBinding, string> PropertyRenames)
  77. >();
  78. foreach (var clipPath in clipPaths)
  79. {
  80. foreach (var asset in AssetDatabase.LoadAllAssetsAtPath(clipPath))
  81. {
  82. if (!(asset is AnimationClip clip))
  83. continue;
  84. var bindings = AnimationUtility.GetCurveBindings(clip).Where(IsMaterialBinding).ToArray();
  85. if (bindings.Length == 0)
  86. continue;
  87. result[(AnimationClipProxy)clip] =
  88. (clipPath, bindings, SerializedShaderPropertyUsage.Unknown, new Dictionary<EditorCurveBinding, string>());
  89. }
  90. }
  91. return result;
  92. }
  93. /// <summary>
  94. /// Get dependency mappings between <see cref="AnimationClip"/> and their dependents.
  95. /// </summary>
  96. /// <param name="clips"> Paths to clips to consider. (See also <seealso cref="GetAssetDataForClipsFiltered"/>.)</param>
  97. /// <param name="assets">Paths to assets to consider.</param>
  98. /// <param name="clipToDependentAssets">Mapping of clip paths to paths of their dependents.</param>
  99. /// <param name="assetToClipDependencies">Mapping of asset paths to their clip dependencies.</param>
  100. /// <typeparam name="T">The type of asset path.</typeparam>
  101. internal static void GetClipDependencyMappings<T>(
  102. IEnumerable<ClipPath> clips,
  103. IEnumerable<T> assets,
  104. out IReadOnlyDictionary<ClipPath, IReadOnlyCollection<T>> clipToDependentAssets,
  105. out IReadOnlyDictionary<T, IReadOnlyCollection<ClipPath>> assetToClipDependencies
  106. ) where T : struct, IAssetPath
  107. {
  108. // ensure there are no duplicate keys
  109. clips = new HashSet<ClipPath>(clips);
  110. assets = new HashSet<T>(assets);
  111. // create mutable builders
  112. var clipsDependentsBuilder = clips.ToDictionary(c => c, c => new HashSet<T>());
  113. var assetsBuilder = new Dictionary<T, HashSet<ClipPath>>();
  114. // build dependency tables
  115. foreach (var asset in assets)
  116. {
  117. assetsBuilder[asset] = new HashSet<ClipPath>();
  118. foreach (var dependencyPath in AssetDatabase.GetDependencies(asset.Path))
  119. {
  120. if (!clipsDependentsBuilder.TryGetValue(dependencyPath, out var dependents))
  121. continue;
  122. dependents.Add(asset);
  123. assetsBuilder[asset].Add(dependencyPath);
  124. }
  125. }
  126. // return readonly results
  127. clipToDependentAssets =
  128. clipsDependentsBuilder.ToDictionary(kv => kv.Key, kv => kv.Value as IReadOnlyCollection<T>);
  129. assetToClipDependencies =
  130. assetsBuilder.ToDictionary(kv => kv.Key, kv => kv.Value as IReadOnlyCollection<ClipPath>);
  131. }
  132. // reusable buffers
  133. static readonly List<Animation> s_AnimationBuffer = new List<Animation>(8);
  134. static readonly List<Animator> s_AnimatorBuffer = new List<Animator>(8);
  135. static readonly List<IAnimationClipSource> s_CustomAnimationBuffer = new List<IAnimationClipSource>(8);
  136. static readonly List<PlayableDirector> s_PlayableDirectorBuffer = new List<PlayableDirector>(8);
  137. /// <summary>
  138. /// Get information about a clip's usage among its dependent scenes to determine whether or not it should be upgraded.
  139. /// </summary>
  140. /// <param name="clipDependents">
  141. /// A table mapping clip asset paths, to asset paths of their dependent prefabs.
  142. /// (See <seealso cref="GetClipDependencyMappings{T}"/>.)
  143. /// </param>
  144. /// <param name="assetDependencies">
  145. /// A table mapping prefab asset paths, to asset paths of their clip dependencies.
  146. /// (See <seealso cref="GetClipDependencyMappings{T}"/>.)
  147. /// </param>
  148. /// <param name="clipData">
  149. /// A table mapping clips to other data about them. (See also <seealso cref="GetAssetDataForClipsFiltered"/>.)
  150. /// </param>
  151. /// <param name="allUpgradePathsToNewShaders">
  152. /// A table of new shader names and all known upgrade paths to them in the target pipeline.
  153. /// (See also <seealso cref="UpgradeUtility.GetAllUpgradePathsToShaders"/>.)
  154. /// </param>
  155. /// <param name="upgradePathsUsedByMaterials">
  156. /// Optional table of materials known to have gone through a specific upgrade path.
  157. /// </param>
  158. /// <param name="progressFunctor">
  159. /// Optional functor to display a progress bar.
  160. /// </param>
  161. internal static void GatherClipsUsageInDependentPrefabs(
  162. IReadOnlyDictionary<ClipPath, IReadOnlyCollection<PrefabPath>> clipDependents,
  163. // TODO: right now, clip dependencies are gathered in Animation/Animator, so this may not be needed...
  164. IReadOnlyDictionary<PrefabPath, IReadOnlyCollection<ClipPath>> assetDependencies,
  165. IDictionary<
  166. IAnimationClip,
  167. (ClipPath Path, EditorCurveBinding[] Bindings, SerializedShaderPropertyUsage Usage, IDictionary<EditorCurveBinding, string> PropertyRenames)
  168. > clipData,
  169. IReadOnlyDictionary<string, IReadOnlyList<MaterialUpgrader>> allUpgradePathsToNewShaders,
  170. IReadOnlyDictionary<UID, MaterialUpgrader> upgradePathsUsedByMaterials = default,
  171. Func<string, float, bool> progressFunctor = null
  172. )
  173. {
  174. int clipIndex = 0;
  175. int totalNumberOfClips = clipDependents.Count;
  176. // check all dependents for usage
  177. foreach (var kv in clipDependents)
  178. {
  179. float currentProgress = (float)++clipIndex / totalNumberOfClips;
  180. if (progressFunctor != null && progressFunctor($"({clipIndex} of {totalNumberOfClips}) {kv.Key.Path}", currentProgress))
  181. break;
  182. foreach (var prefabPath in kv.Value)
  183. {
  184. var go = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
  185. GatherClipsUsageForGameObject(go, clipData, allUpgradePathsToNewShaders, upgradePathsUsedByMaterials);
  186. }
  187. }
  188. }
  189. /// <summary>
  190. /// Get information about a clip's usage among its dependent scenes to determine whether or not it should be upgraded.
  191. /// </summary>
  192. /// <remarks>
  193. /// Because this method will open scenes to search for usages, it is recommended you first prompt for user input.
  194. /// It is also a good idea to first call <see cref="GatherClipsUsageInDependentPrefabs"/> to generate usage data.
  195. /// Clips that are already known to be unsafe for upgrading based on their prefab usage can be skipped here.
  196. /// </remarks>
  197. /// <param name="clipDependents">
  198. /// A table mapping clip asset paths, to asset paths of their dependent scenes.
  199. /// (See <seealso cref="GetClipDependencyMappings{T}"/>.)
  200. /// </param>
  201. /// <param name="assetDependencies">
  202. /// A table mapping scene asset paths, to asset paths of their clip dependencies.
  203. /// (See <seealso cref="GetClipDependencyMappings{T}"/>.)
  204. /// </param>
  205. /// <param name="clipData">
  206. /// A table mapping clips to other data about them. (See also <seealso cref="GetAssetDataForClipsFiltered"/>.)
  207. /// </param>
  208. /// <param name="allUpgradePathsToNewShaders">
  209. /// A table of new shader names and all known upgrade paths to them in the target pipeline.
  210. /// (See also <seealso cref="UpgradeUtility.GetAllUpgradePathsToShaders"/>.)
  211. /// </param>
  212. /// <param name="upgradePathsUsedByMaterials">
  213. /// Optional table of materials known to have gone through a specific upgrade path.
  214. /// </param>
  215. /// <param name="progressFunctor">
  216. /// Optional functor to display a progress bar.
  217. /// </param>
  218. internal static void GatherClipsUsageInDependentScenes(
  219. IReadOnlyDictionary<ClipPath, IReadOnlyCollection<ScenePath>> clipDependents,
  220. // TODO: right now, clip dependencies are gathered in Animation/Animator, so this may not be needed...
  221. IReadOnlyDictionary<ScenePath, IReadOnlyCollection<ClipPath>> assetDependencies,
  222. IDictionary<
  223. IAnimationClip,
  224. (ClipPath Path, EditorCurveBinding[] Bindings, SerializedShaderPropertyUsage Usage, IDictionary<EditorCurveBinding, string>
  225. PropertyRenames)
  226. > clipData,
  227. IReadOnlyDictionary<string, IReadOnlyList<MaterialUpgrader>> allUpgradePathsToNewShaders,
  228. IReadOnlyDictionary<UID, MaterialUpgrader> upgradePathsUsedByMaterials = default,
  229. Func<string, float, bool> progressFunctor = null
  230. )
  231. {
  232. int clipIndex = 0;
  233. int totalNumberOfClips = clipDependents.Count;
  234. // check all dependents for usage
  235. foreach (var kv in clipDependents)
  236. {
  237. float currentProgress = (float)++clipIndex / totalNumberOfClips;
  238. if (progressFunctor != null && progressFunctor($"({clipIndex} of {totalNumberOfClips}) {kv.Key.Path}", currentProgress))
  239. break;
  240. foreach (var scenePath in kv.Value)
  241. {
  242. var scene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single);
  243. foreach (var go in scene.GetRootGameObjects())
  244. GatherClipsUsageForGameObject(go, clipData, allUpgradePathsToNewShaders, upgradePathsUsedByMaterials);
  245. }
  246. }
  247. }
  248. /// <summary>
  249. /// Update usage information about the specified clips in the clip data table.
  250. /// </summary>
  251. /// <param name="go">A prefab, or a <see cref="GameObject"/> in a scene.</param>
  252. /// <param name="clipData">
  253. /// A table mapping clips to other data about them. (See also <seealso cref="GetAssetDataForClipsFiltered"/>.)
  254. /// </param>
  255. /// <param name="allUpgradePathsToNewShaders">
  256. /// A table of new shader names and all known upgrade paths to them in the target pipeline.
  257. /// (See also <seealso cref="UpgradeUtility.GetAllUpgradePathsToShaders"/>.)
  258. /// </param>
  259. /// <param name="upgradePathsUsedByMaterials">
  260. /// Optional table of materials known to have gone through a specific upgrade path.
  261. /// </param>
  262. static void GatherClipsUsageForGameObject(
  263. GameObject go,
  264. IDictionary<
  265. IAnimationClip,
  266. (ClipPath Path, EditorCurveBinding[] Bindings, SerializedShaderPropertyUsage Usage, IDictionary<EditorCurveBinding, string> PropertyRenames)
  267. > clipData,
  268. IReadOnlyDictionary<string, IReadOnlyList<MaterialUpgrader>> allUpgradePathsToNewShaders,
  269. IReadOnlyDictionary<UID, MaterialUpgrader> upgradePathsUsedByMaterials = default
  270. )
  271. {
  272. go.GetComponentsInChildren(true, s_AnimationBuffer);
  273. go.GetComponentsInChildren(true, s_AnimatorBuffer);
  274. go.GetComponentsInChildren(true, s_CustomAnimationBuffer);
  275. // first check clip usage among GameObjects with legacy Animation
  276. var gameObjects = new HashSet<GameObject>(s_AnimationBuffer.Select(a => a.gameObject)
  277. .Union(s_AnimatorBuffer.Select(a => a.gameObject))
  278. .Union(s_CustomAnimationBuffer.Where(a => a is Component).Select(a => ((Component)a).gameObject)));
  279. foreach (var gameObject in gameObjects)
  280. {
  281. var clips = AnimationUtility.GetAnimationClips(gameObject).Select(clip => (IAnimationClip)(AnimationClipProxy)clip);
  282. GatherClipsUsageForAnimatedHierarchy(
  283. gameObject.transform, clips, clipData, allUpgradePathsToNewShaders, upgradePathsUsedByMaterials
  284. );
  285. }
  286. // next check clip usage among GameObjects with PlayableDirector
  287. go.GetComponentsInChildren(true, s_PlayableDirectorBuffer);
  288. foreach (var playableDirector in s_PlayableDirectorBuffer)
  289. {
  290. var playableAsset = playableDirector.playableAsset;
  291. if (playableAsset == null)
  292. continue;
  293. var assetPath = AssetDatabase.GetAssetPath(playableAsset);
  294. // get all clip sub-assets
  295. var clips = new HashSet<IAnimationClip>(
  296. AssetDatabase.LoadAllAssetsAtPath(assetPath)
  297. .Where(asset => asset is AnimationClip)
  298. .Select(asset => (IAnimationClip)(AnimationClipProxy)(asset as AnimationClip))
  299. );
  300. // get all clip dependency-assets
  301. // this will not handle nested clips in FBX like assets, but these are less likely to be editable
  302. clips.UnionWith(AssetDatabase.GetDependencies(assetPath)
  303. .Select(AssetDatabase.LoadAssetAtPath<AnimationClip>)
  304. .Where(asset => asset is AnimationClip)
  305. .Select(asset => (IAnimationClip)(AnimationClipProxy)asset));
  306. // check if the value of a binding is an animator, and examines clip usage relative to it
  307. // this is imprecise, but is suitable to catch the majority of cases (i.e., a single animator binding)
  308. using (var so = new SerializedObject(playableDirector))
  309. {
  310. var clipsProp = so.FindProperty("m_SceneBindings");
  311. for (int i = 0, count = clipsProp.arraySize; i < count; ++i)
  312. {
  313. var elementProp = clipsProp.GetArrayElementAtIndex(i);
  314. var value = elementProp.FindPropertyRelative("value");
  315. if (value.objectReferenceValue is Animator animator)
  316. {
  317. GatherClipsUsageForAnimatedHierarchy(
  318. animator.transform, clips, clipData, allUpgradePathsToNewShaders, upgradePathsUsedByMaterials
  319. );
  320. }
  321. }
  322. }
  323. }
  324. // release UnityObject references
  325. s_AnimationBuffer.Clear();
  326. s_AnimatorBuffer.Clear();
  327. s_CustomAnimationBuffer.Clear();
  328. s_PlayableDirectorBuffer.Clear();
  329. }
  330. // reusable buffers
  331. static readonly List<Renderer> s_RendererBuffer = new List<Renderer>(8);
  332. static readonly Dictionary<string, (IRenderer Renderer, List<IMaterial> Materials)> s_RenderersByPath =
  333. new Dictionary<string, (IRenderer Renderer, List<IMaterial> Materials)>();
  334. /// <summary>
  335. /// Update usage information about the specified clips in the clip data table.
  336. /// </summary>
  337. /// <param name="root">The root of the animated hierarchy (i.e., object with Animation or Animator).</param>
  338. /// <param name="clips">Collection of animation clips</param>
  339. /// <param name="clipData">
  340. /// A table mapping clips to other data about them. (See also <seealso cref="GetAssetDataForClipsFiltered"/>.)
  341. /// </param>
  342. /// <param name="allUpgradePathsToNewShaders">
  343. /// A table of new shader names and all known upgrade paths to them in the target pipeline.
  344. /// (See also <seealso cref="UpgradeUtility.GetAllUpgradePathsToShaders"/>.)
  345. /// </param>
  346. /// <param name="upgradePathsUsedByMaterials">
  347. /// Optional table of materials known to have gone through a specific upgrade path.
  348. /// </param>
  349. static void GatherClipsUsageForAnimatedHierarchy(
  350. Transform root,
  351. IEnumerable<IAnimationClip> clips,
  352. IDictionary<
  353. IAnimationClip,
  354. (ClipPath Path, EditorCurveBinding[] Bindings, SerializedShaderPropertyUsage Usage, IDictionary<EditorCurveBinding, string> PropertyRenames)
  355. > clipData,
  356. IReadOnlyDictionary<string, IReadOnlyList<MaterialUpgrader>> allUpgradePathsToNewShaders,
  357. IReadOnlyDictionary<UID, MaterialUpgrader> upgradePathsUsedByMaterials
  358. )
  359. {
  360. // TODO: report paths of specific assets that contribute to problematic results?
  361. // find all renderers in the animated hierarchy
  362. root.GetComponentsInChildren(true, s_RendererBuffer);
  363. foreach (var renderer in s_RendererBuffer)
  364. {
  365. var path = AnimationUtility.CalculateTransformPath(renderer.transform, root);
  366. var m = ListPool<IMaterial>.Get();
  367. var r = (RendererProxy)renderer;
  368. r.GetSharedMaterials(m);
  369. s_RenderersByPath[path] = (r, m);
  370. }
  371. // if there are any renderers, check all clips for usage
  372. if (s_RendererBuffer.Count > 0)
  373. {
  374. foreach (var clip in clips)
  375. GatherClipUsage(clip, clipData, s_RenderersByPath, allUpgradePathsToNewShaders, upgradePathsUsedByMaterials);
  376. }
  377. // release UnityObject references
  378. s_RendererBuffer.Clear();
  379. foreach (var (_, materials) in s_RenderersByPath.Values)
  380. ListPool<IMaterial>.Release(materials);
  381. s_RenderersByPath.Clear();
  382. }
  383. /// <summary>
  384. /// Update usage information about the specified clip in the clip data table.
  385. /// </summary>
  386. /// <remarks>
  387. /// This method works by looking at shaders used by materials assigned to the specified renderers.
  388. /// Usage and property renames for the clip are updated, if a binding in the clip matches an upgrader.
  389. /// Internal only for testability.
  390. /// </remarks>
  391. /// <param name="clip">An animation clip.</param>
  392. /// <param name="clipData">
  393. /// A table mapping clips to other data about them. (See also <seealso cref="GetAssetDataForClipsFiltered"/>.)
  394. /// </param>
  395. /// <param name="renderersByPath">
  396. /// A table mapping transform paths of renderers to lists of the materials they use.
  397. /// </param>
  398. /// <param name="allUpgradePathsToNewShaders">
  399. /// A table of new shader names and all known upgrade paths to them in the target pipeline.
  400. /// (See also <seealso cref="UpgradeUtility.GetAllUpgradePathsToShaders"/>.)
  401. /// </param>
  402. /// <param name="upgradePathsUsedByMaterials">
  403. /// Optional table of materials known to have gone through a specific upgrade path.
  404. /// </param>
  405. internal static void GatherClipUsage(
  406. IAnimationClip clip,
  407. IDictionary<
  408. IAnimationClip,
  409. (ClipPath Path, EditorCurveBinding[] Bindings, SerializedShaderPropertyUsage Usage, IDictionary<EditorCurveBinding, string> PropertyRenames)
  410. > clipData,
  411. IReadOnlyDictionary<string, (IRenderer Renderer, List<IMaterial> Materials)> renderersByPath,
  412. IReadOnlyDictionary<string, IReadOnlyList<MaterialUpgrader>> allUpgradePathsToNewShaders,
  413. IReadOnlyDictionary<UID, MaterialUpgrader> upgradePathsUsedByMaterials
  414. )
  415. {
  416. // exit if clip is unknown; it may have been filtered at an earlier stage
  417. if (!clipData.TryGetValue(clip, out var data))
  418. return;
  419. // see if any animated material bindings in the clip refer to renderers in animated hierarchy
  420. foreach (var binding in data.Bindings)
  421. {
  422. // skip if binding is not for material, or refers to a nonexistent renderer
  423. if (
  424. !IsMaterialBinding(binding)
  425. || !renderersByPath.TryGetValue(binding.path, out var rendererData)
  426. ) continue;
  427. // determine the shader property name and type from the binding
  428. var shaderProperty = InferShaderProperty(binding);
  429. var renameType = shaderProperty.Type == ShaderPropertyType.Color
  430. ? MaterialUpgrader.MaterialPropertyType.Color
  431. : MaterialUpgrader.MaterialPropertyType.Float;
  432. // material property animations apply to all materials, so check shader usage in all of them
  433. foreach (var material in rendererData.Materials)
  434. {
  435. var usage = UpgradeUtility.GetNewPropertyName(
  436. shaderProperty.Name,
  437. material,
  438. renameType,
  439. allUpgradePathsToNewShaders,
  440. upgradePathsUsedByMaterials,
  441. out var newPropertyName
  442. );
  443. // if the property has already been upgraded with a different name, mark the upgrade as ambiguous
  444. if (
  445. usage == SerializedShaderPropertyUsage.UsedByUpgraded
  446. && data.PropertyRenames.TryGetValue(binding, out var propertyRename)
  447. && propertyRename != newPropertyName
  448. )
  449. usage |= SerializedShaderPropertyUsage.UsedByAmbiguouslyUpgraded;
  450. data.Usage |= usage;
  451. data.PropertyRenames[binding] = newPropertyName;
  452. }
  453. }
  454. clipData[clip] = data;
  455. }
  456. /// <summary>
  457. /// Upgrade the specified clips using the associated property rename table.
  458. /// </summary>
  459. /// <param name="clipsToUpgrade">
  460. /// A table mapping clips to property renaming tables that can be safely applied to their bindings.
  461. /// </param>
  462. /// <param name="excludeFlags">Do not upgrade clips that have any of these flags set.</param>
  463. /// <param name="upgraded">Collector for all clips that are upgraded.</param>
  464. /// <param name="notUpgraded">Collector for all clips that are not upgraded.</param>
  465. /// <param name="progressFunctor">Optional functor to display a progress bar.</param>
  466. internal static void UpgradeClips(
  467. IDictionary<IAnimationClip, (ClipPath Path, EditorCurveBinding[] Bindings, SerializedShaderPropertyUsage Usage, IDictionary<EditorCurveBinding, string> PropertyRenames)> clipsToUpgrade,
  468. SerializedShaderPropertyUsage excludeFlags,
  469. HashSet<(IAnimationClip Clip, ClipPath Path, SerializedShaderPropertyUsage Usage)> upgraded,
  470. HashSet<(IAnimationClip Clip, ClipPath Path, SerializedShaderPropertyUsage Usage)> notUpgraded,
  471. Func<string, float, bool> progressFunctor = null
  472. )
  473. {
  474. upgraded.Clear();
  475. notUpgraded.Clear();
  476. int clipIndex = 0;
  477. int totalNumberOfClips = clipsToUpgrade.Count;
  478. foreach (var kv in clipsToUpgrade)
  479. {
  480. float currentProgress = (float)++clipIndex / totalNumberOfClips;
  481. if (progressFunctor != null && progressFunctor($"({clipIndex} of {totalNumberOfClips}) {kv.Value.Path.Path}", currentProgress))
  482. break;
  483. if (kv.Value.Usage == SerializedShaderPropertyUsage.Unknown || (kv.Value.Usage & excludeFlags) != 0)
  484. {
  485. notUpgraded.Add((kv.Key, kv.Value.Path, kv.Value.Usage));
  486. continue;
  487. }
  488. var renames = kv.Value.PropertyRenames;
  489. var bindings = kv.Key.GetCurveBindings().Where(IsMaterialBinding).ToArray();
  490. if (bindings.Length > 0)
  491. {
  492. var newBindings = new EditorCurveBinding[bindings.Length];
  493. for (int i = 0; i < bindings.Length; ++i)
  494. {
  495. var binding = bindings[i];
  496. newBindings[i] = binding;
  497. if (renames.TryGetValue(binding, out var newName))
  498. newBindings[i].propertyName = k_MatchMaterialPropertyName.Replace(newBindings[i].propertyName, $"material.{newName}$2");
  499. }
  500. kv.Key.ReplaceBindings(bindings, newBindings);
  501. }
  502. upgraded.Add((kv.Key, kv.Value.Path, kv.Value.Usage));
  503. }
  504. }
  505. /// <summary>
  506. /// A function to call from a menu item callback, which will upgrade all <see cref="AnimationClip"/> in the project.
  507. /// </summary>
  508. /// <param name="allUpgraders">All <see cref="MaterialUpgrader"/> for the current render pipeline.</param>
  509. /// <param name="knownUpgradePaths">
  510. /// Optional mapping of materials to upgraders they are known to have used.
  511. /// Without this mapping, the method makes inferences about how a material might have been upgraded.
  512. /// Making these inferences is slower and possibly not sensitive to ambiguous upgrade paths.
  513. /// </param>
  514. /// <param name="filterFlags">
  515. /// Optional flags to filter out clips that are used in ways that are not safe for upgrading.
  516. /// </param>
  517. public static void DoUpgradeAllClipsMenuItem(
  518. IEnumerable<MaterialUpgrader> allUpgraders,
  519. string progressBarName,
  520. IReadOnlyDictionary<UID, MaterialUpgrader> knownUpgradePaths = default,
  521. SerializedShaderPropertyUsage filterFlags = ~(SerializedShaderPropertyUsage.UsedByUpgraded | SerializedShaderPropertyUsage.UsedByNonUpgraded)
  522. )
  523. {
  524. var clipPaths = AssetDatabase.FindAssets("t:AnimationClip")
  525. .Select(p => (ClipPath)AssetDatabase.GUIDToAssetPath(p))
  526. .ToArray();
  527. DoUpgradeClipsMenuItem(clipPaths, allUpgraders, progressBarName, knownUpgradePaths, filterFlags);
  528. }
  529. static void DoUpgradeClipsMenuItem(
  530. ClipPath[] clipPaths,
  531. IEnumerable<MaterialUpgrader> allUpgraders,
  532. string progressBarName,
  533. IReadOnlyDictionary<UID, MaterialUpgrader> upgradePathsUsedByMaterials,
  534. SerializedShaderPropertyUsage filterFlags
  535. )
  536. {
  537. // exit early if no clips
  538. if (clipPaths?.Length == 0)
  539. return;
  540. // display dialog box
  541. var dialogMessage = L10n.Tr(
  542. "Upgrading Material curves in AnimationClips assumes you have already upgraded Materials and shaders as needed. " +
  543. "It also requires loading assets that use clips to inspect their usage, which can be a slow process. " +
  544. "Do you want to proceed?"
  545. );
  546. if (!EditorUtility.DisplayDialog(
  547. L10n.Tr("Upgrade AnimationClips"),
  548. dialogMessage,
  549. DialogText.proceed,
  550. DialogText.cancel))
  551. return;
  552. // only include scene paths if user requested it
  553. var prefabPaths = AssetDatabase.FindAssets("t:Prefab")
  554. .Select(p => (PrefabPath)AssetDatabase.GUIDToAssetPath(p))
  555. .ToArray();
  556. var scenePaths = AssetDatabase.FindAssets("t:Scene")
  557. .Select(p => (ScenePath)AssetDatabase.GUIDToAssetPath(p))
  558. .ToArray();
  559. // retrieve clip assets with material animation
  560. var clipData = GetAssetDataForClipsFiltered(clipPaths);
  561. const float kGatherInPrefabsTotalProgress = 0.33f;
  562. const float kGatherInScenesTotalProgress = 0.66f;
  563. const float kUpgradeClipsTotalProgress = 1f;
  564. // create table mapping all upgrade paths to new shaders
  565. var allUpgradePathsToNewShaders = UpgradeUtility.GetAllUpgradePathsToShaders(allUpgraders);
  566. // retrieve interdependencies with prefabs to figure out which clips can be safely upgraded
  567. GetClipDependencyMappings(clipPaths, prefabPaths, out var clipPrefabDependents, out var prefabDependencies);
  568. GatherClipsUsageInDependentPrefabs(
  569. clipPrefabDependents, prefabDependencies, clipData, allUpgradePathsToNewShaders, upgradePathsUsedByMaterials,
  570. (info, progress) => EditorUtility.DisplayCancelableProgressBar(progressBarName, $"Gathering from prefabs {info}", Mathf.Lerp(0f, kGatherInPrefabsTotalProgress, progress))
  571. );
  572. if (EditorUtility.DisplayCancelableProgressBar(progressBarName, "", kGatherInPrefabsTotalProgress))
  573. {
  574. EditorUtility.ClearProgressBar();
  575. return;
  576. }
  577. // if any scenes should be considered, do the same for clips used by scenes
  578. if (scenePaths.Any())
  579. {
  580. GetClipDependencyMappings(clipPaths, scenePaths, out var clipSceneDependents, out var sceneDependencies);
  581. GatherClipsUsageInDependentScenes(
  582. clipSceneDependents, sceneDependencies, clipData, allUpgradePathsToNewShaders, upgradePathsUsedByMaterials,
  583. (info, progress) => EditorUtility.DisplayCancelableProgressBar(progressBarName, $"Gathering from scenes {info}", Mathf.Lerp(kGatherInPrefabsTotalProgress, kGatherInScenesTotalProgress, progress))
  584. );
  585. }
  586. if (EditorUtility.DisplayCancelableProgressBar(progressBarName, "", kGatherInScenesTotalProgress))
  587. {
  588. EditorUtility.ClearProgressBar();
  589. return;
  590. }
  591. // patch clips that should be upgraded
  592. var upgraded = new HashSet<(IAnimationClip Clip, ClipPath Path, SerializedShaderPropertyUsage Usage)>();
  593. var notUpgraded = new HashSet<(IAnimationClip Clip, ClipPath Path, SerializedShaderPropertyUsage Usage)>();
  594. AssetDatabase.StartAssetEditing();
  595. UpgradeClips(
  596. clipData, filterFlags, upgraded, notUpgraded,
  597. (info, progress) => EditorUtility.DisplayCancelableProgressBar(progressBarName, $"Upgrading clips {info}", Mathf.Lerp(kGatherInScenesTotalProgress, kUpgradeClipsTotalProgress, progress))
  598. );
  599. AssetDatabase.SaveAssets();
  600. AssetDatabase.StopAssetEditing();
  601. EditorUtility.ClearProgressBar();
  602. // report results
  603. if (upgraded.Count > 0)
  604. {
  605. var successes = upgraded.Select(data => $"- {data.Path}: ({data.Usage})");
  606. Debug.Log(
  607. "Upgraded the following clips:\n" +
  608. $"{string.Join("\n", successes)}"
  609. );
  610. }
  611. if (notUpgraded.Count == clipData.Count)
  612. {
  613. Debug.LogWarning("No clips were upgraded. Did you remember to upgrade materials first?");
  614. }
  615. else if (notUpgraded.Count > 0)
  616. {
  617. var errors = notUpgraded.Select(data => $"- {data.Path}: ({data.Usage})");
  618. Debug.LogWarning(
  619. $"Did not modify following clips because they they were used in ways other than {~filterFlags}:\n" +
  620. $"{string.Join("\n", errors)}"
  621. );
  622. }
  623. }
  624. }
  625. }