Ingen beskrivning
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.

NodeUtils.cs 33KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Text.RegularExpressions;
  7. using UnityEditor.ShaderGraph;
  8. using UnityEditor.ShaderGraph.Drawing;
  9. using UnityEngine;
  10. using UnityEngine.Pool;
  11. using UnityEngine.Rendering.ShaderGraph;
  12. namespace UnityEditor.Graphing
  13. {
  14. class SlotConfigurationException : Exception
  15. {
  16. public SlotConfigurationException(string message)
  17. : base(message)
  18. { }
  19. }
  20. static class NodeUtils
  21. {
  22. static string NodeDocSuffix = "-Node";
  23. public static void SlotConfigurationExceptionIfBadConfiguration(AbstractMaterialNode node, IEnumerable<int> expectedInputSlots, IEnumerable<int> expectedOutputSlots)
  24. {
  25. var missingSlots = new List<int>();
  26. var inputSlots = expectedInputSlots as IList<int> ?? expectedInputSlots.ToList();
  27. missingSlots.AddRange(inputSlots.Except(node.GetInputSlots<MaterialSlot>().Select(x => x.id)));
  28. var outputSlots = expectedOutputSlots as IList<int> ?? expectedOutputSlots.ToList();
  29. missingSlots.AddRange(outputSlots.Except(node.GetOutputSlots<MaterialSlot>().Select(x => x.id)));
  30. if (missingSlots.Count == 0)
  31. return;
  32. var toPrint = missingSlots.Select(x => x.ToString());
  33. throw new SlotConfigurationException(string.Format("Missing slots {0} on node {1}", string.Join(", ", toPrint.ToArray()), node));
  34. }
  35. public static IEnumerable<IEdge> GetAllEdges(AbstractMaterialNode node)
  36. {
  37. var result = new List<IEdge>();
  38. var validSlots = ListPool<MaterialSlot>.Get();
  39. validSlots.AddRange(node.GetInputSlots<MaterialSlot>());
  40. for (int index = 0; index < validSlots.Count; index++)
  41. {
  42. var inputSlot = validSlots[index];
  43. result.AddRange(node.owner.GetEdges(inputSlot.slotReference));
  44. }
  45. validSlots.Clear();
  46. validSlots.AddRange(node.GetOutputSlots<MaterialSlot>());
  47. for (int index = 0; index < validSlots.Count; index++)
  48. {
  49. var outputSlot = validSlots[index];
  50. result.AddRange(node.owner.GetEdges(outputSlot.slotReference));
  51. }
  52. ListPool<MaterialSlot>.Release(validSlots);
  53. return result;
  54. }
  55. public static string GetDuplicateSafeNameForSlot(AbstractMaterialNode node, int slotId, string name)
  56. {
  57. List<MaterialSlot> slots = new List<MaterialSlot>();
  58. node.GetSlots(slots);
  59. name = name.Trim();
  60. return GraphUtil.SanitizeName(slots.Where(p => p.id != slotId).Select(p => p.RawDisplayName()), "{0} ({1})", name);
  61. }
  62. // CollectNodesNodeFeedsInto looks at the current node and calculates
  63. // which child nodes it depends on for it's calculation.
  64. // Results are returned depth first so by processing each node in
  65. // order you can generate a valid code block.
  66. public enum IncludeSelf
  67. {
  68. Include,
  69. Exclude
  70. }
  71. public static SlotReference DepthFirstCollectRedirectNodeFromNode(RedirectNodeData node)
  72. {
  73. var inputSlot = node.FindSlot<MaterialSlot>(RedirectNodeData.kInputSlotID);
  74. foreach (var edge in node.owner.GetEdges(inputSlot.slotReference))
  75. {
  76. // get the input details
  77. var outputSlotRef = edge.outputSlot;
  78. var inputNode = outputSlotRef.node;
  79. // If this is a redirect node we continue to look for the top one
  80. if (inputNode is RedirectNodeData redirectNode)
  81. {
  82. return DepthFirstCollectRedirectNodeFromNode(redirectNode);
  83. }
  84. return outputSlotRef;
  85. }
  86. // If no edges it is the first redirect node without an edge going into it and we should return the slot ref
  87. return node.GetSlotReference(RedirectNodeData.kInputSlotID);
  88. }
  89. public static void DepthFirstCollectNodesFromNode(ICollection<AbstractMaterialNode> nodeList, AbstractMaterialNode node,
  90. IncludeSelf includeSelf = IncludeSelf.Include, List<KeyValuePair<ShaderKeyword, int>> keywordPermutation = null, bool ignoreActiveState = false)
  91. {
  92. // no where to start
  93. if (node == null)
  94. return;
  95. // already added this node
  96. if (nodeList.Contains(node))
  97. return;
  98. IEnumerable<int> ids;
  99. // If this node is a keyword node and we have an active keyword permutation
  100. // The only valid port id is the port that corresponds to that keywords value in the active permutation
  101. if (node is KeywordNode keywordNode && keywordPermutation != null)
  102. {
  103. var valueInPermutation = keywordPermutation.Where(x => x.Key == keywordNode.keyword).FirstOrDefault();
  104. ids = new int[] { keywordNode.GetSlotIdForPermutation(valueInPermutation) };
  105. }
  106. else
  107. {
  108. ids = node.GetInputSlots<MaterialSlot>().Select(x => x.id);
  109. }
  110. foreach (var slot in ids)
  111. {
  112. foreach (var edge in node.owner.GetEdges(node.GetSlotReference(slot)))
  113. {
  114. var outputNode = edge.outputSlot.node;
  115. if (outputNode != null)
  116. DepthFirstCollectNodesFromNode(nodeList, outputNode, keywordPermutation: keywordPermutation, ignoreActiveState: ignoreActiveState);
  117. }
  118. }
  119. if (includeSelf == IncludeSelf.Include && (node.isActive || ignoreActiveState))
  120. nodeList.Add(node);
  121. }
  122. internal static List<AbstractMaterialNode> GetParentNodes(AbstractMaterialNode node)
  123. {
  124. List<AbstractMaterialNode> nodeList = new List<AbstractMaterialNode>();
  125. var ids = node.GetInputSlots<MaterialSlot>().Select(x => x.id);
  126. foreach (var slot in ids)
  127. {
  128. if (node.owner == null)
  129. break;
  130. foreach (var edge in node.owner.GetEdges(node.FindSlot<MaterialSlot>(slot).slotReference))
  131. {
  132. var outputNode = ((Edge)edge).outputSlot.node;
  133. if (outputNode != null)
  134. {
  135. nodeList.Add(outputNode);
  136. }
  137. }
  138. }
  139. return nodeList;
  140. }
  141. private static bool ActiveLeafExists(AbstractMaterialNode node)
  142. {
  143. //if our active state has been explicitly set to a value use it
  144. switch (node.activeState)
  145. {
  146. case AbstractMaterialNode.ActiveState.Implicit:
  147. break;
  148. case AbstractMaterialNode.ActiveState.ExplicitInactive:
  149. return false;
  150. case AbstractMaterialNode.ActiveState.ExplicitActive:
  151. return true;
  152. }
  153. List<AbstractMaterialNode> parentNodes = GetParentNodes(node);
  154. //at this point we know we are not explicitly set to a state,
  155. //so there is no reason to be inactive
  156. if (parentNodes.Count == 0)
  157. {
  158. return true;
  159. }
  160. bool output = false;
  161. foreach (var parent in parentNodes)
  162. {
  163. output |= ActiveLeafExists(parent);
  164. if (output)
  165. {
  166. break;
  167. }
  168. }
  169. return output;
  170. }
  171. private static List<AbstractMaterialNode> GetChildNodes(AbstractMaterialNode node)
  172. {
  173. List<AbstractMaterialNode> nodeList = new List<AbstractMaterialNode>();
  174. var slots = node.GetOutputSlots<MaterialSlot>();
  175. var edges = new List<IEdge>();
  176. foreach (var slot in slots)
  177. {
  178. node.owner.GetEdges(slot, edges);
  179. foreach (var edge in edges)
  180. {
  181. var inputNode = ((Edge)edge).inputSlot.node;
  182. if (inputNode != null)
  183. {
  184. nodeList.Add(inputNode);
  185. }
  186. }
  187. edges.Clear();
  188. }
  189. return nodeList;
  190. }
  191. private static bool ActiveRootExists(AbstractMaterialNode node)
  192. {
  193. //if our active state has been explicitly set to a value use it
  194. switch (node.activeState)
  195. {
  196. case AbstractMaterialNode.ActiveState.Implicit:
  197. break;
  198. case AbstractMaterialNode.ActiveState.ExplicitInactive:
  199. return false;
  200. case AbstractMaterialNode.ActiveState.ExplicitActive:
  201. return true;
  202. }
  203. List<AbstractMaterialNode> childNodes = GetChildNodes(node);
  204. //at this point we know we are not explicitly set to a state,
  205. //so there is no reason to be inactive
  206. if (childNodes.Count == 0)
  207. {
  208. return true;
  209. }
  210. bool output = false;
  211. foreach (var child in childNodes)
  212. {
  213. output |= ActiveRootExists(child);
  214. if (output)
  215. {
  216. break;
  217. }
  218. }
  219. return output;
  220. }
  221. private static void ActiveTreeExists(AbstractMaterialNode node, out bool activeLeaf, out bool activeRoot, out bool activeTree)
  222. {
  223. activeLeaf = ActiveLeafExists(node);
  224. activeRoot = ActiveRootExists(node);
  225. activeTree = activeRoot && activeLeaf;
  226. }
  227. //First pass check if node is now active after a change, so just check if there is a valid "tree" : a valid upstream input path,
  228. // and a valid downstream output path, or "leaf" and "root". If this changes the node's active state, then anything connected may
  229. // change as well, so update the "forrest" or all connectected trees of this nodes leaves.
  230. // NOTE: I cannot think if there is any case where the entirety of the connected graph would need to change, but if there are bugs
  231. // on certain nodes farther away from the node not updating correctly, a possible solution may be to get the entirety of the connected
  232. // graph instead of just what I have declared as the "local" connected graph
  233. public static void ReevaluateActivityOfConnectedNodes(AbstractMaterialNode node, PooledHashSet<AbstractMaterialNode> changedNodes = null)
  234. {
  235. var forest = GetForest(node);
  236. ReevaluateActivityOfNodeList(forest, changedNodes);
  237. }
  238. public static void ReevaluateActivityOfNodeList(IEnumerable<AbstractMaterialNode> nodes, PooledHashSet<AbstractMaterialNode> changedNodes = null)
  239. {
  240. bool getChangedNodes = changedNodes != null;
  241. foreach (AbstractMaterialNode n in nodes)
  242. {
  243. if (n.activeState != AbstractMaterialNode.ActiveState.Implicit)
  244. continue;
  245. ActiveTreeExists(n, out _, out _, out bool at);
  246. if (n.isActive != at && getChangedNodes)
  247. {
  248. changedNodes.Add(n);
  249. }
  250. n.SetActive(at, false);
  251. }
  252. }
  253. //Go to the leaves of the node, then get all trees with those leaves
  254. private static HashSet<AbstractMaterialNode> GetForest(AbstractMaterialNode node)
  255. {
  256. var initial = new HashSet<AbstractMaterialNode> { node };
  257. var upstream = new HashSet<AbstractMaterialNode>();
  258. PreviewManager.PropagateNodes(initial, PreviewManager.PropagationDirection.Upstream, upstream);
  259. var forest = new HashSet<AbstractMaterialNode>();
  260. PreviewManager.PropagateNodes(upstream, PreviewManager.PropagationDirection.Downstream, forest);
  261. return forest;
  262. }
  263. public static void GetDownsteamNodesForNode(List<AbstractMaterialNode> nodeList, AbstractMaterialNode node)
  264. {
  265. // no where to start
  266. if (node == null)
  267. return;
  268. // Recursively traverse downstream from the original node
  269. // Traverse down each edge and continue on any connected downstream nodes
  270. // Only nodes with no nodes further downstream are added to node list
  271. bool hasDownstream = false;
  272. var ids = node.GetOutputSlots<MaterialSlot>().Select(x => x.id);
  273. foreach (var slot in ids)
  274. {
  275. foreach (var edge in node.owner.GetEdges(node.FindSlot<MaterialSlot>(slot).slotReference))
  276. {
  277. var inputNode = ((Edge)edge).inputSlot.node;
  278. if (inputNode != null)
  279. {
  280. hasDownstream = true;
  281. GetDownsteamNodesForNode(nodeList, inputNode);
  282. }
  283. }
  284. }
  285. // No more nodes downstream from here
  286. if (!hasDownstream)
  287. nodeList.Add(node);
  288. }
  289. public static void CollectNodeSet(HashSet<AbstractMaterialNode> nodeSet, MaterialSlot slot)
  290. {
  291. var node = slot.owner;
  292. var graph = node.owner;
  293. foreach (var edge in graph.GetEdges(node.GetSlotReference(slot.id)))
  294. {
  295. var outputNode = edge.outputSlot.node;
  296. if (outputNode != null)
  297. {
  298. CollectNodeSet(nodeSet, outputNode);
  299. }
  300. }
  301. }
  302. public static void CollectNodeSet(HashSet<AbstractMaterialNode> nodeSet, AbstractMaterialNode node)
  303. {
  304. if (!nodeSet.Add(node))
  305. {
  306. return;
  307. }
  308. using (ListPool<MaterialSlot>.Get(out var slots))
  309. {
  310. node.GetInputSlots(slots);
  311. foreach (var slot in slots)
  312. {
  313. CollectNodeSet(nodeSet, slot);
  314. }
  315. }
  316. }
  317. public static void CollectNodesNodeFeedsInto(List<AbstractMaterialNode> nodeList, AbstractMaterialNode node, IncludeSelf includeSelf = IncludeSelf.Include)
  318. {
  319. if (node == null)
  320. return;
  321. if (nodeList.Contains(node))
  322. return;
  323. foreach (var slot in node.GetOutputSlots<MaterialSlot>())
  324. {
  325. foreach (var edge in node.owner.GetEdges(slot.slotReference))
  326. {
  327. var inputNode = edge.inputSlot.node;
  328. CollectNodesNodeFeedsInto(nodeList, inputNode);
  329. }
  330. }
  331. if (includeSelf == IncludeSelf.Include)
  332. nodeList.Add(node);
  333. }
  334. public static string GetDocumentationString(string pageName)
  335. {
  336. return Documentation.GetPageLink(pageName.Replace(" ", "-") + NodeDocSuffix);
  337. }
  338. static Stack<MaterialSlot> s_SlotStack = new Stack<MaterialSlot>();
  339. public static ShaderStage GetEffectiveShaderStage(MaterialSlot initialSlot, bool goingBackwards)
  340. {
  341. var graph = initialSlot.owner.owner;
  342. s_SlotStack.Clear();
  343. s_SlotStack.Push(initialSlot);
  344. while (s_SlotStack.Any())
  345. {
  346. var slot = s_SlotStack.Pop();
  347. ShaderStage stage;
  348. if (slot.stageCapability.TryGetShaderStage(out stage))
  349. return stage;
  350. if (goingBackwards && slot.isInputSlot)
  351. {
  352. foreach (var edge in graph.GetEdges(slot.slotReference))
  353. {
  354. var node = edge.outputSlot.node;
  355. s_SlotStack.Push(node.FindOutputSlot<MaterialSlot>(edge.outputSlot.slotId));
  356. }
  357. }
  358. else if (!goingBackwards && slot.isOutputSlot)
  359. {
  360. foreach (var edge in graph.GetEdges(slot.slotReference))
  361. {
  362. var node = edge.inputSlot.node;
  363. s_SlotStack.Push(node.FindInputSlot<MaterialSlot>(edge.inputSlot.slotId));
  364. }
  365. }
  366. else
  367. {
  368. var ownerSlots = Enumerable.Empty<MaterialSlot>();
  369. if (goingBackwards && slot.isOutputSlot)
  370. ownerSlots = slot.owner.GetInputSlots<MaterialSlot>(slot);
  371. else if (!goingBackwards && slot.isInputSlot)
  372. ownerSlots = slot.owner.GetOutputSlots<MaterialSlot>(slot);
  373. foreach (var ownerSlot in ownerSlots)
  374. s_SlotStack.Push(ownerSlot);
  375. }
  376. }
  377. // We default to fragment shader stage if all connected nodes were compatible with both.
  378. return ShaderStage.Fragment;
  379. }
  380. private static ShaderStageCapability GetEffectiveShaderStageCapabilityRecursive(SlotReference slotRef, bool goingBackwards, Dictionary<SlotReference, ShaderStageCapability> lookUp) {
  381. var slot = slotRef.slot;
  382. ShaderStageCapability capabilities = slot.stageCapability;
  383. // Can early out if we know nothing is compatible, otherwise we have to keep checking everything we can reach.
  384. if (capabilities == ShaderStageCapability.None) {
  385. lookUp[slotRef] = capabilities;
  386. return capabilities;
  387. }
  388. var graph = slot.owner.owner;
  389. if (goingBackwards && slot.isInputSlot)
  390. {
  391. foreach (var edge in graph.GetEdges(slot.slotReference))
  392. {
  393. if (!lookUp.TryGetValue(edge.outputSlot, out var childCapabilities)) {
  394. childCapabilities = GetEffectiveShaderStageCapabilityRecursive(edge.outputSlot, goingBackwards, lookUp);
  395. }
  396. capabilities &= childCapabilities;
  397. if (capabilities == ShaderStageCapability.None)
  398. break;
  399. }
  400. }
  401. else if (!goingBackwards && slot.isOutputSlot)
  402. {
  403. foreach (var edge in graph.GetEdges(slot.slotReference))
  404. {
  405. if (!lookUp.TryGetValue(edge.inputSlot, out var childCapabilities)) {
  406. childCapabilities = GetEffectiveShaderStageCapabilityRecursive(edge.inputSlot, goingBackwards, lookUp);
  407. }
  408. capabilities &= childCapabilities;
  409. if (capabilities == ShaderStageCapability.None)
  410. break;
  411. }
  412. }
  413. else
  414. {
  415. var ownerSlots = Enumerable.Empty<MaterialSlot>();
  416. if (goingBackwards && slot.isOutputSlot)
  417. ownerSlots = slot.owner.GetInputSlots<MaterialSlot>(slot);
  418. else if (!goingBackwards && slot.isInputSlot)
  419. ownerSlots = slot.owner.GetOutputSlots<MaterialSlot>(slot);
  420. foreach (var ownerSlot in ownerSlots) {
  421. var childSlotRef = new SlotReference(ownerSlot.owner, ownerSlot.id);
  422. if (!lookUp.TryGetValue(childSlotRef, out var childCapabilities)) {
  423. childCapabilities = GetEffectiveShaderStageCapabilityRecursive(childSlotRef, goingBackwards, lookUp);
  424. }
  425. capabilities &= childCapabilities;
  426. if (capabilities == ShaderStageCapability.None)
  427. break;
  428. }
  429. }
  430. lookUp[slotRef] = capabilities;
  431. return capabilities;
  432. }
  433. public static ShaderStageCapability GetEffectiveShaderStageCapability(MaterialSlot initialSlot, bool goingBackwards, Dictionary<SlotReference, ShaderStageCapability> lookUp = null)
  434. {
  435. if (lookUp == null)
  436. lookUp = new Dictionary<SlotReference, ShaderStageCapability>();
  437. return GetEffectiveShaderStageCapabilityRecursive(new SlotReference(initialSlot.owner, initialSlot.id), goingBackwards, lookUp);
  438. }
  439. public static string GetSlotDimension(ConcreteSlotValueType slotValue)
  440. {
  441. switch (slotValue)
  442. {
  443. case ConcreteSlotValueType.Vector1:
  444. return String.Empty;
  445. case ConcreteSlotValueType.Vector2:
  446. return "2";
  447. case ConcreteSlotValueType.Vector3:
  448. return "3";
  449. case ConcreteSlotValueType.Vector4:
  450. return "4";
  451. case ConcreteSlotValueType.Matrix2:
  452. return "2x2";
  453. case ConcreteSlotValueType.Matrix3:
  454. return "3x3";
  455. case ConcreteSlotValueType.Matrix4:
  456. return "4x4";
  457. case ConcreteSlotValueType.PropertyConnectionState:
  458. return String.Empty;
  459. default:
  460. return "Error";
  461. }
  462. }
  463. // NOTE: there are several bugs here.. we should use ConvertToValidHLSLIdentifier() instead
  464. public static string GetHLSLSafeName(string input)
  465. {
  466. char[] arr = input.ToCharArray();
  467. arr = Array.FindAll<char>(arr, (c => (Char.IsLetterOrDigit(c))));
  468. var safeName = new string(arr);
  469. if (safeName.Length > 1 && char.IsDigit(safeName[0]))
  470. {
  471. safeName = $"var{safeName}";
  472. }
  473. return safeName;
  474. }
  475. static readonly string[] k_HLSLNumericKeywords =
  476. {
  477. "float",
  478. "half", // not technically in HLSL spec, but prob should be
  479. "real", // Unity thing, but included here
  480. "int",
  481. "uint",
  482. "bool",
  483. "min10float",
  484. "min16float",
  485. "min12int",
  486. "min16int",
  487. "min16uint"
  488. };
  489. static readonly string[] k_HLSLNumericKeywordSuffixes =
  490. {
  491. "",
  492. "1", "2", "3", "4",
  493. "1x1", "1x2", "1x3", "1x4",
  494. "2x1", "2x2", "2x3", "2x4",
  495. "3x1", "3x2", "3x3", "3x4",
  496. "4x1", "4x2", "4x3", "4x4"
  497. };
  498. static HashSet<string> m_ShaderLabKeywords = new HashSet<string>()
  499. {
  500. // these should all be lowercase, as shaderlab keywords are case insensitive
  501. "properties",
  502. "range",
  503. "bind",
  504. "bindchannels",
  505. "tags",
  506. "lod",
  507. "shader",
  508. "subshader",
  509. "category",
  510. "fallback",
  511. "dependency",
  512. "customeditor",
  513. "rect",
  514. "any",
  515. "float",
  516. "color",
  517. "int",
  518. "integer",
  519. "vector",
  520. "matrix",
  521. "2d",
  522. "cube",
  523. "3d",
  524. "2darray",
  525. "cubearray",
  526. "name",
  527. "settexture",
  528. "true",
  529. "false",
  530. "on",
  531. "off",
  532. "separatespecular",
  533. "offset",
  534. "zwrite",
  535. "zclip",
  536. "conservative",
  537. "ztest",
  538. "alphatest",
  539. "fog",
  540. "stencil",
  541. "colormask",
  542. "alphatomask",
  543. "cull",
  544. "front",
  545. "material",
  546. "ambient",
  547. "diffuse",
  548. "specular",
  549. "emission",
  550. "shininess",
  551. "blend",
  552. "blendop",
  553. "colormaterial",
  554. "lighting",
  555. "pass",
  556. "grabpass",
  557. "usepass",
  558. "gpuprogramid",
  559. "add",
  560. "sub",
  561. "revsub",
  562. "min",
  563. "max",
  564. "logicalclear",
  565. "logicalset",
  566. "logicalcopy",
  567. "logicalcopyinverted",
  568. "logicalnoop",
  569. "logicalinvert",
  570. "logicaland",
  571. "logicalnand",
  572. "logicalor",
  573. "logicalnor",
  574. "logicalxor",
  575. "logicalequiv",
  576. "logicalandreverse",
  577. "logicalandinverted",
  578. "logicalorreverse",
  579. "logicalorinverted",
  580. "multiply",
  581. "screen",
  582. "overlay",
  583. "darken",
  584. "lighten",
  585. "colordodge",
  586. "colorburn",
  587. "hardlight",
  588. "softlight",
  589. "difference",
  590. "exclusion",
  591. "hslhue",
  592. "hslsaturation",
  593. "hslcolor",
  594. "hslluminosity",
  595. "zero",
  596. "one",
  597. "dstcolor",
  598. "srccolor",
  599. "oneminusdstcolor",
  600. "srcalpha",
  601. "oneminussrccolor",
  602. "dstalpha",
  603. "oneminusdstalpha",
  604. "srcalphasaturate",
  605. "oneminussrcalpha",
  606. "constantcolor",
  607. "oneminusconstantcolor",
  608. "constantalpha",
  609. "oneminusconstantalpha",
  610. };
  611. static HashSet<string> m_HLSLKeywords = new HashSet<string>()
  612. {
  613. "AppendStructuredBuffer",
  614. "asm",
  615. "asm_fragment",
  616. "auto",
  617. "BlendState",
  618. "break",
  619. "Buffer",
  620. "ByteAddressBuffer",
  621. "case",
  622. "catch",
  623. "cbuffer",
  624. "centroid",
  625. "char",
  626. "class",
  627. "column_major",
  628. "compile",
  629. "compile_fragment",
  630. "CompileShader",
  631. "const",
  632. "const_cast",
  633. "continue",
  634. "ComputeShader",
  635. "ConsumeStructuredBuffer",
  636. "default",
  637. "delete",
  638. "DepthStencilState",
  639. "DepthStencilView",
  640. "discard",
  641. "do",
  642. "double",
  643. "DomainShader",
  644. "dynamic_cast",
  645. "dword",
  646. "else",
  647. "enum",
  648. "explicit",
  649. "export",
  650. "extern",
  651. "false",
  652. "for",
  653. "friend",
  654. "fxgroup",
  655. "GeometryShader",
  656. "goto",
  657. "groupshared",
  658. "half",
  659. "Hullshader",
  660. "if",
  661. "in",
  662. "inline",
  663. "inout",
  664. "InputPatch",
  665. "interface",
  666. "line",
  667. "lineadj",
  668. "linear",
  669. "LineStream",
  670. "long",
  671. "matrix",
  672. "mutable",
  673. "namespace",
  674. "new",
  675. "nointerpolation",
  676. "noperspective",
  677. "NULL",
  678. "operator",
  679. "out",
  680. "OutputPatch",
  681. "packoffset",
  682. "pass",
  683. "pixelfragment",
  684. "PixelShader",
  685. "point",
  686. "PointStream",
  687. "precise",
  688. "private",
  689. "protected",
  690. "public",
  691. "RasterizerState",
  692. "reinterpret_cast",
  693. "RenderTargetView",
  694. "return",
  695. "register",
  696. "row_major",
  697. "RWBuffer",
  698. "RWByteAddressBuffer",
  699. "RWStructuredBuffer",
  700. "RWTexture1D",
  701. "RWTexture1DArray",
  702. "RWTexture2D",
  703. "RWTexture2DArray",
  704. "RWTexture3D",
  705. "sample",
  706. "sampler",
  707. "SamplerState",
  708. "SamplerComparisonState",
  709. "shared",
  710. "short",
  711. "signed",
  712. "sizeof",
  713. "snorm",
  714. "stateblock",
  715. "stateblock_state",
  716. "static",
  717. "static_cast",
  718. "string",
  719. "struct",
  720. "switch",
  721. "StructuredBuffer",
  722. "tbuffer",
  723. "technique",
  724. "technique10",
  725. "technique11",
  726. "template",
  727. "texture",
  728. "Texture1D",
  729. "Texture1DArray",
  730. "Texture2D",
  731. "Texture2DArray",
  732. "Texture2DMS",
  733. "Texture2DMSArray",
  734. "Texture3D",
  735. "TextureCube",
  736. "TextureCubeArray",
  737. "this",
  738. "throw",
  739. "true",
  740. "try",
  741. "typedef",
  742. "typename",
  743. "triangle",
  744. "triangleadj",
  745. "TriangleStream",
  746. "uniform",
  747. "unorm",
  748. "union",
  749. "unsigned",
  750. "using",
  751. "vector",
  752. "vertexfragment",
  753. "VertexShader",
  754. "virtual",
  755. "void",
  756. "volatile",
  757. "while"
  758. };
  759. static HashSet<string> m_ShaderGraphKeywords = new HashSet<string>()
  760. {
  761. "Gradient",
  762. "UnitySamplerState",
  763. "UnityTexture2D",
  764. "UnityTexture2DArray",
  765. "UnityTexture3D",
  766. "UnityTextureCube"
  767. };
  768. static bool m_HLSLKeywordDictionaryBuilt = false;
  769. public static bool IsHLSLKeyword(string id)
  770. {
  771. if (!m_HLSLKeywordDictionaryBuilt)
  772. {
  773. foreach (var numericKeyword in k_HLSLNumericKeywords)
  774. foreach (var suffix in k_HLSLNumericKeywordSuffixes)
  775. m_HLSLKeywords.Add(numericKeyword + suffix);
  776. m_HLSLKeywordDictionaryBuilt = true;
  777. }
  778. bool isHLSLKeyword = m_HLSLKeywords.Contains(id);
  779. return isHLSLKeyword;
  780. }
  781. public static bool IsShaderLabKeyWord(string id)
  782. {
  783. bool isShaderLabKeyword = m_ShaderLabKeywords.Contains(id.ToLower());
  784. return isShaderLabKeyword;
  785. }
  786. public static bool IsShaderGraphKeyWord(string id)
  787. {
  788. bool isShaderGraphKeyword = m_ShaderGraphKeywords.Contains(id);
  789. return isShaderGraphKeyword;
  790. }
  791. public static string ConvertToValidHLSLIdentifier(string originalId, Func<string, bool> isDisallowedIdentifier = null)
  792. {
  793. // Converts " 1 var * q-30 ( 0 ) (1) " to "_1_var_q_30_0_1"
  794. if (originalId == null)
  795. originalId = "";
  796. var result = Regex.Replace(originalId, @"^[^A-Za-z0-9_]+|[^A-Za-z0-9_]+$", ""); // trim leading/trailing bad characters (excl '_').
  797. result = Regex.Replace(result, @"[^A-Za-z0-9]+", "_"); // replace sequences of bad characters with underscores (incl '_').
  798. if (result.Length == 0 || Char.IsDigit(result[0]) || IsHLSLKeyword(result) || (isDisallowedIdentifier?.Invoke(result) ?? false))
  799. result = "_" + result;
  800. return result;
  801. }
  802. private static string GetDisplaySafeName(string input)
  803. {
  804. //strip valid display characters from slot name
  805. //current valid characters are whitespace and ( ) _ separators
  806. StringBuilder cleanName = new StringBuilder();
  807. foreach (var c in input)
  808. {
  809. if (c != ' ' && c != '(' && c != ')' && c != '_')
  810. cleanName.Append(c);
  811. }
  812. return cleanName.ToString();
  813. }
  814. public static bool ValidateSlotName(string inName, out string errorMessage)
  815. {
  816. //check for invalid characters between display safe and hlsl safe name
  817. if (GetDisplaySafeName(inName) != GetHLSLSafeName(inName) && GetDisplaySafeName(inName) != ConvertToValidHLSLIdentifier(inName))
  818. {
  819. errorMessage = "Slot name(s) found invalid character(s). Valid characters: A-Z, a-z, 0-9, _ ( ) ";
  820. return true;
  821. }
  822. //if clean, return null and false
  823. errorMessage = null;
  824. return false;
  825. }
  826. public static string FloatToShaderValue(float value)
  827. {
  828. if (Single.IsPositiveInfinity(value))
  829. {
  830. return "1.#INF";
  831. }
  832. if (Single.IsNegativeInfinity(value))
  833. {
  834. return "-1.#INF";
  835. }
  836. if (Single.IsNaN(value))
  837. {
  838. return "NAN";
  839. }
  840. return value.ToString(CultureInfo.InvariantCulture);
  841. }
  842. // A number large enough to become Infinity (~FLOAT_MAX_VALUE * 10) + explanatory comment
  843. private const string k_ShaderLabInfinityAlternatrive = "3402823500000000000000000000000000000000 /* Infinity */";
  844. // ShaderLab doesn't support Scientific Notion nor Infinity. To stop from generating a broken shader we do this.
  845. public static string FloatToShaderValueShaderLabSafe(float value)
  846. {
  847. if (Single.IsPositiveInfinity(value))
  848. {
  849. return k_ShaderLabInfinityAlternatrive;
  850. }
  851. if (Single.IsNegativeInfinity(value))
  852. {
  853. return "-" + k_ShaderLabInfinityAlternatrive;
  854. }
  855. if (Single.IsNaN(value))
  856. {
  857. return "NAN"; // A real error has occured, in this case we should break the shader.
  858. }
  859. // For single point precision, reserve 54 spaces (e-45 min + ~9 digit precision). See floating-point-numeric-types (Microsoft docs).
  860. return value.ToString("0.######################################################", CultureInfo.InvariantCulture);
  861. }
  862. }
  863. }