A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using UnityEngine;
5using UnityEditor.Graphing;
6using UnityEditor.ShaderGraph.Drawing;
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}