No Description
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.

GraphUtil.cs 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. using System;
  2. using System.Text;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.IO;
  6. using System.IO.IsolatedStorage;
  7. using System.Linq;
  8. using System.Text.RegularExpressions;
  9. using UnityEditor.Graphing;
  10. using UnityEditor.Graphing.Util;
  11. using UnityEditorInternal;
  12. using Debug = UnityEngine.Debug;
  13. using System.Reflection;
  14. using System.Runtime.Remoting.Metadata.W3cXsd2001;
  15. using UnityEditor.ProjectWindowCallback;
  16. using UnityEditor.ShaderGraph.Internal;
  17. using UnityEngine;
  18. using UnityEngine.Rendering;
  19. using Object = System.Object;
  20. namespace UnityEditor.ShaderGraph
  21. {
  22. // a structure used to track active variable dependencies in the shader code
  23. // (i.e. the use of uv0 in the pixel shader means we need a uv0 interpolator, etc.)
  24. struct Dependency
  25. {
  26. public string name; // the name of the thing
  27. public string dependsOn; // the thing above depends on this -- it reads it / calls it / requires it to be defined
  28. public Dependency(string name, string dependsOn)
  29. {
  30. this.name = name;
  31. this.dependsOn = dependsOn;
  32. }
  33. };
  34. [System.AttributeUsage(System.AttributeTargets.Struct)]
  35. class InterpolatorPack : System.Attribute
  36. {
  37. public InterpolatorPack()
  38. {
  39. }
  40. }
  41. // attribute used to flag a field as needing an HLSL semantic applied
  42. // i.e. float3 position : POSITION;
  43. // ^ semantic
  44. [System.AttributeUsage(System.AttributeTargets.Field)]
  45. class Semantic : System.Attribute
  46. {
  47. public string semantic;
  48. public Semantic(string semantic)
  49. {
  50. this.semantic = semantic;
  51. }
  52. }
  53. // attribute used to flag a field as being optional
  54. // i.e. if it is not active, then we can omit it from the struct
  55. [System.AttributeUsage(System.AttributeTargets.Field)]
  56. class Optional : System.Attribute
  57. {
  58. public Optional()
  59. {
  60. }
  61. }
  62. // attribute used to override the HLSL type of a field with a custom type string
  63. [System.AttributeUsage(System.AttributeTargets.Field)]
  64. class OverrideType : System.Attribute
  65. {
  66. public string typeName;
  67. public OverrideType(string typeName)
  68. {
  69. this.typeName = typeName;
  70. }
  71. }
  72. // attribute used to force system generated fields to bottom of structs
  73. [System.AttributeUsage(System.AttributeTargets.Field)]
  74. class SystemGenerated : System.Attribute
  75. {
  76. public SystemGenerated()
  77. {
  78. }
  79. }
  80. // attribute used to disable a field using a preprocessor #if
  81. [System.AttributeUsage(System.AttributeTargets.Field)]
  82. class PreprocessorIf : System.Attribute
  83. {
  84. public string conditional;
  85. public PreprocessorIf(string conditional)
  86. {
  87. this.conditional = conditional;
  88. }
  89. }
  90. class NewGraphAction : EndNameEditAction
  91. {
  92. Target[] m_Targets;
  93. public Target[] targets
  94. {
  95. get => m_Targets;
  96. set => m_Targets = value;
  97. }
  98. BlockFieldDescriptor[] m_Blocks;
  99. public BlockFieldDescriptor[] blocks
  100. {
  101. get => m_Blocks;
  102. set => m_Blocks = value;
  103. }
  104. public override void Action(int instanceId, string pathName, string resourceFile)
  105. {
  106. var graph = new GraphData();
  107. graph.AddContexts();
  108. graph.InitializeOutputs(m_Targets, m_Blocks);
  109. graph.AddCategory(CategoryData.DefaultCategory());
  110. graph.path = "Shader Graphs";
  111. FileUtilities.WriteShaderGraphToDisk(pathName, graph);
  112. AssetDatabase.Refresh();
  113. UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath<Shader>(pathName);
  114. Selection.activeObject = obj;
  115. }
  116. }
  117. static class GraphUtil
  118. {
  119. internal static bool CheckForRecursiveDependencyOnPendingSave(string saveFilePath, IEnumerable<SubGraphNode> subGraphNodes, string context = null)
  120. {
  121. var overwriteGUID = AssetDatabase.AssetPathToGUID(saveFilePath);
  122. if (!string.IsNullOrEmpty(overwriteGUID))
  123. {
  124. foreach (var sgNode in subGraphNodes)
  125. {
  126. var asset = sgNode?.asset;
  127. if (asset == null)
  128. {
  129. // cannot read the asset; might be recursive but we can't tell... should we return "maybe"?
  130. // I think to be minimally intrusive to the user we can assume "No" in this case,
  131. // even though this may miss recursions in extraordinary cases.
  132. // it's more important to allow the user to save their files than to catch 100% of recursions
  133. continue;
  134. }
  135. else if ((asset.assetGuid == overwriteGUID) || asset.descendents.Contains(overwriteGUID))
  136. {
  137. if (context != null)
  138. {
  139. Debug.LogWarning(context + " CANCELLED to avoid a generating a reference loop: the SubGraph '" + sgNode.asset.name + "' references the target file '" + saveFilePath + "'");
  140. EditorUtility.DisplayDialog(
  141. context + " CANCELLED",
  142. "Saving the file would generate a reference loop, because the SubGraph '" + sgNode.asset.name + "' references the target file '" + saveFilePath + "'", "Cancel");
  143. }
  144. return true;
  145. }
  146. }
  147. }
  148. return false;
  149. }
  150. internal static string ConvertCamelCase(string text, bool preserveAcronyms)
  151. {
  152. if (string.IsNullOrEmpty(text))
  153. return string.Empty;
  154. StringBuilder newText = new StringBuilder(text.Length * 2);
  155. newText.Append(text[0]);
  156. for (int i = 1; i < text.Length; i++)
  157. {
  158. if (char.IsUpper(text[i]))
  159. if ((text[i - 1] != ' ' && !char.IsUpper(text[i - 1])) ||
  160. (preserveAcronyms && char.IsUpper(text[i - 1]) &&
  161. i < text.Length - 1 && !char.IsUpper(text[i + 1])))
  162. newText.Append(' ');
  163. newText.Append(text[i]);
  164. }
  165. return newText.ToString();
  166. }
  167. public static void CreateNewGraph()
  168. {
  169. var graphItem = ScriptableObject.CreateInstance<NewGraphAction>();
  170. graphItem.targets = null;
  171. ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, graphItem,
  172. string.Format("New Shader Graph.{0}", ShaderGraphImporter.Extension), null, null);
  173. }
  174. public static void CreateNewGraphWithOutputs(Target[] targets, BlockFieldDescriptor[] blockDescriptors)
  175. {
  176. var graphItem = ScriptableObject.CreateInstance<NewGraphAction>();
  177. graphItem.targets = targets;
  178. graphItem.blocks = blockDescriptors;
  179. ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, graphItem,
  180. string.Format("New Shader Graph.{0}", ShaderGraphImporter.Extension), null, null);
  181. }
  182. public static bool TryGetMetadataOfType<T>(this Shader shader, out T obj) where T : ScriptableObject
  183. {
  184. obj = null;
  185. if (!shader.IsShaderGraphAsset())
  186. return false;
  187. var path = AssetDatabase.GetAssetPath(shader);
  188. foreach (var asset in AssetDatabase.LoadAllAssetsAtPath(path))
  189. {
  190. if (asset is T metadataAsset)
  191. {
  192. obj = metadataAsset;
  193. return true;
  194. }
  195. }
  196. return false;
  197. }
  198. // this will work on ALL shadergraph-built shaders, in memory or asset based
  199. public static bool IsShaderGraph(this Material material)
  200. {
  201. var shaderGraphTag = material.GetTag("ShaderGraphShader", false, null);
  202. return !string.IsNullOrEmpty(shaderGraphTag);
  203. }
  204. // NOTE: this ONLY works for ASSET based Shaders, if you created a temporary shader in memory, it won't work
  205. public static bool IsShaderGraphAsset(this Shader shader)
  206. {
  207. var path = AssetDatabase.GetAssetPath(shader);
  208. var importer = AssetImporter.GetAtPath(path);
  209. return importer is ShaderGraphImporter;
  210. }
  211. [Obsolete("Use IsShaderGraphAsset instead", false)]
  212. public static bool IsShaderGraph(this Shader shader) => shader.IsShaderGraphAsset();
  213. static void Visit(List<AbstractMaterialNode> outputList, Dictionary<string, AbstractMaterialNode> unmarkedNodes, AbstractMaterialNode node)
  214. {
  215. if (!unmarkedNodes.ContainsKey(node.objectId))
  216. return;
  217. foreach (var slot in node.GetInputSlots<MaterialSlot>())
  218. {
  219. foreach (var edge in node.owner.GetEdges(slot.slotReference))
  220. {
  221. var inputNode = edge.outputSlot.node;
  222. Visit(outputList, unmarkedNodes, inputNode);
  223. }
  224. }
  225. unmarkedNodes.Remove(node.objectId);
  226. outputList.Add(node);
  227. }
  228. static Dictionary<SerializationHelper.TypeSerializationInfo, SerializationHelper.TypeSerializationInfo> s_LegacyTypeRemapping;
  229. public static Dictionary<SerializationHelper.TypeSerializationInfo, SerializationHelper.TypeSerializationInfo> GetLegacyTypeRemapping()
  230. {
  231. if (s_LegacyTypeRemapping == null)
  232. {
  233. s_LegacyTypeRemapping = new Dictionary<SerializationHelper.TypeSerializationInfo, SerializationHelper.TypeSerializationInfo>();
  234. foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
  235. {
  236. foreach (var type in assembly.GetTypesOrNothing())
  237. {
  238. if (type.IsAbstract)
  239. continue;
  240. foreach (var attribute in type.GetCustomAttributes(typeof(FormerNameAttribute), false))
  241. {
  242. var legacyAttribute = (FormerNameAttribute)attribute;
  243. var serializationInfo = new SerializationHelper.TypeSerializationInfo { fullName = legacyAttribute.fullName };
  244. s_LegacyTypeRemapping[serializationInfo] = SerializationHelper.GetTypeSerializableAsString(type);
  245. }
  246. }
  247. }
  248. }
  249. return s_LegacyTypeRemapping;
  250. }
  251. /// <summary>
  252. /// Sanitizes a supplied string such that it does not collide
  253. /// with any other name in a collection.
  254. /// </summary>
  255. /// <param name="existingNames">
  256. /// A collection of names that the new name should not collide with.
  257. /// </param>
  258. /// <param name="duplicateFormat">
  259. /// The format applied to the name if a duplicate exists.
  260. /// This must be a format string that contains `{0}` and `{1}`
  261. /// once each. An example could be `{0} ({1})`, which will append ` (n)`
  262. /// to the name for the n`th duplicate.
  263. /// </param>
  264. /// <param name="name">
  265. /// The name to be sanitized.
  266. /// </param>
  267. /// <returns>
  268. /// A name that is distinct form any name in `existingNames`.
  269. /// </returns>
  270. internal static string SanitizeName(IEnumerable<string> existingNames, string duplicateFormat, string name, string disallowedPatternRegex = "\"")
  271. {
  272. name = Regex.Replace(name, disallowedPatternRegex, "_");
  273. return DeduplicateName(existingNames, duplicateFormat, name);
  274. }
  275. internal static string SanitizeCategoryName(string categoryName, string disallowedPatternRegex = "\"")
  276. {
  277. return Regex.Replace(categoryName, disallowedPatternRegex, "_");
  278. }
  279. internal static string DeduplicateName(IEnumerable<string> existingNames, string duplicateFormat, string name)
  280. {
  281. if (!existingNames.Contains(name))
  282. return name;
  283. string escapedDuplicateFormat = Regex.Escape(duplicateFormat);
  284. // Escaped format will escape string interpolation, so the escape characters must be removed for these.
  285. escapedDuplicateFormat = escapedDuplicateFormat.Replace(@"\{0}", @"{0}");
  286. escapedDuplicateFormat = escapedDuplicateFormat.Replace(@"\{1}", @"{1}");
  287. var baseRegex = new Regex(string.Format(escapedDuplicateFormat, @"^(.*)", @"(\d+)"));
  288. var baseMatch = baseRegex.Match(name);
  289. if (baseMatch.Success)
  290. name = baseMatch.Groups[1].Value;
  291. string baseNameExpression = string.Format(@"^{0}", Regex.Escape(name));
  292. var regex = new Regex(string.Format(escapedDuplicateFormat, baseNameExpression, @"(\d+)") + "$");
  293. var existingDuplicateNumbers = existingNames.Select(existingName => regex.Match(existingName)).Where(m => m.Success).Select(m => int.Parse(m.Groups[1].Value)).Where(n => n > 0).Distinct().ToList();
  294. var duplicateNumber = 1;
  295. existingDuplicateNumbers.Sort();
  296. if (existingDuplicateNumbers.Any() && existingDuplicateNumbers.First() == 1)
  297. {
  298. duplicateNumber = existingDuplicateNumbers.Last() + 1;
  299. for (var i = 1; i < existingDuplicateNumbers.Count; i++)
  300. {
  301. if (existingDuplicateNumbers[i - 1] != existingDuplicateNumbers[i] - 1)
  302. {
  303. duplicateNumber = existingDuplicateNumbers[i - 1] + 1;
  304. break;
  305. }
  306. }
  307. }
  308. return string.Format(duplicateFormat, name, duplicateNumber);
  309. }
  310. public static bool WriteToFile(string path, string content)
  311. {
  312. try
  313. {
  314. File.WriteAllText(path, content);
  315. return true;
  316. }
  317. catch (Exception e)
  318. {
  319. Debug.LogError(e);
  320. return false;
  321. }
  322. }
  323. public static void OpenFile(string path)
  324. {
  325. string filePath = Path.GetFullPath(path);
  326. if (!File.Exists(filePath))
  327. {
  328. Debug.LogError(string.Format("Path {0} doesn't exists", path));
  329. return;
  330. }
  331. string externalScriptEditor = ScriptEditorUtility.GetExternalScriptEditor();
  332. if (externalScriptEditor != "internal")
  333. {
  334. InternalEditorUtility.OpenFileAtLineExternal(filePath, 0);
  335. }
  336. else
  337. {
  338. Process p = new Process();
  339. p.StartInfo.FileName = filePath;
  340. p.EnableRaisingEvents = true;
  341. p.Exited += (Object obj, EventArgs args) =>
  342. {
  343. if (p.ExitCode != 0)
  344. Debug.LogWarningFormat("Unable to open {0}: Check external editor in preferences", filePath);
  345. };
  346. p.Start();
  347. }
  348. }
  349. //
  350. // Find all nodes of the given type downstream from the given node
  351. // Returns a unique list. So even if a node can be reached through different paths it will be present only once.
  352. //
  353. public static List<NodeType> FindDownStreamNodesOfType<NodeType>(AbstractMaterialNode node) where NodeType : AbstractMaterialNode
  354. {
  355. // Should never be called without a node
  356. Debug.Assert(node != null);
  357. HashSet<AbstractMaterialNode> visitedNodes = new HashSet<AbstractMaterialNode>();
  358. List<NodeType> vtNodes = new List<NodeType>();
  359. Queue<AbstractMaterialNode> nodeStack = new Queue<AbstractMaterialNode>();
  360. nodeStack.Enqueue(node);
  361. visitedNodes.Add(node);
  362. while (nodeStack.Count > 0)
  363. {
  364. AbstractMaterialNode visit = nodeStack.Dequeue();
  365. // Flood fill through all the nodes
  366. foreach (var slot in visit.GetInputSlots<MaterialSlot>())
  367. {
  368. foreach (var edge in visit.owner.GetEdges(slot.slotReference))
  369. {
  370. var inputNode = edge.outputSlot.node;
  371. if (!visitedNodes.Contains(inputNode))
  372. {
  373. nodeStack.Enqueue(inputNode);
  374. visitedNodes.Add(inputNode);
  375. }
  376. }
  377. }
  378. // Extract vt node
  379. if (visit is NodeType)
  380. {
  381. NodeType vtNode = visit as NodeType;
  382. vtNodes.Add(vtNode);
  383. }
  384. }
  385. return vtNodes;
  386. }
  387. }
  388. }