A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using UnityEditor.Graphing;
5using UnityEditor.Graphing.Util;
6using UnityEditor.Rendering;
7using UnityEditor.ShaderGraph.Drawing;
8using UnityEditor.ShaderGraph.Internal;
9using UnityEngine;
10using UnityEngine.Assertions;
11using UnityEngine.Rendering;
12using UnityEngine.UIElements;
13
14namespace UnityEditor.ShaderGraph
15{
16 [Title("Input", "Texture", SampleVirtualTextureNode.DefaultNodeTitle)]
17 class SampleVirtualTextureNode : AbstractMaterialNode, IGeneratesBodyCode, IGeneratesFunction, IMayRequireMeshUV, IMayRequireTime, IMayRequireScreenPosition
18 {
19 public const string DefaultNodeTitle = "Sample Virtual Texture";
20
21 public const int kMinLayers = 1;
22 public const int kMaxLayers = 4;
23
24 // input slots
25 public const int UVInputId = 0;
26 public const int VirtualTextureInputId = 1;
27 public const int LODInputId = 2;
28 public const int BiasInputId = 3;
29 public const int DxInputId = 4;
30 public const int DyInputId = 5;
31
32 // output slots
33 [NonSerialized]
34 public readonly int[] OutputSlotIds = { 11, 12, 13, 14 };
35
36 const string UVInputName = "UV";
37 const string VirtualTextureInputName = "VT";
38 const string LODSlotName = "Lod";
39 const string BiasSlotName = "Bias";
40 const string DxSlotName = "Dx";
41 const string DySlotName = "Dy";
42
43 static string[] OutputSlotNames = { "Out", "Out2", "Out3", "Out4" };
44
45 public override bool hasPreview { get { return false; } }
46
47 // Keep these in sync with "VirtualTexturing.hlsl"
48 public enum LodCalculation
49 {
50 [InspectorName("Automatic")]
51 VtLevel_Automatic = 0,
52 [InspectorName("Lod Level")]
53 VtLevel_Lod = 1,
54 [InspectorName("Lod Bias")]
55 VtLevel_Bias = 2,
56 [InspectorName("Derivatives")]
57 VtLevel_Derivatives = 3
58 }
59
60 public enum AddressMode
61 {
62 [InspectorName("Wrap")]
63 VtAddressMode_Wrap = 0,
64 [InspectorName("Clamp")]
65 VtAddressMode_Clamp = 1,
66 }
67
68 public enum FilterMode
69 {
70 [InspectorName("Anisotropic")]
71 VtFilter_Anisotropic = 0
72 }
73
74 public enum UvSpace
75 {
76 [InspectorName("Regular")]
77 VtUvSpace_Regular = 0,
78 [InspectorName("Pre Transformed")]
79 VtUvSpace_PreTransformed = 1
80 }
81
82 public enum QualityMode
83 {
84 [InspectorName("Low")]
85 VtSampleQuality_Low = 0,
86 [InspectorName("High")]
87 VtSampleQuality_High = 1
88 }
89
90 [SerializeField]
91 AddressMode m_AddressMode = AddressMode.VtAddressMode_Wrap;
92 public AddressMode addressMode
93 {
94 get
95 {
96 return m_AddressMode;
97 }
98 set
99 {
100 if (m_AddressMode == value)
101 return;
102
103 m_AddressMode = value;
104 Dirty(ModificationScope.Graph);
105 }
106 }
107
108 [SerializeField]
109 LodCalculation m_LodCalculation = LodCalculation.VtLevel_Automatic;
110 public LodCalculation lodCalculation
111 {
112 get
113 {
114 return m_LodCalculation;
115 }
116 set
117 {
118 if (m_LodCalculation == value)
119 return;
120
121 m_LodCalculation = value;
122 RebuildAllSlots(true); // LOD calculation may have associated slots that need to be updated
123 Dirty(ModificationScope.Topological); // slots ShaderStageCapability could have changed, so trigger Topo change
124 }
125 }
126
127 [SerializeField]
128 QualityMode m_SampleQuality = QualityMode.VtSampleQuality_High;
129 public QualityMode sampleQuality
130 {
131 get
132 {
133 return m_SampleQuality;
134 }
135 set
136 {
137 if (m_SampleQuality == value)
138 return;
139
140 m_SampleQuality = value;
141 Dirty(ModificationScope.Graph);
142 }
143 }
144
145 [SerializeField]
146 private bool m_EnableGlobalMipBias = true;
147 public bool enableGlobalMipBias
148 {
149 get
150 {
151 return m_EnableGlobalMipBias;
152 }
153 set
154 {
155 if (m_EnableGlobalMipBias == value)
156 return;
157
158 m_EnableGlobalMipBias = value;
159 Dirty(ModificationScope.Graph);
160 }
161 }
162
163 [SerializeField]
164 bool m_NoFeedback; // aka !AutomaticStreaming
165 public bool noFeedback
166 {
167 get
168 {
169 return m_NoFeedback;
170 }
171 set
172 {
173 if (m_NoFeedback == value)
174 return;
175
176 m_NoFeedback = value;
177 RebuildAllSlots(true);
178 Dirty(ModificationScope.Topological); // slots ShaderStageCapability could have changed, so trigger Topo change
179 }
180 }
181
182 public SampleVirtualTextureNode() : this(false, false)
183 { }
184
185 public SampleVirtualTextureNode(bool isLod = false, bool noResolve = false)
186 {
187 name = "Sample Virtual Texture";
188 synonyms = new string[] { "buffer" };
189 UpdateNodeAfterDeserialization();
190 }
191
192 public override void Setup()
193 {
194 UpdateLayerOutputSlots(true);
195 }
196
197 // rebuilds the number of output slots, and also updates their ShaderStageCapability
198 private int outputLayerSlotCount = 0;
199 void UpdateLayerOutputSlots(bool inspectProperty, List<int> usedSlots = null)
200 {
201 // the default is to show all 4 slots, so we don't lose any existing connections
202 int layerCount = kMaxLayers;
203
204 if (inspectProperty)
205 {
206 var vtProperty = GetSlotProperty(VirtualTextureInputId) as VirtualTextureShaderProperty;
207 if (vtProperty != null)
208 {
209 layerCount = vtProperty?.value?.layers?.Count ?? kMaxLayers;
210 }
211 if (outputLayerSlotCount == layerCount)
212 {
213 if (usedSlots != null)
214 for (int i = 0; i < layerCount; i++)
215 usedSlots.Add(OutputSlotIds[i]);
216 return;
217 }
218 }
219
220 for (int i = 0; i < kMaxLayers; i++)
221 {
222 int outputID = OutputSlotIds[i];
223 Vector4MaterialSlot outputSlot = FindSlot<Vector4MaterialSlot>(outputID);
224 if (i < layerCount)
225 {
226 // add or update it
227 if (outputSlot == null)
228 {
229 string outputName = OutputSlotNames[i];
230 outputSlot = new Vector4MaterialSlot(outputID, outputName, outputName, SlotType.Output, Vector4.zero, (noFeedback && m_LodCalculation == LodCalculation.VtLevel_Lod) ? ShaderStageCapability.All : ShaderStageCapability.Fragment);
231 AddSlot(outputSlot);
232 }
233 else
234 {
235 outputSlot.stageCapability = (noFeedback && m_LodCalculation == LodCalculation.VtLevel_Lod) ? ShaderStageCapability.All : ShaderStageCapability.Fragment;
236 }
237 if (usedSlots != null)
238 usedSlots.Add(outputID);
239 }
240 else
241 {
242 // remove it
243 if (outputSlot != null)
244 RemoveSlot(OutputSlotIds[i]);
245 }
246 }
247 outputLayerSlotCount = layerCount;
248 }
249
250 public void RebuildAllSlots(bool inspectProperty)
251 {
252 List<int> usedSlots = new List<int>();
253
254 AddSlot(new UVMaterialSlot(UVInputId, UVInputName, UVInputName, UVChannel.UV0));
255 usedSlots.Add(UVInputId);
256
257 AddSlot(new VirtualTextureInputMaterialSlot(VirtualTextureInputId, VirtualTextureInputName, VirtualTextureInputName));
258 usedSlots.Add(VirtualTextureInputId);
259
260 // at this point we can't tell how many output slots we will have (because we can't find the VT property yet)
261 // so, we create all of the possible output slots, so any edges created will connect properly
262 // then we can trim down the set of slots later..
263 UpdateLayerOutputSlots(inspectProperty, usedSlots);
264
265 // Create slots
266
267 if (m_LodCalculation == LodCalculation.VtLevel_Lod)
268 {
269 var slot = new Vector1MaterialSlot(LODInputId, LODSlotName, LODSlotName, SlotType.Input, 0.0f, ShaderStageCapability.All, LODSlotName);
270 AddSlot(slot);
271 usedSlots.Add(LODInputId);
272 }
273
274 if (m_LodCalculation == LodCalculation.VtLevel_Bias)
275 {
276 var slot = new Vector1MaterialSlot(BiasInputId, BiasSlotName, BiasSlotName, SlotType.Input, 0.0f, ShaderStageCapability.Fragment, BiasSlotName);
277 AddSlot(slot);
278 usedSlots.Add(BiasInputId);
279 }
280
281 if (m_LodCalculation == LodCalculation.VtLevel_Derivatives)
282 {
283 var slot1 = new Vector2MaterialSlot(DxInputId, DxSlotName, DxSlotName, SlotType.Input, Vector2.one, ShaderStageCapability.All, DxSlotName);
284 var slot2 = new Vector2MaterialSlot(DyInputId, DySlotName, DySlotName, SlotType.Input, Vector2.one, ShaderStageCapability.All, DySlotName);
285 AddSlot(slot1);
286 AddSlot(slot2);
287 usedSlots.Add(DxInputId);
288 usedSlots.Add(DyInputId);
289 }
290
291 RemoveSlotsNameNotMatching(usedSlots, true);
292 }
293
294 public override void UpdateNodeAfterDeserialization()
295 {
296 RebuildAllSlots(false);
297 }
298
299 const string k_NoPropertyConnected = "A VirtualTexture property must be connected to the VT slot";
300 public override void ValidateNode()
301 {
302 base.ValidateNode();
303 if (!IsSlotConnected(VirtualTextureInputId))
304 {
305 owner.AddValidationError(objectId, k_NoPropertyConnected);
306 }
307 else
308 {
309 var vtProp = GetSlotProperty(VirtualTextureInputId) as VirtualTextureShaderProperty;
310 if (vtProp == null)
311 {
312 owner.AddValidationError(objectId, $"VT slot is not connected to a valid VirtualTexture property");
313 }
314 }
315 }
316
317 public string GetFeedbackVariableName()
318 {
319 return GetVariableNameForNode() + "_fb";
320 }
321
322 void AppendVtParameters(ShaderStringBuilder sb, string uvExpr, string lodExpr, string dxExpr, string dyExpr, AddressMode address, FilterMode filter, LodCalculation lod, UvSpace space, QualityMode quality, bool enableGlobalMipBias)
323 {
324 sb.AppendLine("VtInputParameters vtParams;");
325 sb.AppendLine("vtParams.uv = " + uvExpr + ";");
326 sb.AppendLine("vtParams.lodOrOffset = " + lodExpr + ";");
327 sb.AppendLine("vtParams.dx = " + dxExpr + ";");
328 sb.AppendLine("vtParams.dy = " + dyExpr + ";");
329 sb.AppendLine("vtParams.addressMode = " + address + ";");
330 sb.AppendLine("vtParams.filterMode = " + filter + ";");
331 sb.AppendLine("vtParams.levelMode = " + lod + ";");
332 sb.AppendLine("vtParams.uvMode = " + space + ";");
333 sb.AppendLine("vtParams.sampleQuality = " + quality + ";");
334 sb.AppendLine("vtParams.enableGlobalMipBias = " + (enableGlobalMipBias ? "1" : "0") + ";");
335 sb.AppendLine("#if defined(SHADER_STAGE_RAY_TRACING)");
336 sb.AppendLine("if (vtParams.levelMode == VtLevel_Automatic || vtParams.levelMode == VtLevel_Bias)");
337 using (sb.BlockScope())
338 {
339 sb.AppendLine("vtParams.levelMode = VtLevel_Lod;");
340 sb.AppendLine("vtParams.lodOrOffset = 0.0f;");
341 }
342 sb.AppendLine("#endif");
343 }
344
345 void AppendVtSample(ShaderStringBuilder sb, string propertiesName, string vtInputVariable, string infoVariable, int layerIndex, string outputVariableName)
346 {
347 sb.TryAppendIndentation();
348 sb.Append(outputVariableName); sb.Append(" = ");
349 sb.Append("SampleVTLayerWithTextureType(");
350 sb.Append(propertiesName); sb.Append(", ");
351 sb.Append(vtInputVariable); sb.Append(", ");
352 sb.Append(infoVariable); sb.Append(", ");
353 sb.Append(layerIndex.ToString()); sb.Append(");");
354 sb.AppendNewLine();
355 }
356
357 // Node generations
358 string GetFunctionName(out List<int> layerIndices)
359 {
360 string name = "SampleVirtualTexture_" + addressMode + "_" + lodCalculation + "_" + m_SampleQuality;
361 layerIndices = new List<int>();
362
363 if (IsSlotConnected(VirtualTextureInputId))
364 {
365 var vtProperty = GetSlotProperty(VirtualTextureInputId) as VirtualTextureShaderProperty;
366 if (vtProperty != null)
367 {
368 int layerCount = vtProperty.value.layers.Count;
369 for (int layer = 0; layer < layerCount; layer++)
370 {
371 if (IsSlotConnected(OutputSlotIds[layer]))
372 {
373 layerIndices.Add(layer);
374 name = name + "_" + layer;
375 }
376 }
377 }
378 }
379
380 return name;
381 }
382
383 public void GenerateNodeFunction(FunctionRegistry registry, GenerationMode generationMode)
384 {
385 string functionName = GetFunctionName(out var layerOutputLayerIndex);
386
387 if (layerOutputLayerIndex.Count <= 0)
388 return;
389
390 registry.ProvideFunction(functionName, s =>
391 {
392 string lodExpr = "0.0f";
393 string dxExpr = "0.0f";
394 string dyExpr = "0.0f";
395
396 // function header
397 s.TryAppendIndentation();
398 s.Append("float4 ");
399 s.Append(functionName);
400 s.Append("(float2 uv");
401 switch (lodCalculation)
402 {
403 case LodCalculation.VtLevel_Lod:
404 s.Append(", float lod");
405 lodExpr = "lod";
406 break;
407 case LodCalculation.VtLevel_Bias:
408 s.Append(", float bias");
409 lodExpr = "bias";
410 break;
411 case LodCalculation.VtLevel_Derivatives:
412 s.Append(", float2 dx, float2 dy");
413 dxExpr = "dx";
414 dyExpr = "dy";
415 break;
416 }
417 s.Append(", VTPropertyWithTextureType vtProperty");
418 for (int i = 0; i < layerOutputLayerIndex.Count; i++)
419 {
420 s.Append(", out float4 Layer" + layerOutputLayerIndex[i]);
421 }
422 s.Append(")");
423 s.AppendNewLine();
424
425 // function body
426 using (s.BlockScope())
427 {
428 AppendVtParameters(
429 s,
430 "uv",
431 lodExpr,
432 dxExpr,
433 dyExpr,
434 m_AddressMode,
435 FilterMode.VtFilter_Anisotropic,
436 m_LodCalculation,
437 UvSpace.VtUvSpace_Regular,
438 m_SampleQuality,
439 m_EnableGlobalMipBias);
440
441 s.AppendLine("StackInfo info = PrepareVT(vtProperty.vtProperty, vtParams);");
442
443 for (int i = 0; i < layerOutputLayerIndex.Count; i++)
444 {
445 // sample virtual texture layer
446 int layer = layerOutputLayerIndex[i];
447 AppendVtSample(s, "vtProperty", "vtParams", "info", layer, "Layer" + layer);
448 }
449
450 s.AppendLine("return GetResolveOutput(info);");
451 }
452 });
453 }
454
455 public void GenerateNodeCode(ShaderStringBuilder sb, GenerationMode generationMode)
456 {
457 bool success = false;
458 if (IsSlotConnected(VirtualTextureInputId))
459 {
460 var vtProperty = GetSlotProperty(VirtualTextureInputId) as VirtualTextureShaderProperty;
461 if (vtProperty != null)
462 {
463 var layerOutputVariables = new List<string>();
464 int layerCount = vtProperty.value.layers.Count;
465 for (int i = 0; i < layerCount; i++)
466 {
467 if (IsSlotConnected(OutputSlotIds[i]))
468 {
469 // declare output variables up front
470 string layerOutputVariable = GetVariableNameForSlot(OutputSlotIds[i]);
471 sb.AppendLine("$precision4 " + layerOutputVariable + ";");
472 layerOutputVariables.Add(layerOutputVariable);
473 }
474 }
475
476 if (layerOutputVariables.Count > 0)
477 {
478 // assign feedback variable
479 sb.TryAppendIndentation();
480 if (!noFeedback)
481 {
482 sb.Append("float4 ");
483 sb.Append(GetFeedbackVariableName());
484 sb.Append(" = ");
485 }
486 sb.Append(GetFunctionName(out var unused));
487 sb.Append("(");
488 sb.Append(GetSlotValue(UVInputId, generationMode));
489 switch (lodCalculation)
490 {
491 case LodCalculation.VtLevel_Lod:
492 case LodCalculation.VtLevel_Bias:
493 sb.Append(", ");
494 sb.Append((lodCalculation == LodCalculation.VtLevel_Lod) ? GetSlotValue(LODInputId, generationMode) : GetSlotValue(BiasInputId, generationMode));
495 break;
496 case LodCalculation.VtLevel_Derivatives:
497 sb.Append(", ");
498 sb.Append(GetSlotValue(DxInputId, generationMode));
499 sb.Append(", ");
500 sb.Append(GetSlotValue(DyInputId, generationMode));
501 break;
502 }
503 sb.Append(", ");
504 sb.Append(vtProperty.referenceName);
505 foreach (string layerOutputVariable in layerOutputVariables)
506 {
507 sb.Append(", ");
508 sb.Append(layerOutputVariable);
509 }
510 sb.Append(");");
511 sb.AppendNewLine();
512 success = true;
513 }
514 }
515 }
516
517
518 if (!success)
519 {
520 // set all outputs to zero
521 for (int i = 0; i < kMaxLayers; i++)
522 {
523 if (IsSlotConnected(OutputSlotIds[i]))
524 {
525 // declare output variables up front
526 string layerOutputVariable = GetVariableNameForSlot(OutputSlotIds[i]);
527 sb.AppendLine("$precision4 " + layerOutputVariable + " = 0;");
528 }
529 }
530 // TODO: should really just disable feedback in this case (need different feedback interface to do this)
531 sb.AppendLine("$precision4 " + GetFeedbackVariableName() + " = 1;");
532 }
533 }
534
535 public override void CollectShaderProperties(PropertyCollector properties, GenerationMode generationMode)
536 {
537 // this adds default properties for all of our unconnected inputs
538 base.CollectShaderProperties(properties, generationMode);
539 }
540
541 public bool RequiresMeshUV(Internal.UVChannel channel, ShaderStageCapability stageCapability)
542 {
543 using (var tempSlots = PooledList<MaterialSlot>.Get())
544 {
545 GetInputSlots(tempSlots);
546 foreach (var slot in tempSlots)
547 {
548 if (slot.RequiresMeshUV(channel))
549 return true;
550 }
551 return false;
552 }
553 }
554
555 public bool RequiresTime()
556 {
557 // HACK: This ensures we repaint in shadergraph so data that gets streamed in also becomes visible.
558 return true;
559 }
560
561 public bool RequiresScreenPosition(ShaderStageCapability stageCapability = ShaderStageCapability.All)
562 {
563 // Feedback dithering requires screen position (and only works in Pixel Shader currently)
564 // Note that the code that makes use of the screen position is not actually in this node,
565 // but is activated by the presence of this node..
566 // via a bit of a hack..
567 return stageCapability.HasFlag(ShaderStageCapability.Fragment) && !noFeedback;
568 }
569 }
570}