A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Linq.Expressions;
5using System.Reflection;
6using System.Text.RegularExpressions;
7using UnityEngine;
8using UnityEditor.Graphing;
9using UnityEditor.Graphing.Util;
10using UnityEditor.Rendering;
11using UnityEditor.ShaderGraph.Internal;
12using UnityEditor.ShaderGraph.Legacy;
13using UnityEditor.ShaderGraph.Serialization;
14using UnityEditor.ShaderGraph.Drawing;
15using Edge = UnityEditor.Graphing.Edge;
16
17using UnityEngine.UIElements;
18using UnityEngine.Assertions;
19using UnityEngine.Pool;
20using UnityEngine.Serialization;
21
22namespace UnityEditor.ShaderGraph
23{
24 [Serializable]
25 [FormerName("UnityEditor.ShaderGraph.MaterialGraph")]
26 [FormerName("UnityEditor.ShaderGraph.SubGraph")]
27 [FormerName("UnityEditor.ShaderGraph.AbstractMaterialGraph")]
28 sealed partial class GraphData : JsonObject
29 {
30 public override int latestVersion => 3;
31
32 public GraphObject owner { get; set; }
33
34 [NonSerialized]
35 internal bool graphIsConcretizing = false;
36
37 #region Input data
38
39 [SerializeField]
40 List<JsonData<AbstractShaderProperty>> m_Properties = new List<JsonData<AbstractShaderProperty>>();
41
42 public DataValueEnumerable<AbstractShaderProperty> properties => m_Properties.SelectValue();
43
44 [SerializeField]
45 List<JsonData<ShaderKeyword>> m_Keywords = new List<JsonData<ShaderKeyword>>();
46
47 public DataValueEnumerable<ShaderKeyword> keywords => m_Keywords.SelectValue();
48
49 [SerializeField]
50 List<JsonData<ShaderDropdown>> m_Dropdowns = new List<JsonData<ShaderDropdown>>();
51
52 public DataValueEnumerable<ShaderDropdown> dropdowns => m_Dropdowns.SelectValue();
53
54 [NonSerialized]
55 List<ShaderInput> m_AddedInputs = new List<ShaderInput>();
56
57 public IEnumerable<ShaderInput> addedInputs
58 {
59 get { return m_AddedInputs; }
60 }
61
62 [NonSerialized]
63 List<ShaderInput> m_RemovedInputs = new List<ShaderInput>();
64
65 public IEnumerable<ShaderInput> removedInputs
66 {
67 get { return m_RemovedInputs; }
68 }
69
70 [NonSerialized]
71 List<ShaderInput> m_MovedInputs = new List<ShaderInput>();
72
73 public IEnumerable<ShaderInput> movedInputs
74 {
75 get { return m_MovedInputs; }
76 }
77
78 [NonSerialized]
79 List<CategoryData> m_AddedCategories = new List<CategoryData>();
80
81 public IEnumerable<CategoryData> addedCategories
82 {
83 get { return m_AddedCategories; }
84 }
85
86 [NonSerialized]
87 List<CategoryData> m_RemovedCategories = new List<CategoryData>();
88
89 public IEnumerable<CategoryData> removedCategories
90 {
91 get { return m_RemovedCategories; }
92 }
93
94 [NonSerialized]
95 List<CategoryData> m_MovedCategories = new List<CategoryData>();
96
97 public IEnumerable<CategoryData> movedCategories
98 {
99 get { return m_MovedCategories; }
100 }
101
102 [NonSerialized]
103 bool m_MovedContexts = false;
104 public bool movedContexts => m_MovedContexts;
105
106 public string assetGuid { get; set; }
107
108 #endregion
109
110 #region Category Data
111
112 [SerializeField]
113 List<JsonData<CategoryData>> m_CategoryData = new List<JsonData<CategoryData>>();
114
115 public DataValueEnumerable<CategoryData> categories => m_CategoryData.SelectValue();
116
117 #endregion
118
119 #region Node data
120
121 [SerializeField]
122 List<JsonData<AbstractMaterialNode>> m_Nodes = new List<JsonData<AbstractMaterialNode>>();
123
124 [NonSerialized]
125 Dictionary<string, AbstractMaterialNode> m_NodeDictionary = new Dictionary<string, AbstractMaterialNode>();
126
127 [NonSerialized]
128 Dictionary<string, AbstractMaterialNode> m_LegacyUpdateDictionary = new Dictionary<string, AbstractMaterialNode>();
129
130 public IEnumerable<T> GetNodes<T>()
131 {
132 return m_Nodes.SelectValue().OfType<T>();
133 }
134
135 [NonSerialized]
136 List<AbstractMaterialNode> m_AddedNodes = new List<AbstractMaterialNode>();
137
138 public IEnumerable<AbstractMaterialNode> addedNodes
139 {
140 get { return m_AddedNodes; }
141 }
142
143 [NonSerialized]
144 List<AbstractMaterialNode> m_RemovedNodes = new List<AbstractMaterialNode>();
145
146 public IEnumerable<AbstractMaterialNode> removedNodes
147 {
148 get { return m_RemovedNodes; }
149 }
150
151 [NonSerialized]
152 List<AbstractMaterialNode> m_PastedNodes = new List<AbstractMaterialNode>();
153
154 public IEnumerable<AbstractMaterialNode> pastedNodes
155 {
156 get { return m_PastedNodes; }
157 }
158 #endregion
159
160 #region Group Data
161
162 [SerializeField]
163 List<JsonData<GroupData>> m_GroupDatas = new List<JsonData<GroupData>>();
164
165 public DataValueEnumerable<GroupData> groups
166 {
167 get { return m_GroupDatas.SelectValue(); }
168 }
169
170 [NonSerialized]
171 List<GroupData> m_AddedGroups = new List<GroupData>();
172
173 public IEnumerable<GroupData> addedGroups
174 {
175 get { return m_AddedGroups; }
176 }
177
178 [NonSerialized]
179 List<GroupData> m_RemovedGroups = new List<GroupData>();
180
181 public IEnumerable<GroupData> removedGroups
182 {
183 get { return m_RemovedGroups; }
184 }
185
186 [NonSerialized]
187 List<GroupData> m_PastedGroups = new List<GroupData>();
188
189 public IEnumerable<GroupData> pastedGroups
190 {
191 get { return m_PastedGroups; }
192 }
193
194 [NonSerialized]
195 List<ParentGroupChange> m_ParentGroupChanges = new List<ParentGroupChange>();
196
197 public IEnumerable<ParentGroupChange> parentGroupChanges
198 {
199 get { return m_ParentGroupChanges; }
200 }
201
202 [NonSerialized]
203 GroupData m_MostRecentlyCreatedGroup;
204
205 public GroupData mostRecentlyCreatedGroup => m_MostRecentlyCreatedGroup;
206
207 [NonSerialized]
208 Dictionary<JsonRef<GroupData>, List<IGroupItem>> m_GroupItems = new Dictionary<JsonRef<GroupData>, List<IGroupItem>>();
209
210 public IEnumerable<IGroupItem> GetItemsInGroup(GroupData groupData)
211 {
212 if (m_GroupItems.TryGetValue(groupData, out var nodes))
213 {
214 return nodes;
215 }
216 return Enumerable.Empty<IGroupItem>();
217 }
218
219 #endregion
220
221 #region StickyNote Data
222 [SerializeField]
223 List<JsonData<StickyNoteData>> m_StickyNoteDatas = new List<JsonData<StickyNoteData>>();
224
225 public DataValueEnumerable<StickyNoteData> stickyNotes => m_StickyNoteDatas.SelectValue();
226
227 [NonSerialized]
228 List<StickyNoteData> m_AddedStickyNotes = new List<StickyNoteData>();
229
230 public List<StickyNoteData> addedStickyNotes => m_AddedStickyNotes;
231
232 [NonSerialized]
233 List<StickyNoteData> m_RemovedNotes = new List<StickyNoteData>();
234
235 public IEnumerable<StickyNoteData> removedNotes => m_RemovedNotes;
236
237 [NonSerialized]
238 List<StickyNoteData> m_PastedStickyNotes = new List<StickyNoteData>();
239
240 public IEnumerable<StickyNoteData> pastedStickyNotes => m_PastedStickyNotes;
241
242 #endregion
243
244 #region Edge data
245
246 [SerializeField]
247 List<Edge> m_Edges = new List<Edge>();
248
249 public IEnumerable<Edge> edges => m_Edges;
250
251 [NonSerialized]
252 Dictionary<string, List<IEdge>> m_NodeEdges = new Dictionary<string, List<IEdge>>();
253
254 [NonSerialized]
255 List<IEdge> m_AddedEdges = new List<IEdge>();
256
257 public IEnumerable<IEdge> addedEdges
258 {
259 get { return m_AddedEdges; }
260 }
261
262 [NonSerialized]
263 List<IEdge> m_RemovedEdges = new List<IEdge>();
264
265 public IEnumerable<IEdge> removedEdges
266 {
267 get { return m_RemovedEdges; }
268 }
269
270 #endregion
271
272 #region Context Data
273
274 [SerializeField]
275 ContextData m_VertexContext;
276
277 [SerializeField]
278 ContextData m_FragmentContext;
279
280 // We build this once and cache it as it uses reflection
281 // This list is used to build the Create Node menu entries for Blocks
282 // as well as when deserializing descriptor fields on serialized Blocks
283 [NonSerialized]
284 List<BlockFieldDescriptor> m_BlockFieldDescriptors;
285
286 public ContextData vertexContext => m_VertexContext;
287 public ContextData fragmentContext => m_FragmentContext;
288 public List<BlockFieldDescriptor> blockFieldDescriptors => m_BlockFieldDescriptors;
289
290 #endregion
291
292 [SerializeField]
293 InspectorPreviewData m_PreviewData = new InspectorPreviewData();
294
295 public InspectorPreviewData previewData
296 {
297 get { return m_PreviewData; }
298 set { m_PreviewData = value; }
299 }
300
301 [SerializeField]
302 string m_Path;
303
304 public string path
305 {
306 get { return m_Path; }
307 set
308 {
309 if (m_Path == value)
310 return;
311 m_Path = value;
312 if (owner != null)
313 owner.RegisterCompleteObjectUndo("Change Path");
314 }
315 }
316
317 public MessageManager messageManager { get; set; }
318 public bool isSubGraph { get; set; }
319
320 // we default this to Graph for subgraphs
321 // but for shadergraphs, this will get replaced with Single
322 [SerializeField]
323 private GraphPrecision m_GraphPrecision = GraphPrecision.Graph;
324
325 public GraphPrecision graphDefaultPrecision
326 {
327 get
328 {
329 // shader graphs are not allowed to have graph precision
330 // we force them to Single if they somehow get set to graph
331 if ((!isSubGraph) && (m_GraphPrecision == GraphPrecision.Graph))
332 return GraphPrecision.Single;
333
334 return m_GraphPrecision;
335 }
336 }
337
338 public ConcretePrecision graphDefaultConcretePrecision
339 {
340 get
341 {
342 // when in "Graph switchable" mode, we choose Half as the default concrete precision
343 // so you can visualize the worst-case
344 return graphDefaultPrecision.ToConcrete(ConcretePrecision.Half);
345 }
346 }
347
348 // Some state has been changed that requires checking for the auto add/removal of blocks.
349 // This needs to be checked at a later point in time so actions like replace (remove + add) don't remove blocks.
350 internal bool checkAutoAddRemoveBlocks { get; set; }
351
352 public void SetGraphDefaultPrecision(GraphPrecision newGraphDefaultPrecision)
353 {
354 if ((!isSubGraph) && (newGraphDefaultPrecision == GraphPrecision.Graph))
355 {
356 // shader graphs can't be set to "Graph", only subgraphs can
357 Debug.LogError("Cannot set ShaderGraph to a default precision of Graph");
358 }
359 else
360 {
361 m_GraphPrecision = newGraphDefaultPrecision;
362 }
363 }
364
365 // NOTE: having preview mode default to 3D preserves the old behavior of pre-existing subgraphs
366 // if we change this, we would have to introduce a versioning step if we want to maintain the old behavior
367 [SerializeField]
368 private PreviewMode m_PreviewMode = PreviewMode.Preview3D;
369
370 public PreviewMode previewMode
371 {
372 get => m_PreviewMode;
373 set => m_PreviewMode = value;
374 }
375
376 [SerializeField]
377 JsonRef<AbstractMaterialNode> m_OutputNode;
378
379 public AbstractMaterialNode outputNode
380 {
381 get => m_OutputNode;
382 set => m_OutputNode = value;
383 }
384
385 internal delegate void SaveGraphDelegate(Shader shader, object context);
386 internal static SaveGraphDelegate onSaveGraph;
387
388
389 #region SubData
390
391 [SerializeField]
392 internal List<JsonData<AbstractShaderGraphDataExtension>> m_SubDatas = new List<JsonData<AbstractShaderGraphDataExtension>>();
393 public DataValueEnumerable<AbstractShaderGraphDataExtension> SubDatas => m_SubDatas.SelectValue();
394
395 #endregion
396
397 #region Targets
398
399 // Serialized list of user-selected active targets, sorted in displayName order (to maintain deterministic serialization order)
400 // some of these may be MultiJsonInternal.UnknownTargetType if we can't recognize the type of the target
401 [SerializeField]
402 internal List<JsonData<Target>> m_ActiveTargets = new List<JsonData<Target>>(); // After adding to this list, you MUST call SortActiveTargets()
403 public DataValueEnumerable<Target> activeTargets => m_ActiveTargets.SelectValue();
404
405 // this stores all of the current possible Target types (including any unknown target types we serialized in)
406 class PotentialTarget
407 {
408 // the potential Target
409 Target m_Target;
410
411 // a Target is either known (we know the Type) or unknown (can't find a matching definition of the Type)
412 // Targets of unknown type are stored in an UnknownTargetType
413 private Type m_KnownType;
414 private MultiJsonInternal.UnknownTargetType m_UnknownTarget;
415
416 public PotentialTarget(Target target)
417 {
418 m_Target = target;
419
420 if (target is MultiJsonInternal.UnknownTargetType)
421 {
422 m_UnknownTarget = (MultiJsonInternal.UnknownTargetType)target;
423 m_KnownType = null;
424 }
425 else
426 {
427 m_UnknownTarget = null;
428 m_KnownType = target.GetType();
429 }
430 }
431
432 public bool IsUnknown()
433 {
434 return m_UnknownTarget != null;
435 }
436
437 public MultiJsonInternal.UnknownTargetType GetUnknown()
438 {
439 return m_UnknownTarget;
440 }
441
442 public Type knownType { get { return m_KnownType; } }
443
444 public bool Is(Target t)
445 {
446 return t == m_Target;
447 }
448
449 public string GetDisplayName()
450 {
451 return m_Target.displayName;
452 }
453
454 public void ReplaceStoredTarget(Target t)
455 {
456 if (m_KnownType != null)
457 Assert.IsTrue(t.GetType() == m_KnownType);
458 m_Target = t;
459 }
460
461 public Target GetTarget()
462 {
463 return m_Target;
464 }
465 }
466 [NonSerialized]
467 List<PotentialTarget> m_AllPotentialTargets = new List<PotentialTarget>();
468 public IEnumerable<Target> allPotentialTargets => m_AllPotentialTargets.Select(x => x.GetTarget());
469
470 public int GetTargetIndexByKnownType(Type targetType)
471 {
472 return m_AllPotentialTargets.FindIndex(pt => pt.knownType == targetType);
473 }
474
475 public int GetTargetIndex(Target t)
476 {
477 int result = m_AllPotentialTargets.FindIndex(pt => pt.Is(t));
478 return result;
479 }
480
481 public IEnumerable<Target> GetValidTargets()
482 {
483 foreach (var potentialTarget in m_AllPotentialTargets)
484 {
485 var target = potentialTarget.GetTarget();
486 if (target is IMayObsolete targetObsolete && targetObsolete.IsObsolete())
487 continue;
488 yield return potentialTarget.GetTarget();
489 }
490 }
491
492 public void SetTargetActive(Target target, bool skipSortAndUpdate = false)
493 {
494 int activeIndex = m_ActiveTargets.IndexOf(target);
495 if (activeIndex < 0)
496 {
497 activeIndex = m_ActiveTargets.Count;
498 m_ActiveTargets.Add(target);
499 }
500
501 // active known targets should replace the stored Target in AllPotentialTargets
502 if (target is MultiJsonInternal.UnknownTargetType unknownTarget)
503 {
504 // find any existing potential target with the same unknown jsonData
505 int targetIndex = m_AllPotentialTargets.FindIndex(
506 pt => pt.IsUnknown() && (pt.GetUnknown().jsonData == unknownTarget.jsonData));
507
508 // replace existing target, or add it if there is none
509 if (targetIndex >= 0)
510 m_AllPotentialTargets[targetIndex] = new PotentialTarget(target);
511 else
512 m_AllPotentialTargets.Add(new PotentialTarget(target));
513 }
514 else
515 {
516 // known types should already have been registered
517 Type targetType = target.GetType();
518 int targetIndex = GetTargetIndexByKnownType(targetType);
519 Assert.IsTrue(targetIndex >= 0);
520 m_AllPotentialTargets[targetIndex].ReplaceStoredTarget(target);
521 }
522
523 if (!skipSortAndUpdate)
524 SortAndUpdateActiveTargets();
525 }
526
527 public void SetTargetActive(int targetIndex, bool skipSortAndUpdate = false)
528 {
529 Target target = m_AllPotentialTargets[targetIndex].GetTarget();
530 SetTargetActive(target, skipSortAndUpdate);
531 }
532
533 public void SetTargetInactive(Target target, bool skipSortAndUpdate = false)
534 {
535 int activeIndex = m_ActiveTargets.IndexOf(target);
536 if (activeIndex < 0)
537 return;
538
539 int targetIndex = GetTargetIndex(target);
540
541 // if a target was in the active targets, it should also have been in the potential targets list
542 Assert.IsTrue(targetIndex >= 0);
543
544 m_ActiveTargets.RemoveAt(activeIndex);
545
546 if (!skipSortAndUpdate)
547 SortAndUpdateActiveTargets();
548 }
549
550 // this list is populated by graph validation, and lists all of the targets that nodes did not like
551 [NonSerialized]
552 List<Target> m_UnsupportedTargets = new List<Target>();
553 public List<Target> unsupportedTargets { get => m_UnsupportedTargets; }
554
555 private Comparison<Target> targetComparison = new Comparison<Target>((a, b) => string.Compare(a.displayName, b.displayName));
556 public void SortActiveTargets()
557 {
558 activeTargets.Sort(targetComparison);
559 }
560
561 // TODO: Need a better way to handle this
562 public bool hasVFXCompatibleTarget => activeTargets.Any(o => o.SupportsVFX());
563#if VFX_GRAPH_10_0_0_OR_NEWER
564 public bool hasVFXTarget
565 {
566 get
567 {
568 bool supports = true;
569 supports &= !isSubGraph;
570 supports &= activeTargets.Any();
571 // Maintain support for VFXTarget and VFX compatible targets.
572 supports &= activeTargets.OfType<VFXTarget>().Any() || hasVFXCompatibleTarget;
573 return supports;
574 }
575 }
576
577 public bool isOnlyVFXTarget => activeTargets.Count() == 1 &&
578 activeTargets.Count(t => t is VFXTarget) == 1;
579#else
580 public bool isVFXTarget => false;
581 public bool isOnlyVFXTarget => false;
582#endif
583 #endregion
584
585 public GraphData()
586 {
587 m_GroupItems[null] = new List<IGroupItem>();
588 GetBlockFieldDescriptors();
589 AddKnownTargetsToPotentialTargets();
590 }
591
592 // used to initialize the graph with targets, i.e. when creating new graphs via the popup menu
593 public void InitializeOutputs(Target[] targets, BlockFieldDescriptor[] blockDescriptors)
594 {
595 if (targets == null)
596 return;
597
598 foreach (var target in targets)
599 {
600 if (GetTargetIndexByKnownType(target.GetType()) >= 0)
601 {
602 SetTargetActive(target, true);
603 }
604 }
605 SortActiveTargets();
606
607 if (blockDescriptors != null)
608 {
609 foreach (var descriptor in blockDescriptors)
610 {
611 var contextData = descriptor.shaderStage == ShaderStage.Fragment ? m_FragmentContext : m_VertexContext;
612 var block = (BlockNode)Activator.CreateInstance(typeof(BlockNode));
613 block.Init(descriptor);
614 AddBlockNoValidate(block, contextData, contextData.blocks.Count);
615 }
616 }
617
618 ValidateGraph();
619 var activeBlocks = GetActiveBlocksForAllActiveTargets();
620 UpdateActiveBlocks(activeBlocks);
621 }
622
623 void GetBlockFieldDescriptors()
624 {
625 m_BlockFieldDescriptors = new List<BlockFieldDescriptor>();
626
627 var asmTypes = TypeCache.GetTypesWithAttribute<GenerateBlocksAttribute>();
628 foreach (var type in asmTypes)
629 {
630 var attrs = type.GetCustomAttributes(typeof(GenerateBlocksAttribute), false);
631 if (attrs == null || attrs.Length <= 0)
632 continue;
633
634 var attribute = attrs[0] as GenerateBlocksAttribute;
635
636 // Get all fields that are BlockFieldDescriptor
637 // If field and context stages match add to list
638 foreach (var fieldInfo in type.GetFields())
639 {
640 if (fieldInfo.GetValue(type) is BlockFieldDescriptor blockFieldDescriptor)
641 {
642 blockFieldDescriptor.path = attribute.path;
643 m_BlockFieldDescriptors.Add(blockFieldDescriptor);
644 }
645 }
646 }
647 }
648
649 void AddKnownTargetsToPotentialTargets()
650 {
651 Assert.AreEqual(m_AllPotentialTargets.Count, 0);
652
653 // Find all valid Targets by looking in the TypeCache
654 var targetTypes = TypeCache.GetTypesDerivedFrom<Target>();
655 foreach (var type in targetTypes)
656 {
657 if (type.IsAbstract || type.IsGenericType || !type.IsClass)
658 continue;
659
660 // create a new instance of the Target, to represent the potential Target
661 // NOTE: this instance may be replaced later if we serialize in an Active Target of that type
662 var target = (Target)Activator.CreateInstance(type);
663 if (!target.isHidden)
664 {
665 m_AllPotentialTargets.Add(new PotentialTarget(target));
666 }
667 }
668 }
669
670 public void SortAndUpdateActiveTargets()
671 {
672 SortActiveTargets();
673 ValidateGraph();
674 NodeUtils.ReevaluateActivityOfNodeList(m_Nodes.SelectValue());
675 }
676
677 public void ClearChanges()
678 {
679 m_AddedNodes.Clear();
680 m_RemovedNodes.Clear();
681 m_PastedNodes.Clear();
682 m_ParentGroupChanges.Clear();
683 m_AddedGroups.Clear();
684 m_RemovedGroups.Clear();
685 m_PastedGroups.Clear();
686 m_AddedEdges.Clear();
687 m_RemovedEdges.Clear();
688 m_AddedInputs.Clear();
689 m_RemovedInputs.Clear();
690 m_MovedInputs.Clear();
691 m_AddedCategories.Clear();
692 m_RemovedCategories.Clear();
693 m_MovedCategories.Clear();
694 m_AddedStickyNotes.Clear();
695 m_RemovedNotes.Clear();
696 m_PastedStickyNotes.Clear();
697 m_MostRecentlyCreatedGroup = null;
698 m_MovedContexts = false;
699 }
700
701 public void AddNode(AbstractMaterialNode node)
702 {
703 if (node is AbstractMaterialNode materialNode)
704 {
705 if (isSubGraph && !materialNode.allowedInSubGraph)
706 {
707 Debug.LogWarningFormat("Attempting to add {0} to Sub Graph. This is not allowed.", materialNode.GetType());
708 return;
709 }
710
711 AddNodeNoValidate(materialNode);
712
713 // If adding a Sub Graph node whose asset contains Keywords
714 // Need to restest Keywords against the variant limit
715 if (node is SubGraphNode subGraphNode &&
716 subGraphNode.asset != null &&
717 subGraphNode.asset.keywords.Any())
718 {
719 OnKeywordChangedNoValidate();
720 }
721
722 ValidateGraph();
723 }
724 else
725 {
726 Debug.LogWarningFormat("Trying to add node {0} to Material graph, but it is not a {1}", node, typeof(AbstractMaterialNode));
727 }
728 }
729
730 public void CreateGroup(GroupData groupData)
731 {
732 if (AddGroup(groupData))
733 {
734 m_MostRecentlyCreatedGroup = groupData;
735 }
736 }
737
738 bool AddGroup(GroupData groupData)
739 {
740 if (m_GroupDatas.Contains(groupData))
741 return false;
742
743 m_GroupDatas.Add(groupData);
744 m_AddedGroups.Add(groupData);
745 m_GroupItems.Add(groupData, new List<IGroupItem>());
746
747 return true;
748 }
749
750 public void RemoveGroup(GroupData groupData)
751 {
752 RemoveGroupNoValidate(groupData);
753 ValidateGraph();
754 }
755
756 void RemoveGroupNoValidate(GroupData group)
757 {
758 if (!m_GroupDatas.Contains(group))
759 throw new InvalidOperationException("Cannot remove a group that doesn't exist.");
760 m_GroupDatas.Remove(group);
761 m_RemovedGroups.Add(group);
762
763 if (m_GroupItems.TryGetValue(group, out var items))
764 {
765 foreach (IGroupItem groupItem in items.ToList())
766 {
767 SetGroup(groupItem, null);
768 }
769
770 m_GroupItems.Remove(group);
771 }
772 }
773
774 public void AddStickyNote(StickyNoteData stickyNote)
775 {
776 if (m_StickyNoteDatas.Contains(stickyNote))
777 {
778 throw new InvalidOperationException("Sticky note has already been added to the graph.");
779 }
780
781 if (!m_GroupItems.ContainsKey(stickyNote.group))
782 {
783 throw new InvalidOperationException("Trying to add sticky note with group that doesn't exist.");
784 }
785
786 m_StickyNoteDatas.Add(stickyNote);
787 m_AddedStickyNotes.Add(stickyNote);
788 m_GroupItems[stickyNote.group].Add(stickyNote);
789 }
790
791 void RemoveNoteNoValidate(StickyNoteData stickyNote)
792 {
793 if (!m_StickyNoteDatas.Contains(stickyNote))
794 {
795 throw new InvalidOperationException("Cannot remove a note that doesn't exist.");
796 }
797
798 m_StickyNoteDatas.Remove(stickyNote);
799 m_RemovedNotes.Add(stickyNote);
800
801 if (m_GroupItems.TryGetValue(stickyNote.group, out var groupItems))
802 {
803 groupItems.Remove(stickyNote);
804 }
805 }
806
807 public void RemoveStickyNote(StickyNoteData stickyNote)
808 {
809 RemoveNoteNoValidate(stickyNote);
810 ValidateGraph();
811 }
812
813 public void SetGroup(IGroupItem node, GroupData group)
814 {
815 var groupChange = new ParentGroupChange()
816 {
817 groupItem = node,
818 oldGroup = node.group,
819 // Checking if the groupdata is null. If it is, then it means node has been removed out of a group.
820 // If the group data is null, then maybe the old group id should be removed
821 newGroup = group,
822 };
823 node.group = groupChange.newGroup;
824
825 var oldGroupNodes = m_GroupItems[groupChange.oldGroup];
826 oldGroupNodes.Remove(node);
827
828 m_GroupItems[groupChange.newGroup].Add(node);
829 m_ParentGroupChanges.Add(groupChange);
830 }
831
832 public void AddContexts()
833 {
834 m_VertexContext = new ContextData();
835 m_VertexContext.shaderStage = ShaderStage.Vertex;
836 m_VertexContext.position = new Vector2(0, 0);
837 m_FragmentContext = new ContextData();
838 m_FragmentContext.shaderStage = ShaderStage.Fragment;
839 m_FragmentContext.position = new Vector2(0, 200);
840 }
841
842 public void AddBlock(BlockNode blockNode, ContextData contextData, int index)
843 {
844 AddBlockNoValidate(blockNode, contextData, index);
845 ValidateGraph();
846
847 var activeBlocks = GetActiveBlocksForAllActiveTargets();
848 UpdateActiveBlocks(activeBlocks);
849 }
850
851 void AddBlockNoValidate(BlockNode blockNode, ContextData contextData, int index)
852 {
853 // Regular AddNode path
854 AddNodeNoValidate(blockNode);
855
856 // Set BlockNode properties
857 blockNode.contextData = contextData;
858
859 // Add to ContextData
860 if (index == -1 || index >= contextData.blocks.Count())
861 {
862 contextData.blocks.Add(blockNode);
863 }
864 else
865 {
866 contextData.blocks.Insert(index, blockNode);
867 }
868 }
869
870 public List<BlockFieldDescriptor> GetActiveBlocksForAllActiveTargets()
871 {
872 // Get list of active Block types
873 var currentBlocks = GetNodes<BlockNode>();
874 var context = new TargetActiveBlockContext(currentBlocks.Select(x => x.descriptor).ToList(), null);
875 foreach (var target in activeTargets)
876 {
877 target.GetActiveBlocks(ref context);
878 }
879
880 // custom blocks aren't going to exist in GetActiveBlocks, we need to ensure we grab those too.
881 foreach (var cibnode in currentBlocks.Where(bn => bn.isCustomBlock))
882 {
883 context.AddBlock(cibnode.descriptor);
884 }
885 return context.activeBlocks;
886 }
887
888 public void UpdateActiveBlocks(List<BlockFieldDescriptor> activeBlockDescriptors)
889 {
890 // Set Blocks as active based on supported Block list
891 //Note: we never want unknown blocks to be active, so explicitly set them to inactive always
892 bool disableCI = activeTargets.All(at => at.ignoreCustomInterpolators);
893 foreach (var vertexBlock in vertexContext.blocks)
894 {
895 if (vertexBlock.value?.isCustomBlock == true)
896 {
897 vertexBlock.value.SetOverrideActiveState(disableCI ? AbstractMaterialNode.ActiveState.ExplicitInactive : AbstractMaterialNode.ActiveState.ExplicitActive);
898 }
899 else if (vertexBlock.value?.descriptor?.isUnknown == true)
900 {
901 vertexBlock.value.SetOverrideActiveState(AbstractMaterialNode.ActiveState.ExplicitInactive);
902 }
903 else
904 {
905 vertexBlock.value.SetOverrideActiveState(activeBlockDescriptors.Contains(vertexBlock.value.descriptor) ? AbstractMaterialNode.ActiveState.ExplicitActive
906 : AbstractMaterialNode.ActiveState.ExplicitInactive);
907 }
908 }
909 foreach (var fragmentBlock in fragmentContext.blocks)
910 {
911 if (fragmentBlock.value?.descriptor?.isUnknown == true)
912 {
913 fragmentBlock.value.SetOverrideActiveState(AbstractMaterialNode.ActiveState.ExplicitInactive);
914 }
915 else
916 {
917 fragmentBlock.value.SetOverrideActiveState(activeBlockDescriptors.Contains(fragmentBlock.value.descriptor) ? AbstractMaterialNode.ActiveState.ExplicitActive
918 : AbstractMaterialNode.ActiveState.ExplicitInactive);
919 }
920 }
921 }
922
923 public void AddRemoveBlocksFromActiveList(List<BlockFieldDescriptor> activeBlockDescriptors)
924 {
925 var blocksToRemove = ListPool<BlockNode>.Get();
926
927 void GetBlocksToRemoveForContext(ContextData contextData)
928 {
929 for (int i = 0; i < contextData.blocks.Count; i++)
930 {
931 if (contextData.blocks[i].value?.isCustomBlock == true) // custom interpolators are fine.
932 continue;
933
934 var block = contextData.blocks[i];
935 if (!activeBlockDescriptors.Contains(block.value.descriptor))
936 {
937 var slot = block.value.FindSlot<MaterialSlot>(0);
938 //Need to check if a slot is not default value OR is an untracked unknown block type
939 if (slot.IsUsingDefaultValue() || block.value.descriptor.isUnknown) // TODO: How to check default value
940 {
941 blocksToRemove.Add(block);
942 }
943 }
944 }
945 }
946
947 void TryAddBlockToContext(BlockFieldDescriptor descriptor, ContextData contextData)
948 {
949 if (descriptor.shaderStage != contextData.shaderStage)
950 return;
951
952 if (contextData.blocks.Any(x => x.value.descriptor.Equals(descriptor)))
953 return;
954
955 var node = (BlockNode)Activator.CreateInstance(typeof(BlockNode));
956 node.Init(descriptor);
957 AddBlockNoValidate(node, contextData, contextData.blocks.Count);
958 }
959
960 // Get inactive Blocks to remove
961 GetBlocksToRemoveForContext(vertexContext);
962 GetBlocksToRemoveForContext(fragmentContext);
963
964 // Remove blocks
965 foreach (var block in blocksToRemove)
966 {
967 RemoveNodeNoValidate(block);
968 }
969
970 // Add active Blocks not currently in Contexts
971 foreach (var descriptor in activeBlockDescriptors)
972 {
973 TryAddBlockToContext(descriptor, vertexContext);
974 TryAddBlockToContext(descriptor, fragmentContext);
975 }
976 }
977
978 void AddNodeNoValidate(AbstractMaterialNode node)
979 {
980 if (node.group != null && !m_GroupItems.ContainsKey(node.group))
981 {
982 throw new InvalidOperationException("Cannot add a node whose group doesn't exist.");
983 }
984 node.owner = this;
985 m_Nodes.Add(node);
986 m_NodeDictionary.Add(node.objectId, node);
987 m_AddedNodes.Add(node);
988 m_GroupItems[node.group].Add(node);
989 }
990
991 public void RemoveNode(AbstractMaterialNode node)
992 {
993 if (!node.canDeleteNode)
994 {
995 throw new InvalidOperationException($"Node {node.name} ({node.objectId}) cannot be deleted.");
996 }
997 RemoveNodeNoValidate(node);
998 ValidateGraph();
999
1000 if (node is BlockNode blockNode)
1001 {
1002 var activeBlocks = GetActiveBlocksForAllActiveTargets();
1003 UpdateActiveBlocks(activeBlocks);
1004 blockNode.Dirty(ModificationScope.Graph);
1005 }
1006 }
1007
1008 void RemoveNodeNoValidate(AbstractMaterialNode node)
1009 {
1010 if (!m_NodeDictionary.ContainsKey(node.objectId) && node.isActive && !m_RemovedNodes.Contains(node))
1011 {
1012 throw new InvalidOperationException("Cannot remove a node that doesn't exist.");
1013 }
1014
1015 m_Nodes.Remove(node);
1016 m_NodeDictionary.Remove(node.objectId);
1017 messageManager?.RemoveNode(node.objectId);
1018 m_RemovedNodes.Add(node);
1019
1020 if (m_GroupItems.TryGetValue(node.group, out var groupItems))
1021 {
1022 groupItems.Remove(node);
1023 }
1024
1025 if (node is BlockNode blockNode && blockNode.contextData != null)
1026 {
1027 // Remove from ContextData
1028 blockNode.contextData.blocks.Remove(blockNode);
1029 }
1030 }
1031
1032 void AddEdgeToNodeEdges(IEdge edge)
1033 {
1034 List<IEdge> inputEdges;
1035 if (!m_NodeEdges.TryGetValue(edge.inputSlot.node.objectId, out inputEdges))
1036 m_NodeEdges[edge.inputSlot.node.objectId] = inputEdges = new List<IEdge>();
1037 inputEdges.Add(edge);
1038
1039 List<IEdge> outputEdges;
1040 if (!m_NodeEdges.TryGetValue(edge.outputSlot.node.objectId, out outputEdges))
1041 m_NodeEdges[edge.outputSlot.node.objectId] = outputEdges = new List<IEdge>();
1042 outputEdges.Add(edge);
1043 }
1044
1045 IEdge ConnectNoValidate(SlotReference fromSlotRef, SlotReference toSlotRef, bool assumeBatchSafety = false)
1046 {
1047 var fromNode = fromSlotRef.node;
1048 var toNode = toSlotRef.node;
1049
1050 if (fromNode == null || toNode == null)
1051 return null;
1052
1053 // both nodes must belong to this graph
1054 if ((fromNode.owner != this) || (toNode.owner != this))
1055 return null;
1056
1057 // if fromNode is already connected to toNode
1058 // do now allow a connection as toNode will then
1059 // have an edge to fromNode creating a cycle.
1060 // if this is parsed it will lead to an infinite loop.
1061 if (!assumeBatchSafety) // This may not be that helpful.
1062 {
1063 var dependentNodes = new List<AbstractMaterialNode>();
1064 NodeUtils.CollectNodesNodeFeedsInto(dependentNodes, toNode);
1065 if (dependentNodes.Contains(fromNode))
1066 return null;
1067 }
1068
1069 var fromSlot = fromNode.FindSlot<MaterialSlot>(fromSlotRef.slotId);
1070 var toSlot = toNode.FindSlot<MaterialSlot>(toSlotRef.slotId);
1071
1072 if (fromSlot == null || toSlot == null)
1073 return null;
1074
1075 if (fromSlot.isOutputSlot == toSlot.isOutputSlot)
1076 return null;
1077
1078 var outputSlot = fromSlot.isOutputSlot ? fromSlotRef : toSlotRef;
1079 var inputSlot = fromSlot.isInputSlot ? fromSlotRef : toSlotRef;
1080
1081 s_TempEdges.Clear();
1082 GetEdges(inputSlot, s_TempEdges);
1083
1084 // remove any inputs that exits before adding
1085 foreach (var edge in s_TempEdges)
1086 {
1087 RemoveEdgeNoValidate(edge);
1088 }
1089
1090 var newEdge = new Edge(outputSlot, inputSlot);
1091 m_Edges.Add(newEdge);
1092 m_AddedEdges.Add(newEdge);
1093 AddEdgeToNodeEdges(newEdge);
1094 if (!assumeBatchSafety)
1095 {
1096 NodeUtils.ReevaluateActivityOfConnectedNodes(toNode);
1097 }
1098
1099 //Debug.LogFormat("Connected edge: {0} -> {1} ({2} -> {3})\n{4}", newEdge.outputSlot.nodeGuid, newEdge.inputSlot.nodeGuid, fromNode.name, toNode.name, Environment.StackTrace);
1100 return newEdge;
1101 }
1102
1103 public IEdge Connect(SlotReference fromSlotRef, SlotReference toSlotRef)
1104 {
1105 var newEdge = ConnectNoValidate(fromSlotRef, toSlotRef);
1106 ValidateGraph();
1107 return newEdge;
1108 }
1109
1110 internal void UnnotifyAddedEdge(IEdge edge)
1111 {
1112 m_AddedEdges.Remove(edge);
1113 }
1114
1115 public void RemoveEdges(IEdge[] edges)
1116 {
1117 if (edges.Length == 0)
1118 return;
1119 foreach (var edge in edges)
1120 {
1121 RemoveEdgeNoValidate(edge);
1122 }
1123 ValidateGraph();
1124 }
1125
1126 public void RemoveEdge(IEdge e)
1127 {
1128 RemoveEdgeNoValidate(e);
1129 ValidateGraph();
1130 }
1131
1132 public void RemoveElements(AbstractMaterialNode[] nodes, IEdge[] edges, GroupData[] groups, StickyNoteData[] notes, ShaderInput[] inputs = null)
1133 {
1134 foreach (var node in nodes)
1135 {
1136 if (!node.canDeleteNode)
1137 {
1138 throw new InvalidOperationException($"Node {node.name} ({node.objectId}) cannot be deleted.");
1139 }
1140 }
1141
1142 foreach (var edge in edges.ToArray())
1143 {
1144 RemoveEdgeNoValidate(edge);
1145 }
1146
1147 foreach (var serializableNode in nodes)
1148 {
1149 // Check if it is a Redirect Node
1150 // Get the edges and then re-create all Edges
1151 // This only works if it has all the edges.
1152 // If one edge is already deleted then we can not re-create.
1153 if (serializableNode is RedirectNodeData redirectNode)
1154 {
1155 redirectNode.GetOutputAndInputSlots(out SlotReference outputSlotRef, out var inputSlotRefs);
1156
1157 foreach (SlotReference slot in inputSlotRefs)
1158 {
1159 ConnectNoValidate(outputSlotRef, slot);
1160 }
1161 }
1162
1163 RemoveNodeNoValidate(serializableNode);
1164 }
1165
1166 foreach (var noteData in notes)
1167 {
1168 RemoveNoteNoValidate(noteData);
1169 }
1170
1171 foreach (var groupData in groups)
1172 {
1173 RemoveGroupNoValidate(groupData);
1174 }
1175
1176 if (inputs != null)
1177 {
1178 foreach (var shaderInput in inputs)
1179 {
1180 RemoveGraphInputNoValidate(shaderInput);
1181 }
1182 }
1183
1184 ValidateGraph();
1185
1186 if (nodes.Any(x => x is BlockNode))
1187 {
1188 var activeBlocks = GetActiveBlocksForAllActiveTargets();
1189 UpdateActiveBlocks(activeBlocks);
1190 }
1191 }
1192
1193 void RemoveEdgeNoValidate(IEdge e, bool reevaluateActivity = true)
1194 {
1195 e = m_Edges.FirstOrDefault(x => x.Equals(e));
1196 if (e == null)
1197 throw new ArgumentException("Trying to remove an edge that does not exist.", "e");
1198 m_Edges.Remove(e as Edge);
1199
1200 AbstractMaterialNode input = e.inputSlot.node, output = e.outputSlot.node;
1201 if (input != null && ShaderGraphPreferences.autoAddRemoveBlocks)
1202 {
1203 checkAutoAddRemoveBlocks = true;
1204 }
1205
1206 List<IEdge> inputNodeEdges;
1207 if (m_NodeEdges.TryGetValue(input.objectId, out inputNodeEdges))
1208 inputNodeEdges.Remove(e);
1209
1210 List<IEdge> outputNodeEdges;
1211 if (m_NodeEdges.TryGetValue(output.objectId, out outputNodeEdges))
1212 outputNodeEdges.Remove(e);
1213
1214 m_AddedEdges.Remove(e);
1215 m_RemovedEdges.Add(e);
1216
1217 if (reevaluateActivity)
1218 {
1219 if (input != null)
1220 {
1221 NodeUtils.ReevaluateActivityOfConnectedNodes(input);
1222 }
1223
1224 if (output != null)
1225 {
1226 NodeUtils.ReevaluateActivityOfConnectedNodes(output);
1227 }
1228 }
1229 }
1230
1231 public AbstractMaterialNode GetNodeFromId(string nodeId)
1232 {
1233 m_NodeDictionary.TryGetValue(nodeId, out var node);
1234 return node;
1235 }
1236
1237 public T GetNodeFromId<T>(string nodeId) where T : class
1238 {
1239 m_NodeDictionary.TryGetValue(nodeId, out var node);
1240 return node as T;
1241 }
1242
1243 internal Texture2DShaderProperty GetMainTexture()
1244 {
1245 foreach (var prop in properties)
1246 {
1247 if (prop is Texture2DShaderProperty tex)
1248 {
1249 if (tex.isMainTexture)
1250 {
1251 return tex;
1252 }
1253 }
1254 }
1255 return null;
1256 }
1257
1258 internal ColorShaderProperty GetMainColor()
1259 {
1260 foreach (var prop in properties)
1261 {
1262 if (prop is ColorShaderProperty col)
1263 {
1264 if (col.isMainColor)
1265 {
1266 return col;
1267 }
1268 }
1269 }
1270 return null;
1271 }
1272
1273 public bool ContainsCategory(CategoryData categoryData)
1274 {
1275 return categories.Contains(categoryData);
1276 }
1277
1278 public bool ContainsInput(ShaderInput shaderInput)
1279 {
1280 if (shaderInput == null)
1281 return false;
1282
1283 return properties.Contains(shaderInput) || keywords.Contains(shaderInput) || dropdowns.Contains(shaderInput);
1284 }
1285
1286 public bool ContainsNode(AbstractMaterialNode node)
1287 {
1288 if (node == null)
1289 return false;
1290
1291 return m_NodeDictionary.TryGetValue(node.objectId, out var foundNode) && node == foundNode;
1292 }
1293
1294 public void GetEdges(MaterialSlot slot, List<IEdge> foundEdges)
1295 {
1296 List<IEdge> candidateEdges;
1297 if (!m_NodeEdges.TryGetValue(slot.owner.objectId, out candidateEdges))
1298 return;
1299
1300 foreach (var edge in candidateEdges)
1301 {
1302 var cs = slot.isInputSlot ? edge.inputSlot : edge.outputSlot;
1303 if (cs.node == slot.owner && cs.slotId == slot.id)
1304 foundEdges.Add(edge);
1305 }
1306 }
1307
1308 public void GetEdges(SlotReference s, List<IEdge> foundEdges)
1309 {
1310 List<IEdge> candidateEdges;
1311 if (!m_NodeEdges.TryGetValue(s.node.objectId, out candidateEdges))
1312 return;
1313
1314 MaterialSlot slot = s.slot;
1315 foreach (var edge in candidateEdges)
1316 {
1317 var cs = slot.isInputSlot ? edge.inputSlot : edge.outputSlot;
1318 if (cs.node == s.node && cs.slotId == s.slotId)
1319 foundEdges.Add(edge);
1320 }
1321 }
1322
1323 public IEnumerable<IEdge> GetEdges(SlotReference s)
1324 {
1325 var edges = new List<IEdge>();
1326 GetEdges(s, edges);
1327 return edges;
1328 }
1329
1330 public void GetEdges(AbstractMaterialNode node, List<IEdge> foundEdges)
1331 {
1332 if (m_NodeEdges.TryGetValue(node.objectId, out var edges))
1333 {
1334 foundEdges.AddRange(edges);
1335 }
1336 }
1337
1338 public IEnumerable<IEdge> GetEdges(AbstractMaterialNode node)
1339 {
1340 List<IEdge> edges = new List<IEdge>();
1341 GetEdges(node, edges);
1342 return edges;
1343 }
1344
1345 public void ForeachHLSLProperty(Action<HLSLProperty> action)
1346 {
1347 foreach (var prop in properties)
1348 prop.ForeachHLSLProperty(action);
1349 }
1350
1351 public void CollectShaderProperties(PropertyCollector collector, GenerationMode generationMode)
1352 {
1353 foreach (var prop in properties)
1354 {
1355 // For VFX Shader generation, we must omit exposed properties from the Material CBuffer.
1356 // This is because VFX computes properties on the fly in the vertex stage, and packed into interpolator.
1357 if (generationMode == GenerationMode.VFX && prop.isExposed)
1358 {
1359 prop.overrideHLSLDeclaration = true;
1360 prop.hlslDeclarationOverride = HLSLDeclaration.DoNotDeclare;
1361 }
1362
1363 // ugh, this needs to be moved to the gradient property implementation
1364 if (prop is GradientShaderProperty gradientProp && generationMode == GenerationMode.Preview)
1365 {
1366 GradientUtil.GetGradientPropertiesForPreview(collector, gradientProp.referenceName, gradientProp.value);
1367 continue;
1368 }
1369
1370 collector.AddShaderProperty(prop);
1371 }
1372 }
1373
1374 public void CollectShaderKeywords(KeywordCollector collector, GenerationMode generationMode)
1375 {
1376 foreach (var keyword in keywords)
1377 {
1378 collector.AddShaderKeyword(keyword);
1379 }
1380
1381 // Alwways calculate permutations when collecting
1382 collector.CalculateKeywordPermutations();
1383 }
1384
1385 public bool IsInputAllowedInGraph(ShaderInput input)
1386 {
1387 return (isSubGraph && input.allowedInSubGraph) || (!isSubGraph && input.allowedInMainGraph);
1388 }
1389
1390 public bool IsInputAllowedInGraph(AbstractMaterialNode node)
1391 {
1392 return (isSubGraph && node.allowedInSubGraph) || (!isSubGraph && node.allowedInMainGraph);
1393 }
1394
1395 // adds the input to the graph, and sanitizes the names appropriately
1396 public void AddGraphInput(ShaderInput input, int index = -1)
1397 {
1398 if (input == null)
1399 return;
1400
1401 // sanitize the display name
1402 input.SetDisplayNameAndSanitizeForGraph(this);
1403
1404 // sanitize the reference name
1405 input.SetReferenceNameAndSanitizeForGraph(this);
1406
1407 AddGraphInputNoSanitization(input, index);
1408 }
1409
1410 // just adds the input to the graph, does not fix colliding or illegal names
1411 internal void AddGraphInputNoSanitization(ShaderInput input, int index = -1)
1412 {
1413 if (input == null)
1414 return;
1415
1416 switch (input)
1417 {
1418 case AbstractShaderProperty property:
1419 if (m_Properties.Contains(property))
1420 return;
1421
1422 if (index < 0)
1423 m_Properties.Add(property);
1424 else
1425 m_Properties.Insert(index, property);
1426
1427 break;
1428 case ShaderKeyword keyword:
1429 if (m_Keywords.Contains(keyword))
1430 return;
1431
1432 if (index < 0)
1433 m_Keywords.Add(keyword);
1434 else
1435 m_Keywords.Insert(index, keyword);
1436
1437 OnKeywordChangedNoValidate();
1438
1439 break;
1440 case ShaderDropdown dropdown:
1441 if (m_Dropdowns.Contains(dropdown))
1442 return;
1443
1444 if (index < 0)
1445 m_Dropdowns.Add(dropdown);
1446 else
1447 m_Dropdowns.Insert(index, dropdown);
1448
1449 OnDropdownChangedNoValidate();
1450
1451 break;
1452 default:
1453 throw new ArgumentOutOfRangeException();
1454 }
1455
1456 m_AddedInputs.Add(input);
1457 }
1458
1459 // only ignores names matching ignoreName on properties matching ignoreGuid
1460 public List<string> BuildPropertyDisplayNameList(AbstractShaderProperty ignoreProperty, string ignoreName)
1461 {
1462 List<String> result = new List<String>();
1463 foreach (var p in properties)
1464 {
1465 int before = result.Count;
1466 p.GetPropertyDisplayNames(result);
1467
1468 if ((p == ignoreProperty) && (ignoreName != null))
1469 {
1470 // remove ignoreName, if it was just added
1471 for (int i = before; i < result.Count; i++)
1472 {
1473 if (result[i] == ignoreName)
1474 {
1475 result.RemoveAt(i);
1476 break;
1477 }
1478 }
1479 }
1480 }
1481
1482 return result;
1483 }
1484
1485 // only ignores names matching ignoreName on properties matching ignoreGuid
1486 public List<string> BuildPropertyReferenceNameList(AbstractShaderProperty ignoreProperty, string ignoreName)
1487 {
1488 List<String> result = new List<String>();
1489 foreach (var p in properties)
1490 {
1491 int before = result.Count;
1492 p.GetPropertyReferenceNames(result);
1493
1494 if ((p == ignoreProperty) && (ignoreName != null))
1495 {
1496 // remove ignoreName, if it was just added
1497 for (int i = before; i < result.Count; i++)
1498 {
1499 if (result[i] == ignoreName)
1500 {
1501 result.RemoveAt(i);
1502 break;
1503 }
1504 }
1505 }
1506 }
1507 return result;
1508 }
1509
1510 public string SanitizeGraphInputName(ShaderInput input, string desiredName)
1511 {
1512 string currentName = input.displayName;
1513 string sanitizedName = desiredName.Trim();
1514 switch (input)
1515 {
1516 case AbstractShaderProperty property:
1517 sanitizedName = GraphUtil.SanitizeName(BuildPropertyDisplayNameList(property, currentName), "{0} ({1})", sanitizedName);
1518 break;
1519 case ShaderKeyword keyword:
1520 sanitizedName = GraphUtil.SanitizeName(keywords.Where(p => p != input).Select(p => p.displayName), "{0} ({1})", sanitizedName);
1521 break;
1522 case ShaderDropdown dropdown:
1523 sanitizedName = GraphUtil.SanitizeName(dropdowns.Where(p => p != input).Select(p => p.displayName), "{0} ({1})", sanitizedName);
1524 break;
1525 default:
1526 throw new ArgumentOutOfRangeException();
1527 }
1528 return sanitizedName;
1529 }
1530
1531 public string SanitizeGraphInputReferenceName(ShaderInput input, string desiredName)
1532 {
1533 var sanitizedName = NodeUtils.ConvertToValidHLSLIdentifier(desiredName, (desiredName) => (NodeUtils.IsShaderLabKeyWord(desiredName) || NodeUtils.IsShaderGraphKeyWord(desiredName)));
1534
1535 switch (input)
1536 {
1537 case AbstractShaderProperty property:
1538 {
1539 // must deduplicate ref names against keywords, dropdowns, and properties, as they occupy the same name space
1540 var existingNames = properties.Where(p => p != property).Select(p => p.referenceName).Union(keywords.Select(p => p.referenceName)).Union(dropdowns.Select(p => p.referenceName));
1541 sanitizedName = GraphUtil.DeduplicateName(existingNames, "{0}_{1}", sanitizedName);
1542 }
1543 break;
1544 case ShaderKeyword keyword:
1545 {
1546 // must deduplicate ref names against keywords, dropdowns, and properties, as they occupy the same name space
1547 sanitizedName = sanitizedName.ToUpper();
1548 var existingNames = properties.Select(p => p.referenceName).Union(keywords.Where(p => p != input).Select(p => p.referenceName)).Union(dropdowns.Select(p => p.referenceName));
1549 sanitizedName = GraphUtil.DeduplicateName(existingNames, "{0}_{1}", sanitizedName);
1550 }
1551 break;
1552 case ShaderDropdown dropdown:
1553 {
1554 // must deduplicate ref names against keywords, dropdowns, and properties, as they occupy the same name space
1555 var existingNames = properties.Select(p => p.referenceName).Union(keywords.Select(p => p.referenceName)).Union(dropdowns.Where(p => p != input).Select(p => p.referenceName));
1556 sanitizedName = GraphUtil.DeduplicateName(existingNames, "{0}_{1}", sanitizedName);
1557 }
1558 break;
1559 default:
1560 throw new ArgumentOutOfRangeException();
1561 }
1562 return sanitizedName;
1563 }
1564
1565 // copies the ShaderInput, and adds it to the graph with proper name sanitization, returning the copy
1566 public ShaderInput AddCopyOfShaderInput(ShaderInput source, int insertIndex = -1)
1567 {
1568 ShaderInput copy = source.Copy();
1569
1570 // some ShaderInputs cannot be copied (unknown types)
1571 if (copy == null)
1572 return null;
1573
1574 // copy common properties that should always be copied over
1575 copy.generatePropertyBlock = source.generatePropertyBlock; // the exposed toggle
1576
1577 if ((source is AbstractShaderProperty sourceProp) && (copy is AbstractShaderProperty copyProp))
1578 {
1579 copyProp.hidden = sourceProp.hidden;
1580 copyProp.precision = sourceProp.precision;
1581 copyProp.overrideHLSLDeclaration = sourceProp.overrideHLSLDeclaration;
1582 copyProp.hlslDeclarationOverride = sourceProp.hlslDeclarationOverride;
1583 copyProp.useCustomSlotLabel = sourceProp.useCustomSlotLabel;
1584 }
1585
1586 // sanitize the display name (we let the .Copy() function actually copy the display name over)
1587 copy.SetDisplayNameAndSanitizeForGraph(this);
1588
1589 // copy and sanitize the reference name (must do this after the display name, so the default is correct)
1590 if (source.IsUsingNewDefaultRefName())
1591 {
1592 // if source was using new default, we can just rely on the default for the copy we made.
1593 // the code above has already handled collisions properly for the default,
1594 // and it will assign the same name as the source if there are no collisions.
1595 // Also it will result better names chosen when there are collisions.
1596 }
1597 else
1598 {
1599 // when the source is using an old default, we set it as an override
1600 copy.SetReferenceNameAndSanitizeForGraph(this, source.referenceName);
1601 }
1602
1603 copy.OnBeforePasteIntoGraph(this);
1604 AddGraphInputNoSanitization(copy, insertIndex);
1605
1606 return copy;
1607 }
1608
1609 public void RemoveGraphInput(ShaderInput input)
1610 {
1611 switch (input)
1612 {
1613 case AbstractShaderProperty property:
1614 var propertyNodes = GetNodes<PropertyNode>().Where(x => x.property == input).ToList();
1615 foreach (var propertyNode in propertyNodes)
1616 ReplacePropertyNodeWithConcreteNodeNoValidate(propertyNode);
1617 break;
1618 }
1619
1620 // Also remove this input from any category it existed in
1621 foreach (var categoryData in categories)
1622 {
1623 if (categoryData.IsItemInCategory(input))
1624 {
1625 categoryData.RemoveItemFromCategory(input);
1626 break;
1627 }
1628 }
1629
1630 RemoveGraphInputNoValidate(input);
1631 ValidateGraph();
1632 }
1633
1634 public void MoveCategory(CategoryData category, int newIndex)
1635 {
1636 if (newIndex > m_CategoryData.Count || newIndex < 0)
1637 {
1638 AssertHelpers.Fail("New index is not within categories list.");
1639 return;
1640 }
1641 var currentIndex = m_CategoryData.IndexOf(category);
1642 if (currentIndex == -1)
1643 {
1644 AssertHelpers.Fail("Category is not in graph.");
1645 return;
1646 }
1647 if (newIndex == currentIndex)
1648 return;
1649 m_CategoryData.RemoveAt(currentIndex);
1650 if (newIndex > currentIndex)
1651 newIndex--;
1652 var isLast = newIndex == m_CategoryData.Count;
1653 if (isLast)
1654 m_CategoryData.Add(category);
1655 else
1656 m_CategoryData.Insert(newIndex, category);
1657 if (!m_MovedCategories.Contains(category))
1658 m_MovedCategories.Add(category);
1659 }
1660
1661 public void MoveItemInCategory(ShaderInput itemToMove, int newIndex, string associatedCategoryGuid)
1662 {
1663 foreach (var categoryData in categories)
1664 {
1665 if (categoryData.categoryGuid == associatedCategoryGuid && categoryData.IsItemInCategory(itemToMove))
1666 {
1667 // Validate new index to move the item to
1668 if (newIndex < -1 || newIndex >= categoryData.childCount)
1669 {
1670 AssertHelpers.Fail("Provided invalid index input to MoveItemInCategory.");
1671 return;
1672 }
1673
1674 categoryData.MoveItemInCategory(itemToMove, newIndex);
1675 break;
1676 }
1677 }
1678 }
1679
1680 public int GetGraphInputIndex(ShaderInput input)
1681 {
1682 switch (input)
1683 {
1684 case AbstractShaderProperty property:
1685 return m_Properties.IndexOf(property);
1686 case ShaderKeyword keyword:
1687 return m_Keywords.IndexOf(keyword);
1688 case ShaderDropdown dropdown:
1689 return m_Dropdowns.IndexOf(dropdown);
1690 default:
1691 throw new ArgumentOutOfRangeException();
1692 }
1693 }
1694
1695 void RemoveGraphInputNoValidate(ShaderInput shaderInput)
1696 {
1697 if (shaderInput is AbstractShaderProperty property && m_Properties.Remove(property) ||
1698 shaderInput is ShaderKeyword keyword && m_Keywords.Remove(keyword) ||
1699 shaderInput is ShaderDropdown dropdown && m_Dropdowns.Remove(dropdown))
1700 {
1701 m_RemovedInputs.Add(shaderInput);
1702 m_AddedInputs.Remove(shaderInput);
1703 m_MovedInputs.Remove(shaderInput);
1704 }
1705 }
1706
1707 static List<IEdge> s_TempEdges = new List<IEdge>();
1708
1709 public void ReplacePropertyNodeWithConcreteNode(PropertyNode propertyNode)
1710 {
1711 ReplacePropertyNodeWithConcreteNodeNoValidate(propertyNode);
1712 ValidateGraph();
1713 }
1714
1715 void ReplacePropertyNodeWithConcreteNodeNoValidate(PropertyNode propertyNode, bool deleteNodeIfNoConcreteFormExists = true)
1716 {
1717 var property = properties.FirstOrDefault(x => x == propertyNode.property) ?? propertyNode.property;
1718 if (property == null)
1719 return;
1720
1721 var node = property.ToConcreteNode() as AbstractMaterialNode;
1722 if (node == null) // Some nodes have no concrete form
1723 {
1724 if (deleteNodeIfNoConcreteFormExists)
1725 RemoveNodeNoValidate(propertyNode);
1726 return;
1727 }
1728
1729 var slot = propertyNode.FindOutputSlot<MaterialSlot>(PropertyNode.OutputSlotId);
1730 var newSlot = node.GetOutputSlots<MaterialSlot>().FirstOrDefault(s => s.valueType == slot.valueType);
1731 if (newSlot == null)
1732 return;
1733
1734 node.drawState = propertyNode.drawState;
1735 node.group = propertyNode.group;
1736 AddNodeNoValidate(node);
1737
1738 foreach (var edge in this.GetEdges(slot.slotReference))
1739 ConnectNoValidate(newSlot.slotReference, edge.inputSlot);
1740
1741 RemoveNodeNoValidate(propertyNode);
1742 }
1743
1744 public void AddCategory(CategoryData categoryDataReference)
1745 {
1746 m_CategoryData.Add(categoryDataReference);
1747 m_AddedCategories.Add(categoryDataReference);
1748 }
1749
1750 public string FindCategoryForInput(ShaderInput input)
1751 {
1752 foreach (var categoryData in categories)
1753 {
1754 if (categoryData.IsItemInCategory(input))
1755 {
1756 return categoryData.categoryGuid;
1757 }
1758 }
1759
1760 AssertHelpers.Fail("Attempted to find category for an input that doesn't exist in the graph.");
1761 return String.Empty;
1762 }
1763
1764 public void ChangeCategoryName(string categoryGUID, string newName)
1765 {
1766 foreach (var categoryData in categories)
1767 {
1768 if (categoryData.categoryGuid == categoryGUID)
1769 {
1770 var sanitizedCategoryName = GraphUtil.SanitizeCategoryName(newName);
1771 categoryData.name = sanitizedCategoryName;
1772 return;
1773 }
1774 }
1775
1776 AssertHelpers.Fail("Attempted to change name of a category that does not exist in the graph.");
1777 }
1778
1779 public void InsertItemIntoCategory(string categoryGUID, ShaderInput itemToAdd, int insertionIndex = -1)
1780 {
1781 foreach (var categoryData in categories)
1782 {
1783 if (categoryData.categoryGuid == categoryGUID)
1784 {
1785 categoryData.InsertItemIntoCategory(itemToAdd, insertionIndex);
1786 }
1787 // Also make sure to remove this items guid from an existing category if it exists within one
1788 else if (categoryData.IsItemInCategory(itemToAdd))
1789 {
1790 categoryData.RemoveItemFromCategory(itemToAdd);
1791 }
1792 }
1793 }
1794
1795 public void RemoveItemFromCategory(string categoryGUID, ShaderInput itemToRemove)
1796 {
1797 foreach (var categoryData in categories)
1798 {
1799 if (categoryData.categoryGuid == categoryGUID)
1800 {
1801 categoryData.RemoveItemFromCategory(itemToRemove);
1802 return;
1803 }
1804 }
1805
1806 AssertHelpers.Fail("Attempted to remove item from a category that does not exist in the graph.");
1807 }
1808
1809 public void RemoveCategory(string categoryGUID)
1810 {
1811 var existingCategory = categories.FirstOrDefault(category => category.categoryGuid == categoryGUID);
1812 if (existingCategory != null)
1813 {
1814 m_CategoryData.Remove(existingCategory);
1815 m_RemovedCategories.Add(existingCategory);
1816
1817 // Whenever a category is removed, also remove any inputs within that category
1818 foreach (var shaderInput in existingCategory.Children)
1819 RemoveGraphInput(shaderInput);
1820 }
1821 else
1822 AssertHelpers.Fail("Attempted to remove a category that does not exist in the graph.");
1823 }
1824
1825 // This differs from the rest of the category handling functions due to how categories can be copied between graphs
1826 // Since we have no guarantee of us owning the categories, we need a direct reference to the category to copy
1827 public CategoryData CopyCategory(CategoryData categoryToCopy)
1828 {
1829 var copiedCategory = new CategoryData(categoryToCopy);
1830 AddCategory(copiedCategory);
1831 // Whenever a category is copied, also copy over all the inputs within that category
1832 foreach (var childInputToCopy in categoryToCopy.Children)
1833 {
1834 var newShaderInput = AddCopyOfShaderInput(childInputToCopy);
1835 copiedCategory.InsertItemIntoCategory(newShaderInput);
1836 }
1837
1838 return copiedCategory;
1839 }
1840
1841 public void OnKeywordChanged()
1842 {
1843 OnKeywordChangedNoValidate();
1844 ValidateGraph();
1845 }
1846
1847 public void OnKeywordChangedNoValidate()
1848 {
1849 DirtyAll<AbstractMaterialNode>(ModificationScope.Topological);
1850 }
1851
1852 public void OnDropdownChanged()
1853 {
1854 OnDropdownChangedNoValidate();
1855 ValidateGraph();
1856 }
1857
1858 public void OnDropdownChangedNoValidate()
1859 {
1860 DirtyAll<AbstractMaterialNode>(ModificationScope.Topological);
1861 }
1862
1863 public void CleanupGraph()
1864 {
1865 //First validate edges, remove any
1866 //orphans. This can happen if a user
1867 //manually modifies serialized data
1868 //of if they delete a node in the inspector
1869 //debug view.
1870 foreach (var edge in edges.ToArray())
1871 {
1872 var outputNode = edge.outputSlot.node;
1873 var inputNode = edge.inputSlot.node;
1874
1875 MaterialSlot outputSlot = null;
1876 MaterialSlot inputSlot = null;
1877 if (ContainsNode(outputNode) && ContainsNode(inputNode))
1878 {
1879 outputSlot = outputNode.FindOutputSlot<MaterialSlot>(edge.outputSlot.slotId);
1880 inputSlot = inputNode.FindInputSlot<MaterialSlot>(edge.inputSlot.slotId);
1881 }
1882
1883 if (outputNode == null
1884 || inputNode == null
1885 || outputSlot == null
1886 || inputSlot == null)
1887 {
1888 //orphaned edge
1889 RemoveEdgeNoValidate(edge, false);
1890 }
1891 }
1892 }
1893
1894 private void DirtyAll<T>(ModificationScope modificationScope) where T : AbstractMaterialNode
1895 {
1896 graphIsConcretizing = true;
1897 try
1898 {
1899 var allNodes = GetNodes<T>();
1900 foreach (var node in allNodes)
1901 {
1902 node.Dirty(modificationScope);
1903 node.ValidateNode();
1904 }
1905 }
1906 catch (System.Exception e)
1907 {
1908 graphIsConcretizing = false;
1909 throw e;
1910 }
1911 graphIsConcretizing = false;
1912 }
1913
1914 public void ValidateGraph()
1915 {
1916 messageManager?.ClearAllFromProvider(this);
1917 CleanupGraph();
1918 GraphSetup.SetupGraph(this);
1919 GraphConcretization.ConcretizeGraph(this);
1920 GraphValidation.ValidateGraph(this);
1921
1922 for (int i = 0; i < m_AddedEdges.Count; ++i)
1923 {
1924 var edge = m_AddedEdges[i];
1925 if (!ContainsNode(edge.outputSlot.node) || !ContainsNode(edge.inputSlot.node))
1926 {
1927 Debug.LogWarningFormat("Added edge is invalid: {0} -> {1}\n{2}", edge.outputSlot.node.objectId, edge.inputSlot.node.objectId, Environment.StackTrace);
1928 m_AddedEdges.Remove(edge);
1929 }
1930 }
1931
1932 for (int i = 0; i < m_ParentGroupChanges.Count; ++i)
1933 {
1934 var groupChange = m_ParentGroupChanges[i];
1935 switch (groupChange.groupItem)
1936 {
1937 case AbstractMaterialNode node when !ContainsNode(node):
1938 case StickyNoteData stickyNote when !m_StickyNoteDatas.Contains(stickyNote):
1939 m_ParentGroupChanges.Remove(groupChange);
1940 break;
1941 }
1942 }
1943
1944 var existingDefaultCategory = categories.FirstOrDefault();
1945 if (existingDefaultCategory?.childCount == 0 && categories.Count() == 1 && (properties.Count() != 0 || keywords.Count() != 0 || dropdowns.Count() != 0))
1946 {
1947 // Have a graph with category data in invalid state
1948 // there is only one category, the default category, and all shader inputs should belong to it
1949 // Clear category data as it will get reconstructed in the BlackboardController constructor
1950 m_CategoryData.Clear();
1951 }
1952
1953 ValidateCustomBlockLimit();
1954 ValidateContextBlocks();
1955 }
1956
1957 public void AddValidationError(string id, string errorMessage,
1958 ShaderCompilerMessageSeverity severity = ShaderCompilerMessageSeverity.Error)
1959 {
1960 messageManager?.AddOrAppendError(this, id, new ShaderMessage("Validation: " + errorMessage, severity));
1961 }
1962
1963 public void AddSetupError(string id, string errorMessage,
1964 ShaderCompilerMessageSeverity severity = ShaderCompilerMessageSeverity.Error)
1965 {
1966 messageManager?.AddOrAppendError(this, id, new ShaderMessage("Setup: " + errorMessage, severity));
1967 }
1968
1969 public void AddConcretizationError(string id, string errorMessage,
1970 ShaderCompilerMessageSeverity severity = ShaderCompilerMessageSeverity.Error)
1971 {
1972 messageManager?.AddOrAppendError(this, id, new ShaderMessage("Concretization: " + errorMessage, severity));
1973 }
1974
1975 public void ClearErrorsForNode(AbstractMaterialNode node)
1976 {
1977 messageManager?.ClearNodesFromProvider(this, node.ToEnumerable());
1978 }
1979
1980 internal bool replaceInProgress = false;
1981 public void ReplaceWith(GraphData other)
1982 {
1983 if (other == null)
1984 throw new ArgumentException("Can only replace with another AbstractMaterialGraph", "other");
1985
1986 replaceInProgress = true;
1987 m_GraphPrecision = other.m_GraphPrecision;
1988 m_PreviewMode = other.m_PreviewMode;
1989 m_OutputNode = other.m_OutputNode;
1990
1991 if ((this.vertexContext.position != other.vertexContext.position) ||
1992 (this.fragmentContext.position != other.fragmentContext.position))
1993 {
1994 this.vertexContext.position = other.vertexContext.position;
1995 this.fragmentContext.position = other.fragmentContext.position;
1996 m_MovedContexts = true;
1997 }
1998
1999 using (var inputsToRemove = PooledList<ShaderInput>.Get())
2000 {
2001 foreach (var property in m_Properties.SelectValue())
2002 inputsToRemove.Add(property);
2003 foreach (var keyword in m_Keywords.SelectValue())
2004 inputsToRemove.Add(keyword);
2005 foreach (var dropdown in m_Dropdowns.SelectValue())
2006 inputsToRemove.Add(dropdown);
2007 foreach (var input in inputsToRemove)
2008 RemoveGraphInputNoValidate(input);
2009 }
2010 foreach (var otherProperty in other.properties)
2011 {
2012 AddGraphInputNoSanitization(otherProperty);
2013 }
2014 foreach (var otherKeyword in other.keywords)
2015 {
2016 AddGraphInputNoSanitization(otherKeyword);
2017 }
2018 foreach (var otherDropdown in other.dropdowns)
2019 {
2020 AddGraphInputNoSanitization(otherDropdown);
2021 }
2022
2023 other.ValidateGraph();
2024 ValidateGraph();
2025
2026 // Current tactic is to remove all nodes and edges and then re-add them, such that depending systems
2027 // will re-initialize with new references.
2028
2029 using (ListPool<GroupData>.Get(out var removedGroupDatas))
2030 {
2031 removedGroupDatas.AddRange(m_GroupDatas.SelectValue());
2032 foreach (var groupData in removedGroupDatas)
2033 {
2034 RemoveGroupNoValidate(groupData);
2035 }
2036 }
2037
2038 using (ListPool<StickyNoteData>.Get(out var removedNoteDatas))
2039 {
2040 removedNoteDatas.AddRange(m_StickyNoteDatas.SelectValue());
2041 foreach (var groupData in removedNoteDatas)
2042 {
2043 RemoveNoteNoValidate(groupData);
2044 }
2045 }
2046
2047 using (var pooledList = ListPool<IEdge>.Get(out var removedNodeEdges))
2048 {
2049 removedNodeEdges.AddRange(m_Edges);
2050 foreach (var edge in removedNodeEdges)
2051 RemoveEdgeNoValidate(edge);
2052 }
2053
2054 using (var nodesToRemove = PooledList<AbstractMaterialNode>.Get())
2055 {
2056 nodesToRemove.AddRange(m_Nodes.SelectValue());
2057 foreach (var node in nodesToRemove)
2058 RemoveNodeNoValidate(node);
2059 }
2060
2061 // Clear category data too before re-adding
2062 m_CategoryData.Clear();
2063
2064 ValidateGraph();
2065
2066 foreach (GroupData groupData in other.groups)
2067 AddGroup(groupData);
2068
2069 // If categories are ever removed completely, make sure there is always one default category that exists
2070 if (!other.categories.Any())
2071 {
2072 AddCategory(CategoryData.DefaultCategory());
2073 }
2074 else
2075 {
2076 foreach (CategoryData categoryData in other.categories)
2077 {
2078 AddCategory(categoryData);
2079 }
2080 }
2081
2082
2083 foreach (var stickyNote in other.stickyNotes)
2084 {
2085 AddStickyNote(stickyNote);
2086 }
2087
2088 foreach (var node in other.GetNodes<AbstractMaterialNode>())
2089 {
2090 if (node is BlockNode blockNode)
2091 {
2092 var contextData = blockNode.descriptor.shaderStage == ShaderStage.Vertex ? vertexContext : fragmentContext;
2093 AddBlockNoValidate(blockNode, contextData, blockNode.index);
2094 }
2095 else
2096 {
2097 AddNodeNoValidate(node);
2098 }
2099 }
2100
2101 foreach (var edge in other.edges)
2102 {
2103 ConnectNoValidate(edge.outputSlot, edge.inputSlot, true);
2104 }
2105
2106 outputNode = other.outputNode;
2107
2108 // clear our local active targets and copy state from the other GraphData
2109
2110 // NOTE: we DO NOT clear or rebuild m_AllPotentialTargets, in order to
2111 // retain the data from any inactive targets.
2112 // this allows the user can add them back and keep the old settings
2113
2114 m_ActiveTargets.Clear();
2115 foreach (var target in other.activeTargets)
2116 {
2117 // Ensure target inits correctly
2118 var context = new TargetSetupContext();
2119 target.Setup(ref context);
2120 SetTargetActive(target, true);
2121 }
2122 SortActiveTargets();
2123
2124 // Active blocks
2125 var activeBlocks = GetActiveBlocksForAllActiveTargets();
2126 UpdateActiveBlocks(activeBlocks);
2127 replaceInProgress = false;
2128 ValidateGraph();
2129 }
2130
2131 internal void PasteGraph(CopyPasteGraph graphToPaste, List<AbstractMaterialNode> remappedNodes,
2132 List<Edge> remappedEdges)
2133 {
2134 var groupMap = new Dictionary<GroupData, GroupData>();
2135 foreach (var group in graphToPaste.groups)
2136 {
2137 var position = group.position;
2138 position.x += 30;
2139 position.y += 30;
2140
2141 GroupData newGroup = new GroupData(group.title, position);
2142
2143 groupMap[group] = newGroup;
2144
2145 AddGroup(newGroup);
2146 m_PastedGroups.Add(newGroup);
2147 }
2148
2149 foreach (var stickyNote in graphToPaste.stickyNotes)
2150 {
2151 var position = stickyNote.position;
2152 position.x += 30;
2153 position.y += 30;
2154
2155 StickyNoteData pastedStickyNote = new StickyNoteData(stickyNote.title, stickyNote.content, position);
2156 pastedStickyNote.textSize = stickyNote.textSize;
2157 pastedStickyNote.theme = stickyNote.theme;
2158 if (stickyNote.group != null && groupMap.ContainsKey(stickyNote.group))
2159 {
2160 pastedStickyNote.group = groupMap[stickyNote.group];
2161 }
2162
2163 AddStickyNote(pastedStickyNote);
2164 m_PastedStickyNotes.Add(pastedStickyNote);
2165 }
2166
2167 var edges = graphToPaste.edges.ToList();
2168 var nodeList = graphToPaste.GetNodes<AbstractMaterialNode>();
2169 foreach (var node in nodeList)
2170 {
2171 // cannot paste block nodes, or unknown node types
2172 if ((node is BlockNode) || (node is MultiJsonInternal.UnknownNodeType))
2173 continue;
2174
2175 if (!IsInputAllowedInGraph(node))
2176 continue;
2177
2178 AbstractMaterialNode pastedNode = node;
2179
2180 // Check if the property nodes need to be made into a concrete node.
2181 if (node is PropertyNode propertyNode)
2182 {
2183 // If the property is not in the current graph, do check if the
2184 // property can be made into a concrete node.
2185 var property = m_Properties.SelectValue().FirstOrDefault(x => x.objectId == propertyNode.property.objectId
2186 || (x.propertyType == propertyNode.property.propertyType && x.referenceName == propertyNode.property.referenceName));
2187 if (property != null)
2188 {
2189 propertyNode.property = property;
2190 }
2191 else
2192 {
2193 pastedNode = propertyNode.property.ToConcreteNode();
2194 // some property nodes cannot be concretized.. fail to paste them
2195 if (pastedNode == null)
2196 continue;
2197 pastedNode.drawState = node.drawState;
2198 for (var i = 0; i < edges.Count; i++)
2199 {
2200 var edge = edges[i];
2201 if (edge.outputSlot.node == node)
2202 {
2203 edges[i] = new Edge(new SlotReference(pastedNode, edge.outputSlot.slotId), edge.inputSlot);
2204 }
2205 else if (edge.inputSlot.node == node)
2206 {
2207 edges[i] = new Edge(edge.outputSlot, new SlotReference(pastedNode, edge.inputSlot.slotId));
2208 }
2209 }
2210 }
2211 }
2212
2213 // If the node has a group guid and no group has been copied, reset the group guid.
2214 // Check if the node is inside a group
2215 if (node.group != null)
2216 {
2217 if (groupMap.ContainsKey(node.group))
2218 {
2219 var absNode = pastedNode;
2220 absNode.group = groupMap[node.group];
2221 pastedNode = absNode;
2222 }
2223 else
2224 {
2225 pastedNode.group = null;
2226 }
2227 }
2228
2229 remappedNodes.Add(pastedNode);
2230 AddNode(pastedNode);
2231
2232 // add the node to the pasted node list
2233 m_PastedNodes.Add(pastedNode);
2234
2235 // Check if the keyword nodes need to have their keywords copied.
2236 if (node is KeywordNode keywordNode)
2237 {
2238 var keyword = m_Keywords.SelectValue().FirstOrDefault(x => x.objectId == keywordNode.keyword.objectId
2239 || (x.keywordType == keywordNode.keyword.keywordType && x.referenceName == keywordNode.keyword.referenceName));
2240 if (keyword != null)
2241 {
2242 keywordNode.keyword = keyword;
2243 }
2244 else
2245 {
2246 owner.graphDataStore.Dispatch(new AddShaderInputAction() { shaderInputReference = keywordNode.keyword });
2247 }
2248
2249 // Always update Keyword nodes to handle any collisions resolved on the Keyword
2250 keywordNode.UpdateNode();
2251 }
2252
2253 // Check if the dropdown nodes need to have their dropdowns copied.
2254 if (node is DropdownNode dropdownNode)
2255 {
2256 var dropdown = m_Dropdowns.SelectValue().FirstOrDefault(x => x.objectId == dropdownNode.dropdown.objectId
2257 || x.referenceName == dropdownNode.dropdown.referenceName);
2258 if (dropdown != null)
2259 {
2260 dropdownNode.dropdown = dropdown;
2261 }
2262 else
2263 {
2264 owner.graphDataStore.Dispatch(new AddShaderInputAction() { shaderInputReference = dropdownNode.dropdown });
2265 }
2266
2267 // Always update Dropdown nodes to handle any collisions resolved on the Keyword
2268 dropdownNode.UpdateNode();
2269 }
2270 }
2271
2272 foreach (var edge in edges)
2273 {
2274 var newEdge = (Edge)Connect(edge.outputSlot, edge.inputSlot);
2275 if (newEdge != null)
2276 {
2277 remappedEdges.Add(newEdge);
2278 }
2279 }
2280
2281 ValidateGraph();
2282 }
2283
2284 public override void OnBeforeSerialize()
2285 {
2286 m_Edges.Sort();
2287 ChangeVersion(latestVersion);
2288 }
2289
2290 static T DeserializeLegacy<T>(string typeString, string json, Guid? overrideObjectId = null) where T : JsonObject
2291 {
2292 var jsonObj = MultiJsonInternal.CreateInstanceForDeserialization(typeString);
2293 var value = jsonObj as T;
2294 if (value == null)
2295 {
2296 Debug.Log($"Cannot create instance for {typeString}");
2297 return null;
2298 }
2299
2300 // by default, MultiJsonInternal.CreateInstance will create a new objectID randomly..
2301 // we need some created objects to have deterministic objectIDs, because they affect the generated shader.
2302 // if the generated shader is not deterministic, it can create ripple effects (i.e. causing Materials to be modified randomly as properties are renamed)
2303 // so we provide this path to allow the calling code to override the objectID with something deterministic
2304 if (overrideObjectId.HasValue)
2305 value.OverrideObjectId(overrideObjectId.Value.ToString("N"));
2306 MultiJsonInternal.Enqueue(value, json);
2307 return value as T;
2308 }
2309
2310 static AbstractMaterialNode DeserializeLegacyNode(string typeString, string json, Guid? overrideObjectId = null)
2311 {
2312 var jsonObj = MultiJsonInternal.CreateInstanceForDeserialization(typeString);
2313 var value = jsonObj as AbstractMaterialNode;
2314 if (value == null)
2315 {
2316 //Special case - want to support nodes of unknwon type for cross pipeline compatability
2317 value = new LegacyUnknownTypeNode(typeString, json);
2318 if (overrideObjectId.HasValue)
2319 value.OverrideObjectId(overrideObjectId.Value.ToString("N"));
2320 MultiJsonInternal.Enqueue(value, json);
2321 return value as AbstractMaterialNode;
2322 }
2323 else
2324 {
2325 if (overrideObjectId.HasValue)
2326 value.OverrideObjectId(overrideObjectId.Value.ToString("N"));
2327 MultiJsonInternal.Enqueue(value, json);
2328 return value as AbstractMaterialNode;
2329 }
2330 }
2331
2332 public override void OnAfterDeserialize(string json)
2333 {
2334 if (sgVersion == 0)
2335 {
2336 var graphData0 = JsonUtility.FromJson<GraphData0>(json);
2337 //If a graph was previously updated to V2, since we had to rename m_Version to m_SGVersion to avoid collision with an upgrade system from
2338 //HDRP, we have to handle the case that our version might not be correct -
2339 if (graphData0.m_Version > 0)
2340 {
2341 sgVersion = graphData0.m_Version;
2342 }
2343 else
2344 {
2345 // graphData.m_Version == 0 (matches current sgVersion)
2346 Guid assetGuid;
2347 if (!Guid.TryParse(this.assetGuid, out assetGuid))
2348 assetGuid = JsonObject.GenerateNamespaceUUID(Guid.Empty, json);
2349
2350 var nodeGuidMap = new Dictionary<string, AbstractMaterialNode>();
2351 var propertyGuidMap = new Dictionary<string, AbstractShaderProperty>();
2352 var keywordGuidMap = new Dictionary<string, ShaderKeyword>();
2353 var groupGuidMap = new Dictionary<string, GroupData>();
2354 var slotsField = typeof(AbstractMaterialNode).GetField("m_Slots", BindingFlags.Instance | BindingFlags.NonPublic);
2355 var propertyField = typeof(PropertyNode).GetField("m_Property", BindingFlags.Instance | BindingFlags.NonPublic);
2356 var keywordField = typeof(KeywordNode).GetField("m_Keyword", BindingFlags.Instance | BindingFlags.NonPublic);
2357 var dropdownField = typeof(DropdownNode).GetField("m_Dropdown", BindingFlags.Instance | BindingFlags.NonPublic);
2358 var defaultReferenceNameField = typeof(ShaderInput).GetField("m_DefaultReferenceName", BindingFlags.Instance | BindingFlags.NonPublic);
2359
2360 m_GroupDatas.Clear();
2361 m_StickyNoteDatas.Clear();
2362
2363 foreach (var group0 in graphData0.m_Groups)
2364 {
2365 var group = new GroupData(group0.m_Title, group0.m_Position);
2366 m_GroupDatas.Add(group);
2367 if (!groupGuidMap.ContainsKey(group0.m_GuidSerialized))
2368 {
2369 groupGuidMap.Add(group0.m_GuidSerialized, group);
2370 }
2371 else if (!groupGuidMap[group0.m_GuidSerialized].Equals(group.objectId))
2372 {
2373 Debug.LogError("Group id mismatch");
2374 }
2375 }
2376
2377 foreach (var serializedProperty in graphData0.m_SerializedProperties)
2378 {
2379 var propObjectId = JsonObject.GenerateNamespaceUUID(assetGuid, serializedProperty.JSONnodeData);
2380 var property = DeserializeLegacy<AbstractShaderProperty>(serializedProperty.typeInfo.fullName, serializedProperty.JSONnodeData, propObjectId);
2381 if (property == null)
2382 continue;
2383
2384 m_Properties.Add(property);
2385
2386 var input0 = JsonUtility.FromJson<ShaderInput0>(serializedProperty.JSONnodeData);
2387 propertyGuidMap[input0.m_Guid.m_GuidSerialized] = property;
2388
2389 // Fix up missing reference names
2390 // Properties on Sub Graphs in V0 never have reference names serialized
2391 // To maintain Sub Graph node property mapping we force guid based reference names on upgrade
2392 if (string.IsNullOrEmpty((string)defaultReferenceNameField.GetValue(property)))
2393 {
2394 // ColorShaderProperty is the only Property case where `GetDefaultReferenceName` was overriden
2395 if (MultiJson.ParseType(serializedProperty.typeInfo.fullName) == typeof(ColorShaderProperty))
2396 {
2397 defaultReferenceNameField.SetValue(property, $"Color_{GuidEncoder.Encode(Guid.Parse(input0.m_Guid.m_GuidSerialized))}");
2398 }
2399 else
2400 {
2401 defaultReferenceNameField.SetValue(property, $"{property.concreteShaderValueType}_{GuidEncoder.Encode(Guid.Parse(input0.m_Guid.m_GuidSerialized))}");
2402 }
2403 }
2404 }
2405
2406 foreach (var serializedKeyword in graphData0.m_SerializedKeywords)
2407 {
2408 var keyword = DeserializeLegacy<ShaderKeyword>(serializedKeyword.typeInfo.fullName, serializedKeyword.JSONnodeData);
2409 if (keyword == null)
2410 {
2411 continue;
2412 }
2413
2414 m_Keywords.Add(keyword);
2415
2416 var input0 = JsonUtility.FromJson<ShaderInput0>(serializedKeyword.JSONnodeData);
2417 keywordGuidMap[input0.m_Guid.m_GuidSerialized] = keyword;
2418 }
2419
2420 foreach (var serializedNode in graphData0.m_SerializableNodes)
2421 {
2422 var node0 = JsonUtility.FromJson<AbstractMaterialNode0>(serializedNode.JSONnodeData);
2423
2424 var nodeObjectId = JsonObject.GenerateNamespaceUUID(node0.m_GuidSerialized, "node");
2425 var node = DeserializeLegacyNode(serializedNode.typeInfo.fullName, serializedNode.JSONnodeData, nodeObjectId);
2426 if (node == null)
2427 {
2428 continue;
2429 }
2430
2431 nodeGuidMap.Add(node0.m_GuidSerialized, node);
2432 m_Nodes.Add(node);
2433
2434 if (!string.IsNullOrEmpty(node0.m_PropertyGuidSerialized) && propertyGuidMap.TryGetValue(node0.m_PropertyGuidSerialized, out var property))
2435 {
2436 propertyField.SetValue(node, (JsonRef<AbstractShaderProperty>)property);
2437 }
2438
2439 if (!string.IsNullOrEmpty(node0.m_KeywordGuidSerialized) && keywordGuidMap.TryGetValue(node0.m_KeywordGuidSerialized, out var keyword))
2440 {
2441 keywordField.SetValue(node, (JsonRef<ShaderKeyword>)keyword);
2442 }
2443
2444 var slots = (List<JsonData<MaterialSlot>>)slotsField.GetValue(node);
2445 slots.Clear();
2446
2447 foreach (var serializedSlot in node0.m_SerializableSlots)
2448 {
2449 var slotObjectId = JsonObject.GenerateNamespaceUUID(node0.m_GuidSerialized, serializedSlot.JSONnodeData);
2450 var slot = DeserializeLegacy<MaterialSlot>(serializedSlot.typeInfo.fullName, serializedSlot.JSONnodeData, slotObjectId);
2451 if (slot == null)
2452 {
2453 continue;
2454 }
2455
2456 slots.Add(slot);
2457 }
2458
2459 if (!String.IsNullOrEmpty(node0.m_GroupGuidSerialized))
2460 {
2461 if (groupGuidMap.TryGetValue(node0.m_GroupGuidSerialized, out GroupData foundGroup))
2462 {
2463 node.group = foundGroup;
2464 }
2465 }
2466 }
2467
2468 foreach (var stickyNote0 in graphData0.m_StickyNotes)
2469 {
2470 var stickyNote = new StickyNoteData(stickyNote0.m_Title, stickyNote0.m_Content, stickyNote0.m_Position);
2471 if (!String.IsNullOrEmpty(stickyNote0.m_GroupGuidSerialized))
2472 {
2473 if (groupGuidMap.TryGetValue(stickyNote0.m_GroupGuidSerialized, out GroupData foundGroup))
2474 {
2475 stickyNote.group = foundGroup;
2476 }
2477 }
2478 stickyNote.theme = stickyNote0.m_Theme;
2479 stickyNote.textSize = stickyNote0.m_TextSize;
2480 m_StickyNoteDatas.Add(stickyNote);
2481 }
2482
2483 var subgraphOuput = GetNodes<SubGraphOutputNode>();
2484 isSubGraph = subgraphOuput.Any();
2485
2486 if (isSubGraph)
2487 {
2488 m_OutputNode = subgraphOuput.FirstOrDefault();
2489 }
2490 else if (!string.IsNullOrEmpty(graphData0.m_ActiveOutputNodeGuidSerialized))
2491 {
2492 m_OutputNode = nodeGuidMap[graphData0.m_ActiveOutputNodeGuidSerialized];
2493 }
2494 else
2495 {
2496 m_OutputNode = (AbstractMaterialNode)GetNodes<IMasterNode1>().FirstOrDefault();
2497 }
2498
2499 foreach (var serializedElement in graphData0.m_SerializableEdges)
2500 {
2501 var edge0 = JsonUtility.FromJson<Edge0>(serializedElement.JSONnodeData);
2502 m_Edges.Add(new Edge(
2503 new SlotReference(
2504 nodeGuidMap[edge0.m_OutputSlot.m_NodeGUIDSerialized],
2505 edge0.m_OutputSlot.m_SlotId),
2506 new SlotReference(
2507 nodeGuidMap[edge0.m_InputSlot.m_NodeGUIDSerialized],
2508 edge0.m_InputSlot.m_SlotId)));
2509 }
2510 }
2511 }
2512 }
2513
2514 [Serializable]
2515 class OldGraphDataReadConcretePrecision
2516 {
2517 // old value just for upgrade
2518 [SerializeField]
2519 public ConcretePrecision m_ConcretePrecision = ConcretePrecision.Single;
2520 };
2521
2522 public override void OnAfterMultiDeserialize(string json)
2523 {
2524 // Deferred upgrades
2525 if (sgVersion != latestVersion)
2526 {
2527 if (sgVersion < 2)
2528 {
2529 var addedBlocks = ListPool<BlockFieldDescriptor>.Get();
2530
2531 void UpgradeFromBlockMap(Dictionary<BlockFieldDescriptor, int> blockMap)
2532 {
2533 // Map master node ports to blocks
2534 if (blockMap != null)
2535 {
2536 foreach (var blockMapping in blockMap)
2537 {
2538 // Create a new BlockNode for each unique map entry
2539 var descriptor = blockMapping.Key;
2540 if (addedBlocks.Contains(descriptor))
2541 continue;
2542
2543 addedBlocks.Add(descriptor);
2544
2545 var contextData = descriptor.shaderStage == ShaderStage.Fragment ? m_FragmentContext : m_VertexContext;
2546 var block = (BlockNode)Activator.CreateInstance(typeof(BlockNode));
2547 block.Init(descriptor);
2548 AddBlockNoValidate(block, contextData, contextData.blocks.Count);
2549
2550 // To avoid having to go around the following deserialization code
2551 // We simply run OnBeforeSerialization here to ensure m_SerializedDescriptor is set
2552 block.OnBeforeSerialize();
2553
2554 // Now remap the incoming edges to blocks
2555 var slotId = blockMapping.Value;
2556 var oldSlot = m_OutputNode.value.FindSlot<MaterialSlot>(slotId);
2557 var newSlot = block.FindSlot<MaterialSlot>(0);
2558 if (oldSlot == null)
2559 continue;
2560
2561 var oldInputSlotRef = m_OutputNode.value.GetSlotReference(slotId);
2562 var newInputSlotRef = block.GetSlotReference(0);
2563
2564 // Always copy the value over for convenience
2565 newSlot.CopyValuesFrom(oldSlot);
2566
2567 for (int i = 0; i < m_Edges.Count; i++)
2568 {
2569 // Find all edges connected to the master node using slot ID from the block map
2570 // Remove them and replace them with new edges connected to the block nodes
2571 var edge = m_Edges[i];
2572 if (edge.inputSlot.Equals(oldInputSlotRef))
2573 {
2574 var outputSlot = edge.outputSlot;
2575 m_Edges.Remove(edge);
2576 m_Edges.Add(new Edge(outputSlot, newInputSlotRef));
2577 }
2578 }
2579
2580 // manually handle a bug where fragment normal slots could get out of sync of the master node's set fragment normal space
2581 if (descriptor == BlockFields.SurfaceDescription.NormalOS)
2582 {
2583 NormalMaterialSlot norm = newSlot as NormalMaterialSlot;
2584 if (norm.space != CoordinateSpace.Object)
2585 {
2586 norm.space = CoordinateSpace.Object;
2587 }
2588 }
2589 else if (descriptor == BlockFields.SurfaceDescription.NormalTS)
2590 {
2591 NormalMaterialSlot norm = newSlot as NormalMaterialSlot;
2592 if (norm.space != CoordinateSpace.Tangent)
2593 {
2594 norm.space = CoordinateSpace.Tangent;
2595 }
2596 }
2597 else if (descriptor == BlockFields.SurfaceDescription.NormalWS)
2598 {
2599 NormalMaterialSlot norm = newSlot as NormalMaterialSlot;
2600 if (norm.space != CoordinateSpace.World)
2601 {
2602 norm.space = CoordinateSpace.World;
2603 }
2604 }
2605 }
2606
2607 // We need to call AddBlockNoValidate but this adds to m_AddedNodes resulting in duplicates
2608 // Therefore we need to clear this list before the view is created
2609 m_AddedNodes.Clear();
2610 }
2611 }
2612
2613 var masterNode = m_OutputNode.value as IMasterNode1;
2614
2615 // This is required for edge lookup during Target upgrade
2616 if (m_OutputNode.value != null)
2617 {
2618 m_OutputNode.value.owner = this;
2619 }
2620 foreach (var edge in m_Edges)
2621 {
2622 AddEdgeToNodeEdges(edge);
2623 }
2624
2625 // Ensure correct initialization of Contexts
2626 AddContexts();
2627
2628 // Position Contexts to the match master node
2629 var oldPosition = Vector2.zero;
2630 if (m_OutputNode.value != null)
2631 {
2632 oldPosition = m_OutputNode.value.drawState.position.position;
2633 }
2634 m_VertexContext.position = oldPosition;
2635 m_FragmentContext.position = new Vector2(oldPosition.x, oldPosition.y + 200);
2636
2637 // Try to upgrade all potential targets from master node
2638 if (masterNode != null)
2639 {
2640 foreach (var potentialTarget in m_AllPotentialTargets)
2641 {
2642 if (potentialTarget.IsUnknown())
2643 continue;
2644
2645 var target = potentialTarget.GetTarget();
2646 if (!(target is ILegacyTarget legacyTarget))
2647 continue;
2648
2649 if (!legacyTarget.TryUpgradeFromMasterNode(masterNode, out var newBlockMap))
2650 continue;
2651
2652 // upgrade succeeded! Activate it
2653 SetTargetActive(target, true);
2654 UpgradeFromBlockMap(newBlockMap);
2655 }
2656 SortActiveTargets();
2657 }
2658
2659 // Clean up after upgrade
2660 if (!isSubGraph)
2661 {
2662 m_OutputNode = null;
2663 }
2664
2665 var masterNodes = GetNodes<IMasterNode1>().ToArray();
2666 for (int i = 0; i < masterNodes.Length; i++)
2667 {
2668 var node = masterNodes.ElementAt(i) as AbstractMaterialNode;
2669 m_Nodes.Remove(node);
2670 }
2671
2672 m_NodeEdges.Clear();
2673 }
2674
2675 if (sgVersion < 3)
2676 {
2677 var oldGraph = JsonUtility.FromJson<OldGraphDataReadConcretePrecision>(json);
2678
2679 // upgrade concrete precision to the new graph precision
2680 switch (oldGraph.m_ConcretePrecision)
2681 {
2682 case ConcretePrecision.Half:
2683 m_GraphPrecision = GraphPrecision.Half;
2684 break;
2685 case ConcretePrecision.Single:
2686 m_GraphPrecision = GraphPrecision.Single;
2687 break;
2688 }
2689 }
2690
2691 ChangeVersion(latestVersion);
2692 }
2693
2694 PooledList<(LegacyUnknownTypeNode, AbstractMaterialNode)> updatedNodes = PooledList<(LegacyUnknownTypeNode, AbstractMaterialNode)>.Get();
2695 foreach (var node in m_Nodes.SelectValue())
2696 {
2697 if (node is LegacyUnknownTypeNode lNode && lNode.foundType != null)
2698 {
2699 AbstractMaterialNode legacyNode = (AbstractMaterialNode)Activator.CreateInstance(lNode.foundType);
2700 JsonUtility.FromJsonOverwrite(lNode.serializedData, legacyNode);
2701 legacyNode.group = lNode.group;
2702 updatedNodes.Add((lNode, legacyNode));
2703 }
2704 }
2705 foreach (var nodePair in updatedNodes)
2706 {
2707 m_Nodes.Add(nodePair.Item2);
2708 ReplaceNodeWithNode(nodePair.Item1, nodePair.Item2);
2709 }
2710 updatedNodes.Dispose();
2711
2712 m_NodeDictionary = new Dictionary<string, AbstractMaterialNode>(m_Nodes.Count);
2713
2714 foreach (var group in m_GroupDatas.SelectValue())
2715 {
2716 m_GroupItems.Add(group, new List<IGroupItem>());
2717 }
2718
2719 foreach (var node in m_Nodes.SelectValue())
2720 {
2721 node.owner = this;
2722 node.UpdateNodeAfterDeserialization();
2723 node.SetupSlots();
2724 m_NodeDictionary.Add(node.objectId, node);
2725 if (m_GroupItems.TryGetValue(node.group, out var groupItems))
2726 {
2727 groupItems.Add(node);
2728 }
2729 else
2730 {
2731 node.group = null;
2732 }
2733 }
2734
2735 foreach (var stickyNote in m_StickyNoteDatas.SelectValue())
2736 {
2737 if (m_GroupItems.TryGetValue(stickyNote.group, out var groupItems))
2738 {
2739 groupItems.Add(stickyNote);
2740 }
2741 else
2742 {
2743 stickyNote.group = null;
2744 }
2745 }
2746
2747 foreach (var edge in m_Edges)
2748 AddEdgeToNodeEdges(edge);
2749
2750 // --------------------------------------------------
2751 // Deserialize Contexts & Blocks
2752
2753 void DeserializeContextData(ContextData contextData, ShaderStage stage)
2754 {
2755 // Because Vertex/Fragment Contexts are serialized explicitly
2756 // we do not need to serialize the Stage value on the ContextData
2757 contextData.shaderStage = stage;
2758
2759 var blocks = contextData.blocks.SelectValue().ToList();
2760 var blockCount = blocks.Count;
2761 for (int i = 0; i < blockCount; i++)
2762 {
2763 // Update NonSerialized data on the BlockNode
2764 var block = blocks[i];
2765 // custom interpolators fully regenerate their own descriptor on deserialization
2766 if (!block.isCustomBlock)
2767 {
2768 block.descriptor = m_BlockFieldDescriptors.FirstOrDefault(x => $"{x.tag}.{x.name}" == block.serializedDescriptor);
2769 }
2770 if (block.descriptor == null)
2771 {
2772 //Hit a descriptor that was not recognized from the assembly (likely from a different SRP)
2773 //create a new entry for it and continue on
2774 if (string.IsNullOrEmpty(block.serializedDescriptor))
2775 {
2776 throw new Exception($"Block {block} had no serialized descriptor");
2777 }
2778
2779 var tmp = block.serializedDescriptor.Split('.');
2780 if (tmp.Length != 2)
2781 {
2782 throw new Exception($"Block {block}'s serialized descriptor {block.serializedDescriptor} did not match expected format {{x.tag}}.{{x.name}}");
2783 }
2784 //right thing to do?
2785 block.descriptor = new BlockFieldDescriptor(tmp[0], tmp[1], null, null, stage, true, true);
2786 m_BlockFieldDescriptors.Add(block.descriptor);
2787 }
2788 block.contextData = contextData;
2789 }
2790 }
2791
2792 // First deserialize the ContextDatas
2793 DeserializeContextData(m_VertexContext, ShaderStage.Vertex);
2794 DeserializeContextData(m_FragmentContext, ShaderStage.Fragment);
2795
2796 // there should be no unknown potential targets at this point
2797 Assert.IsFalse(m_AllPotentialTargets.Any(pt => pt.IsUnknown()));
2798
2799 foreach (var target in m_ActiveTargets.SelectValue())
2800 {
2801 var targetType = target.GetType();
2802 if (targetType == typeof(MultiJsonInternal.UnknownTargetType))
2803 {
2804 // register any active UnknownTargetType as a potential target
2805 m_AllPotentialTargets.Add(new PotentialTarget(target));
2806 }
2807 else
2808 {
2809 // active known targets should replace the stored Target in AllPotentialTargets
2810 int targetIndex = m_AllPotentialTargets.FindIndex(pt => pt.knownType == targetType);
2811 m_AllPotentialTargets[targetIndex].ReplaceStoredTarget(target);
2812 }
2813 }
2814
2815 SortActiveTargets();
2816 }
2817
2818 private void ReplaceNodeWithNode(LegacyUnknownTypeNode nodeToReplace, AbstractMaterialNode nodeReplacement)
2819 {
2820 var oldSlots = new List<MaterialSlot>();
2821 nodeToReplace.GetSlots(oldSlots);
2822 var newSlots = new List<MaterialSlot>();
2823 nodeReplacement.GetSlots(newSlots);
2824
2825 for (int i = 0; i < oldSlots.Count; i++)
2826 {
2827 newSlots[i].CopyValuesFrom(oldSlots[i]);
2828 var oldSlotRef = nodeToReplace.GetSlotReference(oldSlots[i].id);
2829 var newSlotRef = nodeReplacement.GetSlotReference(newSlots[i].id);
2830
2831 for (int x = 0; x < m_Edges.Count; x++)
2832 {
2833 var edge = m_Edges[x];
2834 if (edge.inputSlot.Equals(oldSlotRef))
2835 {
2836 var outputSlot = edge.outputSlot;
2837 m_Edges.Remove(edge);
2838 m_Edges.Add(new Edge(outputSlot, newSlotRef));
2839 }
2840 else if (edge.outputSlot.Equals(oldSlotRef))
2841 {
2842 var inputSlot = edge.inputSlot;
2843 m_Edges.Remove(edge);
2844 m_Edges.Add(new Edge(newSlotRef, inputSlot));
2845 }
2846 }
2847 }
2848 }
2849
2850 public void OnEnable()
2851 {
2852 foreach (var node in GetNodes<AbstractMaterialNode>().OfType<IOnAssetEnabled>())
2853 {
2854 node.OnEnable();
2855 }
2856
2857 ShaderGraphPreferences.onVariantLimitChanged += OnKeywordChanged;
2858 }
2859
2860 public void OnDisable()
2861 {
2862 ShaderGraphPreferences.onVariantLimitChanged -= OnKeywordChanged;
2863
2864 foreach (var node in GetNodes<AbstractMaterialNode>())
2865 node.Dispose();
2866 }
2867
2868 internal void ValidateCustomBlockLimit()
2869 {
2870 if (m_ActiveTargets.Count() == 0)
2871 return;
2872
2873 int nonCustomUsage = 0;
2874 foreach (var bnode in vertexContext.blocks.Where(jb => !jb.value.isCustomBlock).Select(b => b.value))
2875 {
2876 if (bnode == null || bnode.descriptor == null)
2877 continue;
2878
2879 if (bnode.descriptor.HasPreprocessor() || bnode.descriptor.HasSemantic() || bnode.descriptor.vectorCount == 0) // not packable.
2880 nonCustomUsage += 4;
2881 else nonCustomUsage += bnode.descriptor.vectorCount;
2882 }
2883 int maxTargetUsage = m_ActiveTargets.Select(jt => jt.value.padCustomInterpolatorLimit).Max() * 4;
2884
2885 int padding = nonCustomUsage + maxTargetUsage;
2886
2887 int errRange = ShaderGraphProjectSettings.instance.customInterpolatorErrorThreshold;
2888 int warnRange = ShaderGraphProjectSettings.instance.customInterpolatorWarningThreshold;
2889
2890 int errorLevel = errRange * 4 - padding;
2891 int warnLevel = warnRange * 4 - padding;
2892
2893 int total = 0;
2894
2895 // warn based on the interpolator's location in the block list.
2896 foreach (var cib in vertexContext.blocks.Where(jb => jb.value.isCustomBlock).Select(b => b.value))
2897 {
2898 ClearErrorsForNode(cib);
2899 total += (int)cib.customWidth;
2900 if (total > errorLevel)
2901 {
2902 AddValidationError(cib.objectId, $"{cib.customName} exceeds the interpolation channel error threshold: {errRange}. See ShaderGraph project settings.");
2903 }
2904 else if (total > warnLevel)
2905 {
2906 AddValidationError(cib.objectId, $"{cib.customName} exceeds the interpolation channel warning threshold: {warnRange}. See ShaderGraph project settings.", ShaderCompilerMessageSeverity.Warning);
2907 }
2908 }
2909 }
2910
2911 void ValidateContextBlocks()
2912 {
2913 void ValidateContext(ContextData contextData, ShaderStage expectedShaderStage)
2914 {
2915 if (contextData == null)
2916 return;
2917
2918 foreach (var block in contextData.blocks)
2919 {
2920 var slots = block.value.GetInputSlots<MaterialSlot>();
2921 foreach (var slot in slots)
2922 FindAndReportSlotErrors(slot, expectedShaderStage);
2923 }
2924 };
2925
2926 ValidateContext(vertexContext, ShaderStage.Vertex);
2927 ValidateContext(fragmentContext, ShaderStage.Fragment);
2928 }
2929
2930 void FindAndReportSlotErrors(MaterialSlot initialSlot, ShaderStage expectedShaderStage)
2931 {
2932 var expectedCapability = expectedShaderStage.GetShaderStageCapability();
2933 var errorSourceSlots = new HashSet<MaterialSlot>();
2934 var visitedNodes = new HashSet<AbstractMaterialNode>();
2935
2936 var graph = initialSlot.owner.owner;
2937 var slotStack = new Stack<MaterialSlot>();
2938 slotStack.Clear();
2939 slotStack.Push(initialSlot);
2940
2941 // Trace back and find any edges that introduce an error
2942 while (slotStack.Any())
2943 {
2944 var slot = slotStack.Pop();
2945
2946 // If the slot is an input, jump across the connected edge to the output it's connected to
2947 if (slot.isInputSlot)
2948 {
2949 foreach (var edge in graph.GetEdges(slot.slotReference))
2950 {
2951 var node = edge.outputSlot.node;
2952
2953 var outputSlot = node.FindOutputSlot<MaterialSlot>(edge.outputSlot.slotId);
2954 // If the output slot this is connected to is invalid then this is a source of an error.
2955 // Mark the slot and stop iterating, otherwise continue the recursion
2956 if (!outputSlot.stageCapability.HasFlag(expectedCapability))
2957 errorSourceSlots.Add(outputSlot);
2958 else
2959 slotStack.Push(outputSlot);
2960 }
2961 }
2962 else
2963 {
2964 // No need to double visit nodes
2965 if (visitedNodes.Contains(slot.owner))
2966 continue;
2967 visitedNodes.Add(slot.owner);
2968
2969 var ownerSlots = slot.owner.GetInputSlots<MaterialSlot>(slot);
2970 foreach (var ownerSlot in ownerSlots)
2971 slotStack.Push(ownerSlot);
2972 }
2973 }
2974
2975 bool IsEntireNodeStageLocked(AbstractMaterialNode node, ShaderStageCapability expectedNodeCapability)
2976 {
2977 var slots = node.GetOutputSlots<MaterialSlot>();
2978 foreach (var slot in slots)
2979 {
2980 if (expectedNodeCapability != slot.stageCapability)
2981 return false;
2982 }
2983 return true;
2984 };
2985
2986 foreach (var errorSourceSlot in errorSourceSlots)
2987 {
2988 var errorNode = errorSourceSlot.owner;
2989
2990 // Determine if only one slot or the entire node is at fault. Currently only slots are
2991 // denoted with stage capabilities so deduce this by checking all outputs
2992 string errorSource;
2993 if (IsEntireNodeStageLocked(errorNode, errorSourceSlot.stageCapability))
2994 errorSource = $"Node {errorNode.name}";
2995 else
2996 errorSource = $"Slot {errorSourceSlot.RawDisplayName()}";
2997
2998 // Determine what action they can take. If the stage capability is None then this can't be connected to anything.
2999 string actionToTake;
3000 if (errorSourceSlot.stageCapability != ShaderStageCapability.None)
3001 {
3002 var validStageName = errorSourceSlot.stageCapability.ToString().ToLower();
3003 actionToTake = $"reconnect to a {validStageName} block or delete invalid connection";
3004 }
3005 else
3006 actionToTake = "delete invalid connection";
3007
3008 var invalidStageName = expectedShaderStage.ToString().ToLower();
3009 string message = $"{errorSource} is not compatible with {invalidStageName} block {initialSlot.RawDisplayName()}, {actionToTake}.";
3010 AddValidationError(errorNode.objectId, message, ShaderCompilerMessageSeverity.Error);
3011 }
3012 }
3013 }
3014
3015 [Serializable]
3016 class InspectorPreviewData
3017 {
3018 public SerializableMesh serializedMesh = new SerializableMesh();
3019 public bool preventRotation;
3020
3021 [NonSerialized]
3022 public Quaternion rotation = Quaternion.identity;
3023
3024 [NonSerialized]
3025 public float scale = 1f;
3026 }
3027}