1011 lines
35 KiB
C#
1011 lines
35 KiB
C#
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Linq;
|
||
|
using UnityEngine;
|
||
|
using UnityEditor.Graphing;
|
||
|
using UnityEditor.ShaderGraph.Drawing.Colors;
|
||
|
using UnityEditor.ShaderGraph.Internal;
|
||
|
using UnityEditor.ShaderGraph.Drawing;
|
||
|
using UnityEditor.ShaderGraph.Serialization;
|
||
|
using UnityEngine.Assertions;
|
||
|
using UnityEngine.Pool;
|
||
|
|
||
|
namespace UnityEditor.ShaderGraph
|
||
|
{
|
||
|
[Serializable]
|
||
|
abstract class AbstractMaterialNode : JsonObject, IGroupItem, IRectInterface
|
||
|
{
|
||
|
[SerializeField]
|
||
|
JsonRef<GroupData> m_Group = null;
|
||
|
|
||
|
[SerializeField]
|
||
|
private string m_Name;
|
||
|
|
||
|
[SerializeField]
|
||
|
private DrawState m_DrawState;
|
||
|
|
||
|
[NonSerialized]
|
||
|
bool m_HasError;
|
||
|
|
||
|
[NonSerialized]
|
||
|
bool m_IsValid = true;
|
||
|
|
||
|
[NonSerialized]
|
||
|
bool m_IsActive = true;
|
||
|
|
||
|
[NonSerialized]
|
||
|
bool m_WasUsedByGenerator = false;
|
||
|
|
||
|
[SerializeField]
|
||
|
List<JsonData<MaterialSlot>> m_Slots = new List<JsonData<MaterialSlot>>();
|
||
|
|
||
|
public GraphData owner { get; set; }
|
||
|
|
||
|
internal virtual bool ExposeToSearcher => true;
|
||
|
|
||
|
OnNodeModified m_OnModified;
|
||
|
|
||
|
Action m_UnregisterAll;
|
||
|
|
||
|
public GroupData group
|
||
|
{
|
||
|
get => m_Group;
|
||
|
set
|
||
|
{
|
||
|
if (m_Group == value)
|
||
|
return;
|
||
|
|
||
|
m_Group = value;
|
||
|
Dirty(ModificationScope.Topological);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void RegisterCallback(OnNodeModified callback)
|
||
|
{
|
||
|
m_OnModified += callback;
|
||
|
|
||
|
// Setup so we can unregister this callback later at teardown time
|
||
|
m_UnregisterAll += () => m_OnModified -= callback;
|
||
|
}
|
||
|
|
||
|
public void UnregisterCallback(OnNodeModified callback)
|
||
|
{
|
||
|
m_OnModified -= callback;
|
||
|
}
|
||
|
|
||
|
public void Dirty(ModificationScope scope)
|
||
|
{
|
||
|
// Calling m_OnModified immediately upon dirtying the node can result in a lot of churn. For example,
|
||
|
// nodes can cause cascading view updates *multiple times* per operation.
|
||
|
// If this call causes future performance issues, we should investigate some kind of deferral or early out
|
||
|
// until all of the dirty nodes have been identified.
|
||
|
if (m_OnModified != null && !owner.replaceInProgress)
|
||
|
m_OnModified(this, scope);
|
||
|
NodeValidation.HandleValidationExtensions(this);
|
||
|
}
|
||
|
|
||
|
public string name
|
||
|
{
|
||
|
get { return m_Name; }
|
||
|
set { m_Name = value; }
|
||
|
}
|
||
|
|
||
|
public virtual string displayName => name;
|
||
|
|
||
|
public string[] synonyms;
|
||
|
|
||
|
protected virtual string documentationPage => name;
|
||
|
public virtual string documentationURL => NodeUtils.GetDocumentationString(documentationPage);
|
||
|
|
||
|
public virtual bool canDeleteNode => owner != null && owner.outputNode != this;
|
||
|
|
||
|
public DrawState drawState
|
||
|
{
|
||
|
get { return m_DrawState; }
|
||
|
set
|
||
|
{
|
||
|
m_DrawState = value;
|
||
|
Dirty(ModificationScope.Layout);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Rect IRectInterface.rect
|
||
|
{
|
||
|
get => drawState.position;
|
||
|
set
|
||
|
{
|
||
|
var state = drawState;
|
||
|
state.position = value;
|
||
|
drawState = state;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public virtual bool canSetPrecision
|
||
|
{
|
||
|
get { return true; }
|
||
|
}
|
||
|
|
||
|
// this is the precision after the inherit/automatic behavior has been calculated
|
||
|
// it does NOT include fallback to any graph default precision
|
||
|
public GraphPrecision graphPrecision { get; set; } = GraphPrecision.Single;
|
||
|
|
||
|
private ConcretePrecision m_ConcretePrecision = ConcretePrecision.Single;
|
||
|
|
||
|
public ConcretePrecision concretePrecision
|
||
|
{
|
||
|
get => m_ConcretePrecision;
|
||
|
set => m_ConcretePrecision = value;
|
||
|
}
|
||
|
|
||
|
[SerializeField]
|
||
|
private Precision m_Precision = Precision.Inherit;
|
||
|
|
||
|
public Precision precision
|
||
|
{
|
||
|
get => m_Precision;
|
||
|
set => m_Precision = value;
|
||
|
}
|
||
|
|
||
|
[SerializeField]
|
||
|
bool m_PreviewExpanded = true;
|
||
|
|
||
|
public bool previewExpanded
|
||
|
{
|
||
|
get { return m_PreviewExpanded; }
|
||
|
set
|
||
|
{
|
||
|
if (previewExpanded == value)
|
||
|
return;
|
||
|
m_PreviewExpanded = value;
|
||
|
Dirty(ModificationScope.Node);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[SerializeField]
|
||
|
protected int m_DismissedVersion = 0;
|
||
|
public int dismissedUpdateVersion { get => m_DismissedVersion; set => m_DismissedVersion = value; }
|
||
|
|
||
|
// by default, if this returns null, the system will allow creation of any previous version
|
||
|
public virtual IEnumerable<int> allowedNodeVersions => null;
|
||
|
|
||
|
// Nodes that want to have a preview area can override this and return true
|
||
|
public virtual bool hasPreview
|
||
|
{
|
||
|
get { return false; }
|
||
|
}
|
||
|
|
||
|
[SerializeField]
|
||
|
internal PreviewMode m_PreviewMode = PreviewMode.Inherit;
|
||
|
public virtual PreviewMode previewMode
|
||
|
{
|
||
|
get { return m_PreviewMode; }
|
||
|
}
|
||
|
|
||
|
public virtual bool allowedInSubGraph
|
||
|
{
|
||
|
get { return !(this is BlockNode); }
|
||
|
}
|
||
|
|
||
|
public virtual bool allowedInMainGraph
|
||
|
{
|
||
|
get { return true; }
|
||
|
}
|
||
|
|
||
|
public virtual bool allowedInLayerGraph
|
||
|
{
|
||
|
get { return true; }
|
||
|
}
|
||
|
|
||
|
public virtual bool hasError
|
||
|
{
|
||
|
get { return m_HasError; }
|
||
|
protected set { m_HasError = value; }
|
||
|
}
|
||
|
|
||
|
public virtual bool isActive
|
||
|
{
|
||
|
get { return m_IsActive; }
|
||
|
}
|
||
|
|
||
|
internal virtual bool wasUsedByGenerator
|
||
|
{
|
||
|
get { return m_WasUsedByGenerator; }
|
||
|
}
|
||
|
|
||
|
internal void SetUsedByGenerator()
|
||
|
{
|
||
|
m_WasUsedByGenerator = true;
|
||
|
}
|
||
|
|
||
|
//There are times when isActive needs to be set to a value explicitly, and
|
||
|
//not be changed by active forest parsing (what we do when we need to figure out
|
||
|
//what nodes should or should not be active, usually from an edit; see NodeUtils).
|
||
|
//In this case, we allow for explicit setting of an active value that cant be overriden.
|
||
|
//Implicit implies that active forest parsing can edit the nodes isActive property
|
||
|
public enum ActiveState
|
||
|
{
|
||
|
Implicit = 0,
|
||
|
ExplicitInactive = 1,
|
||
|
ExplicitActive = 2
|
||
|
}
|
||
|
|
||
|
private ActiveState m_ActiveState = ActiveState.Implicit;
|
||
|
public ActiveState activeState
|
||
|
{
|
||
|
get => m_ActiveState;
|
||
|
}
|
||
|
|
||
|
public void SetOverrideActiveState(ActiveState overrideState, bool updateConnections = true)
|
||
|
{
|
||
|
if (m_ActiveState == overrideState)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
m_ActiveState = overrideState;
|
||
|
switch (m_ActiveState)
|
||
|
{
|
||
|
case ActiveState.Implicit:
|
||
|
if (updateConnections)
|
||
|
{
|
||
|
NodeUtils.ReevaluateActivityOfConnectedNodes(this);
|
||
|
}
|
||
|
break;
|
||
|
case ActiveState.ExplicitInactive:
|
||
|
if (m_IsActive == false)
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_IsActive = false;
|
||
|
Dirty(ModificationScope.Node);
|
||
|
if (updateConnections)
|
||
|
{
|
||
|
NodeUtils.ReevaluateActivityOfConnectedNodes(this);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
case ActiveState.ExplicitActive:
|
||
|
if (m_IsActive == true)
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_IsActive = true;
|
||
|
Dirty(ModificationScope.Node);
|
||
|
if (updateConnections)
|
||
|
{
|
||
|
NodeUtils.ReevaluateActivityOfConnectedNodes(this);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void SetActive(bool value, bool updateConnections = true)
|
||
|
{
|
||
|
if (m_IsActive == value)
|
||
|
return;
|
||
|
|
||
|
if (m_ActiveState != ActiveState.Implicit)
|
||
|
{
|
||
|
Debug.LogError($"Cannot set IsActive on Node {this} when value is explicitly overriden by ActiveState {m_ActiveState}");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Update this node
|
||
|
m_IsActive = value;
|
||
|
Dirty(ModificationScope.Node);
|
||
|
|
||
|
if (updateConnections)
|
||
|
{
|
||
|
NodeUtils.ReevaluateActivityOfConnectedNodes(this);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public virtual bool isValid
|
||
|
{
|
||
|
get { return m_IsValid; }
|
||
|
set
|
||
|
{
|
||
|
if (m_IsValid == value)
|
||
|
return;
|
||
|
|
||
|
m_IsValid = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
string m_DefaultVariableName;
|
||
|
string m_NameForDefaultVariableName;
|
||
|
|
||
|
string defaultVariableName
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (m_NameForDefaultVariableName != name)
|
||
|
{
|
||
|
m_DefaultVariableName = string.Format("{0}_{1}", NodeUtils.GetHLSLSafeName(name ?? "node"), objectId);
|
||
|
m_NameForDefaultVariableName = name;
|
||
|
}
|
||
|
return m_DefaultVariableName;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#region Custom Colors
|
||
|
|
||
|
[SerializeField]
|
||
|
CustomColorData m_CustomColors = new CustomColorData();
|
||
|
|
||
|
public bool TryGetColor(string provider, ref Color color)
|
||
|
{
|
||
|
return m_CustomColors.TryGetColor(provider, out color);
|
||
|
}
|
||
|
|
||
|
public void ResetColor(string provider)
|
||
|
{
|
||
|
m_CustomColors.Remove(provider);
|
||
|
}
|
||
|
|
||
|
public void SetColor(string provider, Color color)
|
||
|
{
|
||
|
m_CustomColors.Set(provider, color);
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
protected AbstractMaterialNode()
|
||
|
{
|
||
|
m_DrawState.expanded = true;
|
||
|
}
|
||
|
|
||
|
public void GetInputSlots<T>(List<T> foundSlots) where T : MaterialSlot
|
||
|
{
|
||
|
foreach (var slot in m_Slots.SelectValue())
|
||
|
{
|
||
|
if (slot.isInputSlot && slot is T)
|
||
|
foundSlots.Add((T)slot);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public virtual void GetInputSlots<T>(MaterialSlot startingSlot, List<T> foundSlots) where T : MaterialSlot
|
||
|
{
|
||
|
GetInputSlots(foundSlots);
|
||
|
}
|
||
|
|
||
|
public void GetOutputSlots<T>(List<T> foundSlots) where T : MaterialSlot
|
||
|
{
|
||
|
foreach (var slot in m_Slots.SelectValue())
|
||
|
{
|
||
|
if (slot.isOutputSlot && slot is T materialSlot)
|
||
|
{
|
||
|
foundSlots.Add(materialSlot);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public virtual void GetOutputSlots<T>(MaterialSlot startingSlot, List<T> foundSlots) where T : MaterialSlot
|
||
|
{
|
||
|
GetOutputSlots(foundSlots);
|
||
|
}
|
||
|
|
||
|
public void GetSlots<T>(List<T> foundSlots) where T : MaterialSlot
|
||
|
{
|
||
|
foreach (var slot in m_Slots.SelectValue())
|
||
|
{
|
||
|
if (slot is T materialSlot)
|
||
|
{
|
||
|
foundSlots.Add(materialSlot);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public virtual void CollectShaderProperties(PropertyCollector properties, GenerationMode generationMode)
|
||
|
{
|
||
|
foreach (var inputSlot in this.GetInputSlots<MaterialSlot>())
|
||
|
{
|
||
|
var edges = owner.GetEdges(inputSlot.slotReference);
|
||
|
if (edges.Any(e => e.outputSlot.node.isActive))
|
||
|
continue;
|
||
|
|
||
|
inputSlot.AddDefaultProperty(properties, generationMode);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public string GetSlotValue(int inputSlotId, GenerationMode generationMode, ConcretePrecision concretePrecision)
|
||
|
{
|
||
|
string slotValue = GetSlotValue(inputSlotId, generationMode);
|
||
|
return slotValue.Replace(PrecisionUtil.Token, concretePrecision.ToShaderString());
|
||
|
}
|
||
|
|
||
|
public string GetSlotValue(int inputSlotId, GenerationMode generationMode)
|
||
|
{
|
||
|
var inputSlot = FindSlot<MaterialSlot>(inputSlotId);
|
||
|
if (inputSlot == null)
|
||
|
return string.Empty;
|
||
|
|
||
|
var edges = owner.GetEdges(inputSlot.slotReference);
|
||
|
|
||
|
if (edges.Any())
|
||
|
{
|
||
|
var fromSocketRef = edges.First().outputSlot;
|
||
|
var fromNode = fromSocketRef.node;
|
||
|
return fromNode.GetOutputForSlot(fromSocketRef, inputSlot.concreteValueType, generationMode);
|
||
|
}
|
||
|
|
||
|
return inputSlot.GetDefaultValue(generationMode);
|
||
|
}
|
||
|
|
||
|
public AbstractShaderProperty GetSlotProperty(int inputSlotId)
|
||
|
{
|
||
|
if (owner == null)
|
||
|
return null;
|
||
|
|
||
|
var inputSlot = FindSlot<MaterialSlot>(inputSlotId);
|
||
|
if (inputSlot?.slotReference.node == null)
|
||
|
return null;
|
||
|
|
||
|
var edges = owner.GetEdges(inputSlot.slotReference);
|
||
|
if (edges.Any())
|
||
|
{
|
||
|
var fromSocketRef = edges.First().outputSlot;
|
||
|
var fromNode = fromSocketRef.node;
|
||
|
if (fromNode == null)
|
||
|
return null; // this is an error condition... we have an edge that connects to a non-existant node?
|
||
|
|
||
|
if (fromNode is PropertyNode propNode)
|
||
|
{
|
||
|
return propNode.property;
|
||
|
}
|
||
|
|
||
|
if (fromNode is RedirectNodeData redirectNode)
|
||
|
{
|
||
|
return redirectNode.GetSlotProperty(RedirectNodeData.kInputSlotID);
|
||
|
}
|
||
|
|
||
|
#if PROCEDURAL_VT_IN_GRAPH
|
||
|
if (fromNode is ProceduralVirtualTextureNode pvtNode)
|
||
|
{
|
||
|
return pvtNode.AsShaderProperty();
|
||
|
}
|
||
|
#endif // PROCEDURAL_VT_IN_GRAPH
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
protected internal virtual string GetOutputForSlot(SlotReference fromSocketRef, ConcreteSlotValueType valueType, GenerationMode generationMode)
|
||
|
{
|
||
|
var slot = FindOutputSlot<MaterialSlot>(fromSocketRef.slotId);
|
||
|
if (slot == null)
|
||
|
return string.Empty;
|
||
|
|
||
|
if (fromSocketRef.node.isActive)
|
||
|
return GenerationUtils.AdaptNodeOutput(this, slot.id, valueType);
|
||
|
else
|
||
|
return slot.GetDefaultValue(generationMode);
|
||
|
}
|
||
|
|
||
|
public AbstractMaterialNode GetInputNodeFromSlot(int inputSlotId)
|
||
|
{
|
||
|
var inputSlot = FindSlot<MaterialSlot>(inputSlotId);
|
||
|
if (inputSlot == null)
|
||
|
return null;
|
||
|
|
||
|
var edges = owner.GetEdges(inputSlot.slotReference).ToArray();
|
||
|
AbstractMaterialNode fromNode = null;
|
||
|
if (edges.Count() > 0)
|
||
|
{
|
||
|
var fromSocketRef = edges[0].outputSlot;
|
||
|
fromNode = fromSocketRef.node;
|
||
|
}
|
||
|
return fromNode;
|
||
|
}
|
||
|
|
||
|
public static ConcreteSlotValueType ConvertDynamicVectorInputTypeToConcrete(IEnumerable<ConcreteSlotValueType> inputTypes)
|
||
|
{
|
||
|
var concreteSlotValueTypes = inputTypes as IList<ConcreteSlotValueType> ?? inputTypes.ToList();
|
||
|
|
||
|
var inputTypesDistinct = concreteSlotValueTypes.Distinct().ToList();
|
||
|
switch (inputTypesDistinct.Count)
|
||
|
{
|
||
|
case 0:
|
||
|
// nothing connected -- use Vec1 by default
|
||
|
return ConcreteSlotValueType.Vector1;
|
||
|
case 1:
|
||
|
if (SlotValueHelper.AreCompatible(SlotValueType.DynamicVector, inputTypesDistinct.First()))
|
||
|
{
|
||
|
if (inputTypesDistinct.First() == ConcreteSlotValueType.Boolean)
|
||
|
return ConcreteSlotValueType.Vector1;
|
||
|
return inputTypesDistinct.First();
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
// find the 'minumum' channel width excluding 1 as it can promote
|
||
|
inputTypesDistinct.RemoveAll(x => (x == ConcreteSlotValueType.Vector1) || (x == ConcreteSlotValueType.Boolean));
|
||
|
var ordered = inputTypesDistinct.OrderByDescending(x => x);
|
||
|
if (ordered.Any())
|
||
|
{
|
||
|
var first = ordered.FirstOrDefault();
|
||
|
return first;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
return ConcreteSlotValueType.Vector1;
|
||
|
}
|
||
|
|
||
|
public static ConcreteSlotValueType ConvertDynamicMatrixInputTypeToConcrete(IEnumerable<ConcreteSlotValueType> inputTypes)
|
||
|
{
|
||
|
var concreteSlotValueTypes = inputTypes as IList<ConcreteSlotValueType> ?? inputTypes.ToList();
|
||
|
|
||
|
var inputTypesDistinct = concreteSlotValueTypes.Distinct().ToList();
|
||
|
switch (inputTypesDistinct.Count)
|
||
|
{
|
||
|
case 0:
|
||
|
return ConcreteSlotValueType.Matrix2;
|
||
|
case 1:
|
||
|
return inputTypesDistinct.FirstOrDefault();
|
||
|
default:
|
||
|
var ordered = inputTypesDistinct.OrderByDescending(x => x);
|
||
|
if (ordered.Any())
|
||
|
return ordered.FirstOrDefault();
|
||
|
break;
|
||
|
}
|
||
|
return ConcreteSlotValueType.Matrix2;
|
||
|
}
|
||
|
|
||
|
protected const string k_validationErrorMessage = "Error found during node validation";
|
||
|
|
||
|
// evaluate ALL the precisions...
|
||
|
public virtual void UpdatePrecision(List<MaterialSlot> inputSlots)
|
||
|
{
|
||
|
// first let's reduce from precision ==> graph precision
|
||
|
if (precision == Precision.Inherit)
|
||
|
{
|
||
|
// inherit means calculate it automatically based on inputs
|
||
|
|
||
|
// If no inputs were found use the precision of the Graph
|
||
|
if (inputSlots.Count == 0)
|
||
|
{
|
||
|
graphPrecision = GraphPrecision.Graph;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
int curGraphPrecision = (int)GraphPrecision.Half;
|
||
|
foreach (var inputSlot in inputSlots)
|
||
|
{
|
||
|
// If input port doesn't have an edge use the Graph's precision for that input
|
||
|
var edges = owner?.GetEdges(inputSlot.slotReference).ToList();
|
||
|
if (!edges.Any())
|
||
|
{
|
||
|
// disconnected inputs use graph precision
|
||
|
curGraphPrecision = Math.Min(curGraphPrecision, (int)GraphPrecision.Graph);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
var outputSlotRef = edges[0].outputSlot;
|
||
|
var outputNode = outputSlotRef.node;
|
||
|
curGraphPrecision = Math.Min(curGraphPrecision, (int)outputNode.graphPrecision);
|
||
|
}
|
||
|
}
|
||
|
graphPrecision = (GraphPrecision)curGraphPrecision;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// not inherited, just use the node's selected precision
|
||
|
graphPrecision = precision.ToGraphPrecision(GraphPrecision.Graph);
|
||
|
}
|
||
|
|
||
|
// calculate the concrete precision, with fall-back to the graph concrete precision
|
||
|
concretePrecision = graphPrecision.ToConcrete(owner.graphDefaultConcretePrecision);
|
||
|
}
|
||
|
|
||
|
public virtual void EvaluateDynamicMaterialSlots(List<MaterialSlot> inputSlots, List<MaterialSlot> outputSlots)
|
||
|
{
|
||
|
var dynamicInputSlotsToCompare = DictionaryPool<DynamicVectorMaterialSlot, ConcreteSlotValueType>.Get();
|
||
|
var skippedDynamicSlots = ListPool<DynamicVectorMaterialSlot>.Get();
|
||
|
|
||
|
var dynamicMatrixInputSlotsToCompare = DictionaryPool<DynamicMatrixMaterialSlot, ConcreteSlotValueType>.Get();
|
||
|
var skippedDynamicMatrixSlots = ListPool<DynamicMatrixMaterialSlot>.Get();
|
||
|
|
||
|
// iterate the input slots
|
||
|
{
|
||
|
foreach (var inputSlot in inputSlots)
|
||
|
{
|
||
|
inputSlot.hasError = false;
|
||
|
// if there is a connection
|
||
|
var edges = owner.GetEdges(inputSlot.slotReference).ToList();
|
||
|
if (!edges.Any())
|
||
|
{
|
||
|
if (inputSlot is DynamicVectorMaterialSlot)
|
||
|
skippedDynamicSlots.Add(inputSlot as DynamicVectorMaterialSlot);
|
||
|
if (inputSlot is DynamicMatrixMaterialSlot)
|
||
|
skippedDynamicMatrixSlots.Add(inputSlot as DynamicMatrixMaterialSlot);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// get the output details
|
||
|
var outputSlotRef = edges[0].outputSlot;
|
||
|
var outputNode = outputSlotRef.node;
|
||
|
if (outputNode == null)
|
||
|
continue;
|
||
|
|
||
|
var outputSlot = outputNode.FindOutputSlot<MaterialSlot>(outputSlotRef.slotId);
|
||
|
if (outputSlot == null)
|
||
|
continue;
|
||
|
|
||
|
if (outputSlot.hasError)
|
||
|
{
|
||
|
inputSlot.hasError = true;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
var outputConcreteType = outputSlot.concreteValueType;
|
||
|
// dynamic input... depends on output from other node.
|
||
|
// we need to compare ALL dynamic inputs to make sure they
|
||
|
// are compatible.
|
||
|
if (inputSlot is DynamicVectorMaterialSlot)
|
||
|
{
|
||
|
dynamicInputSlotsToCompare.Add((DynamicVectorMaterialSlot)inputSlot, outputConcreteType);
|
||
|
continue;
|
||
|
}
|
||
|
else if (inputSlot is DynamicMatrixMaterialSlot)
|
||
|
{
|
||
|
dynamicMatrixInputSlotsToCompare.Add((DynamicMatrixMaterialSlot)inputSlot, outputConcreteType);
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// we can now figure out the dynamic slotType
|
||
|
// from here set all the
|
||
|
var dynamicType = ConvertDynamicVectorInputTypeToConcrete(dynamicInputSlotsToCompare.Values);
|
||
|
foreach (var dynamicKvP in dynamicInputSlotsToCompare)
|
||
|
dynamicKvP.Key.SetConcreteType(dynamicType);
|
||
|
foreach (var skippedSlot in skippedDynamicSlots)
|
||
|
skippedSlot.SetConcreteType(dynamicType);
|
||
|
|
||
|
// and now dynamic matrices
|
||
|
var dynamicMatrixType = ConvertDynamicMatrixInputTypeToConcrete(dynamicMatrixInputSlotsToCompare.Values);
|
||
|
foreach (var dynamicKvP in dynamicMatrixInputSlotsToCompare)
|
||
|
dynamicKvP.Key.SetConcreteType(dynamicMatrixType);
|
||
|
foreach (var skippedSlot in skippedDynamicMatrixSlots)
|
||
|
skippedSlot.SetConcreteType(dynamicMatrixType);
|
||
|
|
||
|
bool inputError = inputSlots.Any(x => x.hasError);
|
||
|
if (inputError)
|
||
|
{
|
||
|
owner.AddConcretizationError(objectId, string.Format("Node {0} had input error", objectId));
|
||
|
hasError = true;
|
||
|
}
|
||
|
|
||
|
// configure the output slots now
|
||
|
// their slotType will either be the default output slotType
|
||
|
// or the above dynamic slotType for dynamic nodes
|
||
|
// or error if there is an input error
|
||
|
foreach (var outputSlot in outputSlots)
|
||
|
{
|
||
|
outputSlot.hasError = false;
|
||
|
|
||
|
if (inputError)
|
||
|
{
|
||
|
outputSlot.hasError = true;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (outputSlot is DynamicVectorMaterialSlot dynamicVectorMaterialSlot)
|
||
|
{
|
||
|
dynamicVectorMaterialSlot.SetConcreteType(dynamicType);
|
||
|
continue;
|
||
|
}
|
||
|
else if (outputSlot is DynamicMatrixMaterialSlot dynamicMatrixMaterialSlot)
|
||
|
{
|
||
|
dynamicMatrixMaterialSlot.SetConcreteType(dynamicMatrixType);
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (outputSlots.Any(x => x.hasError))
|
||
|
{
|
||
|
owner.AddConcretizationError(objectId, string.Format("Node {0} had output error", objectId));
|
||
|
hasError = true;
|
||
|
}
|
||
|
CalculateNodeHasError();
|
||
|
|
||
|
ListPool<DynamicVectorMaterialSlot>.Release(skippedDynamicSlots);
|
||
|
DictionaryPool<DynamicVectorMaterialSlot, ConcreteSlotValueType>.Release(dynamicInputSlotsToCompare);
|
||
|
|
||
|
ListPool<DynamicMatrixMaterialSlot>.Release(skippedDynamicMatrixSlots);
|
||
|
DictionaryPool<DynamicMatrixMaterialSlot, ConcreteSlotValueType>.Release(dynamicMatrixInputSlotsToCompare);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public virtual void Concretize()
|
||
|
{
|
||
|
hasError = false;
|
||
|
owner?.ClearErrorsForNode(this);
|
||
|
|
||
|
using (var inputSlots = PooledList<MaterialSlot>.Get())
|
||
|
using (var outputSlots = PooledList<MaterialSlot>.Get())
|
||
|
{
|
||
|
GetInputSlots(inputSlots);
|
||
|
GetOutputSlots(outputSlots);
|
||
|
|
||
|
UpdatePrecision(inputSlots);
|
||
|
EvaluateDynamicMaterialSlots(inputSlots, outputSlots);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public virtual void ValidateNode()
|
||
|
{
|
||
|
if ((sgVersion < latestVersion) && (dismissedUpdateVersion < latestVersion))
|
||
|
owner.messageManager?.AddOrAppendError(owner, objectId, new ShaderMessage("There is a newer version of this node available. Inspect node for details.", Rendering.ShaderCompilerMessageSeverity.Warning));
|
||
|
}
|
||
|
|
||
|
public virtual bool canCutNode => true;
|
||
|
public virtual bool canCopyNode => true;
|
||
|
|
||
|
protected virtual void CalculateNodeHasError()
|
||
|
{
|
||
|
foreach (var slot in this.GetInputSlots<MaterialSlot>())
|
||
|
{
|
||
|
if (slot.isConnected)
|
||
|
{
|
||
|
var edge = owner.GetEdges(slot.slotReference).First();
|
||
|
var outputNode = edge.outputSlot.node;
|
||
|
var outputSlot = outputNode.GetOutputSlots<MaterialSlot>().First(s => s.id == edge.outputSlot.slotId);
|
||
|
if (!slot.IsCompatibleWith(outputSlot))
|
||
|
{
|
||
|
owner.AddConcretizationError(objectId, $"Slot {slot.RawDisplayName()} cannot accept input of type {outputSlot.concreteValueType}.");
|
||
|
hasError = true;
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected string GetRayTracingError() => $@"
|
||
|
#if defined(SHADER_STAGE_RAY_TRACING) && defined(RAYTRACING_SHADER_GRAPH_DEFAULT)
|
||
|
#error '{name}' node is not supported in ray tracing, please provide an alternate implementation, relying for instance on the 'Raytracing Quality' keyword
|
||
|
#endif";
|
||
|
|
||
|
public virtual void CollectPreviewMaterialProperties(List<PreviewProperty> properties)
|
||
|
{
|
||
|
using (var tempSlots = PooledList<MaterialSlot>.Get())
|
||
|
using (var tempPreviewProperties = PooledList<PreviewProperty>.Get())
|
||
|
using (var tempEdges = PooledList<IEdge>.Get())
|
||
|
{
|
||
|
GetInputSlots(tempSlots);
|
||
|
foreach (var s in tempSlots)
|
||
|
{
|
||
|
tempPreviewProperties.Clear();
|
||
|
tempEdges.Clear();
|
||
|
if (owner != null)
|
||
|
{
|
||
|
owner.GetEdges(s.slotReference, tempEdges);
|
||
|
if (tempEdges.Any())
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
s.GetPreviewProperties(tempPreviewProperties, GetVariableNameForSlot(s.id));
|
||
|
for (int i = 0; i < tempPreviewProperties.Count; i++)
|
||
|
{
|
||
|
if (tempPreviewProperties[i].name == null)
|
||
|
continue;
|
||
|
|
||
|
properties.Add(tempPreviewProperties[i]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public virtual string GetVariableNameForSlot(int slotId)
|
||
|
{
|
||
|
var slot = FindSlot<MaterialSlot>(slotId);
|
||
|
if (slot == null)
|
||
|
throw new ArgumentException(string.Format("Attempting to use MaterialSlot({0}) on node of type {1} where this slot can not be found", slotId, this), "slotId");
|
||
|
return string.Format("_{0}_{1}_{2}_{3}", GetVariableNameForNode(), NodeUtils.GetHLSLSafeName(slot.shaderOutputName), unchecked((uint)slotId), slot.concreteValueType.ToPropertyType().ToString());
|
||
|
}
|
||
|
|
||
|
public string GetConnnectionStateVariableNameForSlot(int slotId)
|
||
|
{
|
||
|
return ShaderInput.GetConnectionStateVariableName(GetVariableNameForSlot(slotId));
|
||
|
}
|
||
|
|
||
|
public virtual string GetVariableNameForNode()
|
||
|
{
|
||
|
return defaultVariableName;
|
||
|
}
|
||
|
|
||
|
public MaterialSlot AddSlot(MaterialSlot slot, bool attemptToModifyExistingInstance = true)
|
||
|
{
|
||
|
if (slot == null)
|
||
|
{
|
||
|
throw new ArgumentException($"Trying to add null slot to node {this}");
|
||
|
}
|
||
|
MaterialSlot foundSlot = FindSlot<MaterialSlot>(slot.id);
|
||
|
|
||
|
if (slot == foundSlot)
|
||
|
return foundSlot;
|
||
|
|
||
|
// Try to keep the existing instance to avoid unnecessary changes to file
|
||
|
if (attemptToModifyExistingInstance && foundSlot != null && slot.GetType() == foundSlot.GetType())
|
||
|
{
|
||
|
foundSlot.displayName = slot.RawDisplayName();
|
||
|
foundSlot.CopyDefaultValue(slot);
|
||
|
return foundSlot;
|
||
|
}
|
||
|
|
||
|
// keep the same ordering by replacing the first match, if it exists
|
||
|
int firstIndex = m_Slots.FindIndex(s => s.value.id == slot.id);
|
||
|
if (firstIndex >= 0)
|
||
|
{
|
||
|
m_Slots[firstIndex] = slot;
|
||
|
|
||
|
// remove additional matches to get rid of unused duplicates
|
||
|
m_Slots.RemoveAllFromRange(s => s.value.id == slot.id, firstIndex + 1, m_Slots.Count - (firstIndex + 1));
|
||
|
}
|
||
|
else
|
||
|
m_Slots.Add(slot);
|
||
|
|
||
|
slot.owner = this;
|
||
|
|
||
|
OnSlotsChanged();
|
||
|
|
||
|
if (foundSlot == null)
|
||
|
return slot;
|
||
|
|
||
|
// foundSlot is of a different type; try to copy values
|
||
|
// I think this is to support casting if implemented in CopyValuesFrom ?
|
||
|
slot.CopyValuesFrom(foundSlot);
|
||
|
foundSlot.owner = null;
|
||
|
|
||
|
return slot;
|
||
|
}
|
||
|
|
||
|
public void RemoveSlot(int slotId)
|
||
|
{
|
||
|
// Remove edges that use this slot
|
||
|
// no owner can happen after creation
|
||
|
// but before added to graph
|
||
|
if (owner != null)
|
||
|
{
|
||
|
var edges = owner.GetEdges(GetSlotReference(slotId));
|
||
|
owner.RemoveEdges(edges.ToArray());
|
||
|
}
|
||
|
|
||
|
//remove slots
|
||
|
m_Slots.RemoveAll(x => x.value.id == slotId);
|
||
|
|
||
|
OnSlotsChanged();
|
||
|
}
|
||
|
|
||
|
protected virtual void OnSlotsChanged()
|
||
|
{
|
||
|
Dirty(ModificationScope.Topological);
|
||
|
owner?.ClearErrorsForNode(this);
|
||
|
}
|
||
|
|
||
|
public void RemoveSlotsNameNotMatching(IEnumerable<int> slotIds, bool supressWarnings = false)
|
||
|
{
|
||
|
var invalidSlots = m_Slots.Select(x => x.value.id).Except(slotIds);
|
||
|
|
||
|
foreach (var invalidSlot in invalidSlots.ToArray())
|
||
|
{
|
||
|
if (!supressWarnings)
|
||
|
Debug.LogWarningFormat("Removing Invalid MaterialSlot: {0}", invalidSlot);
|
||
|
RemoveSlot(invalidSlot);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public bool SetSlotOrder(List<int> desiredOrderSlotIds)
|
||
|
{
|
||
|
bool changed = false;
|
||
|
int writeIndex = 0;
|
||
|
for (int orderIndex = 0; orderIndex < desiredOrderSlotIds.Count; orderIndex++)
|
||
|
{
|
||
|
var id = desiredOrderSlotIds[orderIndex];
|
||
|
var matchIndex = m_Slots.FindIndex(s => s.value.id == id);
|
||
|
if (matchIndex < 0)
|
||
|
{
|
||
|
// no matching slot with that id.. skip it
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (writeIndex != matchIndex)
|
||
|
{
|
||
|
// swap the matching slot into position
|
||
|
var slot = m_Slots[matchIndex];
|
||
|
m_Slots[matchIndex] = m_Slots[writeIndex];
|
||
|
m_Slots[writeIndex] = slot;
|
||
|
changed = true;
|
||
|
}
|
||
|
writeIndex++;
|
||
|
}
|
||
|
}
|
||
|
return changed;
|
||
|
}
|
||
|
|
||
|
public SlotReference GetSlotReference(int slotId)
|
||
|
{
|
||
|
var slot = FindSlot<MaterialSlot>(slotId);
|
||
|
if (slot == null)
|
||
|
throw new ArgumentException("Slot could not be found", "slotId");
|
||
|
return new SlotReference(this, slotId);
|
||
|
}
|
||
|
|
||
|
public T FindSlot<T>(int slotId) where T : MaterialSlot
|
||
|
{
|
||
|
foreach (var slot in m_Slots.SelectValue())
|
||
|
{
|
||
|
if (slot.id == slotId && slot is T)
|
||
|
return (T)slot;
|
||
|
}
|
||
|
return default(T);
|
||
|
}
|
||
|
|
||
|
public T FindInputSlot<T>(int slotId) where T : MaterialSlot
|
||
|
{
|
||
|
foreach (var slot in m_Slots.SelectValue())
|
||
|
{
|
||
|
if (slot.isInputSlot && slot.id == slotId && slot is T)
|
||
|
return (T)slot;
|
||
|
}
|
||
|
return default(T);
|
||
|
}
|
||
|
|
||
|
public T FindOutputSlot<T>(int slotId) where T : MaterialSlot
|
||
|
{
|
||
|
foreach (var slot in m_Slots.SelectValue())
|
||
|
{
|
||
|
if (slot.isOutputSlot && slot.id == slotId && slot is T)
|
||
|
return (T)slot;
|
||
|
}
|
||
|
return default(T);
|
||
|
}
|
||
|
|
||
|
public virtual IEnumerable<MaterialSlot> GetInputsWithNoConnection()
|
||
|
{
|
||
|
return this.GetInputSlots<MaterialSlot>().Where(x => !owner.GetEdges(GetSlotReference(x.id)).Any());
|
||
|
}
|
||
|
|
||
|
public void SetupSlots()
|
||
|
{
|
||
|
foreach (var s in m_Slots.SelectValue())
|
||
|
s.owner = this;
|
||
|
}
|
||
|
|
||
|
public virtual void UpdateNodeAfterDeserialization()
|
||
|
{ }
|
||
|
|
||
|
public bool IsSlotConnected(int slotId)
|
||
|
{
|
||
|
var slot = FindSlot<MaterialSlot>(slotId);
|
||
|
return slot != null && owner.GetEdges(slot.slotReference).Any();
|
||
|
}
|
||
|
|
||
|
public virtual void Setup() { }
|
||
|
|
||
|
protected void EnqueSlotsForSerialization()
|
||
|
{
|
||
|
foreach (var slot in m_Slots)
|
||
|
{
|
||
|
slot.OnBeforeSerialize();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public virtual void Dispose()
|
||
|
{
|
||
|
foreach (var slot in m_Slots)
|
||
|
slot.value.Dispose();
|
||
|
|
||
|
m_UnregisterAll?.Invoke();
|
||
|
m_UnregisterAll = null;
|
||
|
}
|
||
|
}
|
||
|
}
|