A game about forced loneliness, made by TACStudios
1using System.Collections.Generic;
2using UnityEngine;
3using System;
4using UnityEngine.Rendering;
5using System.Text;
6
7namespace UnityEditor.Rendering
8{
9 /// <summary>
10 /// Material Upgrader dialog text.
11 /// </summary>
12 public static class DialogText
13 {
14 /// <summary>Material Upgrader title.</summary>
15 public static readonly string title = "Material Upgrader";
16 /// <summary>Material Upgrader proceed.</summary>
17 public static readonly string proceed = "Proceed";
18 /// <summary>Material Upgrader Ok.</summary>
19 public static readonly string ok = "OK";
20 /// <summary>Material Upgrader cancel.</summary>
21 public static readonly string cancel = "Cancel";
22 /// <summary>Material Upgrader no selection message.</summary>
23 public static readonly string noSelectionMessage = "You must select at least one material.";
24 /// <summary>Material Upgrader project backup message.</summary>
25 public static readonly string projectBackMessage = "Make sure to have a project backup before proceeding.";
26 }
27
28 /// <summary>
29 /// Material Upgrader class.
30 /// </summary>
31 public class MaterialUpgrader
32 {
33 /// <summary>
34 /// Material Upgrader finalizer delegate.
35 /// </summary>
36 /// <param name="mat">Material</param>
37 public delegate void MaterialFinalizer(Material mat);
38
39 string m_OldShader;
40 string m_NewShader;
41
42 private static string[] s_PathsWhiteList = new[]
43 {
44 "Hidden/",
45 "HDRP/",
46 "Shader Graphs/"
47 };
48
49 /// <summary>
50 /// Retrieves path to new shader.
51 /// </summary>
52 public string NewShaderPath
53 {
54 get => m_NewShader;
55 }
56
57 MaterialFinalizer m_Finalizer;
58
59 Dictionary<string, string> m_TextureRename = new Dictionary<string, string>();
60 Dictionary<string, string> m_FloatRename = new Dictionary<string, string>();
61 Dictionary<string, string> m_ColorRename = new Dictionary<string, string>();
62
63 Dictionary<string, float> m_FloatPropertiesToSet = new Dictionary<string, float>();
64 Dictionary<string, Color> m_ColorPropertiesToSet = new Dictionary<string, Color>();
65 List<string> m_TexturesToRemove = new List<string>();
66 Dictionary<string, Texture> m_TexturesToSet = new Dictionary<string, Texture>();
67
68 class KeywordFloatRename
69 {
70 public string keyword;
71 public string property;
72 public float setVal, unsetVal;
73 }
74 List<KeywordFloatRename> m_KeywordFloatRename = new List<KeywordFloatRename>();
75
76 /// <summary>
77 /// Type of property to rename.
78 /// </summary>
79 public enum MaterialPropertyType
80 {
81 /// <summary>Texture reference property.</summary>
82 Texture,
83 /// <summary>Float property.</summary>
84 Float,
85 /// <summary>Color property.</summary>
86 Color
87 }
88
89 /// <summary>
90 /// Retrieves a collection of renamed parameters of a specific MaterialPropertyType.
91 /// </summary>
92 /// <param name="type">Material Property Type</param>
93 /// <returns>Dictionary of property names to their renamed values.</returns>
94 /// <exception cref="ArgumentException">type is not valid.</exception>
95 public IReadOnlyDictionary<string, string> GetPropertyRenameMap(MaterialPropertyType type)
96 {
97 switch (type)
98 {
99 case MaterialPropertyType.Texture: return m_TextureRename;
100 case MaterialPropertyType.Float: return m_FloatRename;
101 case MaterialPropertyType.Color: return m_ColorRename;
102 default: throw new ArgumentException(nameof(type));
103 }
104 }
105
106 /// <summary>
107 /// Upgrade Flags
108 /// </summary>
109 [Flags]
110 public enum UpgradeFlags
111 {
112 /// <summary>None.</summary>
113 None = 0,
114 /// <summary>LogErrorOnNonExistingProperty.</summary>
115 LogErrorOnNonExistingProperty = 1,
116 /// <summary>CleanupNonUpgradedProperties.</summary>
117 CleanupNonUpgradedProperties = 2,
118 /// <summary>LogMessageWhenNoUpgraderFound.</summary>
119 LogMessageWhenNoUpgraderFound = 4
120 }
121
122 /// <summary>
123 /// Upgrade method.
124 /// </summary>
125 /// <param name="material">Material to upgrade.</param>
126 /// <param name="flags">Upgrade flag</param>
127 public void Upgrade(Material material, UpgradeFlags flags)
128 {
129 Material newMaterial;
130 if ((flags & UpgradeFlags.CleanupNonUpgradedProperties) != 0)
131 {
132 newMaterial = new Material(Shader.Find(m_NewShader));
133 }
134 else
135 {
136 newMaterial = UnityEngine.Object.Instantiate(material) as Material;
137 newMaterial.shader = Shader.Find(m_NewShader);
138 }
139
140 Convert(material, newMaterial);
141
142 material.shader = Shader.Find(m_NewShader);
143 material.CopyPropertiesFromMaterial(newMaterial);
144 UnityEngine.Object.DestroyImmediate(newMaterial);
145
146 if (m_Finalizer != null)
147 m_Finalizer(material);
148 }
149
150 // Overridable function to implement custom material upgrading functionality
151 /// <summary>
152 /// Custom material conversion method.
153 /// </summary>
154 /// <param name="srcMaterial">Source material.</param>
155 /// <param name="dstMaterial">Destination material.</param>
156 public virtual void Convert(Material srcMaterial, Material dstMaterial)
157 {
158 foreach (var t in m_TextureRename)
159 {
160 if (!srcMaterial.HasProperty(t.Key) || !dstMaterial.HasProperty(t.Value))
161 continue;
162
163 dstMaterial.SetTextureScale(t.Value, srcMaterial.GetTextureScale(t.Key));
164 dstMaterial.SetTextureOffset(t.Value, srcMaterial.GetTextureOffset(t.Key));
165 dstMaterial.SetTexture(t.Value, srcMaterial.GetTexture(t.Key));
166 }
167
168 foreach (var t in m_FloatRename)
169 {
170 if (!srcMaterial.HasProperty(t.Key) || !dstMaterial.HasProperty(t.Value))
171 continue;
172
173 dstMaterial.SetFloat(t.Value, srcMaterial.GetFloat(t.Key));
174 }
175
176 foreach (var t in m_ColorRename)
177 {
178 if (!srcMaterial.HasProperty(t.Key) || !dstMaterial.HasProperty(t.Value))
179 continue;
180
181 dstMaterial.SetColor(t.Value, srcMaterial.GetColor(t.Key));
182 }
183
184 foreach (var prop in m_TexturesToRemove)
185 {
186 if (!dstMaterial.HasProperty(prop))
187 continue;
188
189 dstMaterial.SetTexture(prop, null);
190 }
191
192 foreach (var prop in m_TexturesToSet)
193 {
194 if (!dstMaterial.HasProperty(prop.Key))
195 continue;
196
197 dstMaterial.SetTexture(prop.Key, prop.Value);
198 }
199
200 foreach (var prop in m_FloatPropertiesToSet)
201 {
202 if (!dstMaterial.HasProperty(prop.Key))
203 continue;
204
205 dstMaterial.SetFloat(prop.Key, prop.Value);
206 }
207
208 foreach (var prop in m_ColorPropertiesToSet)
209 {
210 if (!dstMaterial.HasProperty(prop.Key))
211 continue;
212
213 dstMaterial.SetColor(prop.Key, prop.Value);
214 }
215
216 foreach (var t in m_KeywordFloatRename)
217 {
218 if (!dstMaterial.HasProperty(t.property))
219 continue;
220
221 dstMaterial.SetFloat(t.property, srcMaterial.IsKeywordEnabled(t.keyword) ? t.setVal : t.unsetVal);
222 }
223 }
224
225 /// <summary>
226 /// Rename shader.
227 /// </summary>
228 /// <param name="oldName">Old name.</param>
229 /// <param name="newName">New name.</param>
230 /// <param name="finalizer">Finalizer delegate.</param>
231 public void RenameShader(string oldName, string newName, MaterialFinalizer finalizer = null)
232 {
233 m_OldShader = oldName;
234 m_NewShader = newName;
235 m_Finalizer = finalizer;
236 }
237
238 /// <summary>
239 /// Rename Texture Parameter.
240 /// </summary>
241 /// <param name="oldName">Old name.</param>
242 /// <param name="newName">New name.</param>
243 public void RenameTexture(string oldName, string newName)
244 {
245 m_TextureRename[oldName] = newName;
246 }
247
248 /// <summary>
249 /// Rename Float Parameter.
250 /// </summary>
251 /// <param name="oldName">Old name.</param>
252 /// <param name="newName">New name.</param>
253 public void RenameFloat(string oldName, string newName)
254 {
255 m_FloatRename[oldName] = newName;
256 }
257
258 /// <summary>
259 /// Rename Color Parameter.
260 /// </summary>
261 /// <param name="oldName">Old name.</param>
262 /// <param name="newName">New name.</param>
263 public void RenameColor(string oldName, string newName)
264 {
265 m_ColorRename[oldName] = newName;
266 }
267
268 /// <summary>
269 /// Remove Texture Parameter.
270 /// </summary>
271 /// <param name="name">Parameter name.</param>
272 public void RemoveTexture(string name)
273 {
274 m_TexturesToRemove.Add(name);
275 }
276
277 /// <summary>
278 /// Set float property.
279 /// </summary>
280 /// <param name="propertyName">Property name.</param>
281 /// <param name="value">Property value.</param>
282 public void SetFloat(string propertyName, float value)
283 {
284 m_FloatPropertiesToSet[propertyName] = value;
285 }
286
287 /// <summary>
288 /// Set color property.
289 /// </summary>
290 /// <param name="propertyName">Property name.</param>
291 /// <param name="value">Property value.</param>
292 public void SetColor(string propertyName, Color value)
293 {
294 m_ColorPropertiesToSet[propertyName] = value;
295 }
296
297 /// <summary>
298 /// Set texture property.
299 /// </summary>
300 /// <param name="propertyName">Property name.</param>
301 /// <param name="value">Property value.</param>
302 public void SetTexture(string propertyName, Texture value)
303 {
304 m_TexturesToSet[propertyName] = value;
305 }
306
307 /// <summary>
308 /// Rename a keyword to float.
309 /// </summary>
310 /// <param name="oldName">Old name.</param>
311 /// <param name="newName">New name.</param>
312 /// <param name="setVal">Value when set.</param>
313 /// <param name="unsetVal">Value when unset.</param>
314 public void RenameKeywordToFloat(string oldName, string newName, float setVal, float unsetVal)
315 {
316 m_KeywordFloatRename.Add(new KeywordFloatRename { keyword = oldName, property = newName, setVal = setVal, unsetVal = unsetVal });
317 }
318
319 static MaterialUpgrader GetUpgrader(List<MaterialUpgrader> upgraders, Material material)
320 {
321 if (material == null || material.shader == null)
322 return null;
323
324 string shaderName = material.shader.name;
325 for (int i = 0; i != upgraders.Count; i++)
326 {
327 if (upgraders[i].m_OldShader == shaderName)
328 return upgraders[i];
329 }
330
331 return null;
332 }
333
334 //@TODO: Only do this when it exceeds memory consumption...
335 static void SaveAssetsAndFreeMemory()
336 {
337 AssetDatabase.SaveAssets();
338 GC.Collect();
339 EditorUtility.UnloadUnusedAssetsImmediate();
340 AssetDatabase.Refresh();
341 }
342
343 /// <summary>
344 /// Checking if the passed in value is a path to a Material.
345 /// </summary>
346 /// <param name="material">Material to check.</param>
347 /// <param name="shaderNamesToIgnore">HashSet of strings to ignore.</param>
348 /// <returns>Returns true if the passed in material's shader is not in the passed in ignore list.</returns>
349 static bool ShouldUpgradeShader(Material material, HashSet<string> shaderNamesToIgnore)
350 {
351 if (material == null)
352 return false;
353
354 if (material.shader == null)
355 return false;
356
357 return !shaderNamesToIgnore.Contains(material.shader.name);
358 }
359
360 /// <summary>
361 /// Check if the materials in the list are variants of upgradable materials, and logs a infomative message to the user..
362 /// </summary>
363 /// <param name="materialGUIDs">Array of materials GUIDs.</param>
364 /// <param name="upgraders">List or available upgraders.</param>
365 static void LogMaterialVariantMessage(string[] materialGUIDs, List<MaterialUpgrader> upgraders)
366 {
367 List<Material> materials = new List<Material>();
368 foreach (var guid in materialGUIDs)
369 materials.Add(AssetDatabase.LoadAssetAtPath<Material>(AssetDatabase.GUIDToAssetPath(guid)));
370
371 LogMaterialVariantMessage(materials, upgraders);
372 }
373
374 /// <summary>
375 /// Check if the materials in the list are variants of upgradable materials, and logs a infomative message to the user..
376 /// </summary>
377 /// <param name="objects">Array of objects.</param>
378 /// <param name="upgraders">List or available upgraders.</param>
379 static void LogMaterialVariantMessage(UnityEngine.Object[] objects, List<MaterialUpgrader> upgraders)
380 {
381 Material mat;
382 List<Material> materials = new List<Material>();
383 for (int i = 0; i<objects.Length; i++)
384 {
385 mat = objects[i] as Material;
386 if (mat != null)
387 materials.Add(mat);
388 }
389
390 LogMaterialVariantMessage(materials, upgraders);
391 }
392
393 /// <summary>
394 /// Check if the materials in the list are variants of upgradable materials, and logs a infomative message to the user..
395 /// </summary>
396 /// <param name="materials">List of materials.</param>
397 /// <param name="upgraders">List or available upgraders.</param>
398 static void LogMaterialVariantMessage(List<Material> materials, List<MaterialUpgrader> upgraders)
399 {
400 StringBuilder sb = new StringBuilder();
401 sb.AppendLine("Can not upgrade Material Variants, the following assets were skipped:");
402 bool needsLogging = false;
403
404 Material rootMaterial;
405
406 foreach (Material material in materials)
407 {
408 if (material.isVariant)
409 {
410 rootMaterial = material;
411 while (rootMaterial.isVariant)
412 rootMaterial = rootMaterial.parent;
413
414 if (GetUpgrader(upgraders, rootMaterial) != null)
415 {
416 needsLogging = true;
417 sb.AppendLine($"- <a href=\"{AssetDatabase.GetAssetPath(material)}\" >{material.name}</a>, variant of {material.parent.name} with shader {rootMaterial.shader.name}.");
418 }
419 }
420 }
421
422 if (needsLogging)
423 Debug.Log(sb.ToString(), null);
424 }
425
426
427 private static bool IsNotAutomaticallyUpgradable(List<MaterialUpgrader> upgraders, Material material)
428 {
429 return GetUpgrader(upgraders, material) == null && !material.shader.name.ContainsAny(s_PathsWhiteList);
430 }
431
432
433 /// <summary>
434 /// Checking if project folder contains any materials that are not using built-in shaders.
435 /// </summary>
436 /// <param name="upgraders">List if MaterialUpgraders</param>
437 /// <returns>Returns true if at least one material uses a non-built-in shader (ignores Hidden, HDRP and Shader Graph Shaders)</returns>
438 public static bool ProjectFolderContainsNonBuiltinMaterials(List<MaterialUpgrader> upgraders)
439 {
440 foreach (var material in AssetDatabaseHelper.FindAssets<Material>(".mat"))
441 {
442 if(IsNotAutomaticallyUpgradable(upgraders, material))
443 return true;
444 }
445
446 return false;
447 }
448
449 /// <summary>
450 /// Upgrade the project folder.
451 /// </summary>
452 /// <param name="upgraders">List of upgraders.</param>
453 /// <param name="progressBarName">Name of the progress bar.</param>
454 /// <param name="flags">Material Upgrader flags.</param>
455 public static void UpgradeProjectFolder(List<MaterialUpgrader> upgraders, string progressBarName, UpgradeFlags flags = UpgradeFlags.None)
456 {
457 HashSet<string> shaderNamesToIgnore = new HashSet<string>();
458 UpgradeProjectFolder(upgraders, shaderNamesToIgnore, progressBarName, flags);
459 }
460
461 /// <summary>
462 /// Upgrade the project folder.
463 /// </summary>
464 /// <param name="upgraders">List of upgraders.</param>
465 /// <param name="shaderNamesToIgnore">Set of shader names to ignore.</param>
466 /// <param name="progressBarName">Name of the progress bar.</param>
467 /// <param name="flags">Material Upgrader flags.</param>
468 public static void UpgradeProjectFolder(List<MaterialUpgrader> upgraders, HashSet<string> shaderNamesToIgnore, string progressBarName, UpgradeFlags flags = UpgradeFlags.None)
469 {
470 if ((!Application.isBatchMode) && (!EditorUtility.DisplayDialog(DialogText.title, "The upgrade will overwrite materials in your project. " + DialogText.projectBackMessage, DialogText.proceed, DialogText.cancel)))
471 return;
472
473 var materialAssets = AssetDatabase.FindAssets($"t:{nameof(Material)} glob:\"**/*.mat\"");
474 int materialIndex = 0;
475
476 LogMaterialVariantMessage(materialAssets, upgraders);
477
478 foreach (var guid in materialAssets)
479 {
480 Material material = AssetDatabase.LoadAssetAtPath<Material>(AssetDatabase.GUIDToAssetPath(guid));
481 materialIndex++;
482 if (UnityEditor.EditorUtility.DisplayCancelableProgressBar(progressBarName, string.Format("({0} of {1}) {2}", materialIndex, materialAssets.Length, material), (float)materialIndex / (float)materialAssets.Length))
483 break;
484
485 if (!ShouldUpgradeShader(material, shaderNamesToIgnore))
486 continue;
487
488 if (material.isVariant)
489 continue;
490
491 Upgrade(material, upgraders, flags);
492
493 }
494
495 // Upgrade terrain specifically since it is a builtin material
496 if (Terrain.activeTerrains.Length > 0)
497 {
498 Material terrainMat = Terrain.activeTerrain.materialTemplate;
499 Upgrade(terrainMat, upgraders, flags);
500 }
501
502 UnityEditor.EditorUtility.ClearProgressBar();
503 }
504
505 /// <summary>
506 /// Upgrade a material.
507 /// </summary>
508 /// <param name="material">Material to upgrade.</param>
509 /// <param name="upgrader">Material upgrader.</param>
510 /// <param name="flags">Material Upgrader flags.</param>
511 public static void Upgrade(Material material, MaterialUpgrader upgrader, UpgradeFlags flags)
512 {
513 using (ListPool<MaterialUpgrader>.Get(out List<MaterialUpgrader> upgraders))
514 {
515 upgraders.Add(upgrader);
516 Upgrade(material, upgraders, flags);
517 }
518 }
519
520 /// <summary>
521 /// Upgrade a material.
522 /// </summary>
523 /// <param name="material">Material to upgrade.</param>
524 /// <param name="upgraders">List of Material upgraders.</param>
525 /// <param name="flags">Material Upgrader flags.</param>
526 public static void Upgrade(Material material, List<MaterialUpgrader> upgraders, UpgradeFlags flags)
527 {
528 string message = string.Empty;
529 if (Upgrade(material, upgraders, flags, ref message))
530 return;
531
532 if (!string.IsNullOrEmpty(message))
533 {
534 Debug.Log(message);
535 }
536 }
537
538 /// <summary>
539 /// Upgrade a material.
540 /// </summary>
541 /// <param name="material">Material to upgrade.</param>
542 /// <param name="upgraders">List of Material upgraders.</param>
543 /// <param name="flags">Material upgrader flags.</param>
544 /// <param name="message">Error message to be outputted when no material upgraders are suitable for given material if the flags <see cref="UpgradeFlags.LogMessageWhenNoUpgraderFound"/> is used.</param>
545 /// <returns>Returns true if the upgrader was found for the passed in material.</returns>
546 public static bool Upgrade(Material material, List<MaterialUpgrader> upgraders, UpgradeFlags flags, ref string message)
547 {
548 if (material == null)
549 return false;
550
551 var upgrader = GetUpgrader(upgraders, material);
552
553 if (upgrader != null)
554 {
555 upgrader.Upgrade(material, flags);
556 return true;
557 }
558 if ((flags & UpgradeFlags.LogMessageWhenNoUpgraderFound) == UpgradeFlags.LogMessageWhenNoUpgraderFound)
559 {
560 message =
561 $"{material.name} material was not upgraded. There's no upgrader to convert {material.shader.name} shader to selected pipeline";
562 return false;
563 }
564
565 return true;
566 }
567
568 /// <summary>
569 /// Upgrade the selection.
570 /// </summary>
571 /// <param name="upgraders">List of upgraders.</param>
572 /// <param name="progressBarName">Name of the progress bar.</param>
573 /// <param name="flags">Material Upgrader flags.</param>
574 public static void UpgradeSelection(List<MaterialUpgrader> upgraders, string progressBarName, UpgradeFlags flags = UpgradeFlags.None)
575 {
576 HashSet<string> shaderNamesToIgnore = new HashSet<string>();
577 UpgradeSelection(upgraders, shaderNamesToIgnore, progressBarName, flags);
578 }
579
580 /// <summary>
581 /// Upgrade the selection.
582 /// </summary>
583 /// <param name="upgraders">List of upgraders.</param>
584 /// <param name="shaderNamesToIgnore">Set of shader names to ignore.</param>
585 /// <param name="progressBarName">Name of the progress bar.</param>
586 /// <param name="flags">Material Upgrader flags.</param>
587 public static void UpgradeSelection(List<MaterialUpgrader> upgraders, HashSet<string> shaderNamesToIgnore, string progressBarName, UpgradeFlags flags = UpgradeFlags.None)
588 {
589 var selection = Selection.objects;
590
591 if (selection == null)
592 {
593 EditorUtility.DisplayDialog(DialogText.title, DialogText.noSelectionMessage, DialogText.ok);
594 return;
595 }
596
597 List<Material> selectedMaterials = new List<Material>(selection.Length);
598
599 LogMaterialVariantMessage(selection, upgraders);
600
601 for (int i = 0; i < selection.Length; ++i)
602 {
603 Material mat = selection[i] as Material;
604 if (mat != null && !mat.isVariant)
605 selectedMaterials.Add(mat);
606 }
607
608 int selectedMaterialsCount = selectedMaterials.Count;
609 if (selectedMaterialsCount == 0)
610 {
611 EditorUtility.DisplayDialog(DialogText.title, DialogText.noSelectionMessage, DialogText.ok);
612 return;
613 }
614
615 if (!EditorUtility.DisplayDialog(DialogText.title, string.Format("The upgrade will overwrite {0} selected material{1}. ", selectedMaterialsCount, selectedMaterialsCount > 1 ? "s" : "") +
616 DialogText.projectBackMessage, DialogText.proceed, DialogText.cancel))
617 return;
618
619 string lastMaterialName = "";
620 for (int i = 0; i < selectedMaterialsCount; i++)
621 {
622 if (UnityEditor.EditorUtility.DisplayCancelableProgressBar(progressBarName, string.Format("({0} of {1}) {2}", i, selectedMaterialsCount, lastMaterialName), (float)i / (float)selectedMaterialsCount))
623 break;
624
625 var material = selectedMaterials[i];
626
627 if (!ShouldUpgradeShader(material, shaderNamesToIgnore))
628 continue;
629
630 Upgrade(material, upgraders, flags);
631 if (material != null)
632 lastMaterialName = material.name;
633 }
634
635 UnityEditor.EditorUtility.ClearProgressBar();
636 }
637 }
638}