A game about forced loneliness, made by TACStudios
at master 216 lines 8.3 kB view raw
1using System; 2using System.Collections.Generic; 3using System.Linq; 4using UnityEngine; 5using UnityEditor.Graphing; 6using UnityEditor.ShaderGraph.Drawing; 7using UnityEditor.ShaderGraph.Internal; 8using UnityEditor.ShaderGraph.Serialization; 9 10namespace UnityEditor.ShaderGraph 11{ 12 [Serializable] 13 [Title("Utility", "Keyword")] 14 class KeywordNode : AbstractMaterialNode, IOnAssetEnabled, IGeneratesBodyCode, IShaderInputObserver 15 { 16 internal const int k_MinEnumEntries = 2; 17 internal const int k_MaxEnumEntries = 8; 18 19 public KeywordNode() 20 { 21 UpdateNodeAfterDeserialization(); 22 } 23 24 [SerializeField] 25 JsonRef<ShaderKeyword> m_Keyword; 26 27 public ShaderKeyword keyword 28 { 29 get { return m_Keyword; } 30 set 31 { 32 if (m_Keyword == value) 33 return; 34 35 m_Keyword = value; 36 UpdateNode(); 37 Dirty(ModificationScope.Topological); 38 } 39 } 40 41 public override bool canSetPrecision => false; 42 public override bool hasPreview => true; 43 public const int OutputSlotId = 0; 44 45 public void UpdateNodeDisplayName(string newDisplayName) 46 { 47 MaterialSlot foundSlot = FindSlot<MaterialSlot>(OutputSlotId); 48 49 if (foundSlot != null) 50 foundSlot.displayName = newDisplayName; 51 } 52 53 public void OnEnable() 54 { 55 UpdateNode(); 56 } 57 58 public void UpdateNode() 59 { 60 name = keyword.displayName; 61 UpdatePorts(); 62 } 63 64 void UpdatePorts() 65 { 66 switch (keyword.keywordType) 67 { 68 case KeywordType.Boolean: 69 { 70 // Boolean type has preset slots 71 PooledList<MaterialSlot> temp = PooledList<MaterialSlot>.Get(); 72 GetInputSlots(temp); 73 if (temp.Any()) 74 { 75 temp.Dispose(); 76 break; 77 } 78 else 79 { 80 temp.Dispose(); 81 } 82 AddSlot(new DynamicVectorMaterialSlot(OutputSlotId, "Out", "Out", SlotType.Output, Vector4.zero)); 83 AddSlot(new DynamicVectorMaterialSlot(1, "On", "On", SlotType.Input, Vector4.zero)); 84 AddSlot(new DynamicVectorMaterialSlot(2, "Off", "Off", SlotType.Input, Vector4.zero)); 85 RemoveSlotsNameNotMatching(new int[] { 0, 1, 2 }); 86 break; 87 } 88 case KeywordType.Enum: 89 using (var inputSlots = PooledList<MaterialSlot>.Get()) 90 using (var slotIDs = PooledList<int>.Get()) 91 { 92 // Get slots 93 GetInputSlots(inputSlots); 94 95 96 // Add output slot 97 AddSlot(new DynamicVectorMaterialSlot(OutputSlotId, "Out", "Out", SlotType.Output, Vector4.zero)); 98 slotIDs.Add(OutputSlotId); 99 100 // Add input slots 101 for (int i = 0; i < keyword.entries.Count; i++) 102 { 103 // Get slot based on entry id 104 MaterialSlot slot = inputSlots.Find(x => 105 x.id == keyword.entries[i].id && 106 x.RawDisplayName() == keyword.entries[i].displayName && 107 x.shaderOutputName == keyword.entries[i].referenceName); 108 109 // If slot doesn't exist, it's new so create it 110 if (slot == null) 111 { 112 slot = new DynamicVectorMaterialSlot(keyword.entries[i].id, keyword.entries[i].displayName, keyword.entries[i].referenceName, SlotType.Input, Vector4.zero); 113 } 114 115 AddSlot(slot); 116 slotIDs.Add(keyword.entries[i].id); 117 } 118 RemoveSlotsNameNotMatching(slotIDs); 119 bool orderChanged = SetSlotOrder(slotIDs); 120 if (orderChanged) 121 { 122 // unfortunately there is no way to get the view to update slot order other than Topological 123 Dirty(ModificationScope.Topological); 124 } 125 break; 126 } 127 } 128 129 ValidateNode(); 130 } 131 132 public void GenerateNodeCode(ShaderStringBuilder sb, GenerationMode generationMode) 133 { 134 var outputSlot = FindOutputSlot<MaterialSlot>(OutputSlotId); 135 switch (keyword.keywordType) 136 { 137 case KeywordType.Boolean: 138 { 139 // Get values 140 var onValue = GetSlotValue(1, generationMode); 141 var offValue = GetSlotValue(2, generationMode); 142 143 // Append code 144 sb.AppendLine($"#if defined({keyword.referenceName})"); 145 sb.AppendLine(string.Format($"{outputSlot.concreteValueType.ToShaderString()} {GetVariableNameForSlot(OutputSlotId)} = {onValue};")); 146 sb.AppendLine("#else"); 147 sb.AppendLine(string.Format($"{outputSlot.concreteValueType.ToShaderString()} {GetVariableNameForSlot(OutputSlotId)} = {offValue};")); 148 sb.AppendLine("#endif"); 149 break; 150 } 151 case KeywordType.Enum: 152 { 153 // Iterate all entries in the keyword 154 for (int i = 0; i < keyword.entries.Count; i++) 155 { 156 // Insert conditional 157 if (i == 0) 158 { 159 sb.AppendLine($"#if defined({keyword.referenceName}_{keyword.entries[i].referenceName})"); 160 } 161 else if (i == keyword.entries.Count - 1) 162 { 163 sb.AppendLine("#else"); 164 } 165 else 166 { 167 sb.AppendLine($"#elif defined({keyword.referenceName}_{keyword.entries[i].referenceName})"); 168 } 169 170 // Append per-slot code 171 var value = GetSlotValue(GetSlotIdForPermutation(new KeyValuePair<ShaderKeyword, int>(keyword, i)), generationMode); 172 sb.AppendLine(string.Format($"{outputSlot.concreteValueType.ToShaderString()} {GetVariableNameForSlot(OutputSlotId)} = {value};")); 173 } 174 175 // End condition 176 sb.AppendLine("#endif"); 177 break; 178 } 179 default: 180 throw new ArgumentOutOfRangeException(); 181 } 182 } 183 184 public int GetSlotIdForPermutation(KeyValuePair<ShaderKeyword, int> permutation) 185 { 186 switch (permutation.Key.keywordType) 187 { 188 // Slot 0 is output 189 case KeywordType.Boolean: 190 return 1 + permutation.Value; 191 // Ids are stored manually as slots are added 192 case KeywordType.Enum: 193 return permutation.Key.entries[permutation.Value].id; 194 default: 195 throw new ArgumentOutOfRangeException(); 196 } 197 } 198 199 protected override void CalculateNodeHasError() 200 { 201 if (keyword == null || !owner.keywords.Any(x => x == keyword)) 202 { 203 owner.AddConcretizationError(objectId, "Keyword Node has no associated keyword."); 204 hasError = true; 205 } 206 } 207 208 public void OnShaderInputUpdated(ModificationScope modificationScope) 209 { 210 if(modificationScope == ModificationScope.Layout) 211 UpdateNodeDisplayName(keyword.displayName); 212 UpdateNode(); 213 Dirty(modificationScope); 214 } 215 } 216}