You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

570 lines
22 KiB

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.Graphing;
using UnityEditor.Graphing.Util;
using UnityEditor.Rendering;
using UnityEditor.ShaderGraph.Drawing;
using UnityEditor.ShaderGraph.Internal;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.Rendering;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph
{
[Title("Input", "Texture", SampleVirtualTextureNode.DefaultNodeTitle)]
class SampleVirtualTextureNode : AbstractMaterialNode, IGeneratesBodyCode, IGeneratesFunction, IMayRequireMeshUV, IMayRequireTime, IMayRequireScreenPosition
{
public const string DefaultNodeTitle = "Sample Virtual Texture";
public const int kMinLayers = 1;
public const int kMaxLayers = 4;
// input slots
public const int UVInputId = 0;
public const int VirtualTextureInputId = 1;
public const int LODInputId = 2;
public const int BiasInputId = 3;
public const int DxInputId = 4;
public const int DyInputId = 5;
// output slots
[NonSerialized]
public readonly int[] OutputSlotIds = { 11, 12, 13, 14 };
const string UVInputName = "UV";
const string VirtualTextureInputName = "VT";
const string LODSlotName = "Lod";
const string BiasSlotName = "Bias";
const string DxSlotName = "Dx";
const string DySlotName = "Dy";
static string[] OutputSlotNames = { "Out", "Out2", "Out3", "Out4" };
public override bool hasPreview { get { return false; } }
// Keep these in sync with "VirtualTexturing.hlsl"
public enum LodCalculation
{
[InspectorName("Automatic")]
VtLevel_Automatic = 0,
[InspectorName("Lod Level")]
VtLevel_Lod = 1,
[InspectorName("Lod Bias")]
VtLevel_Bias = 2,
[InspectorName("Derivatives")]
VtLevel_Derivatives = 3
}
public enum AddressMode
{
[InspectorName("Wrap")]
VtAddressMode_Wrap = 0,
[InspectorName("Clamp")]
VtAddressMode_Clamp = 1,
}
public enum FilterMode
{
[InspectorName("Anisotropic")]
VtFilter_Anisotropic = 0
}
public enum UvSpace
{
[InspectorName("Regular")]
VtUvSpace_Regular = 0,
[InspectorName("Pre Transformed")]
VtUvSpace_PreTransformed = 1
}
public enum QualityMode
{
[InspectorName("Low")]
VtSampleQuality_Low = 0,
[InspectorName("High")]
VtSampleQuality_High = 1
}
[SerializeField]
AddressMode m_AddressMode = AddressMode.VtAddressMode_Wrap;
public AddressMode addressMode
{
get
{
return m_AddressMode;
}
set
{
if (m_AddressMode == value)
return;
m_AddressMode = value;
Dirty(ModificationScope.Graph);
}
}
[SerializeField]
LodCalculation m_LodCalculation = LodCalculation.VtLevel_Automatic;
public LodCalculation lodCalculation
{
get
{
return m_LodCalculation;
}
set
{
if (m_LodCalculation == value)
return;
m_LodCalculation = value;
RebuildAllSlots(true); // LOD calculation may have associated slots that need to be updated
Dirty(ModificationScope.Topological); // slots ShaderStageCapability could have changed, so trigger Topo change
}
}
[SerializeField]
QualityMode m_SampleQuality = QualityMode.VtSampleQuality_High;
public QualityMode sampleQuality
{
get
{
return m_SampleQuality;
}
set
{
if (m_SampleQuality == value)
return;
m_SampleQuality = value;
Dirty(ModificationScope.Graph);
}
}
[SerializeField]
private bool m_EnableGlobalMipBias = true;
public bool enableGlobalMipBias
{
get
{
return m_EnableGlobalMipBias;
}
set
{
if (m_EnableGlobalMipBias == value)
return;
m_EnableGlobalMipBias = value;
Dirty(ModificationScope.Graph);
}
}
[SerializeField]
bool m_NoFeedback; // aka !AutomaticStreaming
public bool noFeedback
{
get
{
return m_NoFeedback;
}
set
{
if (m_NoFeedback == value)
return;
m_NoFeedback = value;
RebuildAllSlots(true);
Dirty(ModificationScope.Topological); // slots ShaderStageCapability could have changed, so trigger Topo change
}
}
public SampleVirtualTextureNode() : this(false, false)
{ }
public SampleVirtualTextureNode(bool isLod = false, bool noResolve = false)
{
name = "Sample Virtual Texture";
synonyms = new string[] { "buffer" };
UpdateNodeAfterDeserialization();
}
public override void Setup()
{
UpdateLayerOutputSlots(true);
}
// rebuilds the number of output slots, and also updates their ShaderStageCapability
private int outputLayerSlotCount = 0;
void UpdateLayerOutputSlots(bool inspectProperty, List<int> usedSlots = null)
{
// the default is to show all 4 slots, so we don't lose any existing connections
int layerCount = kMaxLayers;
if (inspectProperty)
{
var vtProperty = GetSlotProperty(VirtualTextureInputId) as VirtualTextureShaderProperty;
if (vtProperty != null)
{
layerCount = vtProperty?.value?.layers?.Count ?? kMaxLayers;
}
if (outputLayerSlotCount == layerCount)
{
if (usedSlots != null)
for (int i = 0; i < layerCount; i++)
usedSlots.Add(OutputSlotIds[i]);
return;
}
}
for (int i = 0; i < kMaxLayers; i++)
{
int outputID = OutputSlotIds[i];
Vector4MaterialSlot outputSlot = FindSlot<Vector4MaterialSlot>(outputID);
if (i < layerCount)
{
// add or update it
if (outputSlot == null)
{
string outputName = OutputSlotNames[i];
outputSlot = new Vector4MaterialSlot(outputID, outputName, outputName, SlotType.Output, Vector4.zero, (noFeedback && m_LodCalculation == LodCalculation.VtLevel_Lod) ? ShaderStageCapability.All : ShaderStageCapability.Fragment);
AddSlot(outputSlot);
}
else
{
outputSlot.stageCapability = (noFeedback && m_LodCalculation == LodCalculation.VtLevel_Lod) ? ShaderStageCapability.All : ShaderStageCapability.Fragment;
}
if (usedSlots != null)
usedSlots.Add(outputID);
}
else
{
// remove it
if (outputSlot != null)
RemoveSlot(OutputSlotIds[i]);
}
}
outputLayerSlotCount = layerCount;
}
public void RebuildAllSlots(bool inspectProperty)
{
List<int> usedSlots = new List<int>();
AddSlot(new UVMaterialSlot(UVInputId, UVInputName, UVInputName, UVChannel.UV0));
usedSlots.Add(UVInputId);
AddSlot(new VirtualTextureInputMaterialSlot(VirtualTextureInputId, VirtualTextureInputName, VirtualTextureInputName));
usedSlots.Add(VirtualTextureInputId);
// at this point we can't tell how many output slots we will have (because we can't find the VT property yet)
// so, we create all of the possible output slots, so any edges created will connect properly
// then we can trim down the set of slots later..
UpdateLayerOutputSlots(inspectProperty, usedSlots);
// Create slots
if (m_LodCalculation == LodCalculation.VtLevel_Lod)
{
var slot = new Vector1MaterialSlot(LODInputId, LODSlotName, LODSlotName, SlotType.Input, 0.0f, ShaderStageCapability.All, LODSlotName);
AddSlot(slot);
usedSlots.Add(LODInputId);
}
if (m_LodCalculation == LodCalculation.VtLevel_Bias)
{
var slot = new Vector1MaterialSlot(BiasInputId, BiasSlotName, BiasSlotName, SlotType.Input, 0.0f, ShaderStageCapability.Fragment, BiasSlotName);
AddSlot(slot);
usedSlots.Add(BiasInputId);
}
if (m_LodCalculation == LodCalculation.VtLevel_Derivatives)
{
var slot1 = new Vector2MaterialSlot(DxInputId, DxSlotName, DxSlotName, SlotType.Input, Vector2.one, ShaderStageCapability.All, DxSlotName);
var slot2 = new Vector2MaterialSlot(DyInputId, DySlotName, DySlotName, SlotType.Input, Vector2.one, ShaderStageCapability.All, DySlotName);
AddSlot(slot1);
AddSlot(slot2);
usedSlots.Add(DxInputId);
usedSlots.Add(DyInputId);
}
RemoveSlotsNameNotMatching(usedSlots, true);
}
public override void UpdateNodeAfterDeserialization()
{
RebuildAllSlots(false);
}
const string k_NoPropertyConnected = "A VirtualTexture property must be connected to the VT slot";
public override void ValidateNode()
{
base.ValidateNode();
if (!IsSlotConnected(VirtualTextureInputId))
{
owner.AddValidationError(objectId, k_NoPropertyConnected);
}
else
{
var vtProp = GetSlotProperty(VirtualTextureInputId) as VirtualTextureShaderProperty;
if (vtProp == null)
{
owner.AddValidationError(objectId, $"VT slot is not connected to a valid VirtualTexture property");
}
}
}
public string GetFeedbackVariableName()
{
return GetVariableNameForNode() + "_fb";
}
void AppendVtParameters(ShaderStringBuilder sb, string uvExpr, string lodExpr, string dxExpr, string dyExpr, AddressMode address, FilterMode filter, LodCalculation lod, UvSpace space, QualityMode quality, bool enableGlobalMipBias)
{
sb.AppendLine("VtInputParameters vtParams;");
sb.AppendLine("vtParams.uv = " + uvExpr + ";");
sb.AppendLine("vtParams.lodOrOffset = " + lodExpr + ";");
sb.AppendLine("vtParams.dx = " + dxExpr + ";");
sb.AppendLine("vtParams.dy = " + dyExpr + ";");
sb.AppendLine("vtParams.addressMode = " + address + ";");
sb.AppendLine("vtParams.filterMode = " + filter + ";");
sb.AppendLine("vtParams.levelMode = " + lod + ";");
sb.AppendLine("vtParams.uvMode = " + space + ";");
sb.AppendLine("vtParams.sampleQuality = " + quality + ";");
sb.AppendLine("vtParams.enableGlobalMipBias = " + (enableGlobalMipBias ? "1" : "0") + ";");
sb.AppendLine("#if defined(SHADER_STAGE_RAY_TRACING)");
sb.AppendLine("if (vtParams.levelMode == VtLevel_Automatic || vtParams.levelMode == VtLevel_Bias)");
using (sb.BlockScope())
{
sb.AppendLine("vtParams.levelMode = VtLevel_Lod;");
sb.AppendLine("vtParams.lodOrOffset = 0.0f;");
}
sb.AppendLine("#endif");
}
void AppendVtSample(ShaderStringBuilder sb, string propertiesName, string vtInputVariable, string infoVariable, int layerIndex, string outputVariableName)
{
sb.TryAppendIndentation();
sb.Append(outputVariableName); sb.Append(" = ");
sb.Append("SampleVTLayerWithTextureType(");
sb.Append(propertiesName); sb.Append(", ");
sb.Append(vtInputVariable); sb.Append(", ");
sb.Append(infoVariable); sb.Append(", ");
sb.Append(layerIndex.ToString()); sb.Append(");");
sb.AppendNewLine();
}
// Node generations
string GetFunctionName(out List<int> layerIndices)
{
string name = "SampleVirtualTexture_" + addressMode + "_" + lodCalculation + "_" + m_SampleQuality;
layerIndices = new List<int>();
if (IsSlotConnected(VirtualTextureInputId))
{
var vtProperty = GetSlotProperty(VirtualTextureInputId) as VirtualTextureShaderProperty;
if (vtProperty != null)
{
int layerCount = vtProperty.value.layers.Count;
for (int layer = 0; layer < layerCount; layer++)
{
if (IsSlotConnected(OutputSlotIds[layer]))
{
layerIndices.Add(layer);
name = name + "_" + layer;
}
}
}
}
return name;
}
public void GenerateNodeFunction(FunctionRegistry registry, GenerationMode generationMode)
{
string functionName = GetFunctionName(out var layerOutputLayerIndex);
if (layerOutputLayerIndex.Count <= 0)
return;
registry.ProvideFunction(functionName, s =>
{
string lodExpr = "0.0f";
string dxExpr = "0.0f";
string dyExpr = "0.0f";
// function header
s.TryAppendIndentation();
s.Append("float4 ");
s.Append(functionName);
s.Append("(float2 uv");
switch (lodCalculation)
{
case LodCalculation.VtLevel_Lod:
s.Append(", float lod");
lodExpr = "lod";
break;
case LodCalculation.VtLevel_Bias:
s.Append(", float bias");
lodExpr = "bias";
break;
case LodCalculation.VtLevel_Derivatives:
s.Append(", float2 dx, float2 dy");
dxExpr = "dx";
dyExpr = "dy";
break;
}
s.Append(", VTPropertyWithTextureType vtProperty");
for (int i = 0; i < layerOutputLayerIndex.Count; i++)
{
s.Append(", out float4 Layer" + layerOutputLayerIndex[i]);
}
s.Append(")");
s.AppendNewLine();
// function body
using (s.BlockScope())
{
AppendVtParameters(
s,
"uv",
lodExpr,
dxExpr,
dyExpr,
m_AddressMode,
FilterMode.VtFilter_Anisotropic,
m_LodCalculation,
UvSpace.VtUvSpace_Regular,
m_SampleQuality,
m_EnableGlobalMipBias);
s.AppendLine("StackInfo info = PrepareVT(vtProperty.vtProperty, vtParams);");
for (int i = 0; i < layerOutputLayerIndex.Count; i++)
{
// sample virtual texture layer
int layer = layerOutputLayerIndex[i];
AppendVtSample(s, "vtProperty", "vtParams", "info", layer, "Layer" + layer);
}
s.AppendLine("return GetResolveOutput(info);");
}
});
}
public void GenerateNodeCode(ShaderStringBuilder sb, GenerationMode generationMode)
{
bool success = false;
if (IsSlotConnected(VirtualTextureInputId))
{
var vtProperty = GetSlotProperty(VirtualTextureInputId) as VirtualTextureShaderProperty;
if (vtProperty != null)
{
var layerOutputVariables = new List<string>();
int layerCount = vtProperty.value.layers.Count;
for (int i = 0; i < layerCount; i++)
{
if (IsSlotConnected(OutputSlotIds[i]))
{
// declare output variables up front
string layerOutputVariable = GetVariableNameForSlot(OutputSlotIds[i]);
sb.AppendLine("$precision4 " + layerOutputVariable + ";");
layerOutputVariables.Add(layerOutputVariable);
}
}
if (layerOutputVariables.Count > 0)
{
// assign feedback variable
sb.TryAppendIndentation();
if (!noFeedback)
{
sb.Append("float4 ");
sb.Append(GetFeedbackVariableName());
sb.Append(" = ");
}
sb.Append(GetFunctionName(out var unused));
sb.Append("(");
sb.Append(GetSlotValue(UVInputId, generationMode));
switch (lodCalculation)
{
case LodCalculation.VtLevel_Lod:
case LodCalculation.VtLevel_Bias:
sb.Append(", ");
sb.Append((lodCalculation == LodCalculation.VtLevel_Lod) ? GetSlotValue(LODInputId, generationMode) : GetSlotValue(BiasInputId, generationMode));
break;
case LodCalculation.VtLevel_Derivatives:
sb.Append(", ");
sb.Append(GetSlotValue(DxInputId, generationMode));
sb.Append(", ");
sb.Append(GetSlotValue(DyInputId, generationMode));
break;
}
sb.Append(", ");
sb.Append(vtProperty.referenceName);
foreach (string layerOutputVariable in layerOutputVariables)
{
sb.Append(", ");
sb.Append(layerOutputVariable);
}
sb.Append(");");
sb.AppendNewLine();
success = true;
}
}
}
if (!success)
{
// set all outputs to zero
for (int i = 0; i < kMaxLayers; i++)
{
if (IsSlotConnected(OutputSlotIds[i]))
{
// declare output variables up front
string layerOutputVariable = GetVariableNameForSlot(OutputSlotIds[i]);
sb.AppendLine("$precision4 " + layerOutputVariable + " = 0;");
}
}
// TODO: should really just disable feedback in this case (need different feedback interface to do this)
sb.AppendLine("$precision4 " + GetFeedbackVariableName() + " = 1;");
}
}
public override void CollectShaderProperties(PropertyCollector properties, GenerationMode generationMode)
{
// this adds default properties for all of our unconnected inputs
base.CollectShaderProperties(properties, generationMode);
}
public bool RequiresMeshUV(Internal.UVChannel channel, ShaderStageCapability stageCapability)
{
using (var tempSlots = PooledList<MaterialSlot>.Get())
{
GetInputSlots(tempSlots);
foreach (var slot in tempSlots)
{
if (slot.RequiresMeshUV(channel))
return true;
}
return false;
}
}
public bool RequiresTime()
{
// HACK: This ensures we repaint in shadergraph so data that gets streamed in also becomes visible.
return true;
}
public bool RequiresScreenPosition(ShaderStageCapability stageCapability = ShaderStageCapability.All)
{
// Feedback dithering requires screen position (and only works in Pixel Shader currently)
// Note that the code that makes use of the screen position is not actually in this node,
// but is activated by the presence of this node..
// via a bit of a hack..
return stageCapability.HasFlag(ShaderStageCapability.Fragment) && !noFeedback;
}
}
}