A game about forced loneliness, made by TACStudios
1using System;
2using System.Linq;
3using System.Collections.Generic;
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("Input", "Property")]
14 class PropertyNode : AbstractMaterialNode, IGeneratesBodyCode, IOnAssetEnabled, IShaderInputObserver
15 {
16 public PropertyNode()
17 {
18 name = "Property";
19 UpdateNodeAfterDeserialization();
20 }
21
22 // Property-Types
23 public override string documentationURL => UnityEngine.Rendering.ShaderGraph.Documentation.GetPageLink("Property-Types");
24
25 public override void UpdateNodeAfterDeserialization()
26 {
27 base.UpdateNodeAfterDeserialization();
28
29 if (owner == null)
30 return;
31
32 if (property is Vector1ShaderProperty vector1ShaderProperty && vector1ShaderProperty.floatType == FloatType.Slider)
33 {
34 // Previously, the Slider vector1 property allowed the min value to be greater than the max
35 // We no longer want to support that behavior so if such a property is encountered, swap the values
36 if (vector1ShaderProperty.rangeValues.x > vector1ShaderProperty.rangeValues.y)
37 {
38 vector1ShaderProperty.rangeValues = new Vector2(vector1ShaderProperty.rangeValues.y, vector1ShaderProperty.rangeValues.x);
39 Dirty(ModificationScope.Graph);
40 }
41 }
42 }
43
44 [SerializeField]
45 JsonRef<AbstractShaderProperty> m_Property;
46
47 public AbstractShaderProperty property
48 {
49 get { return m_Property; }
50 set
51 {
52 if (m_Property == value)
53 return;
54
55 m_Property = value;
56 AddOutputSlot();
57 Dirty(ModificationScope.Topological);
58 }
59 }
60
61 // this node's precision is always controlled by the property precision
62 public override bool canSetPrecision => false;
63
64 public void UpdateNodeDisplayName(string newDisplayName)
65 {
66 MaterialSlot foundSlot = FindSlot<MaterialSlot>(OutputSlotId);
67
68 if (foundSlot != null)
69 foundSlot.displayName = newDisplayName;
70 }
71
72 public void OnEnable()
73 {
74 AddOutputSlot();
75 }
76
77 public const int OutputSlotId = 0;
78
79 void AddOutputSlot()
80 {
81 if (property is MultiJsonInternal.UnknownShaderPropertyType uspt)
82 {
83 // keep existing slots, don't modify them
84 return;
85 }
86 switch (property.concreteShaderValueType)
87 {
88 case ConcreteSlotValueType.Boolean:
89 AddSlot(new BooleanMaterialSlot(OutputSlotId, property.displayName, "Out", SlotType.Output, false));
90 RemoveSlotsNameNotMatching(new[] { OutputSlotId });
91 break;
92 case ConcreteSlotValueType.Vector1:
93 AddSlot(new Vector1MaterialSlot(OutputSlotId, property.displayName, "Out", SlotType.Output, 0));
94 RemoveSlotsNameNotMatching(new[] { OutputSlotId });
95 break;
96 case ConcreteSlotValueType.Vector2:
97 AddSlot(new Vector2MaterialSlot(OutputSlotId, property.displayName, "Out", SlotType.Output, Vector4.zero));
98 RemoveSlotsNameNotMatching(new[] { OutputSlotId });
99 break;
100 case ConcreteSlotValueType.Vector3:
101 AddSlot(new Vector3MaterialSlot(OutputSlotId, property.displayName, "Out", SlotType.Output, Vector4.zero));
102 RemoveSlotsNameNotMatching(new[] { OutputSlotId });
103 break;
104 case ConcreteSlotValueType.Vector4:
105 AddSlot(new Vector4MaterialSlot(OutputSlotId, property.displayName, "Out", SlotType.Output, Vector4.zero));
106 RemoveSlotsNameNotMatching(new[] { OutputSlotId });
107 break;
108 case ConcreteSlotValueType.Matrix2:
109 AddSlot(new Matrix2MaterialSlot(OutputSlotId, property.displayName, "Out", SlotType.Output));
110 RemoveSlotsNameNotMatching(new[] { OutputSlotId });
111 break;
112 case ConcreteSlotValueType.Matrix3:
113 AddSlot(new Matrix3MaterialSlot(OutputSlotId, property.displayName, "Out", SlotType.Output));
114 RemoveSlotsNameNotMatching(new[] { OutputSlotId });
115 break;
116 case ConcreteSlotValueType.Matrix4:
117 AddSlot(new Matrix4MaterialSlot(OutputSlotId, property.displayName, "Out", SlotType.Output));
118 RemoveSlotsNameNotMatching(new[] { OutputSlotId });
119 break;
120 case ConcreteSlotValueType.Texture2D:
121 AddSlot(new Texture2DMaterialSlot(OutputSlotId, property.displayName, "Out", SlotType.Output));
122 RemoveSlotsNameNotMatching(new[] { OutputSlotId });
123 break;
124 case ConcreteSlotValueType.Texture2DArray:
125 AddSlot(new Texture2DArrayMaterialSlot(OutputSlotId, property.displayName, "Out", SlotType.Output));
126 RemoveSlotsNameNotMatching(new[] { OutputSlotId });
127 break;
128 case ConcreteSlotValueType.Texture3D:
129 AddSlot(new Texture3DMaterialSlot(OutputSlotId, property.displayName, "Out", SlotType.Output));
130 RemoveSlotsNameNotMatching(new[] { OutputSlotId });
131 break;
132 case ConcreteSlotValueType.Cubemap:
133 AddSlot(new CubemapMaterialSlot(OutputSlotId, property.displayName, "Out", SlotType.Output));
134 RemoveSlotsNameNotMatching(new[] { OutputSlotId });
135 break;
136 case ConcreteSlotValueType.SamplerState:
137 AddSlot(new SamplerStateMaterialSlot(OutputSlotId, property.displayName, "Out", SlotType.Output));
138 RemoveSlotsNameNotMatching(new[] { OutputSlotId });
139 break;
140 case ConcreteSlotValueType.Gradient:
141 AddSlot(new GradientMaterialSlot(OutputSlotId, property.displayName, "Out", SlotType.Output));
142 RemoveSlotsNameNotMatching(new[] { OutputSlotId });
143 break;
144 case ConcreteSlotValueType.VirtualTexture:
145 AddSlot(new VirtualTextureMaterialSlot(OutputSlotId, property.displayName, "Out", SlotType.Output));
146 RemoveSlotsNameNotMatching(new[] { OutputSlotId });
147 break;
148 default:
149 throw new ArgumentOutOfRangeException();
150 }
151 }
152
153 public void GenerateNodeCode(ShaderStringBuilder sb, GenerationMode mode)
154 {
155 // preview is always generating a full shader, even when previewing within a subgraph
156 bool isGeneratingSubgraph = owner.isSubGraph && (mode != GenerationMode.Preview);
157
158 switch (property.propertyType)
159 {
160 case PropertyType.Boolean:
161 sb.AppendLine($"$precision {GetVariableNameForSlot(OutputSlotId)} = {property.GetHLSLVariableName(isGeneratingSubgraph, mode)};");
162 break;
163 case PropertyType.Float:
164 sb.AppendLine($"$precision {GetVariableNameForSlot(OutputSlotId)} = {property.GetHLSLVariableName(isGeneratingSubgraph, mode)};");
165 break;
166 case PropertyType.Vector2:
167 sb.AppendLine($"$precision2 {GetVariableNameForSlot(OutputSlotId)} = {property.GetHLSLVariableName(isGeneratingSubgraph, mode)};");
168 break;
169 case PropertyType.Vector3:
170 sb.AppendLine($"$precision3 {GetVariableNameForSlot(OutputSlotId)} = {property.GetHLSLVariableName(isGeneratingSubgraph, mode)};");
171 break;
172 case PropertyType.Vector4:
173 sb.AppendLine($"$precision4 {GetVariableNameForSlot(OutputSlotId)} = {property.GetHLSLVariableName(isGeneratingSubgraph, mode)};");
174 break;
175 case PropertyType.Color:
176 switch (property.sgVersion)
177 {
178 case 0:
179 case 2:
180 sb.AppendLine($"$precision4 {GetVariableNameForSlot(OutputSlotId)} = {property.GetHLSLVariableName(isGeneratingSubgraph, mode)};");
181 break;
182 case 1:
183 case 3:
184 //Exposed color properties get put into the correct space automagikally by Unity UNLESS tagged as HDR, then they just get passed in as is.
185 //for consistency with other places in the editor, we assume HDR colors are in linear space, and correct for gamma space here
186 if ((property as ColorShaderProperty).colorMode == ColorMode.HDR)
187 {
188 sb.AppendLine($"$precision4 {GetVariableNameForSlot(OutputSlotId)} = IsGammaSpace() ? LinearToSRGB({property.GetHLSLVariableName(isGeneratingSubgraph, mode)}) : {property.GetHLSLVariableName(isGeneratingSubgraph, mode)};");
189 }
190 else
191 {
192 sb.AppendLine($"$precision4 {GetVariableNameForSlot(OutputSlotId)} = {property.GetHLSLVariableName(isGeneratingSubgraph, mode)};");
193 }
194 break;
195 default:
196 throw new Exception($"Unknown Color Property Version on property {property.displayName}");
197 }
198 break;
199 case PropertyType.Matrix2:
200 sb.AppendLine($"$precision2x2 {GetVariableNameForSlot(OutputSlotId)} = {property.GetHLSLVariableName(isGeneratingSubgraph, mode)};");
201 break;
202 case PropertyType.Matrix3:
203 sb.AppendLine($"$precision3x3 {GetVariableNameForSlot(OutputSlotId)} = {property.GetHLSLVariableName(isGeneratingSubgraph, mode)};");
204 break;
205 case PropertyType.Matrix4:
206 sb.AppendLine($"$precision4x4 {GetVariableNameForSlot(OutputSlotId)} = {property.GetHLSLVariableName(isGeneratingSubgraph, mode)};");
207 break;
208 case PropertyType.Texture2D:
209 sb.AppendLine($"UnityTexture2D {GetVariableNameForSlot(OutputSlotId)} = {property.GetHLSLVariableName(isGeneratingSubgraph, mode)};");
210 break;
211 case PropertyType.Texture3D:
212 sb.AppendLine($"UnityTexture3D {GetVariableNameForSlot(OutputSlotId)} = {property.GetHLSLVariableName(isGeneratingSubgraph, mode)};");
213 break;
214 case PropertyType.Texture2DArray:
215 sb.AppendLine($"UnityTexture2DArray {GetVariableNameForSlot(OutputSlotId)} = {property.GetHLSLVariableName(isGeneratingSubgraph, mode)};");
216 break;
217 case PropertyType.Cubemap:
218 sb.AppendLine($"UnityTextureCube {GetVariableNameForSlot(OutputSlotId)} = {property.GetHLSLVariableName(isGeneratingSubgraph, mode)};");
219 break;
220 case PropertyType.SamplerState:
221 sb.AppendLine($"UnitySamplerState {GetVariableNameForSlot(OutputSlotId)} = {property.GetHLSLVariableName(isGeneratingSubgraph, mode)};");
222 break;
223 case PropertyType.Gradient:
224 if (mode == GenerationMode.Preview)
225 sb.AppendLine($"Gradient {GetVariableNameForSlot(OutputSlotId)} = {GradientUtil.GetGradientForPreview(property.GetHLSLVariableName(isGeneratingSubgraph, mode))};");
226 else
227 sb.AppendLine($"Gradient {GetVariableNameForSlot(OutputSlotId)} = {property.GetHLSLVariableName(isGeneratingSubgraph, mode)};");
228 break;
229 }
230
231 if (property.isConnectionTestable)
232 {
233 // If in a subgraph, the value will be read from a function parameter.
234 // If generating preview mode code, we always inline the value, according to code gen requirements.
235 // The parent graph always sets the explicit value to be passed to a subgraph function.
236 sb.AppendLine("bool {0} = {1};", GetConnectionStateVariableNameForSlot(OutputSlotId), (mode == GenerationMode.Preview || !isGeneratingSubgraph) ? (IsSlotConnected(OutputSlotId) ? "true" : "false") : property.GetConnectionStateHLSLVariableName());
237 }
238 }
239
240 public override string GetVariableNameForSlot(int slotId)
241 {
242 // TODO: we should switch VirtualTexture away from the macro-based variables and towards using the same approach as Texture2D
243 switch (property.propertyType)
244 {
245 case PropertyType.VirtualTexture:
246 return property.GetHLSLVariableName(owner.isSubGraph, GenerationMode.ForReals);
247 }
248
249 return base.GetVariableNameForSlot(slotId);
250 }
251
252 public string GetConnectionStateVariableNameForSlot(int slotId)
253 {
254 return ShaderInput.GetConnectionStateVariableName(GetVariableNameForSlot(slotId));
255 }
256
257 protected override void CalculateNodeHasError()
258 {
259 if (property == null || !owner.properties.Any(x => x == property))
260 {
261 owner.AddConcretizationError(objectId, "Property Node has no associated Blackboard property.");
262 }
263 else if (property is MultiJsonInternal.UnknownShaderPropertyType)
264 {
265 owner.AddValidationError(objectId, "Property is of unknown type, a package may be missing.", Rendering.ShaderCompilerMessageSeverity.Warning);
266 }
267 }
268
269 public override void UpdatePrecision(List<MaterialSlot> inputSlots)
270 {
271 // Get precision from Property
272 if (property == null)
273 {
274 owner.AddConcretizationError(objectId, string.Format("No matching poperty found on owner for node {0}", objectId));
275 hasError = true;
276 return;
277 }
278
279 // this node's precision is always controlled by the property precision
280 precision = property.precision;
281
282 graphPrecision = precision.ToGraphPrecision(GraphPrecision.Graph);
283 concretePrecision = graphPrecision.ToConcrete(owner.graphDefaultConcretePrecision);
284 }
285
286 public void OnShaderInputUpdated(ModificationScope modificationScope)
287 {
288 if(modificationScope == ModificationScope.Layout)
289 UpdateNodeDisplayName(property.displayName);
290 Dirty(modificationScope);
291 }
292 }
293}