A game about forced loneliness, made by TACStudios
at master 36 kB view raw
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}