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