-
Notifications
You must be signed in to change notification settings - Fork 869
Expand file tree
/
Copy pathAnimationClipUpgrader.cs
More file actions
696 lines (625 loc) · 35.6 KB
/
AnimationClipUpgrader.cs
File metadata and controls
696 lines (625 loc) · 35.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
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.Length > 0)
{
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)}"
);
}
}
}
}