A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using UnityEngine;
5using UnityEditor.Graphing;
6using UnityEditor.ShaderGraph.Drawing.Colors;
7using UnityEditor.ShaderGraph.Internal;
8using UnityEditor.ShaderGraph.Drawing;
9using UnityEditor.ShaderGraph.Serialization;
10using UnityEngine.Assertions;
11using UnityEngine.Pool;
12
13namespace UnityEditor.ShaderGraph
14{
15 [Serializable]
16 abstract class AbstractMaterialNode : JsonObject, IGroupItem, IRectInterface
17 {
18 [SerializeField]
19 JsonRef<GroupData> m_Group = null;
20
21 [SerializeField]
22 private string m_Name;
23
24 [SerializeField]
25 private DrawState m_DrawState;
26
27 [NonSerialized]
28 bool m_HasError;
29
30 [NonSerialized]
31 bool m_IsValid = true;
32
33 [NonSerialized]
34 bool m_IsActive = true;
35
36 [NonSerialized]
37 bool m_WasUsedByGenerator = false;
38
39 [SerializeField]
40 List<JsonData<MaterialSlot>> m_Slots = new List<JsonData<MaterialSlot>>();
41
42 public GraphData owner { get; set; }
43
44 internal virtual bool ExposeToSearcher => true;
45
46 OnNodeModified m_OnModified;
47
48 Action m_UnregisterAll;
49
50 public GroupData group
51 {
52 get => m_Group;
53 set
54 {
55 if (m_Group == value)
56 return;
57
58 m_Group = value;
59 Dirty(ModificationScope.Topological);
60 }
61 }
62
63 public void RegisterCallback(OnNodeModified callback)
64 {
65 m_OnModified += callback;
66
67 // Setup so we can unregister this callback later at teardown time
68 m_UnregisterAll += () => m_OnModified -= callback;
69 }
70
71 public void UnregisterCallback(OnNodeModified callback)
72 {
73 m_OnModified -= callback;
74 }
75
76 public void Dirty(ModificationScope scope)
77 {
78 // Calling m_OnModified immediately upon dirtying the node can result in a lot of churn. For example,
79 // nodes can cause cascading view updates *multiple times* per operation.
80 // If this call causes future performance issues, we should investigate some kind of deferral or early out
81 // until all of the dirty nodes have been identified.
82 if (m_OnModified != null && !owner.replaceInProgress)
83 m_OnModified(this, scope);
84 NodeValidation.HandleValidationExtensions(this);
85 }
86
87 public string name
88 {
89 get { return m_Name; }
90 set { m_Name = value; }
91 }
92
93 public virtual string displayName => name;
94
95 public string[] synonyms;
96
97 protected virtual string documentationPage => name;
98 public virtual string documentationURL => NodeUtils.GetDocumentationString(documentationPage);
99
100 public virtual bool canDeleteNode => owner != null && owner.outputNode != this;
101
102 public DrawState drawState
103 {
104 get { return m_DrawState; }
105 set
106 {
107 m_DrawState = value;
108 Dirty(ModificationScope.Layout);
109 }
110 }
111
112 Rect IRectInterface.rect
113 {
114 get => drawState.position;
115 set
116 {
117 var state = drawState;
118 state.position = value;
119 drawState = state;
120 }
121 }
122
123 public virtual bool canSetPrecision
124 {
125 get { return true; }
126 }
127
128 // this is the precision after the inherit/automatic behavior has been calculated
129 // it does NOT include fallback to any graph default precision
130 public GraphPrecision graphPrecision { get; set; } = GraphPrecision.Single;
131
132 private ConcretePrecision m_ConcretePrecision = ConcretePrecision.Single;
133
134 public ConcretePrecision concretePrecision
135 {
136 get => m_ConcretePrecision;
137 set => m_ConcretePrecision = value;
138 }
139
140 [SerializeField]
141 private Precision m_Precision = Precision.Inherit;
142
143 public Precision precision
144 {
145 get => m_Precision;
146 set => m_Precision = value;
147 }
148
149 [SerializeField]
150 bool m_PreviewExpanded = true;
151
152 public bool previewExpanded
153 {
154 get { return m_PreviewExpanded; }
155 set
156 {
157 if (previewExpanded == value)
158 return;
159 m_PreviewExpanded = value;
160 Dirty(ModificationScope.Node);
161 }
162 }
163
164 [SerializeField]
165 protected int m_DismissedVersion = 0;
166 public int dismissedUpdateVersion { get => m_DismissedVersion; set => m_DismissedVersion = value; }
167
168 // by default, if this returns null, the system will allow creation of any previous version
169 public virtual IEnumerable<int> allowedNodeVersions => null;
170
171 // Nodes that want to have a preview area can override this and return true
172 public virtual bool hasPreview
173 {
174 get { return false; }
175 }
176
177 [SerializeField]
178 internal PreviewMode m_PreviewMode = PreviewMode.Inherit;
179 public virtual PreviewMode previewMode
180 {
181 get { return m_PreviewMode; }
182 }
183
184 public virtual bool allowedInSubGraph
185 {
186 get { return !(this is BlockNode); }
187 }
188
189 public virtual bool allowedInMainGraph
190 {
191 get { return true; }
192 }
193
194 public virtual bool allowedInLayerGraph
195 {
196 get { return true; }
197 }
198
199 public virtual bool hasError
200 {
201 get { return m_HasError; }
202 protected set { m_HasError = value; }
203 }
204
205 public virtual bool isActive
206 {
207 get { return m_IsActive; }
208 }
209
210 internal virtual bool wasUsedByGenerator
211 {
212 get { return m_WasUsedByGenerator; }
213 }
214
215 internal void SetUsedByGenerator()
216 {
217 m_WasUsedByGenerator = true;
218 }
219
220 //There are times when isActive needs to be set to a value explicitly, and
221 //not be changed by active forest parsing (what we do when we need to figure out
222 //what nodes should or should not be active, usually from an edit; see NodeUtils).
223 //In this case, we allow for explicit setting of an active value that cant be overriden.
224 //Implicit implies that active forest parsing can edit the nodes isActive property
225 public enum ActiveState
226 {
227 Implicit = 0,
228 ExplicitInactive = 1,
229 ExplicitActive = 2
230 }
231
232 private ActiveState m_ActiveState = ActiveState.Implicit;
233 public ActiveState activeState
234 {
235 get => m_ActiveState;
236 }
237
238 public void SetOverrideActiveState(ActiveState overrideState, bool updateConnections = true)
239 {
240 if (m_ActiveState == overrideState)
241 {
242 return;
243 }
244
245 m_ActiveState = overrideState;
246 switch (m_ActiveState)
247 {
248 case ActiveState.Implicit:
249 if (updateConnections)
250 {
251 NodeUtils.ReevaluateActivityOfConnectedNodes(this);
252 }
253 break;
254 case ActiveState.ExplicitInactive:
255 if (m_IsActive == false)
256 {
257 break;
258 }
259 else
260 {
261 m_IsActive = false;
262 Dirty(ModificationScope.Node);
263 if (updateConnections)
264 {
265 NodeUtils.ReevaluateActivityOfConnectedNodes(this);
266 }
267 break;
268 }
269 case ActiveState.ExplicitActive:
270 if (m_IsActive == true)
271 {
272 break;
273 }
274 else
275 {
276 m_IsActive = true;
277 Dirty(ModificationScope.Node);
278 if (updateConnections)
279 {
280 NodeUtils.ReevaluateActivityOfConnectedNodes(this);
281 }
282 break;
283 }
284 }
285 }
286
287 public void SetActive(bool value, bool updateConnections = true)
288 {
289 if (m_IsActive == value)
290 return;
291
292 if (m_ActiveState != ActiveState.Implicit)
293 {
294 Debug.LogError($"Cannot set IsActive on Node {this} when value is explicitly overriden by ActiveState {m_ActiveState}");
295 return;
296 }
297
298 // Update this node
299 m_IsActive = value;
300 Dirty(ModificationScope.Node);
301
302 if (updateConnections)
303 {
304 NodeUtils.ReevaluateActivityOfConnectedNodes(this);
305 }
306 }
307
308 public virtual bool isValid
309 {
310 get { return m_IsValid; }
311 set
312 {
313 if (m_IsValid == value)
314 return;
315
316 m_IsValid = value;
317 }
318 }
319
320
321 string m_DefaultVariableName;
322 string m_NameForDefaultVariableName;
323
324 string defaultVariableName
325 {
326 get
327 {
328 if (m_NameForDefaultVariableName != name)
329 {
330 m_DefaultVariableName = string.Format("{0}_{1}", NodeUtils.GetHLSLSafeName(name ?? "node"), objectId);
331 m_NameForDefaultVariableName = name;
332 }
333 return m_DefaultVariableName;
334 }
335 }
336
337 #region Custom Colors
338
339 [SerializeField]
340 CustomColorData m_CustomColors = new CustomColorData();
341
342 public bool TryGetColor(string provider, ref Color color)
343 {
344 return m_CustomColors.TryGetColor(provider, out color);
345 }
346
347 public void ResetColor(string provider)
348 {
349 m_CustomColors.Remove(provider);
350 }
351
352 public void SetColor(string provider, Color color)
353 {
354 m_CustomColors.Set(provider, color);
355 }
356
357 #endregion
358
359 protected AbstractMaterialNode()
360 {
361 m_DrawState.expanded = true;
362 }
363
364 public void GetInputSlots<T>(List<T> foundSlots) where T : MaterialSlot
365 {
366 foreach (var slot in m_Slots.SelectValue())
367 {
368 if (slot.isInputSlot && slot is T)
369 foundSlots.Add((T)slot);
370 }
371 }
372
373 public virtual void GetInputSlots<T>(MaterialSlot startingSlot, List<T> foundSlots) where T : MaterialSlot
374 {
375 GetInputSlots(foundSlots);
376 }
377
378 public void GetOutputSlots<T>(List<T> foundSlots) where T : MaterialSlot
379 {
380 foreach (var slot in m_Slots.SelectValue())
381 {
382 if (slot.isOutputSlot && slot is T materialSlot)
383 {
384 foundSlots.Add(materialSlot);
385 }
386 }
387 }
388
389 public virtual void GetOutputSlots<T>(MaterialSlot startingSlot, List<T> foundSlots) where T : MaterialSlot
390 {
391 GetOutputSlots(foundSlots);
392 }
393
394 public void GetSlots<T>(List<T> foundSlots) where T : MaterialSlot
395 {
396 foreach (var slot in m_Slots.SelectValue())
397 {
398 if (slot is T materialSlot)
399 {
400 foundSlots.Add(materialSlot);
401 }
402 }
403 }
404
405 public virtual void CollectShaderProperties(PropertyCollector properties, GenerationMode generationMode)
406 {
407 foreach (var inputSlot in this.GetInputSlots<MaterialSlot>())
408 {
409 var edges = owner.GetEdges(inputSlot.slotReference);
410 if (edges.Any(e => e.outputSlot.node.isActive))
411 continue;
412
413 inputSlot.AddDefaultProperty(properties, generationMode);
414 }
415 }
416
417 public string GetSlotValue(int inputSlotId, GenerationMode generationMode, ConcretePrecision concretePrecision)
418 {
419 string slotValue = GetSlotValue(inputSlotId, generationMode);
420 return slotValue.Replace(PrecisionUtil.Token, concretePrecision.ToShaderString());
421 }
422
423 public string GetSlotValue(int inputSlotId, GenerationMode generationMode)
424 {
425 var inputSlot = FindSlot<MaterialSlot>(inputSlotId);
426 if (inputSlot == null)
427 return string.Empty;
428
429 var edges = owner.GetEdges(inputSlot.slotReference);
430
431 if (edges.Any())
432 {
433 var fromSocketRef = edges.First().outputSlot;
434 var fromNode = fromSocketRef.node;
435 return fromNode.GetOutputForSlot(fromSocketRef, inputSlot.concreteValueType, generationMode);
436 }
437
438 return inputSlot.GetDefaultValue(generationMode);
439 }
440
441 public AbstractShaderProperty GetSlotProperty(int inputSlotId)
442 {
443 if (owner == null)
444 return null;
445
446 var inputSlot = FindSlot<MaterialSlot>(inputSlotId);
447 if (inputSlot?.slotReference.node == null)
448 return null;
449
450 var edges = owner.GetEdges(inputSlot.slotReference);
451 if (edges.Any())
452 {
453 var fromSocketRef = edges.First().outputSlot;
454 var fromNode = fromSocketRef.node;
455 if (fromNode == null)
456 return null; // this is an error condition... we have an edge that connects to a non-existant node?
457
458 if (fromNode is PropertyNode propNode)
459 {
460 return propNode.property;
461 }
462
463 if (fromNode is RedirectNodeData redirectNode)
464 {
465 return redirectNode.GetSlotProperty(RedirectNodeData.kInputSlotID);
466 }
467
468#if PROCEDURAL_VT_IN_GRAPH
469 if (fromNode is ProceduralVirtualTextureNode pvtNode)
470 {
471 return pvtNode.AsShaderProperty();
472 }
473#endif // PROCEDURAL_VT_IN_GRAPH
474
475 return null;
476 }
477
478 return null;
479 }
480
481 protected internal virtual string GetOutputForSlot(SlotReference fromSocketRef, ConcreteSlotValueType valueType, GenerationMode generationMode)
482 {
483 var slot = FindOutputSlot<MaterialSlot>(fromSocketRef.slotId);
484 if (slot == null)
485 return string.Empty;
486
487 if (fromSocketRef.node.isActive)
488 return GenerationUtils.AdaptNodeOutput(this, slot.id, valueType);
489 else
490 return slot.GetDefaultValue(generationMode);
491 }
492
493 public AbstractMaterialNode GetInputNodeFromSlot(int inputSlotId)
494 {
495 var inputSlot = FindSlot<MaterialSlot>(inputSlotId);
496 if (inputSlot == null)
497 return null;
498
499 var edges = owner.GetEdges(inputSlot.slotReference).ToArray();
500 AbstractMaterialNode fromNode = null;
501 if (edges.Count() > 0)
502 {
503 var fromSocketRef = edges[0].outputSlot;
504 fromNode = fromSocketRef.node;
505 }
506 return fromNode;
507 }
508
509 public static ConcreteSlotValueType ConvertDynamicVectorInputTypeToConcrete(IEnumerable<ConcreteSlotValueType> inputTypes)
510 {
511 var concreteSlotValueTypes = inputTypes as IList<ConcreteSlotValueType> ?? inputTypes.ToList();
512
513 var inputTypesDistinct = concreteSlotValueTypes.Distinct().ToList();
514 switch (inputTypesDistinct.Count)
515 {
516 case 0:
517 // nothing connected -- use Vec1 by default
518 return ConcreteSlotValueType.Vector1;
519 case 1:
520 if (SlotValueHelper.AreCompatible(SlotValueType.DynamicVector, inputTypesDistinct.First()))
521 {
522 if (inputTypesDistinct.First() == ConcreteSlotValueType.Boolean)
523 return ConcreteSlotValueType.Vector1;
524 return inputTypesDistinct.First();
525 }
526 break;
527 default:
528 // find the 'minumum' channel width excluding 1 as it can promote
529 inputTypesDistinct.RemoveAll(x => (x == ConcreteSlotValueType.Vector1) || (x == ConcreteSlotValueType.Boolean));
530 var ordered = inputTypesDistinct.OrderByDescending(x => x);
531 if (ordered.Any())
532 {
533 var first = ordered.FirstOrDefault();
534 return first;
535 }
536 break;
537 }
538 return ConcreteSlotValueType.Vector1;
539 }
540
541 public static ConcreteSlotValueType ConvertDynamicMatrixInputTypeToConcrete(IEnumerable<ConcreteSlotValueType> inputTypes)
542 {
543 var concreteSlotValueTypes = inputTypes as IList<ConcreteSlotValueType> ?? inputTypes.ToList();
544
545 var inputTypesDistinct = concreteSlotValueTypes.Distinct().ToList();
546 switch (inputTypesDistinct.Count)
547 {
548 case 0:
549 return ConcreteSlotValueType.Matrix2;
550 case 1:
551 return inputTypesDistinct.FirstOrDefault();
552 default:
553 var ordered = inputTypesDistinct.OrderByDescending(x => x);
554 if (ordered.Any())
555 return ordered.FirstOrDefault();
556 break;
557 }
558 return ConcreteSlotValueType.Matrix2;
559 }
560
561 protected const string k_validationErrorMessage = "Error found during node validation";
562
563 // evaluate ALL the precisions...
564 public virtual void UpdatePrecision(List<MaterialSlot> inputSlots)
565 {
566 // first let's reduce from precision ==> graph precision
567 if (precision == Precision.Inherit)
568 {
569 // inherit means calculate it automatically based on inputs
570
571 // If no inputs were found use the precision of the Graph
572 if (inputSlots.Count == 0)
573 {
574 graphPrecision = GraphPrecision.Graph;
575 }
576 else
577 {
578 int curGraphPrecision = (int)GraphPrecision.Half;
579 foreach (var inputSlot in inputSlots)
580 {
581 // If input port doesn't have an edge use the Graph's precision for that input
582 var edges = owner?.GetEdges(inputSlot.slotReference).ToList();
583 if (!edges.Any())
584 {
585 // disconnected inputs use graph precision
586 curGraphPrecision = Math.Min(curGraphPrecision, (int)GraphPrecision.Graph);
587 }
588 else
589 {
590 var outputSlotRef = edges[0].outputSlot;
591 var outputNode = outputSlotRef.node;
592 curGraphPrecision = Math.Min(curGraphPrecision, (int)outputNode.graphPrecision);
593 }
594 }
595 graphPrecision = (GraphPrecision)curGraphPrecision;
596 }
597 }
598 else
599 {
600 // not inherited, just use the node's selected precision
601 graphPrecision = precision.ToGraphPrecision(GraphPrecision.Graph);
602 }
603
604 // calculate the concrete precision, with fall-back to the graph concrete precision
605 concretePrecision = graphPrecision.ToConcrete(owner.graphDefaultConcretePrecision);
606 }
607
608 public virtual void EvaluateDynamicMaterialSlots(List<MaterialSlot> inputSlots, List<MaterialSlot> outputSlots)
609 {
610 var dynamicInputSlotsToCompare = DictionaryPool<DynamicVectorMaterialSlot, ConcreteSlotValueType>.Get();
611 var skippedDynamicSlots = ListPool<DynamicVectorMaterialSlot>.Get();
612
613 var dynamicMatrixInputSlotsToCompare = DictionaryPool<DynamicMatrixMaterialSlot, ConcreteSlotValueType>.Get();
614 var skippedDynamicMatrixSlots = ListPool<DynamicMatrixMaterialSlot>.Get();
615
616 // iterate the input slots
617 {
618 foreach (var inputSlot in inputSlots)
619 {
620 inputSlot.hasError = false;
621 // if there is a connection
622 var edges = owner.GetEdges(inputSlot.slotReference).ToList();
623 if (!edges.Any())
624 {
625 if (inputSlot is DynamicVectorMaterialSlot)
626 skippedDynamicSlots.Add(inputSlot as DynamicVectorMaterialSlot);
627 if (inputSlot is DynamicMatrixMaterialSlot)
628 skippedDynamicMatrixSlots.Add(inputSlot as DynamicMatrixMaterialSlot);
629 continue;
630 }
631
632 // get the output details
633 var outputSlotRef = edges[0].outputSlot;
634 var outputNode = outputSlotRef.node;
635 if (outputNode == null)
636 continue;
637
638 var outputSlot = outputNode.FindOutputSlot<MaterialSlot>(outputSlotRef.slotId);
639 if (outputSlot == null)
640 continue;
641
642 if (outputSlot.hasError)
643 {
644 inputSlot.hasError = true;
645 continue;
646 }
647
648 var outputConcreteType = outputSlot.concreteValueType;
649 // dynamic input... depends on output from other node.
650 // we need to compare ALL dynamic inputs to make sure they
651 // are compatible.
652 if (inputSlot is DynamicVectorMaterialSlot)
653 {
654 dynamicInputSlotsToCompare.Add((DynamicVectorMaterialSlot)inputSlot, outputConcreteType);
655 continue;
656 }
657 else if (inputSlot is DynamicMatrixMaterialSlot)
658 {
659 dynamicMatrixInputSlotsToCompare.Add((DynamicMatrixMaterialSlot)inputSlot, outputConcreteType);
660 continue;
661 }
662 }
663
664 // we can now figure out the dynamic slotType
665 // from here set all the
666 var dynamicType = ConvertDynamicVectorInputTypeToConcrete(dynamicInputSlotsToCompare.Values);
667 foreach (var dynamicKvP in dynamicInputSlotsToCompare)
668 dynamicKvP.Key.SetConcreteType(dynamicType);
669 foreach (var skippedSlot in skippedDynamicSlots)
670 skippedSlot.SetConcreteType(dynamicType);
671
672 // and now dynamic matrices
673 var dynamicMatrixType = ConvertDynamicMatrixInputTypeToConcrete(dynamicMatrixInputSlotsToCompare.Values);
674 foreach (var dynamicKvP in dynamicMatrixInputSlotsToCompare)
675 dynamicKvP.Key.SetConcreteType(dynamicMatrixType);
676 foreach (var skippedSlot in skippedDynamicMatrixSlots)
677 skippedSlot.SetConcreteType(dynamicMatrixType);
678
679 bool inputError = inputSlots.Any(x => x.hasError);
680 if (inputError)
681 {
682 owner.AddConcretizationError(objectId, string.Format("Node {0} had input error", objectId));
683 hasError = true;
684 }
685
686 // configure the output slots now
687 // their slotType will either be the default output slotType
688 // or the above dynamic slotType for dynamic nodes
689 // or error if there is an input error
690 foreach (var outputSlot in outputSlots)
691 {
692 outputSlot.hasError = false;
693
694 if (inputError)
695 {
696 outputSlot.hasError = true;
697 continue;
698 }
699
700 if (outputSlot is DynamicVectorMaterialSlot dynamicVectorMaterialSlot)
701 {
702 dynamicVectorMaterialSlot.SetConcreteType(dynamicType);
703 continue;
704 }
705 else if (outputSlot is DynamicMatrixMaterialSlot dynamicMatrixMaterialSlot)
706 {
707 dynamicMatrixMaterialSlot.SetConcreteType(dynamicMatrixType);
708 continue;
709 }
710 }
711
712 if (outputSlots.Any(x => x.hasError))
713 {
714 owner.AddConcretizationError(objectId, string.Format("Node {0} had output error", objectId));
715 hasError = true;
716 }
717 CalculateNodeHasError();
718
719 ListPool<DynamicVectorMaterialSlot>.Release(skippedDynamicSlots);
720 DictionaryPool<DynamicVectorMaterialSlot, ConcreteSlotValueType>.Release(dynamicInputSlotsToCompare);
721
722 ListPool<DynamicMatrixMaterialSlot>.Release(skippedDynamicMatrixSlots);
723 DictionaryPool<DynamicMatrixMaterialSlot, ConcreteSlotValueType>.Release(dynamicMatrixInputSlotsToCompare);
724 }
725 }
726
727 public virtual void Concretize()
728 {
729 hasError = false;
730 owner?.ClearErrorsForNode(this);
731
732 using (var inputSlots = PooledList<MaterialSlot>.Get())
733 using (var outputSlots = PooledList<MaterialSlot>.Get())
734 {
735 GetInputSlots(inputSlots);
736 GetOutputSlots(outputSlots);
737
738 UpdatePrecision(inputSlots);
739 EvaluateDynamicMaterialSlots(inputSlots, outputSlots);
740 }
741 }
742
743 public virtual void ValidateNode()
744 {
745 if ((sgVersion < latestVersion) && (dismissedUpdateVersion < latestVersion))
746 owner.messageManager?.AddOrAppendError(owner, objectId, new ShaderMessage("There is a newer version of this node available. Inspect node for details.", Rendering.ShaderCompilerMessageSeverity.Warning));
747 }
748
749 public virtual bool canCutNode => true;
750 public virtual bool canCopyNode => true;
751
752 protected virtual void CalculateNodeHasError()
753 {
754 foreach (var slot in this.GetInputSlots<MaterialSlot>())
755 {
756 if (slot.isConnected)
757 {
758 var edge = owner.GetEdges(slot.slotReference).First();
759 var outputNode = edge.outputSlot.node;
760 var outputSlot = outputNode.GetOutputSlots<MaterialSlot>().First(s => s.id == edge.outputSlot.slotId);
761 if (!slot.IsCompatibleWith(outputSlot))
762 {
763 owner.AddConcretizationError(objectId, $"Slot {slot.RawDisplayName()} cannot accept input of type {outputSlot.concreteValueType}.");
764 hasError = true;
765 return;
766 }
767 }
768 }
769 }
770
771 protected string GetRayTracingError() => $@"
772 #if defined(SHADER_STAGE_RAY_TRACING) && defined(RAYTRACING_SHADER_GRAPH_DEFAULT)
773 #error '{name}' node is not supported in ray tracing, please provide an alternate implementation, relying for instance on the 'Raytracing Quality' keyword
774 #endif";
775
776 public virtual void CollectPreviewMaterialProperties(List<PreviewProperty> properties)
777 {
778 using (var tempSlots = PooledList<MaterialSlot>.Get())
779 using (var tempPreviewProperties = PooledList<PreviewProperty>.Get())
780 using (var tempEdges = PooledList<IEdge>.Get())
781 {
782 GetInputSlots(tempSlots);
783 foreach (var s in tempSlots)
784 {
785 tempPreviewProperties.Clear();
786 tempEdges.Clear();
787 if (owner != null)
788 {
789 owner.GetEdges(s.slotReference, tempEdges);
790 if (tempEdges.Any())
791 continue;
792 }
793
794 s.GetPreviewProperties(tempPreviewProperties, GetVariableNameForSlot(s.id));
795 for (int i = 0; i < tempPreviewProperties.Count; i++)
796 {
797 if (tempPreviewProperties[i].name == null)
798 continue;
799
800 properties.Add(tempPreviewProperties[i]);
801 }
802 }
803 }
804 }
805
806 public virtual string GetVariableNameForSlot(int slotId)
807 {
808 var slot = FindSlot<MaterialSlot>(slotId);
809 if (slot == null)
810 throw new ArgumentException(string.Format("Attempting to use MaterialSlot({0}) on node of type {1} where this slot can not be found", slotId, this), "slotId");
811 return string.Format("_{0}_{1}_{2}_{3}", GetVariableNameForNode(), NodeUtils.GetHLSLSafeName(slot.shaderOutputName), unchecked((uint)slotId), slot.concreteValueType.ToPropertyType().ToString());
812 }
813
814 public string GetConnnectionStateVariableNameForSlot(int slotId)
815 {
816 return ShaderInput.GetConnectionStateVariableName(GetVariableNameForSlot(slotId));
817 }
818
819 public virtual string GetVariableNameForNode()
820 {
821 return defaultVariableName;
822 }
823
824 public MaterialSlot AddSlot(MaterialSlot slot, bool attemptToModifyExistingInstance = true)
825 {
826 if (slot == null)
827 {
828 throw new ArgumentException($"Trying to add null slot to node {this}");
829 }
830 MaterialSlot foundSlot = FindSlot<MaterialSlot>(slot.id);
831
832 if (slot == foundSlot)
833 return foundSlot;
834
835 // Try to keep the existing instance to avoid unnecessary changes to file
836 if (attemptToModifyExistingInstance && foundSlot != null && slot.GetType() == foundSlot.GetType())
837 {
838 foundSlot.displayName = slot.RawDisplayName();
839 foundSlot.CopyDefaultValue(slot);
840 return foundSlot;
841 }
842
843 // keep the same ordering by replacing the first match, if it exists
844 int firstIndex = m_Slots.FindIndex(s => s.value.id == slot.id);
845 if (firstIndex >= 0)
846 {
847 m_Slots[firstIndex] = slot;
848
849 // remove additional matches to get rid of unused duplicates
850 m_Slots.RemoveAllFromRange(s => s.value.id == slot.id, firstIndex + 1, m_Slots.Count - (firstIndex + 1));
851 }
852 else
853 m_Slots.Add(slot);
854
855 slot.owner = this;
856
857 OnSlotsChanged();
858
859 if (foundSlot == null)
860 return slot;
861
862 // foundSlot is of a different type; try to copy values
863 // I think this is to support casting if implemented in CopyValuesFrom ?
864 slot.CopyValuesFrom(foundSlot);
865 foundSlot.owner = null;
866
867 return slot;
868 }
869
870 public void RemoveSlot(int slotId)
871 {
872 // Remove edges that use this slot
873 // no owner can happen after creation
874 // but before added to graph
875 if (owner != null)
876 {
877 var edges = owner.GetEdges(GetSlotReference(slotId));
878 owner.RemoveEdges(edges.ToArray());
879 }
880
881 //remove slots
882 m_Slots.RemoveAll(x => x.value.id == slotId);
883
884 OnSlotsChanged();
885 }
886
887 protected virtual void OnSlotsChanged()
888 {
889 Dirty(ModificationScope.Topological);
890 owner?.ClearErrorsForNode(this);
891 }
892
893 public void RemoveSlotsNameNotMatching(IEnumerable<int> slotIds, bool supressWarnings = false)
894 {
895 var invalidSlots = m_Slots.Select(x => x.value.id).Except(slotIds);
896
897 foreach (var invalidSlot in invalidSlots.ToArray())
898 {
899 if (!supressWarnings)
900 Debug.LogWarningFormat("Removing Invalid MaterialSlot: {0}", invalidSlot);
901 RemoveSlot(invalidSlot);
902 }
903 }
904
905 public bool SetSlotOrder(List<int> desiredOrderSlotIds)
906 {
907 bool changed = false;
908 int writeIndex = 0;
909 for (int orderIndex = 0; orderIndex < desiredOrderSlotIds.Count; orderIndex++)
910 {
911 var id = desiredOrderSlotIds[orderIndex];
912 var matchIndex = m_Slots.FindIndex(s => s.value.id == id);
913 if (matchIndex < 0)
914 {
915 // no matching slot with that id.. skip it
916 }
917 else
918 {
919 if (writeIndex != matchIndex)
920 {
921 // swap the matching slot into position
922 var slot = m_Slots[matchIndex];
923 m_Slots[matchIndex] = m_Slots[writeIndex];
924 m_Slots[writeIndex] = slot;
925 changed = true;
926 }
927 writeIndex++;
928 }
929 }
930 return changed;
931 }
932
933 public SlotReference GetSlotReference(int slotId)
934 {
935 var slot = FindSlot<MaterialSlot>(slotId);
936 if (slot == null)
937 throw new ArgumentException("Slot could not be found", "slotId");
938 return new SlotReference(this, slotId);
939 }
940
941 public T FindSlot<T>(int slotId) where T : MaterialSlot
942 {
943 foreach (var slot in m_Slots.SelectValue())
944 {
945 if (slot.id == slotId && slot is T)
946 return (T)slot;
947 }
948 return default(T);
949 }
950
951 public T FindInputSlot<T>(int slotId) where T : MaterialSlot
952 {
953 foreach (var slot in m_Slots.SelectValue())
954 {
955 if (slot.isInputSlot && slot.id == slotId && slot is T)
956 return (T)slot;
957 }
958 return default(T);
959 }
960
961 public T FindOutputSlot<T>(int slotId) where T : MaterialSlot
962 {
963 foreach (var slot in m_Slots.SelectValue())
964 {
965 if (slot.isOutputSlot && slot.id == slotId && slot is T)
966 return (T)slot;
967 }
968 return default(T);
969 }
970
971 public virtual IEnumerable<MaterialSlot> GetInputsWithNoConnection()
972 {
973 return this.GetInputSlots<MaterialSlot>().Where(x => !owner.GetEdges(GetSlotReference(x.id)).Any());
974 }
975
976 public void SetupSlots()
977 {
978 foreach (var s in m_Slots.SelectValue())
979 s.owner = this;
980 }
981
982 public virtual void UpdateNodeAfterDeserialization()
983 { }
984
985 public bool IsSlotConnected(int slotId)
986 {
987 var slot = FindSlot<MaterialSlot>(slotId);
988 return slot != null && owner.GetEdges(slot.slotReference).Any();
989 }
990
991 public virtual void Setup() { }
992
993 protected void EnqueSlotsForSerialization()
994 {
995 foreach (var slot in m_Slots)
996 {
997 slot.OnBeforeSerialize();
998 }
999 }
1000
1001 public virtual void Dispose()
1002 {
1003 foreach (var slot in m_Slots)
1004 slot.value.Dispose();
1005
1006 m_UnregisterAll?.Invoke();
1007 m_UnregisterAll = null;
1008 }
1009 }
1010}