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.
273 lines
11 KiB
273 lines
11 KiB
using System;
|
|
using System.Linq;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEditor.Graphing;
|
|
using UnityEditor.ShaderGraph.Internal;
|
|
using UnityEditor.Rendering;
|
|
|
|
namespace UnityEditor.ShaderGraph
|
|
{
|
|
[Serializable]
|
|
[Title("Custom Interpolators", "Instance")]
|
|
class CustomInterpolatorNode : AbstractMaterialNode
|
|
{
|
|
[SerializeField]
|
|
internal string customBlockNodeName = "K_INVALID";
|
|
|
|
[SerializeField]
|
|
private BlockNode.CustomBlockType serializedType = BlockNode.CustomBlockType.Vector4;
|
|
|
|
public override bool hasPreview { get { return true; } }
|
|
|
|
internal override bool ExposeToSearcher { get => false; } // This is exposed in a special way.
|
|
|
|
public override bool allowedInSubGraph { get => false; }
|
|
|
|
internal BlockNode e_targetBlockNode // weak indirection via customBlockNodeName
|
|
{
|
|
get => (owner?.vertexContext.blocks.Find(cib => cib.value.descriptor.name == customBlockNodeName))?.value ?? null;
|
|
}
|
|
|
|
public CustomInterpolatorNode()
|
|
{
|
|
UpdateNodeAfterDeserialization();
|
|
}
|
|
|
|
internal void ConnectToCustomBlock(BlockNode node)
|
|
{
|
|
// if somehow we were already connected, be sure to unregister.
|
|
if (e_targetBlockNode != null)
|
|
{
|
|
e_targetBlockNode.UnregisterCallback(OnCustomBlockModified);
|
|
}
|
|
// if a new cib is renamed to match us when we didn't have a target (unusual case, but covering all bases here).
|
|
if (node?.isCustomBlock ?? false)
|
|
{
|
|
name = node.customName + " (Custom Interpolator)";
|
|
customBlockNodeName = node.customName;
|
|
serializedType = node.customWidth;
|
|
BuildSlot();
|
|
node.RegisterCallback(OnCustomBlockModified);
|
|
}
|
|
}
|
|
|
|
internal void ConnectToCustomBlockByName(string customBlockName)
|
|
{
|
|
// see above
|
|
if (e_targetBlockNode != null)
|
|
{
|
|
e_targetBlockNode.UnregisterCallback(OnCustomBlockModified);
|
|
}
|
|
|
|
name = customBlockName + " (Custom Interpolator)";
|
|
customBlockNodeName = customBlockName;
|
|
if (e_targetBlockNode != null)
|
|
{
|
|
serializedType = e_targetBlockNode.customWidth;
|
|
BuildSlot();
|
|
e_targetBlockNode.RegisterCallback(OnCustomBlockModified);
|
|
}
|
|
else
|
|
{
|
|
// We should get badged in OnValidate.
|
|
}
|
|
}
|
|
|
|
void OnCustomBlockModified(AbstractMaterialNode node, Graphing.ModificationScope scope)
|
|
{
|
|
if (node is BlockNode bnode)
|
|
{
|
|
if (bnode?.isCustomBlock ?? false)
|
|
{
|
|
name = bnode.customName + " (Custom Interpolator)";
|
|
customBlockNodeName = bnode.customName;
|
|
if (e_targetBlockNode != null && e_targetBlockNode.owner != null)
|
|
{
|
|
serializedType = e_targetBlockNode.customWidth;
|
|
BuildSlot();
|
|
Dirty(ModificationScope.Node);
|
|
Dirty(ModificationScope.Topological);
|
|
}
|
|
}
|
|
}
|
|
// bnode information we got is somehow invalid, this is probably case for an exception.
|
|
}
|
|
|
|
public override void ValidateNode()
|
|
{
|
|
// Our node was deleted or we had bad deserialization, we need to badge.
|
|
if (e_targetBlockNode == null || e_targetBlockNode.owner == null)
|
|
{
|
|
e_targetBlockNode?.UnregisterCallback(OnCustomBlockModified);
|
|
owner.AddValidationError(objectId, String.Format("Custom Block Interpolator '{0}' not found.", customBlockNodeName), ShaderCompilerMessageSeverity.Error);
|
|
}
|
|
else
|
|
{
|
|
// our blockNode reference is somehow valid again after it wasn't,
|
|
// we can reconnect and everything should be restored.
|
|
ConnectToCustomBlockByName(customBlockNodeName);
|
|
}
|
|
}
|
|
|
|
public override void UpdateNodeAfterDeserialization()
|
|
{
|
|
// our e_targetBlockNode is unsafe here, so we build w/our serialization info and hope for the best!
|
|
BuildSlot();
|
|
base.UpdateNodeAfterDeserialization();
|
|
}
|
|
|
|
void BuildSlot()
|
|
{
|
|
switch (serializedType)
|
|
{
|
|
case BlockNode.CustomBlockType.Float:
|
|
AddSlot(new Vector1MaterialSlot(0, "Out", "Out", SlotType.Output, default(float), ShaderStageCapability.Fragment));
|
|
break;
|
|
case BlockNode.CustomBlockType.Vector2:
|
|
AddSlot(new Vector2MaterialSlot(0, "Out", "Out", SlotType.Output, default(Vector2), ShaderStageCapability.Fragment));
|
|
break;
|
|
case BlockNode.CustomBlockType.Vector3:
|
|
AddSlot(new Vector3MaterialSlot(0, "Out", "Out", SlotType.Output, default(Vector3), ShaderStageCapability.Fragment));
|
|
break;
|
|
case BlockNode.CustomBlockType.Vector4:
|
|
AddSlot(new Vector4MaterialSlot(0, "Out", "Out", SlotType.Output, default(Vector4), ShaderStageCapability.Fragment));
|
|
break;
|
|
}
|
|
RemoveSlotsNameNotMatching(new[] { 0 });
|
|
}
|
|
|
|
public override string GetVariableNameForSlot(int slotid)
|
|
{
|
|
var slotRef = GetSlotReference(0);
|
|
return GetOutputForSlot(slotRef, slotRef.slot.concreteValueType, GenerationMode.Preview);
|
|
}
|
|
|
|
protected internal override string GetOutputForSlot(SlotReference fromSocketRef, ConcreteSlotValueType valueType, GenerationMode generationMode)
|
|
{
|
|
// check to see if we can inline a value.
|
|
List<PreviewProperty> props = new List<PreviewProperty>();
|
|
e_targetBlockNode?.CollectPreviewMaterialProperties(props);
|
|
|
|
// if the cib is inActive, this node still might be in an active branch.
|
|
bool isActive = e_targetBlockNode?.isActive ?? false;
|
|
|
|
// if the cib has no input node, we can use the input property to inline a magic value.
|
|
bool canInline = e_targetBlockNode?.GetInputNodeFromSlot(0) == null && props.Count != 0;
|
|
|
|
// vector width of target slot
|
|
int toWidth = SlotTypeToWidth(valueType);
|
|
|
|
string finalResult = "";
|
|
|
|
// If cib is inactive (or doesn't exist), then we default to black (as is the case for other nodes).
|
|
if (!isActive || CustomInterpolatorUtils.generatorSkipFlag)
|
|
{
|
|
finalResult = ConvertVector("$precision4(0,0,0,0)", 4, toWidth);
|
|
}
|
|
// cib has no input; we can directly use the inline value instead.
|
|
else if (canInline)
|
|
{
|
|
Vector4 v = default;
|
|
if (props[0].propType != PropertyType.Float)
|
|
v = props[0].vector4Value;
|
|
|
|
int outWidth = 4;
|
|
string result;
|
|
switch (props[0].propType)
|
|
{
|
|
case PropertyType.Float:
|
|
result = $" $precision1({props[0].floatValue}) ";
|
|
outWidth = 1;
|
|
break;
|
|
default:
|
|
result = $" $precision4({v.x},{v.y},{v.z},{v.w}) ";
|
|
outWidth = 4;
|
|
break;
|
|
}
|
|
finalResult = ConvertVector(result, outWidth, toWidth);
|
|
}
|
|
// Preview Node doesn't support CI, but we can fake it by asking the cib's source input for it's value instead.
|
|
else if (CustomInterpolatorUtils.generatorNodeOnly)
|
|
{
|
|
var sourceSlot = FindSourceSlot(out var found);
|
|
// CIB's type needs to constrain the incoming value (eg. vec2(out)->float(cib) | float(cin)->vec2(in))
|
|
// If we didn't do this next line, we'd get vec2(out)->vec2(in), which would ignore the truncation in the preview.
|
|
var result = sourceSlot.node.GetOutputForSlot(sourceSlot, FindSlot<MaterialSlot>(0).concreteValueType, GenerationMode.Preview);
|
|
finalResult = ConvertVector(result, (int)e_targetBlockNode.customWidth, toWidth);
|
|
}
|
|
// If we made it this far, then cib is in a valid and meaningful configuration in the SDI struct.
|
|
else
|
|
{
|
|
// pull directly out of the SDI and just use it.
|
|
var result = string.Format("IN.{0}", customBlockNodeName);
|
|
finalResult = ConvertVector(result, (int)e_targetBlockNode.customWidth, toWidth);
|
|
}
|
|
return finalResult.Replace(PrecisionUtil.Token, concretePrecision.ToShaderString());
|
|
}
|
|
|
|
SlotReference FindSourceSlot(out bool found)
|
|
{
|
|
try
|
|
{
|
|
found = true;
|
|
return owner.GetEdges(e_targetBlockNode).First().outputSlot;
|
|
}
|
|
catch
|
|
{
|
|
found = false;
|
|
return default;
|
|
}
|
|
}
|
|
|
|
private static int SlotTypeToWidth(ConcreteSlotValueType valueType)
|
|
{
|
|
switch (valueType)
|
|
{
|
|
case ConcreteSlotValueType.Boolean:
|
|
case ConcreteSlotValueType.Vector1: return 1;
|
|
case ConcreteSlotValueType.Vector2: return 2;
|
|
case ConcreteSlotValueType.Vector3: return 3;
|
|
default: return 4;
|
|
}
|
|
}
|
|
|
|
private static string ConvertVector(string name, int fromLen, int toLen)
|
|
{
|
|
if (fromLen == toLen)
|
|
return name;
|
|
|
|
var key = new char[] { 'x', 'y', 'z', 'w' };
|
|
|
|
string begin = $"$precision{toLen}({name}.";
|
|
var mid = "";
|
|
string end = ")";
|
|
|
|
if (toLen == 4)
|
|
{
|
|
// We assume homogenous coordinates for some reason.
|
|
end = ", 1.0)";
|
|
toLen -= 1;
|
|
}
|
|
|
|
if (fromLen == 1)
|
|
{
|
|
// we expand floats for each component for some reason.
|
|
fromLen = toLen;
|
|
key = new char[] { 'x', 'x', 'x' };
|
|
}
|
|
|
|
// expand the swizzle
|
|
int swizzLen = Math.Min(fromLen, toLen);
|
|
for (int i = 0; i < swizzLen; ++i)
|
|
mid += key[i];
|
|
|
|
// fill gaps
|
|
for (int i = fromLen; i < toLen; ++i)
|
|
mid += ", 0.0";
|
|
|
|
// float<toLen>(<name>.<swizz>, <gap...>, 1.0)"
|
|
return $"({begin}{mid}{end})";
|
|
}
|
|
}
|
|
}
|