Adaug proiectul Unity

This commit is contained in:
2026-11-01 23:39:55 +02:00
parent cb14d9d37d
commit 863eac8f2c
23463 changed files with 5412601 additions and 0 deletions
@@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine.UIElements;
using UnityEditor.Graphing;
using UnityEditor.ShaderGraph.Drawing;
using UnityEditor.ShaderGraph.Drawing.Controls;
using Unity.GraphAuthoring.Editor.ProviderSystem;
namespace UnityEditor.ShaderGraph.ProviderSystem
{
[AttributeUsage(AttributeTargets.Property)]
class GroupVariantControlAttribute : Attribute, IControlAttribute
{
public VisualElement InstantiateControl(AbstractMaterialNode node, PropertyInfo propertyInfo)
{
if (node is not ProviderNode pn)
return null;
return new GroupVariantControlView(pn);
}
}
class GroupVariantControlView : VisualElement, AbstractMaterialNodeModificationListener
{
ProviderNode m_Node;
DropdownField m_Dropdown;
internal GroupVariantControlView(ProviderNode node)
{
m_Node = node;
Rebuild();
}
public void OnNodeModified(ModificationScope scope)
{
if (scope == ModificationScope.Graph)
Rebuild();
}
void Rebuild()
{
Clear();
m_Dropdown = null;
var candidates = m_Node.Header?.groupCandidates;
if (candidates == null || candidates.Length == 0)
return;
var choices = new List<string>(candidates.Length);
foreach (var c in candidates)
choices.Add(c.label);
m_Dropdown = new DropdownField(m_Node.Header.groupLabel ?? "Variant", choices, CurrentIndex());
m_Dropdown.RegisterValueChangedCallback(OnSelectionChanged);
Add(m_Dropdown);
}
int CurrentIndex()
{
var candidates = m_Node.Header?.groupCandidates;
if (candidates == null) return 0;
var key = m_Node.Provider?.ProviderKey;
for (int i = 0; i < candidates.Length; i++)
if (candidates[i].provider.ProviderKey == key)
return i;
return 0;
}
void OnSelectionChanged(ChangeEvent<string> evt)
{
var candidates = m_Node.Header?.groupCandidates;
if (candidates == null) return;
int newIdx = m_Dropdown.index;
if (candidates[newIdx].provider.ProviderKey == m_Node.Provider?.ProviderKey)
return;
m_Node.owner.owner.RegisterCompleteObjectUndo($"Change {m_Node.name} Variant");
m_Node.InitializeFromProvider(candidates[newIdx].provider);
m_Node.ValidateNode();
m_Node.Dirty(ModificationScope.Graph);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: cec81f6c3e8754945851d1776f968cac
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ea92addb84cbc48fdb625329928f7b2c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,89 @@
using System;
using UnityEditor.Graphing;
using UnityEditor.ShaderGraph.Drawing.Controls;
using Unity.GraphAuthoring.Editor.ProviderSystem;
namespace UnityEditor.ShaderGraph.ProviderSystem
{
// Stateless filter that defers to ShaderGraph's keyword tables.
// Defined here rather than in com.unity.graph-authoring so it can reference NodeUtils,
// and serialized via [SerializeReference] in ExpressionProvider.
[Serializable]
sealed class ShaderGraphKeywordFilter : ExpressionProvider.IKeywordFilter
{
public bool IsReserved(string name)
=> NodeUtils.IsShaderLabKeyWord(name) || NodeUtils.IsShaderGraphKeyWord(name) || NodeUtils.IsHLSLKeyword(name);
}
[Serializable]
[ProviderModel(ExpressionProvider.kExpressionProviderKey)]
internal class ExpressionNode : ProviderNode
{
ExpressionProvider TypedProvider => Provider as ExpressionProvider;
string hlslFunctionName => $"ExpressionNode_{this.objectId}";
static readonly ShaderGraphKeywordFilter s_keywordFilter = new();
enum SupportedTypes { Vector1, Vector2, Vector3, Vector4 }
[EnumControl("Type")]
SupportedTypes SelectedType {
get => FromTypeName(TypedProvider.ShaderType);
set
{
TypedProvider.UpdateExpression(hlslFunctionName, Expression, ToTypeName(value), s_keywordFilter);
Refresh();
}
}
internal override bool requiresGeneration => true;
[TextControl(null, true)]
internal string Expression
{
get => TypedProvider.Expression;
set
{
if (value == null) // Text control can misbehave in undo redo scenarios; we'll need to visit that separately.
return;
TypedProvider.UpdateExpression(hlslFunctionName, value, TypedProvider.ShaderType, s_keywordFilter);
Refresh();
}
}
public ExpressionNode() { }
public override void UpdateNodeAfterDeserialization()
{
// Ensure the keyword filter is in place before base calls Refresh() → Provider.Reload().
// Handles fresh nodes and graphs saved before [SerializeReference] was introduced.
TypedProvider?.UpdateExpression(hlslFunctionName, TypedProvider.Expression, TypedProvider.ShaderType, s_keywordFilter);
base.UpdateNodeAfterDeserialization();
}
static SupportedTypes FromTypeName(string stype)
{
switch (stype)
{
default:
case "float": return SupportedTypes.Vector1;
case "float2": return SupportedTypes.Vector2;
case "float3": return SupportedTypes.Vector3;
case "float4": return SupportedTypes.Vector4;
}
}
static string ToTypeName(SupportedTypes etype)
{
switch (etype)
{
default:
case SupportedTypes.Vector1: return "float";
case SupportedTypes.Vector2: return "float2";
case SupportedTypes.Vector3: return "float3";
case SupportedTypes.Vector4: return "float4";
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: de4ff50ed93d84498b2e949adfb6a307
@@ -0,0 +1,69 @@
using System;
using UnityEngine;
using UnityEditor.Graphing;
namespace UnityEditor.ShaderGraph.ProviderSystem
{
interface INeedsExplicitCompatibilityTest
{
bool HasExplicitCompatibility(MaterialSlot other);
static bool TestExplicitCompatibility(MaterialSlot a, MaterialSlot b)
{
return a is INeedsExplicitCompatibilityTest atester
&& b is INeedsExplicitCompatibilityTest btester
&& atester.HasExplicitCompatibility(b)
&& btester.HasExplicitCompatibility(a);
}
}
[Serializable]
internal class ExternalMaterialSlot : MaterialSlot, INeedsExplicitCompatibilityTest
{
public string TypeName => m_typeName;
[SerializeField]
string m_typeName;
[SerializeField]
string m_rawDefaultValueString;
public override bool isDefaultValue => throw new System.NotImplementedException();
public override SlotValueType valueType => SlotValueType.External;
public override ConcreteSlotValueType concreteValueType => ConcreteSlotValueType.External;
public ExternalMaterialSlot() : base()
{
}
public ExternalMaterialSlot(
int slotId,
string displayName,
string shaderOutputName,
string typeName,
SlotType slotType,
string rawDefaultValue = null,
ShaderStageCapability stageCapability = ShaderStageCapability.All,
bool hidden = false)
: base(slotId, displayName, shaderOutputName, slotType, stageCapability, hidden)
{
m_typeName = typeName;
m_rawDefaultValueString = rawDefaultValue ?? "0";
}
public override void AddDefaultProperty(PropertyCollector properties, GenerationMode generationMode) { }
public override void CopyValuesFrom(MaterialSlot foundSlot) { }
public override string GetDefaultValue(GenerationMode generationMode)
{
return $"({TypeName})({m_rawDefaultValueString})";
}
public bool HasExplicitCompatibility(MaterialSlot other)
=> other is ExternalMaterialSlot externalSlot && externalSlot.TypeName == this.TypeName;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7372d18840093464cb19d9432af6b71f
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9181c578b5a41418bbb0056172a05e4f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,127 @@
using Unity.GraphAuthoring.Editor.ProviderSystem;
using Unity.GraphAuthoring.Editor.ProviderSystem.Hints;
using static Unity.GraphAuthoring.Editor.ProviderSystem.HintUtils;
using System.Collections.Generic;
namespace UnityEditor.ShaderGraph.ProviderSystem
{
internal class FunctionHeader : StrongHeader<IShaderFunction>
{
internal string referenceName { get; private set; }
internal string displayName { get; private set; }
internal string tooltip { get; private set; }
internal string returnDisplayName { get; private set; }
internal string returnTooltip { get; private set; }
internal IShaderType returnType { get; private set; }
internal bool hasReturnValueType => returnType.Name != "void";
internal string searchName { get; private set; }
internal string[] searchTerms { get; private set; }
internal string searchCategory { get; private set; }
internal ParameterHeader returnHeader { get; private set; }
internal bool allowPrecision { get; private set; }
internal string helpUrl { get; private set; }
internal string version { get; private set; }
internal string groupKey { get; private set; }
internal string groupDropdownEntryName { get; private set; }
internal string groupLabel { get; private set; }
internal string[] groupSearchTerms { get; private set; }
internal (IProvider<IShaderFunction> provider, string label)[] groupCandidates { get; private set; }
internal (IProvider<IShaderFunction> provider, string version)[] versionCandidates { get; private set; }
bool skipGroup = false;
private void OnProcessNoGroup(IShaderFunction func, IProvider provider)
{
skipGroup = true;
OnProcess(func, provider);
skipGroup = false;
}
protected override void OnProcess(IShaderFunction func, IProvider provider)
{
referenceName = func.Name;
returnType = func.ReturnType;
version = provider.Version;
displayName = func.Hints.GetHint<DisplayName>()?.Value as string;
tooltip = func.Hints.GetHint<Tooltip>()?.Value as string;
returnDisplayName = func.Hints.GetHint<ReturnDisplayName>()?.Value as string;
returnTooltip = func.Hints.GetHint<ReturnTooltip>()?.Value as string;
searchName = func.Hints.GetHint<SearchName>()?.Value as string;
searchTerms = func.Hints.GetHint<SearchTerms>()?.Value as string[];
searchCategory = func.Hints.GetHint<SearchCategory>()?.Value as string;
allowPrecision = func.Hints.GetHint<Precision>()?.IsResolved ?? false;
helpUrl = func.Hints.GetHint<HelpURL>()?.Value as string;
if (provider.Group != null)
{
var groupKeyHint = func.Hints.GetHint<GroupKey>();
groupDropdownEntryName = displayName;
displayName = groupKeyHint?.DisplayName ?? provider.Group;
searchName = displayName;
groupLabel = groupKeyHint?.Label ?? "Variant";
List<string> searchTerms = new();
List<(IProvider<IShaderFunction> provider, string label)> groupEntries = new();
if (!skipGroup && ProviderLibrary.TryGetInstance(out var lib))
{
var groupHeader = new FunctionHeader();
bool first = true;
foreach (var groupProvider in lib.ProvidersByGroup<IShaderFunction>(provider.Group))
{
groupHeader.OnProcessNoGroup(groupProvider.Definition, groupProvider);
if (first) // Main provider's information wins.
{
displayName = groupHeader.displayName;
groupLabel = groupHeader.groupLabel;
helpUrl ??= groupHeader.helpUrl;
first = false;
}
groupEntries.Add((groupProvider, groupHeader.groupDropdownEntryName));
// aggregate search terms from all headers.
searchTerms.AddRange(groupHeader.searchTerms);
}
groupCandidates = groupEntries.ToArray();
this.searchTerms = searchTerms.ToArray();
}
else
{
groupEntries.Add(((IProvider<IShaderFunction>)provider, groupDropdownEntryName));
}
}
// Version:
List<(IProvider<IShaderFunction> provider, string version)> rawVersions = new();
if (ProviderLibrary.TryGetInstance(out var versionLib))
{
foreach(var versionedProvider in versionLib.ProvidersByVersion<IShaderFunction>(provider.ProviderKey))
rawVersions.Add((versionedProvider, versionedProvider.Version));
}
else
{
rawVersions.Add((provider as IProvider<IShaderFunction>, provider.Version));
}
versionCandidates = rawVersions.ToArray();
// Return:
returnHeader = new ParameterHeader(returnDisplayName, func.ReturnType, returnTooltip, provider);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 58b9d763af91544dc85fbb161b64d9c0
@@ -0,0 +1,229 @@
using UnityEngine;
using UnityEditor.Graphing;
using System.Collections.Generic;
using System;
using Unity.GraphAuthoring.Editor.ProviderSystem;
using Hints = Unity.GraphAuthoring.Editor.ProviderSystem.Hints;
using static Unity.GraphAuthoring.Editor.ProviderSystem.HintUtils;
namespace UnityEditor.ShaderGraph.ProviderSystem
{
internal static class HeaderUtils
{
internal static string ToShaderType(this MaterialSlot slot)
{
if (slot is ExternalMaterialSlot eslot)
return eslot.TypeName;
return slot.concreteValueType.ToShaderString();
}
internal static MaterialSlot MakeSlotFromParameter(ParameterHeader header, int slotId, SlotType dir)
{
MaterialSlot slot = null;
Enum.TryParse<Internal.UVChannel>(header.defaultString, true, out var defaultUVChannel);
if (!Enum.TryParse<Internal.CoordinateSpace>(header.defaultString, true, out var defaultCoordinateSpace))
defaultCoordinateSpace = Internal.CoordinateSpace.Object;
Enum.TryParse<ScreenSpaceType>(header.defaultString, true, out var defaultScreenSpaceType);
// absolute world only works for position.
if (defaultCoordinateSpace == Internal.CoordinateSpace.AbsoluteWorld && header.Referable != Hints.Param.Ref.kPosition)
defaultCoordinateSpace = Internal.CoordinateSpace.Object;
// Screen doesn't work for any of the Space Slots-- _why do they use this enum?_
if (defaultCoordinateSpace == Internal.CoordinateSpace.Screen)
defaultCoordinateSpace = Internal.CoordinateSpace.Object;
if (header.isReferable && dir == SlotType.Input)
{
switch(header.Referable)
{
case Hints.Param.Ref.kUV:
slot = new UVMaterialSlot(slotId, header.displayName, header.referenceName, defaultUVChannel);
break;
case Hints.Param.Ref.kPosition:
slot = new PositionMaterialSlot(slotId, header.displayName, header.referenceName, defaultCoordinateSpace);
break;
case Hints.Param.Ref.kNormal:
slot = new NormalMaterialSlot(slotId, header.displayName, header.referenceName, defaultCoordinateSpace);
break;
case Hints.Param.Ref.kTangent:
slot = new TangentMaterialSlot(slotId, header.displayName, header.referenceName, defaultCoordinateSpace);
break;
case Hints.Param.Ref.kBitangent:
slot = new BitangentMaterialSlot(slotId, header.displayName, header.referenceName, defaultCoordinateSpace);
break;
case Hints.Param.Ref.kVertexColor:
slot = new VertexColorMaterialSlot(slotId, header.displayName, header.referenceName);
break;
case Hints.Param.Ref.kViewDirection:
slot = new ViewDirectionMaterialSlot(slotId, header.displayName, header.referenceName, defaultCoordinateSpace);
break;
case Hints.Param.Ref.kScreenPosition:
slot = new ScreenPositionMaterialSlot(slotId, header.displayName, header.referenceName, defaultScreenSpaceType);
break;
}
}
if (slot == null)
{
if (header.isDynamic)
{
TryCast<Vector4>(header.typeName, header.defaultValue, out var v4, Vector4.zero);
slot = new DynamicVectorMaterialSlot(slotId, header.displayName, header.referenceName, dir, v4);
}
else if (header.isDropdown && dir == SlotType.Input)
{
TryCast<float>(header.typeName, header.defaultValue, out var asIdx, 0.0f);
slot = new Vector1MaterialEnumSlot(slotId, header.displayName, header.referenceName, dir, header.options, asIdx);
}
else if (header.isSlider && dir == SlotType.Input)
{
TryCast<float>(header.typeName, header.defaultValue, out var asRng, 0);
slot = new Vector1MaterialRangeSlot(slotId, header.displayName, header.referenceName, dir, asRng, new Vector2(header.sliderMin, header.sliderMax));
}
else switch (header.typeName)
{
case "uint1":
case "uint":
TryCast<float>(header.typeName, header.defaultValue, out var asUint, 0u);
slot = new Vector1MaterialIntegerSlot(slotId, header.displayName, header.referenceName, dir, asUint, unsigned: true); break;
case "int1":
case "int":
TryCast<float>(header.typeName, header.defaultValue, out var asInt, 0);
slot = new Vector1MaterialIntegerSlot(slotId, header.displayName, header.referenceName, dir, asInt); break;
case "half":
case "half1":
case "float1":
case "float":
TryCast<float>(header.typeName, header.defaultValue, out var asFloat, 0.0f);
slot = new Vector1MaterialSlot(slotId, header.displayName, header.referenceName, dir, asFloat); break;
case "bool1":
case "bool":
TryCast<bool>(header.typeName, header.defaultValue, out var asBool, false);
slot = new BooleanMaterialSlot(slotId, header.displayName, header.referenceName, dir, asBool); break;
case "half2":
case "float2":
TryCast<Vector2>(header.typeName, header.defaultValue, out var asV2, Vector2.zero);
slot = new Vector2MaterialSlot(slotId, header.displayName, header.referenceName, dir, asV2); break;
case "half3":
case "float3":
TryCast<Vector3>(header.typeName, header.defaultValue, out var asV3, Vector3.zero);
slot = !header.isColor
? new Vector3MaterialSlot(slotId, header.displayName, header.referenceName, dir, asV3)
: new ColorRGBMaterialSlot(slotId, header.displayName, header.referenceName, dir, new Vector4(asV3.x, asV3.y, asV3.z, 1), Internal.ColorMode.Default);
break;
case "half4":
case "float4":
TryCast<Vector4>(header.typeName, header.defaultValue, out var asV4, header.isColor ? new Vector4(0, 0, 0, 1) : Vector4.zero);
slot = header.isColor
? new ColorRGBAMaterialSlot(slotId, header.displayName, header.referenceName, dir, asV4)
: new Vector4MaterialSlot(slotId, header.displayName, header.referenceName, dir, asV4);
break;
case "half2x2":
case "float2x2": slot = new Matrix2MaterialSlot(slotId, header.displayName, header.referenceName, dir); break;
case "half3x3":
case "float3x3": slot = new Matrix3MaterialSlot(slotId, header.displayName, header.referenceName, dir); break;
case "half4x4":
case "float4x4": slot = new Matrix4MaterialSlot(slotId, header.displayName, header.referenceName, dir); break;
case "SamplerState":
case "UnitySamplerState": slot = new SamplerStateMaterialSlot(slotId, header.displayName, header.referenceName, dir); break;
case "Texture2D":
case "UnityTexture2D":
slot = dir == SlotType.Input
? new Texture2DInputMaterialSlot(slotId, header.displayName, header.referenceName)
: new Texture2DMaterialSlot(slotId, header.displayName, header.referenceName, dir);
break;
case "Texture2DArray":
case "UnityTexture2DArray":
slot = dir == SlotType.Input
? new Texture2DArrayInputMaterialSlot(slotId, header.displayName, header.referenceName)
: new Texture2DArrayMaterialSlot(slotId, header.displayName, header.referenceName, dir); break;
case "TextureCube":
case "UnityTextureCube":
slot = dir == SlotType.Input
? new CubemapInputMaterialSlot(slotId, header.displayName, header.referenceName)
: new CubemapMaterialSlot(slotId, header.displayName, header.referenceName, dir); break;
case "Texture3D":
case "UnityTexture3D":
slot = dir == SlotType.Input
? new Texture3DInputMaterialSlot(slotId, header.displayName, header.referenceName)
: new Texture3DMaterialSlot(slotId, header.displayName, header.referenceName, dir); break;
default:
slot = new ExternalMaterialSlot(slotId, header.displayName, header.referenceName, header.externalQualifiedTypeName, dir, header.defaultString);
break;
}
}
slot.hideConnector = header.isStatic && dir == SlotType.Input;
slot.hidden = header.isLocal;
slot.bareResource = header.isBareResource;
if (header.hasCustomBinding)
slot.CustomBinding = header.customBinding;
return slot;
}
private static IShaderType ApplyLegacy(IShaderType type, string precisionToken, int dynamicLength)
{
var baseType = type.Name.Split('[')[0];
var newType = baseType;
// Dynamic hint currently only supports float, float2, float3, float4 and half, half2, half3, half4
if (dynamicLength >= 1 && dynamicLength <= 4)
{
string halfType = $"half{(dynamicLength == 1 ? null : dynamicLength)}";
string floatType = $"float{(dynamicLength == 1 ? null : dynamicLength)}";
newType = System.Text.RegularExpressions.Regex.Replace(newType, "half[1-4]|half", halfType);
newType = System.Text.RegularExpressions.Regex.Replace(newType, "float[1-4]|float", floatType);
}
if (precisionToken != null)
newType = newType.Replace("float", precisionToken).Replace("half", precisionToken);
return new ShaderType(type.Name.Replace(baseType, newType));
}
internal static bool TryApplyLegacy(IShaderFunction func, FunctionHeader funcHeader, IReadOnlyDictionary<string, ParameterHeader> paramHeaders, string precisionToken, int dynamicLength, out IShaderFunction result)
{
List<IShaderField> fields = new();
result = null;
bool applyPrecision = funcHeader.allowPrecision;
bool anyDynamics = false;
Debug.Assert(dynamicLength >= 0);
foreach(var param in func.Parameters)
{
bool applyDynamics = paramHeaders.TryGetValue(param.Name, out var paramHeader) && paramHeader.isDynamic;
anyDynamics |= applyDynamics;
var legacyType = ApplyLegacy(param.ShaderType, applyPrecision ? precisionToken : null, applyDynamics ? dynamicLength : 0);
var field = new ShaderField(param.Name, param.IsInput, param.IsOutput, legacyType, param.Hints);
fields.Add(field);
}
if (!anyDynamics && !applyPrecision)
{
result = func;
return false;
}
List<string> namespaces = new();
namespaces.AddRange(func.Namespace);
if (applyPrecision || anyDynamics)
namespaces.Add("unity_sg_generated");
string funcName = $"{func.Name}{(applyPrecision ? $"_{precisionToken}" : null)}{(anyDynamics ? dynamicLength : null)}";
// Dynamic typing isn't supported on return values.
IShaderType returnType = ApplyLegacy(func.ReturnType, applyPrecision ? precisionToken : null, 0);
result = new ShaderFunction(funcName, namespaces, fields, returnType, func.FunctionBody, func.Hints);
return true;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9fe94b29e1463438e8bc28a5e7905e1d
@@ -0,0 +1,135 @@
using Unity.GraphAuthoring.Editor.ProviderSystem;
using Unity.GraphAuthoring.Editor.ProviderSystem.Hints;
using static Unity.GraphAuthoring.Editor.ProviderSystem.HintUtils;
namespace UnityEditor.ShaderGraph.ProviderSystem
{
internal class ParameterHeader : StrongHeader<IShaderField>
{
internal string referenceName { get; private set; }
internal IShaderType shaderType { get; private set; }
internal string typeName => shaderType.Name;
internal string displayName { get; private set; }
internal string tooltip { get; private set; }
internal bool isInput { get; private set; }
internal bool isOutput { get; private set; }
internal bool isColor { get; private set; }
internal bool isStatic { get; private set; }
internal bool isLocal { get; private set; }
internal bool isDropdown { get; private set; }
internal string[] options { get; private set; }
internal bool isSlider { get; private set; }
internal float sliderMin { get; private set; }
internal float sliderMax { get; private set; }
internal float[] defaultValue { get; private set; }
internal string defaultString { get; private set; }
internal string externalQualifiedTypeName { get; private set; }
internal bool isLiteral { get; private set; }
internal bool isDynamic { get; private set; }
internal bool isBareResource { get; private set; }
internal bool isReferable { get; private set; }
internal string Referable { get; private set; }
internal bool isLinkage { get; private set; }
internal string linkTarget { get; private set; }
internal bool hasCustomBinding { get; private set; }
internal string customBinding { get; private set; }
protected override void OnProcess(IShaderField param, IProvider provider)
{
referenceName = param.Name;
isInput = param.IsInput;
isOutput = param.IsOutput;
shaderType = param.ShaderType;
string typeTest = shaderType.Name.ToLowerInvariant();
bool isSampler = typeTest.Contains("sampler");
bool isTexture = typeTest.Contains("texture");
isBareResource = !typeTest.Contains("unity") && (isSampler || isTexture);
displayName = param.Hints.GetHint<DisplayName>()?.Value as string;
tooltip = param.Hints.GetHint<Tooltip>()?.Value as string;
isStatic = param.Hints.GetHint<Static>()?.IsResolved ?? false;
isLocal = param.Hints.GetHint<Local>()?.IsResolved ?? false;
var dropdown = param.Hints.GetHint<Dropdown>();
if (isDropdown = dropdown?.IsResolved ?? false)
options = dropdown.Value as string[];
isColor = param.Hints.GetHint<Color>()?.IsResolved ?? false;
var range = param.Hints.GetHint<Range>();
if (isSlider = range?.IsResolved ?? false)
{
var r = range.Value as float[];
sliderMin = r[0];
sliderMax = r[1];
}
var defaultHint = param.Hints.GetHint<Default>();
if (defaultHint?.IsResolved ?? false)
{
defaultString = defaultHint.Value as string;
defaultValue = LazyTokenFloat(defaultString);
}
externalQualifiedTypeName = typeName;
var externalNamespace = param.Hints.GetHint<External>()?.Value as string;
if (!string.IsNullOrWhiteSpace(externalNamespace))
externalQualifiedTypeName = $"{externalNamespace}::{typeName}";
isLiteral = param.Hints.GetHint<Literal>()?.IsResolved ?? false;
var referableHint = param.Hints.GetHint<Referable>();
if (isReferable = referableHint?.IsResolved ?? false)
Referable = referableHint.Value as string;
isDynamic = param.Hints.GetHint<Dynamic>()?.IsResolved ?? false;
var linkage = param.Hints.GetHint<Linkage>();
if (isLinkage = linkage?.IsResolved ?? false)
{
linkTarget = linkage.Value as string;
isLocal = true;
}
var customBindingHint = param.Hints.GetHint<CustomBinding>();
if (hasCustomBinding = customBindingHint?.IsResolved ?? false)
customBinding = customBindingHint.Value as string;
}
internal ParameterHeader(IShaderField param, IProvider provider)
{
Process(param, provider);
}
// For returns.
internal ParameterHeader(string displayName, IShaderType shaderType, string tooltip, IProvider provider)
{
var hints = new IStrongHint[]
{
new DisplayName(Common.kDisplayName, displayName, true),
new Tooltip(Common.kTooltip, tooltip, true),
};
var field = new ShaderField("__UNITY_SHADERGRAPH_UNUSED", false, true, shaderType, hints);
Process(field, provider);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 73f8700d5e86e4ec397ed9c8ef0ffe90
@@ -0,0 +1,347 @@
using UnityEngine;
using System.Collections.Generic;
using UnityEditor.Graphing;
using System;
using Unity.GraphAuthoring.Editor.ProviderSystem;
namespace UnityEditor.ShaderGraph.ProviderSystem
{
// A Base Node serving as the entry point for Provider based function definitions.
// This Model object can work with any sort of function provider, interpret the function definition,
// and generate a valid model representation of that object. This abstracts the need for node definitions
// to be aware of how the model functions.
// NOTE: There exists an ExpressionProvider currently, so it's not a problem to have Models that are specializations,
// but ideally the expression input type would be gracefully handled in a generic way and it wouldn't be necessary.
[Serializable]
[HasDependencies(typeof(MinimalProviderNode))]
internal class ProviderNode : AbstractMaterialNode, IHasAssetDependencies, IGeneratesBodyCode, IGeneratesFunction
{
[Serializable]
internal class MinimalProviderNode : IHasDependencies
{
[SerializeReference]
IProvider<IShaderFunction> m_provider;
public void GetSourceAssetDependencies(AssetCollection assetCollection)
{
if (m_provider == null || m_provider.AssetID == default)
return;
assetCollection.AddAssetDependency(m_provider.AssetID,
AssetCollection.Flags.SourceDependency
| AssetCollection.Flags.ArtifactDependency
| AssetCollection.Flags.IncludeInExportPackage);
}
}
// Reserves a control for the function selector. This is a bit of a hack.
[GroupVariantControl]
internal ProviderNode groupVariantSentinel => this;
[SerializeReference]
IProvider<IShaderFunction> m_provider;
internal FunctionHeader Header { get; private set; }
internal Dictionary<string, ParameterHeader> ParamHeaders { get; private set; }
internal IProvider<IShaderFunction> Provider => m_provider;
const int kReservedOutputSlot = 0;
public override bool hasPreview => true;
public override bool hasUserAuthoredCode => true;
public override bool canSetPrecision => Header?.allowPrecision ?? false;
public override string documentationURL => Header?.helpUrl ?? base.documentationURL;
internal override bool ExposeToSearcher => false;
internal virtual bool requiresGeneration => false;
public ProviderNode()
{
name = "Provider Based Node";
Header = new();
}
internal void InitializeFromProvider(IProvider<IShaderFunction> provider)
{
this.m_provider = (IProvider<IShaderFunction>)provider.Clone();
UpdateModel();
}
public override void UpdateNodeAfterDeserialization()
{
base.UpdateNodeAfterDeserialization();
Refresh();
}
public override void Concretize()
{
UpdateModel();
base.Concretize();
}
public bool Reload(HashSet<string> changedAssetGuids)
{
// Scripted providers don't react to asset changes — their definition is in-memory.
if (Provider.AssetID != default && changedAssetGuids.Contains(Provider.AssetID.ToString()))
{
Refresh();
return true;
}
return false;
}
protected void Refresh()
{
Provider?.Reload();
UpdateModel();
ValidateNode();
Dirty(ModificationScope.Graph);
}
internal void UpdateModel()
{
if (Provider == null || Provider.Definition == null || !Provider.IsValid)
return;
Header.Process(Provider.Definition, Provider);
var header = Header;
this.name = header.displayName;
this.synonyms = header.searchTerms;
var parameters = Provider.Definition.Parameters;
List<MaterialSlot> previousSlots = new();
GetSlots(previousSlots);
Dictionary<string, (int inputId, int outputId)> oldSlotMap = new();
HashSet<int> usedSlotIds = new();
// build a map of parameter names to old slot ids (tuple is to support inout params).
foreach (var oldSlot in previousSlots)
{
if (!oldSlotMap.TryGetValue(oldSlot.shaderOutputName, out var idTuple))
idTuple = (-1, -1);
if (oldSlot.isInputSlot) idTuple.inputId = oldSlot.id;
if (oldSlot.isOutputSlot) idTuple.outputId = oldSlot.id;
oldSlotMap[oldSlot.shaderOutputName] = idTuple;
}
ParamHeaders = new();
List<ParameterHeader> paramOrder = new();
List<int> desiredSlotOrder = new();
// return type is a special case, because it has no parameter but still needs a slot
// we reserve the 0 slotId for this and always make sure it's added first/at the top.
if (header.hasReturnValueType)
{
usedSlotIds.Add(kReservedOutputSlot);
desiredSlotOrder.Add(kReservedOutputSlot);
AddSlotFromParameter(header.returnHeader, kReservedOutputSlot, SlotType.Output);
}
// build the header data for our parameters and mark which slot ids are being reused.
foreach(var param in parameters)
{
var paramHeader = new ParameterHeader(param, Provider);
ParamHeaders.TryAdd(param.Name, paramHeader);
paramOrder.Add(paramHeader);
if (oldSlotMap.TryGetValue(param.Name, out var idTuple))
{
if (idTuple.inputId > -1 && param.IsInput)
usedSlotIds.Add(idTuple.inputId);
if (idTuple.outputId > -1 && param.IsOutput)
usedSlotIds.Add(idTuple.outputId);
}
}
// walk through our header data and build the actual slots.
int nextSlot = kReservedOutputSlot;
foreach (var paramHeader in paramOrder)
{
if (!oldSlotMap.TryGetValue(paramHeader.referenceName, out var idTuple))
idTuple = (-1, -1);
void DoSlot(int slotId, SlotType dir)
{
if (slotId == -1)
{
while (usedSlotIds.Contains(++nextSlot));
usedSlotIds.Add(nextSlot);
slotId = nextSlot;
}
AddSlotFromParameter(paramHeader, slotId, dir);
desiredSlotOrder.Add(slotId);
}
if (paramHeader.isInput)
DoSlot(idTuple.inputId, SlotType.Input);
if (paramHeader.isOutput)
DoSlot(idTuple.outputId, SlotType.Output);
}
RemoveSlotsNameNotMatching(usedSlotIds, true);
SetSlotOrder(desiredSlotOrder);
}
static Rendering.ShaderCompilerMessageSeverity ToCompilerSeverity(MessageType severity) => severity switch
{
MessageType.Error => Rendering.ShaderCompilerMessageSeverity.Error,
_ => Rendering.ShaderCompilerMessageSeverity.Warning,
};
void AddSlotFromParameter(ParameterHeader header, int slotId, SlotType dir)
{
var slot = HeaderUtils.MakeSlotFromParameter(header, slotId, dir);
if (slot != null)
// Slots that use label control have no indication in the Model that they use them.
// However, the only _existing_ case where they'd meaningfully change are SpaceMaterialSlots,
// which also don't handle default values well either. In this case, we go nuclear and completely
// rebuild the slot.
AddSlot(slot, slot is not SpaceMaterialSlot, slot is not SpaceMaterialSlot);
}
public void GenerateNodeCode(ShaderStringBuilder sb, GenerationMode generationMode)
{
if (this.Provider == null || this.Provider.Definition == null)
return;
// Apply legacy precision/dynamic only before code generation/usage.
HeaderUtils.TryApplyLegacy(Provider.Definition, this.Header, this.ParamHeaders, PrecisionUtil.Token, lastKnownDynamicVectorLength, out var func);
using (UnityEngine.Pool.ListPool<MaterialSlot>.Get(out var slots))
{
GetSlots<MaterialSlot>(slots);
Dictionary<string, (MaterialSlot input, MaterialSlot output)> paramSlotMap = new();
MaterialSlot returnSlot = null;
// for various reasons, slots won't match one-to-one with parameters;
// we'll need to make sure slots are matched to parameters in the order expected
// while also accounting for inout duplicity.
foreach (var slot in slots)
{
if (slot.id == kReservedOutputSlot)
returnSlot = slot;
else
{
paramSlotMap.TryGetValue(slot.shaderOutputName, out var tup);
if (slot.isInputSlot) tup.input = slot;
if (slot.isOutputSlot) tup.output = slot;
paramSlotMap[slot.shaderOutputName] = tup;
}
}
ShaderStringBuilder call = new(); // for the callsite.
ShaderStringBuilder args = new(); // for building up the argument list.
// The return result is an output slot, but will instead get initialized by the function call.
if (returnSlot != null)
{
call.Append($"{HeaderUtils.ToShaderType(returnSlot)} {GetVariableNameForSlot(returnSlot.id)} = ");
}
bool first = true;
foreach (var param in func.Parameters)
{
var inputSlot = paramSlotMap[param.Name].input;
var outputSlot = paramSlotMap[param.Name].output;
var typeString = outputSlot != null ? HeaderUtils.ToShaderType(outputSlot) : HeaderUtils.ToShaderType(inputSlot);
var variableName = outputSlot != null ? GetVariableNameForSlot(outputSlot.id) : null;
var valueString = inputSlot != null ? GetSlotValue(inputSlot.id, generationMode) : null;
if (inputSlot != null && inputSlot.bareResource) // Texture/Sampler slots are normally 'Unity' wrapper structures
valueString += inputSlot is SamplerStateMaterialSlot ? ".samplerstate" : ".tex";
var argument = valueString; // assume it's an input, in which case the arg will be the upstream value/connection.
// apply linkage override
if (ParamHeaders.TryGetValue(param.Name, out var ph) && ph.isLinkage)
{
var targetSlots = paramSlotMap[ph.linkTarget];
// this concept is awkward for inout slots, but that could be improved in the future.
// ie. add support for int and set it to 0 = none, 1 = in, 2 = out, 3 = both.
if (targetSlots.input != null && targetSlots.input.isConnected
|| targetSlots.output != null && targetSlots.output.isConnected)
{
argument = "true";
}
else argument = "false";
}
if (outputSlot != null)
{
// inout and out define a variable to be used as the argument.
// but only inout gets initialized by the value.
argument = variableName;
sb.AddLine($"{typeString} {variableName}{(inputSlot != null ? $" = {valueString}" : "")};");
}
if (!first)
args.Append(", ");
first = false;
args.Append(argument);
}
call.Append(ShaderObjectUtils.GenerateCall(func, args.ToString()));
sb.AddLine(call.ToString());
}
}
public void GenerateNodeFunction(FunctionRegistry registry, GenerationMode generationMode)
{
if (Provider == null || !Provider.IsValid || Provider.Definition == null)
return;
// Asset-backed providers contribute their source .hlsl as an include; scripted
// providers carry their source inline via the generated function body below.
if (Provider.AssetID != default)
{
var includePath = AssetDatabase.GUIDToAssetPath(Provider.AssetID);
registry.RequiresIncludePath(includePath, false);
}
if (HeaderUtils.TryApplyLegacy(Provider.Definition, Header, ParamHeaders, PrecisionUtil.Token, lastKnownDynamicVectorLength, out var func) || requiresGeneration)
{
string code = ShaderObjectUtils.GenerateCode(func, false, false, true);
registry.ProvideFunction(func.Name, s => s.AppendLine(code));
}
}
public override void ValidateNode()
{
base.ValidateNode();
if (Provider == null)
{
owner?.AddSetupError(this.objectId, "Node is in an invalid state and cannot recover.");
return;
}
else if (Provider.AssetID != default && (Provider.Definition == null || !Provider.Definition.IsValid))
{
var path = AssetDatabase.GUIDToAssetPath(Provider.AssetID);
owner?.AddSetupError(this.objectId, $"Data for '{Provider.ProviderKey}' expected in '{path}'.");
return;
}
else
{
foreach (var (msg, severity) in Header.Messages)
owner?.AddValidationError(this.objectId, msg, ToCompilerSeverity(severity));
foreach (var param in ParamHeaders.Values)
foreach (var (msg, severity) in param.Messages)
owner?.AddValidationError(this.objectId, msg, ToCompilerSeverity(severity));
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5f258658ff6f1c842b38d746f69fa370
@@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
using UnityEditor.Graphing;
using UnityEditor.ShaderGraph.ProviderSystem;
using Unity.GraphAuthoring.Editor.ProviderSystem;
using System.Text;
namespace UnityEditor.ShaderGraph.Drawing.Inspector.PropertyDrawers
{
[SGPropertyDrawer(typeof(ProviderNode))]
class ProviderNodeNodePropertyDrawer : AbstractMaterialNodePropertyDrawer
{
private ProviderNode node;
internal override void AddCustomNodeProperties(VisualElement parentElement, AbstractMaterialNode nodeBase, Action setNodesAsDirtyCallback, Action updateNodeViewsCallback)
{
node = nodeBase as ProviderNode;
var provider = node.Provider;
bool hasAssetSource = provider.AssetID != default;
string sourcePath = hasAssetSource ? AssetDatabase.GUIDToAssetPath(provider.AssetID) : null;
string providerKey = provider.ProviderKey;
if (!provider.IsValid)
{
var errorMessage = hasAssetSource
? $"Could not find '{providerKey}' in '{sourcePath}'."
: $"Could not find '{providerKey}'.";
parentElement.Add(new HelpBoxRow(errorMessage, MessageType.Error));
return;
}
// Version selector — shown whenever versioned registrations exist for this key (even a single
// version), so users can always see which version is active and switch when more become available.
var versionCandidates = node.Header.versionCandidates;
if (versionCandidates != null && versionCandidates.Length >= 1)
{
string currentVersion = node.Header.version;
var choices = new List<string>(versionCandidates.Length);
int currentIdx = 0;
for (int i = 0; i < versionCandidates.Length; i++)
{
choices.Add(versionCandidates[i].version);
if (versionCandidates[i].version == currentVersion)
currentIdx = i;
}
var versionDropdown = new DropdownField("Version", choices, currentIdx);
versionDropdown.RegisterValueChangedCallback(_ =>
{
int newIdx = versionDropdown.index;
if (versionCandidates[newIdx].version == node.Header.version)
return;
setNodesAsDirtyCallback?.Invoke();
node.owner.owner.RegisterCompleteObjectUndo($"Change {node.name} Version");
node.InitializeFromProvider(versionCandidates[newIdx].provider);
node.ValidateNode();
node.Dirty(ModificationScope.Graph);
updateNodeViewsCallback?.Invoke();
inspectorUpdateDelegate?.Invoke();
});
parentElement.Add(versionDropdown);
}
if (hasAssetSource)
{
parentElement.Add(new Label("Provider Key"));
parentElement.Add(new HelpBoxRow(providerKey, MessageType.None));
parentElement.Add(new Label("Source Path"));
parentElement.Add(new HelpBoxRow(sourcePath, MessageType.None));
string qualifiedSignature = ShaderObjectUtils.QualifySignature(node.Provider.Definition, true, true);
parentElement.Add(new Label("Qualified Signature"));
parentElement.Add(new HelpBoxRow(qualifiedSignature, MessageType.None));
string code = ShaderObjectUtils.GenerateCode(provider.Definition, false, false, false);
parentElement.Add(new Label("Expected Definition"));
parentElement.Add(new HelpBoxRow(code, MessageType.None));
}
StringBuilder sb = new();
bool hasMsg = false;
var worstSeverity = MessageType.Warning;
// Function
foreach (var (msg, severity) in node.Header.Messages)
{
hasMsg = true;
sb.AppendLine(msg);
if (severity > worstSeverity) worstSeverity = severity;
}
if (hasMsg)
{
parentElement.Add(new Label("Function Hint Messages"));
parentElement.Add(new HelpBoxRow(sb.ToString(), worstSeverity));
}
// Parameters
foreach (var paramHeader in node.ParamHeaders.Values)
{
hasMsg = false;
worstSeverity = MessageType.Warning;
sb.Clear();
foreach (var (msg, severity) in paramHeader.Messages)
{
hasMsg = true;
sb.AppendLine(msg);
if (severity > worstSeverity) worstSeverity = severity;
}
if (hasMsg)
{
parentElement.Add(new Label($"Parameter '{paramHeader.referenceName}' Hint Messages"));
parentElement.Add(new HelpBoxRow(sb.ToString(), worstSeverity));
}
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 46fe9e42c0090d24391ef04f22ea4514
@@ -0,0 +1,27 @@
using System.Collections.Generic;
using Unity.GraphAuthoring.Editor.ProviderSystem;
using UnityEditor.ShaderGraph.Drawing;
using UnityEngine;
namespace UnityEditor.ShaderGraph.ProviderSystem
{
// Forwards ProviderLibrary reload notifications to any open MaterialGraphEditWindow so
// graphs displaying the affected providers refresh their state.
[InitializeOnLoad]
static class ShaderGraphProviderSystemInit
{
static ShaderGraphProviderSystemInit()
{
ProviderLibrary.OnProvidersReloaded += OnProvidersReloaded;
}
static void OnProvidersReloaded(IReadOnlyList<GUID> modifiedAssetIDs)
{
if (modifiedAssetIDs == null || modifiedAssetIDs.Count == 0) return;
var editors = Resources.FindObjectsOfTypeAll<MaterialGraphEditWindow>();
foreach (var editor in editors)
editor.NotifyDependencyUpdated(modifiedAssetIDs);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 30e4e0132b9be4f4fbd731d287369285