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}