A game about forced loneliness, made by TACStudios
at master 22 kB view raw
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}