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,8 @@
fileFormatVersion: 2
guid: a4d891bc7986be44b9b3e3c3d194be55
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using UnityEngine;
using UnityEngine.Scripting.APIUpdating;
using Unity.GraphAuthoring.Editor.ProviderSystem.Hints;
namespace Unity.GraphAuthoring.Editor.ProviderSystem
{
[Serializable]
[ScriptedProvider]
[MovedFrom(false, "UnityEditor.ShaderGraph.ProviderSystem", "Unity.ShaderGraph.Editor")]
internal class ExpressionProvider : IProvider<IShaderFunction>
{
// Expression provider is domain agnostic, but we need some sort of keyword filtering that is domain specific.
internal interface IKeywordFilter
{
bool IsReserved(string name);
}
internal const string kExpressionProviderKey = "unity:shadergraph:scripted:Expression";
public string ProviderKey => kExpressionProviderKey;
public GUID AssetID => default;
private static readonly string[] kNamespace = { "unity_sg_expression" };
internal string Expression => m_expression;
internal string ShaderType => m_type;
public IShaderFunction Definition
{
get
{
if (m_definition == null || !m_definition.IsValid)
Reload();
return m_definition;
}
}
[SerializeField]
string m_name;
[SerializeField]
string m_expression;
[SerializeField]
string m_type;
[SerializeReference]
IKeywordFilter m_keywordFilter;
[NonSerialized]
IShaderFunction m_definition;
internal ExpressionProvider() : this("Expression", "A", "float") { }
internal ExpressionProvider(string name, string expression, string type, IKeywordFilter filter = null)
{
UpdateExpression(name, expression, type, filter);
}
public void Reload()
{
m_definition = ExpressionToShaderFunction(m_name, m_expression, m_type, out _, m_keywordFilter);
}
static IReadOnlyCollection<IStrongHint> BuildFuncHints() =>
new IStrongHint[]
{
new DisplayName("Expression", true),
new ProviderKey(kExpressionProviderKey),
new SearchName("Expression"),
new SearchCategory("Utility"),
new SearchTerms(new[] { "equation", "calculation", "inline", "code" }),
new Precision(true),
};
static IReadOnlyCollection<IStrongHint> BuildParamHints(string displayName) =>
new IStrongHint[]
{
new DisplayName(displayName, true)
};
internal void UpdateExpression(string name, string expression, string type, IKeywordFilter filter = null)
{
m_name = name;
m_type = type;
m_expression = expression;
m_keywordFilter = filter;
Reload();
}
public IProvider Clone()
=> new ExpressionProvider(m_name, m_expression, m_type, m_keywordFilter);
internal static IShaderFunction ExpressionToShaderFunction(string name, string expression, string type, out string finalExpression, IKeywordFilter keywordFilter = null)
{
// don't create fields from comments.
const string kCommentRegex = @"(\/\*.*?\*\/)|(\/\/.*)";
// pattern for getting valid identifiers that aren't functions or members,
// includes expanded european character set.
const string kIdentifierRegex = @"(?<!\.)\b[a-zA-ZŽžÀ-ÿ_][a-zA-ZŽžÀ-ÿ0-9_]*\b(?!\s*[\(])";
List<string> orderedNames = new();
HashSet<string> usedNames = new();
// gather the list of identifiers, deduplicate them, and ignore if they are reserved keywords.
string HandleName(string name)
{
if (!usedNames.Contains(name) && keywordFilter?.IsReserved(name) != true)
{
usedNames.Add(name);
orderedNames.Add(name);
}
return name;
}
// clean out comments.
expression = Regex.Replace(expression, kCommentRegex, "");
// process identifiers.
expression = Regex.Replace(expression, kIdentifierRegex, e => HandleName(e.Value));
finalExpression = expression;
// default initialize if there is no expression left.
if (string.IsNullOrWhiteSpace(expression))
expression = $"({type})0";
IShaderType shaderType = new ShaderType(type);
List<IShaderField> parameters = new();
foreach (var paramName in orderedNames)
parameters.Add(new ShaderField(paramName, true, false, shaderType, BuildParamHints(paramName)));
// trust that the name and type is valid.
return new ShaderFunction(name, kNamespace, parameters, shaderType, $"return {expression};", BuildFuncHints());
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d946e351cb0d51c47ab87647ccc8002a
@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEditor;
namespace Unity.GraphAuthoring.Editor.ProviderSystem
{
internal class ProviderModelAttribute : Attribute
{
public readonly string ProviderKey;
public ProviderModelAttribute(string providerKey) { ProviderKey = providerKey; }
}
internal class ScriptedProviderAttribute : Attribute { }
internal static class ProviderTypeCache
{
static Dictionary<string, Type> s_providerModels = null;
static List<Type> s_scriptedProviders = null;
private static void BuildCache()
{
if (s_providerModels == null && s_scriptedProviders == null)
{
s_providerModels = new();
s_scriptedProviders = new();
foreach(var type in TypeCache.GetTypesWithAttribute<ProviderModelAttribute>())
{
var providerKey = type.GetCustomAttribute<ProviderModelAttribute>()?.ProviderKey ?? null;
if (providerKey != null)
if (!s_providerModels.TryAdd(providerKey, type))
Debug.LogError($"Attempted to register '{type.Name}' with provider key '{providerKey}', but key is already registered to '{s_providerModels[providerKey]}'.");
}
foreach (var type in TypeCache.GetTypesWithAttribute<ScriptedProviderAttribute>())
s_scriptedProviders.Add(type);
}
}
internal static IEnumerable<Type> GetScriptedProviderTypes()
{
BuildCache();
return s_scriptedProviders;
}
internal static bool TryCreateModel(string key, out object model)
{
BuildCache();
if (s_providerModels.TryGetValue(key, out Type type))
{
model = Activator.CreateInstance(type, true);
return true;
}
model = null;
return false;
}
internal static void PopulateLibrary(ProviderLibrary lib)
{
foreach (var type in GetScriptedProviderTypes())
if (Activator.CreateInstance(type, true) is IProvider provider)
lib.TryAdd(provider);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: df3079b6682f2854d83dbf2c603f771e
@@ -0,0 +1,92 @@
using System;
using UnityEditor;
using UnityEditor.ShaderApiReflection;
using UnityEngine;
using UnityEngine.Scripting.APIUpdating;
namespace Unity.GraphAuthoring.Editor.ProviderSystem
{
[Serializable]
[MovedFrom(false, "UnityEditor.ShaderGraph.ProviderSystem", "Unity.ShaderGraph.Editor")]
internal class ReflectedFunctionProvider : IProvider<IShaderFunction>
{
public string ProviderKey => m_providerKey;
public string Version => m_version ?? Hints.Version.kUnversioned;
public string Group => m_group;
public int GroupPriority => m_groupPriority;
public GUID AssetID => m_sourceAssetId;
public IShaderFunction Definition
{
get
{
if (m_definition == null || !m_definition.IsValid)
Reload();
return m_definition;
}
}
[SerializeField]
string m_providerKey;
[SerializeField]
string m_version;
[SerializeField]
GUID m_sourceAssetId;
[NonSerialized]
string m_group;
[NonSerialized]
int m_groupPriority;
[NonSerialized]
IShaderFunction m_definition;
internal ReflectedFunctionProvider(GUID assetId, IShaderFunction definition)
{
m_sourceAssetId = assetId;
m_definition = definition;
if (definition.IsValid)
{
m_providerKey = definition.Hints.GetHint<Hints.ProviderKey>()?.Value as string;
m_version = definition.Hints.GetHint<Hints.Version>()?.Value as string;
var groupHint = definition.Hints.GetHint<Hints.GroupKey>();
m_group = groupHint?.Value as string;
m_groupPriority = groupHint?.Priority ?? 0;
}
}
internal ReflectedFunctionProvider(GUID assetId, ReflectedFunction function)
: this(assetId, ShaderReflectionUtils.ToShaderFunction(function))
{
}
public void Reload()
{
if (ShaderReflectionUtils.TryResolve(m_providerKey, m_sourceAssetId, out var function, m_version))
{
m_definition = ShaderReflectionUtils.ToShaderFunction(function, this);
var groupHint = m_definition.Hints.GetHint<Hints.GroupKey>();
m_group = groupHint?.Value as string;
m_groupPriority = groupHint?.Priority ?? 0;
}
else
{
m_definition = null;
m_group = null;
m_groupPriority = 0;
}
}
public IProvider Clone()
// NOTE: Definition is read only, so it's fine for a clone to reference the same object.
=> new ReflectedFunctionProvider(m_sourceAssetId, m_definition);
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f38ec703e65a04642bf6995418d4ee82
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a8555a979e70c2d4c922c336de6b72f5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,67 @@
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.IO;
namespace Unity.GraphAuthoring.Editor.ProviderSystem
{
internal class ShaderReflectionAssetPostProcessor : AssetPostprocessor
{
internal static void PopulateFromFiles(ProviderLibrary lib)
{
foreach (var guid in AssetDatabase.FindAssetGUIDs("t: ShaderInclude"))
AnalyzeFile(lib, guid);
}
internal static bool AnalyzeFile(ProviderLibrary lib, GUID assetID)
{
lib.ClearByAssetID(assetID);
var shaderInclude = AssetDatabase.LoadAssetByGUID<ShaderInclude>(assetID);
if (shaderInclude == null)
return false;
var reflection = shaderInclude.Reflection;
if (reflection == null)
return false;
foreach (var func in reflection.ReflectedFunctions)
lib.TryAdd(new ReflectedFunctionProvider(assetID, func));
return true;
}
static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths, bool didDomainReload)
{
// This asset post processor isn't meaningful unless the ProviderLibrary is already initialized.
if (!ProviderLibrary.IsInitialized || !ProviderLibrary.TryGetInstance(out var lib))
return;
var modifiedFiles = new List<GUID>();
// ProviderLibrary tracks GUIDs whereas this hook only provides paths;
// for deleted assets, we have no way of determining their GUIDs, so we can just test which are orphaned.
foreach (var assetID in new List<GUID>(lib.GetTrackedAssetIDs()))
{
if (!string.IsNullOrEmpty(AssetDatabase.GUIDToAssetPath(assetID)))
continue;
lib.ClearByAssetID(assetID);
modifiedFiles.Add(assetID);
}
foreach (string path in importedAssets)
{
// We are really looking for ShaderInclude assets.
if (Path.GetExtension(path).ToLower() != ".hlsl")
continue;
var assetID = AssetDatabase.GUIDFromAssetPath(path);
if (AnalyzeFile(lib, assetID))
modifiedFiles.Add(assetID);
}
if (modifiedFiles.Count > 0)
ProviderLibrary.NotifyProvidersReloaded(modifiedFiles);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4bc1cd11275415e4391e7089a2a670ac
@@ -0,0 +1,281 @@
using System;
using System.Collections.Generic;
using System.Text;
using UnityEditor.ShaderApiReflection;
using UnityEditor;
using GUID = UnityEngine.GUID;
using Unity.GraphAuthoring.Editor.ProviderSystem.Hints;
namespace Unity.GraphAuthoring.Editor.ProviderSystem
{
// TODO (SVFXG-914): Hints processing and representation improvements planned.
internal static class ShaderReflectionUtils
{
// All registered strong hints and derived lookup maps — built once via TypeCache, reused across all calls.
static IStrongHint[] s_strongHints;
static Dictionary<string, IStrongHint> s_hintsByKey;
static Dictionary<string, List<(string StrongHintKey, int Priority)>> s_synonymMap;
static IStrongHint[] GetStrongHints()
{
if (s_strongHints != null)
return s_strongHints;
var discovered = TypeCache.GetTypesWithAttribute<StrongHintAttribute>();
var strongHints = new List<IStrongHint>();
foreach (var type in discovered)
{
if (!type.IsAbstract && !type.IsInterface && typeof(IStrongHint).IsAssignableFrom(type))
strongHints.Add((IStrongHint)Activator.CreateInstance(type, nonPublic: true));
}
s_strongHints = strongHints.ToArray();
// Build synonym map from hint metadata — cached alongside s_strongHints.
s_hintsByKey = new Dictionary<string, IStrongHint>();
s_synonymMap = new Dictionary<string, List<(string StrongHintKey, int Priority)>>();
foreach (var hint in s_strongHints)
{
s_hintsByKey[hint.Key] = hint;
int priority = 0;
if (!s_synonymMap.TryAdd(hint.Key, new() { (hint.Key, priority) }))
s_synonymMap[hint.Key].Add((hint.Key, priority));
if (!hint.AllowDisqualifiedSynonyms)
continue;
var alternates = new List<string>(DisqualifyKey(hint.Key));
if (hint.Synonyms != null)
foreach (var synonym in hint.Synonyms)
alternates.AddRange(DisqualifyKey(synonym, true));
foreach (var alternate in alternates)
{
priority++;
if (!s_synonymMap.TryAdd(alternate, new() { (hint.Key, priority) }))
s_synonymMap[alternate].Add((hint.Key, priority));
}
}
return s_strongHints;
}
readonly struct FieldHintContext : IProvider<IShaderFunction>
{
readonly IProvider m_real;
public string ProviderKey => m_real?.ProviderKey ?? "";
public GUID AssetID => m_real?.AssetID ?? default;
public IShaderFunction Definition { get; }
public IProvider Clone() => this;
internal FieldHintContext(IProvider real, IShaderFunction contextFunc)
{
m_real = real;
Definition = contextFunc;
}
}
internal static bool TryResolve(string providerKey, GUID assetId, out ReflectedFunction function, string version = null)
{
var source = AssetDatabase.LoadAssetByGUID<ShaderInclude>(assetId)?.Reflection;
function = default;
if (source == null)
return false;
ReflectedFunction firstMatch = default;
bool hasFirstMatch = false;
foreach (var func in source.ReflectedFunctions)
{
string key = ShaderObjectUtils.EvaluateProviderKey(func.Hints, BuildUnresolvedFunction(func));
if (providerKey != key)
continue;
// When no version disambiguation is requested, first match wins.
if (string.IsNullOrEmpty(version))
{
function = func;
return true;
}
// Version disambiguation: prefer the entry whose sg:Version matches exactly.
func.Hints.TryGetValue(Func.kVersion, out var funcVersion);
if (funcVersion == version)
{
function = func;
return true;
}
if (!hasFirstMatch)
{
firstMatch = func;
hasFirstMatch = true;
}
}
if (hasFirstMatch)
{
function = firstMatch;
return true;
}
return false;
}
// Pass 1: build a function with unresolved (hint-free) fields, used as context
// for cross-parameter hint validation and for provider key lookup.
static IShaderFunction BuildUnresolvedFunction(ReflectedFunction refFunc)
{
var returnType = ToShaderType(refFunc.ReturnTypeName);
List<IShaderField> fields = new();
foreach (var param in refFunc.Parameters)
{
var type = ToShaderType(param.TypeName);
bool isInput = param.DirectionFlags == ReflectedParameter.Direction.In || param.DirectionFlags == ReflectedParameter.Direction.InOut;
bool isOutput = param.DirectionFlags == ReflectedParameter.Direction.Out || param.DirectionFlags == ReflectedParameter.Direction.InOut;
fields.Add(new ShaderField(param.Name, isInput, isOutput, type, null));
}
return new ShaderFunction(refFunc.Name, refFunc.EnclosingNamespace, fields, returnType, refFunc.BodyText, null);
}
internal static IShaderType ToShaderType(string typeName)
=> new ShaderType(typeName);
internal static IShaderFunction ToShaderFunction(ReflectedFunction refFunc, IProvider provider = null)
{
var returnType = ToShaderType(refFunc.ReturnTypeName);
// Pass 1: unresolved fields — gives cross-parameter hints (e.g. Linkage)
// a fully populated sibling list to validate against.
var unresolvedFunc = BuildUnresolvedFunction(refFunc);
var fieldContext = new FieldHintContext(provider, unresolvedFunc);
// Pass 2: resolve field hints with the context function available.
List<IShaderField> resolvedFields = new();
foreach (var param in refFunc.Parameters)
{
var type = ToShaderType(param.TypeName);
bool isInput = param.DirectionFlags == ReflectedParameter.Direction.In || param.DirectionFlags == ReflectedParameter.Direction.InOut;
bool isOutput = param.DirectionFlags == ReflectedParameter.Direction.Out || param.DirectionFlags == ReflectedParameter.Direction.InOut;
var tempField = new ShaderField(param.Name, isInput, isOutput, type, null);
var fieldHints = ResolveRawHints(param.Hints, tempField, fieldContext);
resolvedFields.Add(new ShaderField(param.Name, isInput, isOutput, type, fieldHints));
}
// Resolve function hints using the final resolved fields.
var tempFunc = new ShaderFunction(refFunc.Name, refFunc.EnclosingNamespace, resolvedFields, returnType, refFunc.BodyText, null);
var funcHints = ResolveRawHints(refFunc.Hints, tempFunc, provider);
return new ShaderFunction(refFunc.Name, refFunc.EnclosingNamespace, resolvedFields, returnType, refFunc.BodyText, funcHints);
}
// Resolves a raw string hint dict against all registered hints, returning
// a collection of resolved IStrongHint instances. Handles synonym matching,
// conflict detection, and AlwaysProcess fallbacks.
static IReadOnlyCollection<IStrongHint> ResolveRawHints(
IReadOnlyDictionary<string, string> rawHints,
IDefinition obj,
IProvider provider)
{
var hints = GetStrongHints();
var hintsByKey = s_hintsByKey;
var synonymMap = s_synonymMap;
var result = new List<IStrongHint>();
// Match raw hint keys to hint keys via synonym map.
var foundHints = new Dictionary<string, (string Synonym, int Priority)>();
var conflictCases = new Dictionary<string, HashSet<string>>();
var conflictedHints = new HashSet<string>();
foreach (var rawHintKey in rawHints.Keys)
{
if (!synonymMap.TryGetValue(rawHintKey, out var matches))
continue;
foreach (var match in matches)
{
if (!foundHints.TryAdd(match.StrongHintKey, (rawHintKey, match.Priority)))
{
if (match.Priority < foundHints[match.StrongHintKey].Priority)
foundHints[match.StrongHintKey] = (rawHintKey, match.Priority);
}
if (hintsByKey[match.StrongHintKey].Conflicts != null)
foreach (var conflictClass in hintsByKey[match.StrongHintKey].Conflicts)
if (!conflictCases.TryAdd(conflictClass, new HashSet<string>() { match.StrongHintKey }))
conflictCases[conflictClass].Add(match.StrongHintKey);
}
}
// Emit conflict messages and collect conflicted hint keys.
foreach (var caseKV in conflictCases)
{
if (caseKV.Value.Count == 1)
continue;
conflictedHints.UnionWith(caseKV.Value);
StringBuilder sb = new();
bool first = true;
sb.Append($"Conflicting hints of class '{caseKV.Key}' found, ignoring: ");
foreach (var conflictKey in caseKV.Value)
{
if (!first) sb.Append(", ");
sb.Append($"'{conflictKey}'");
first = false;
}
result.Add(new MessageHint(caseKV.Key, sb.ToString()));
}
// Resolve each hint.
foreach (var hint in hints)
{
if (conflictedHints.Contains(hint.Key))
continue;
string synonymUsed = null;
string rawHintValue = null;
bool found;
bool shouldProcess = hint.AlwaysProcess;
if (found = foundHints.TryGetValue(hint.Key, out var rawHintData))
{
shouldProcess = true;
synonymUsed = rawHintData.Synonym;
rawHintValue = rawHints[synonymUsed];
}
if (!shouldProcess)
continue;
var instance = (IStrongHint)Activator.CreateInstance(hint.GetType(), nonPublic: true);
instance.Initialize(found, rawHintValue, obj, provider, synonymUsed, rawHints);
if (instance.IsResolved || instance.Message != null)
result.Add(instance);
}
return result;
}
// eg. "unity:engine:sg:HintKey" => "engine:sg:HintKey", "sg:HintKey", "HintKey"
static IEnumerable<string> DisqualifyKey(string key, bool inclusive = false)
{
if (inclusive)
yield return key;
for (int i = 0; i < key.Length - 1; ++i)
{
if (key[i] == ':')
{
int j;
for (j = i + 1; j < key.Length && key[j] == ':'; ++j);
string candidate = key[j..];
if (!string.IsNullOrEmpty(candidate))
yield return candidate;
}
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 0ec8bccc9e164dc43a2fdc214146b251
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 31edee6589f1955409b2de461b4b2acf
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,60 @@
using System.Collections.Generic;
using System.Collections.Immutable;
namespace Unity.GraphAuthoring.Editor.ProviderSystem
{
// TODO(SVFXG-869): ShaderType to provide more robust typing information and improved type handling.
internal struct ShaderType : IShaderType
{
public bool IsValid { get; private set; }
public string Name { get; private set; }
internal ShaderType(string name)
{
IsValid = true;
Name = name;
}
}
internal struct ShaderField : IShaderField
{
public bool IsValid { get; private set; }
public string Name { get; private set; }
public bool IsInput { get; private set; }
public bool IsOutput { get; private set; }
public IShaderType ShaderType { get; private set; }
public IReadOnlyCollection<IStrongHint> Hints { get; private set; }
internal ShaderField(string name, bool isInput, bool isOutput, IShaderType shaderType, IReadOnlyCollection<IStrongHint> hints)
{
IsValid = true;
Name = name;
IsInput = isInput;
IsOutput = isOutput;
ShaderType = shaderType;
Hints = hints ?? ImmutableArray<IStrongHint>.Empty;
}
}
internal struct ShaderFunction : IShaderFunction
{
public bool IsValid { get; private set; }
public string Name { get; private set; }
public IShaderType ReturnType { get; private set; }
public string FunctionBody { get; private set; }
public IEnumerable<string> Namespace { get; private set; }
public IEnumerable<IShaderField> Parameters { get; private set; }
public IReadOnlyCollection<IStrongHint> Hints { get; private set; }
internal ShaderFunction(string name, IEnumerable<string> namespaces, IEnumerable<IShaderField> parameters, IShaderType returnType, string functionBody, IReadOnlyCollection<IStrongHint> hints)
{
IsValid = true;
Name = name;
ReturnType = returnType;
FunctionBody = functionBody;
Parameters = parameters ?? ImmutableArray<IShaderField>.Empty;
Namespace = namespaces ?? ImmutableArray<string>.Empty;
Hints = hints ?? ImmutableArray<IStrongHint>.Empty;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 60eb5edc9e8f1644ab7af39eff49a691
@@ -0,0 +1,163 @@
using System.Collections.Generic;
using UnityEngine;
namespace Unity.GraphAuthoring.Editor.ProviderSystem
{
internal static class HintUtils
{
internal static T GetHint<T>(this IReadOnlyCollection<IStrongHint> hints) where T : class, IStrongHint
{
if (hints == null) return null;
foreach (var h in hints)
if (h.GetType() == typeof(T)) return (T)h;
return null;
}
internal static bool IsNumeric(string typeName)
{
return TryParseTypeInfo(typeName, out _, out _, out _, out _, out _, out _, out _);
}
internal static bool TryParseTypeInfo(
string type,
out string prim,
out bool isScalar,
out bool isVector,
out bool isMatrix,
out int rows,
out int cols,
out int length)
{
prim = null;
isScalar = false;
isVector = false;
isMatrix = false;
rows = -1;
cols = -1;
length = -1;
// Arrays aren't supported yet.
if (type.Contains('['))
return false;
if (type.StartsWith("uint")) prim = "uint";
if (type.StartsWith("int")) prim = "int";
if (type.StartsWith("bool")) prim = "bool";
if (type.StartsWith("half")) prim = "half";
if (type.StartsWith("float")) prim = "float";
if (prim == null)
return false;
if (type == prim)
return isScalar = true;
var remainder = type.Substring(prim.Length);
if (remainder.Length == 1)
{
return isVector = int.TryParse(remainder, out rows) && rows >= 1 && rows <= 4;
}
if (remainder.Length == 3)
{
return isMatrix
= int.TryParse(remainder.Substring(0, 1), out rows) && rows >= 1 && rows <= 4
&& int.TryParse(remainder.Substring(2, 1), out cols) && cols >= 1 && cols <= 4;
}
return false;
}
internal static bool TryCast<T>(string shaderType, float[] values, out T value, T fallback = default)
{
try
{
return Cast<T>(shaderType, values, out value, fallback);
}
catch
{
value = default;
return false;
}
}
private static bool Cast<T>(string shaderType, float[] values, out T value, T fallback = default)
{
object obj = default;
value = fallback;
if (values == null || values.Length == 0)
return false;
float[] v = new float[16];
for (int i = 0; i < values.Length; ++i)
v[i] = values[i];
if (!TryParseTypeInfo(shaderType, out string prim, out bool isScalar, out bool isVector, out bool isMatrix, out int rows, out int cols, out int length))
return false;
if (isScalar || isVector && rows == 1 || isMatrix && rows == 1 && cols == 1)
{
var fv = v[0];
switch (prim)
{
case "bool": obj = fv != 0; break;
case "uint": obj = fv; break;
case "int": obj = fv; break;
case "float":
case "half": obj = fv; break;
default: return false;
}
}
if (isVector)
{
switch (rows)
{
case 2: obj = new Vector2(v[0], v[1]); break;
case 3: obj = new Vector3(v[0], v[1], v[2]); break;
case 4: obj = new Vector4(v[0], v[1], v[2], v[3]); break;
default: return false;
}
}
if (isMatrix)
{
Matrix4x4 m = new();
for (int i = 0; i < cols * rows; ++i)
{
m[i % rows + rows * (i / rows)] = v[i];
}
}
value = (T)obj;
return true;
}
internal static float[] LazyTokenFloat(string arg)
{
var tokens = LazyTokenize(arg);
List<float> asfloat = new();
foreach (var token in tokens)
if (float.TryParse(token, out float r))
asfloat.Add(r);
return asfloat.ToArray();
}
internal static string[] LazyTokenString(string arg)
{
List<string> tokens = new(LazyTokenize(arg));
return tokens.ToArray();
}
private static IEnumerable<string> LazyTokenize(string arg)
{
foreach (var e in arg.Split(','))
{
var result = e.Trim();
if (!string.IsNullOrWhiteSpace(result))
yield return e.Trim();
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: bc1a7e8c79fbc8347b285edc784176af
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d2ef992c77ae08e499bfa4ddc2e2fb5a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,116 @@
using System.Collections.Generic;
namespace Unity.GraphAuthoring.Editor.ProviderSystem.Hints
{
internal static class Common
{
internal const string kDisplayName = "sg:DisplayName";
internal const string kTooltip = "sg:Tooltip";
}
[StrongHint]
internal class DisplayName : IStrongHint<IDefinition>
{
public string Key { get; }
public bool AlwaysProcess => true;
public IReadOnlyCollection<string> Synonyms { get; } = new string[] { "Label", "Title" };
public object Value { get; private set; }
public string Message { get; private set; }
public bool IsResolved { get; private set; }
readonly string m_fallback;
// Default constructor — used by TypeCache auto-discovery.
internal DisplayName() { Key = Common.kDisplayName; }
internal DisplayName(string name, string fallback = null)
{
Key = name;
m_fallback = fallback;
}
// Direct-value constructor — used by ExpressionProvider.
internal DisplayName(string name, string resolvedValue, bool resolved)
{
Key = name;
Value = resolvedValue;
IsResolved = true;
}
// Standard-key direct-value constructor — key defaults to Common.kDisplayName.
internal DisplayName(string resolvedValue, bool resolved)
{
Key = Common.kDisplayName;
Value = resolvedValue;
IsResolved = true;
}
public void Initialize(bool found, string rawValue, IDefinition obj, IProvider provider, string actualHintKey, IReadOnlyDictionary<string, string> rawHints)
{
Value = found ? rawValue : m_fallback ?? obj.Name;
IsResolved = true;
}
}
[StrongHint]
internal class Tooltip : IStrongHint<IDefinition>
{
public string Key { get; }
public IReadOnlyCollection<string> Synonyms { get; } = new[] { "summary" };
public object Value { get; private set; }
public string Message { get; private set; }
public bool IsResolved { get; private set; }
// Default constructor — used by TypeCache auto-discovery.
internal Tooltip() { Key = Common.kTooltip; }
internal Tooltip(string name) { Key = name; }
// Direct-value constructor — used by ExpressionProvider.
internal Tooltip(string name, string resolvedValue, bool resolved)
{
Key = name;
Value = resolvedValue;
IsResolved = resolvedValue != null;
}
public void Initialize(bool found, string rawValue, IDefinition obj, IProvider provider, string actualHintKey, IReadOnlyDictionary<string, string> rawHints)
{
Value = found ? rawValue : null;
IsResolved = found && rawValue != null;
}
}
internal abstract class Flag : IStrongHint<IDefinition>
{
public string Key { get; }
public IReadOnlyCollection<string> Conflicts { get; }
public IReadOnlyCollection<string> Synonyms { get; }
public object Value { get; private set; }
public string Message { get; private set; }
public bool IsResolved { get; private set; }
// Base constructors — used by named subclasses.
internal Flag(string name) { Key = name; }
internal Flag(string name, string conflict) { Key = name; Conflicts = conflict != null ? new[] { conflict } : null; }
internal Flag(string name, string[] conflicts) { Key = name; Conflicts = conflicts; }
internal Flag(string name, string[] conflicts, string[] synonyms) { Key = name; Conflicts = conflicts; Synonyms = synonyms; }
// Resolved-value constructor — for subclasses that need to set IsResolved at construction time.
internal Flag(string name, string[] conflicts, string[] synonyms, bool resolved)
{
Key = name; Conflicts = conflicts; Synonyms = synonyms; IsResolved = resolved;
}
public void Initialize(bool found, string rawValue, IDefinition obj, IProvider provider, string actualHintKey, IReadOnlyDictionary<string, string> rawHints)
{
Value = rawValue;
IsResolved = found;
if (found && !string.IsNullOrWhiteSpace(rawValue))
Message = "Expects an empty argument string.";
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f6dbb282170632844b643899f786605a
@@ -0,0 +1,461 @@
using System.Collections.Generic;
using System.Text;
using UnityEditor;
namespace Unity.GraphAuthoring.Editor.ProviderSystem.Hints
{
internal static class Func
{
internal const string kProviderKey = "sg:ProviderKey";
internal const string kReturnDisplayName = "sg:ReturnDisplayName";
internal const string kSearchTerms = "sg:SearchTerms";
internal const string kSearchName = "sg:SearchName";
internal const string kSearchCategory = "sg:SearchCategory";
internal const string kPrecision = "sg:DynamicPrecision";
internal const string kGroupKey = "sg:GroupKey";
internal const string kReturnTooltip = "sg:ReturnTooltip";
internal const string kDocumentationLink = "sg:HelpURL";
internal const string kUnityManualBaseUrl = "https://docs.unity3d.com/Manual/";
internal const string kPackageDocsBaseUrl = "https://docs.unity3d.com/Packages/";
// Lifecycle and versioning.
internal const string kVersion = "sg:Version";
internal const string kDeprecated = "sg:Deprecated";
internal const string kObsolete = "sg:Obsolete";
// Conflict class shared by Deprecated and Obsolete — a function can be one or the other, not both.
internal const string kLifecycleConflictClass = "sg:Lifecycle";
}
[StrongHint] sealed class ReturnDisplayName : DisplayName
{
internal ReturnDisplayName() : base(Func.kReturnDisplayName, "Out") { }
}
[StrongHint] sealed class ReturnTooltip : Tooltip
{
internal ReturnTooltip() : base(Func.kReturnTooltip) { }
}
[StrongHint] sealed class Precision : Flag
{
// Default constructor — used by TypeCache auto-discovery and HLSL hint processing.
internal Precision() : base(Func.kPrecision, (string[])null, new string[] { "Precision" }) { }
// Direct-value constructor — for use in code when precision support is known at construction time.
internal Precision(bool resolved) : base(Func.kPrecision, null, new string[] { "Precision" }, resolved) { }
}
[StrongHint]
class ProviderKey : IStrongHint<IShaderFunction>
{
public string Key => Func.kProviderKey;
public bool AlwaysProcess => true;
public bool AllowDisqualifiedSynonyms => false;
public object Value { get; private set; }
public string Message { get; private set; }
public bool IsResolved { get; private set; }
// Default constructor — used by TypeCache auto-discovery.
internal ProviderKey() { }
// Direct-value constructor.
internal ProviderKey(string resolvedValue)
{
Value = resolvedValue;
IsResolved = true;
}
public void Initialize(bool found, string rawValue, IShaderFunction func, IProvider provider, string actualHintKey, IReadOnlyDictionary<string, string> rawHints)
{
if (found && !string.IsNullOrWhiteSpace(rawValue))
{
Value = rawValue;
}
else
{
Value = provider?.ProviderKey ?? ShaderObjectUtils.QualifySignature(func);
if (!found)
Message = $"Expected; but none found for '{func.Name}'.";
}
IsResolved = true;
}
}
[StrongHint]
class SearchName : IStrongHint<IShaderFunction>
{
public string Key => Func.kSearchName;
public bool AlwaysProcess => true;
public object Value { get; private set; }
public string Message { get; private set; }
public bool IsResolved { get; private set; }
// Default constructor — used by TypeCache auto-discovery.
internal SearchName() { }
// Direct-value constructor.
internal SearchName(string resolvedValue)
{
Value = resolvedValue;
IsResolved = true;
}
public void Initialize(bool found, string rawValue, IShaderFunction func, IProvider provider, string actualHintKey, IReadOnlyDictionary<string, string> rawHints)
{
Value = found ? rawValue : ShaderObjectUtils.QualifySignature(func, false, true);
IsResolved = true;
}
}
[StrongHint]
class SearchTerms : IStrongHint<IShaderFunction>
{
public string Key => Func.kSearchTerms;
public bool AlwaysProcess => true;
public object Value { get; private set; }
public string Message { get; private set; }
public bool IsResolved { get; private set; }
// Default constructor — used by TypeCache auto-discovery.
internal SearchTerms() { }
// Direct-value constructor (pre-split array).
internal SearchTerms(string[] resolvedValue)
{
Value = resolvedValue;
IsResolved = true;
}
public void Initialize(bool found, string rawValue, IShaderFunction func, IProvider provider, string actualHintKey, IReadOnlyDictionary<string, string> rawHints)
{
Value = found ? HintUtils.LazyTokenString(rawValue) : new string[] { func.Name };
IsResolved = true;
}
}
[StrongHint]
class SearchCategory : IStrongHint<IShaderFunction>
{
public string Key => Func.kSearchCategory;
public bool AlwaysProcess => true;
public IReadOnlyCollection<string> Synonyms { get; } = new[] { "Category" };
public object Value { get; private set; }
public string Message { get; private set; }
public bool IsResolved { get; private set; }
// Default constructor — used by TypeCache auto-discovery.
internal SearchCategory() { }
// Direct-value constructor.
internal SearchCategory(string resolvedValue)
{
Value = resolvedValue;
IsResolved = true;
}
public void Initialize(bool found, string rawValue, IShaderFunction func, IProvider provider, string actualHintKey, IReadOnlyDictionary<string, string> rawHints)
{
if (found)
{
Value = rawValue;
}
else
{
StringBuilder catsb = new();
foreach (var name in func.Namespace)
catsb.Append($"/{name}");
var category = catsb.ToString();
if (!string.IsNullOrWhiteSpace(category))
Value = $"Reflected by Namespace{category}";
else if (provider != null && provider.AssetID != default)
Value = $"Reflected by Path/{AssetDatabase.GUIDToAssetPath(provider.AssetID)}";
else
Value = "Uncategorized";
}
IsResolved = true;
}
}
// Resolves to a fully-constructed URL string. Three raw value forms are accepted:
//
// "https://example.com/custom" — raw URL, passed through unchanged
// "com.unity.shadergraph, MyPage" — package + page, builds package docs URL
// "MyPage" — Unity Manual page name, builds manual URL
//
// Value is always the final URL string, ready for use as-is.
[StrongHint]
class HelpURL : IStrongHint<IShaderFunction>
{
public string Key => Func.kDocumentationLink;
public object Value { get; private set; }
public string Message { get; private set; }
public bool IsResolved { get; private set; }
internal HelpURL() { }
internal HelpURL(string resolvedUrl)
{
Value = resolvedUrl;
IsResolved = true;
}
public void Initialize(bool found, string rawValue, IShaderFunction func, IProvider provider, string actualHintKey, IReadOnlyDictionary<string, string> rawHints)
{
// TODO(SVFXG-919): Handle manual pages and, for unity owned definitions, fallback based on Group/Provider key.
if (!found) return;
if (string.IsNullOrWhiteSpace(rawValue)) { Message = "Expected a URL, a page name, or 'packageName, pageName'."; return; }
// Raw URL — contains a scheme separator.
if (rawValue.Contains("://"))
{
Value = rawValue.Trim();
IsResolved = true;
return;
}
var tokens = HintUtils.LazyTokenString(rawValue);
if (tokens.Length > 0)
{
string page = tokens[0];
UnityEditor.PackageManager.PackageInfo packageInfo = null;
if (tokens.Length == 2) // package name is explicitly provided.
{
var packageName = tokens[1];
packageInfo = UnityEditor.PackageManager.PackageInfo.FindForPackageName(packageName);
packageInfo ??= UnityEditor.PackageManager.PackageInfo.FindForPackageName($"com.unity.{packageName}");
if (packageInfo == null)
{
Message = $"Package named '{packageName}' could not be found.";
return;
}
}
else if (provider.AssetID == default) // provider is scripted, so assume the package based on the assembly.
{
packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssembly(provider.GetType().Assembly);
if (packageInfo == null)
{
Message = $"Could not find package for type '{provider.GetType()}'.";
return;
}
}
else // has an assetID, so assume the package of the asset.
{
var path = AssetDatabase.GUIDToAssetPath(provider.AssetID);
if (path == null)
{
Message = $"Failed to resolve valid path from asset '{provider.AssetID}'.";
return;
}
packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssetPath(path);
if (packageInfo == null)
{
Message = $"Could not find package for asset at {path}.";
return;
}
}
var name = packageInfo.name;
var version = packageInfo.version.Substring(0, packageInfo.version.LastIndexOf('.')); // pkg versions are x.y.z; doc pages are x.y.
Value = $"{Func.kPackageDocsBaseUrl}{name}@{version}/manual/{page}.html";
IsResolved = true;
return;
}
Message = $"Expected a URL, a page name, or 'packageName, pageName', but found {tokens.Length} comma-separated tokens.";
}
}
// Version string for a function provider. The raw string is stored in Value; if it parses
// as a System.Version (e.g. "1.2.3"), ParsedVersion is also populated for ordered comparison.
[StrongHint]
class Version : IStrongHint<IShaderFunction>
{
// Reserved version string for providers that don't declare an sg:Version hint.
// IProvider.Version defaults to this value, and Initialize rejects it so a user
// can't declare it explicitly.
internal const string kUnversioned = "<unversioned>";
public string Key => Func.kVersion;
public object Value { get; private set; }
public string Message { get; private set; }
public bool IsResolved { get; private set; }
// Non-null when Value parses as a dotted numeric version (e.g. "1.2.3").
internal System.Version ParsedVersion { get; private set; }
internal Version() { }
internal Version(string resolvedValue)
{
Value = resolvedValue;
System.Version.TryParse(resolvedValue, out var parsed);
ParsedVersion = parsed;
IsResolved = true;
}
public void Initialize(bool found, string rawValue, IShaderFunction func, IProvider provider, string actualHintKey, IReadOnlyDictionary<string, string> rawHints)
{
if (!found) return;
if (string.IsNullOrWhiteSpace(rawValue)) { Message = "Expected a version string."; return; }
if (rawValue == kUnversioned) { Message = $"'{kUnversioned}' is reserved; pick a different version string."; return; }
Value = rawValue;
System.Version.TryParse(rawValue, out var parsed);
ParsedVersion = parsed;
IsResolved = true;
}
// Compares two Version hints. Null is treated as less than any non-null version.
// Uses ParsedVersion for dotted numeric strings; falls back to ordinal string compare.
internal static int Compare(Version a, Version b)
{
if (a?.ParsedVersion != null && b?.ParsedVersion != null)
return a.ParsedVersion.CompareTo(b.ParsedVersion);
return string.Compare(a?.Value as string, b?.Value as string, System.StringComparison.Ordinal);
}
}
// Marks a function as deprecated — still functional, but users should migrate away.
// Value is a human-readable message explaining why and what to use instead.
[StrongHint]
class Deprecated : IStrongHint<IShaderFunction>
{
public string Key => Func.kDeprecated;
public IReadOnlyCollection<string> Conflicts { get; } = new[] { Func.kLifecycleConflictClass };
public object Value { get; private set; }
public string Message { get; private set; }
public MessageType MessageSeverity => MessageType.Warning;
public bool IsResolved { get; private set; }
internal Deprecated() { }
internal Deprecated(string message)
{
Value = message;
IsResolved = true;
}
public void Initialize(bool found, string rawValue, IShaderFunction func, IProvider provider, string actualHintKey, IReadOnlyDictionary<string, string> rawHints)
{
if (!found) return;
Value = rawValue; // may be null/empty — that's valid (bare flag)
Message = string.IsNullOrWhiteSpace(rawValue)
? $"'{func.Name}' is deprecated."
: $"'{func.Name}' is deprecated: {rawValue}";
IsResolved = true;
}
}
// Marks a function as obsolete — scheduled for removal. Stronger than Deprecated.
// Value is a human-readable message explaining when it will be removed and what replaces it.
[StrongHint]
class Obsolete : IStrongHint<IShaderFunction>
{
public string Key => Func.kObsolete;
public IReadOnlyCollection<string> Conflicts { get; } = new[] { Func.kLifecycleConflictClass };
public object Value { get; private set; }
public string Message { get; private set; }
public MessageType MessageSeverity => MessageType.Error;
public bool IsResolved { get; private set; }
internal Obsolete() { }
internal Obsolete(string message)
{
Value = message;
IsResolved = true;
}
public void Initialize(bool found, string rawValue, IShaderFunction func, IProvider provider, string actualHintKey, IReadOnlyDictionary<string, string> rawHints)
{
if (!found) return;
Value = rawValue;
Message = string.IsNullOrWhiteSpace(rawValue)
? $"'{func.Name}' is obsolete and will be removed."
: $"'{func.Name}' is obsolete and will be removed: {rawValue}";
IsResolved = true;
}
}
// Groups multiple providers under a shared identity key so callers can discover
// all providers belonging to a conceptual set (e.g. several implementations of
// the same operation). An optional integer priority lets the calling code
// determine a preferred default without the library imposing an ordering.
//
// Raw value forms:
// "MyGroup" — group key only, priority defaults to 0
// "MyGroup, 5" — group key with explicit priority
[StrongHint]
class GroupKey : IStrongHint<IShaderFunction>
{
public string Key => Func.kGroupKey;
public object Value { get; private set; } // group key string
internal int Priority { get; private set; } // 0 if not specified
internal string DisplayName { get; private set; } // null if not declared
internal string Label { get; private set; } // null if not declared
public string Message { get; private set; }
public bool IsResolved { get; private set; }
internal GroupKey() { }
internal GroupKey(string groupKey, int priority = 0, string displayName = null, string label = null)
{
Value = groupKey;
Priority = priority;
DisplayName = displayName;
Label = label;
IsResolved = true;
}
public void Initialize(bool found, string rawValue, IShaderFunction func, IProvider provider, string actualHintKey, IReadOnlyDictionary<string, string> rawHints)
{
if (!found) return;
if (string.IsNullOrWhiteSpace(rawValue)) { Message = "Expected a group key string."; return; }
var tokens = HintUtils.LazyTokenString(rawValue);
if (tokens.Length == 1)
{
Value = tokens[0];
DisplayName = tokens[0];
IsResolved = true;
}
else if (tokens.Length >= 2)
{
if (!int.TryParse(tokens[1], out var priority))
{
Message = $"Expected an integer priority as the second token, but found '{tokens[1]}'.";
return;
}
Value = tokens[0];
DisplayName = tokens[0];
Priority = priority;
if (tokens.Length >= 3) DisplayName = tokens[2];
if (tokens.Length >= 4) Label = tokens[3];
if (tokens.Length > 4)
{
Message = $"Expected 1-4 comma-separated tokens (key[, priority[, displayName[, label]]]), but found {tokens.Length}.";
return;
}
IsResolved = true;
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f8161e4e684587e468cd9de3de9568d3
@@ -0,0 +1,490 @@
using System.Collections.Generic;
using UnityEngine;
namespace Unity.GraphAuthoring.Editor.ProviderSystem.Hints
{
internal static class Param
{
internal const string kAccessModifier = "AccessModifier";
internal const string kCustomEditor = "CustomEditor";
internal const string kStatic = "sg:Static";
internal const string kLocal = "sg:Local";
internal const string kLiteral = "sg:Literal";
internal const string kColor = "sg:Color";
internal const string kRange = "sg:Range";
internal const string kDropdown = "sg:Dropdown";
internal const string kDefault = "sg:Default";
internal const string kExternal = "sg:External";
internal const string kSetting = "sg:Setting";
internal const string kLinkage = "sg:Linkage";
internal const string kDynamic = "sg:DynamicVector";
internal const string kReferable = "sg:Referable";
internal const string kCustomBinding = "sg:CustomBinding";
internal static class Ref
{
internal const string kUV = "UV";
internal const string kPosition = "Position";
internal const string kNormal = "Normal";
internal const string kBitangent = "Bitangent";
internal const string kTangent = "Tangent";
internal const string kViewDirection = "ViewDirection";
internal const string kScreenPosition = "ScreenPosition";
internal const string kVertexColor = "VertexColor";
}
}
[StrongHint] sealed class Local : Flag
{
internal Local() : base(Param.kLocal, new string[] { Param.kAccessModifier }) { }
}
[StrongHint]
internal class Range : IStrongHint<IShaderField>
{
public string Key => Param.kRange;
public IReadOnlyCollection<string> Conflicts { get; } = new string[] { Param.kCustomEditor };
public IReadOnlyCollection<string> Synonyms { get; } = new string[] { "Slider" };
public object Value { get; private set; }
public string Message { get; private set; }
public bool IsResolved { get; private set; }
// Default constructor — used by TypeCache auto-discovery.
internal Range() { }
// Direct-value constructor.
internal Range(float[] resolvedValue)
{
Value = resolvedValue;
IsResolved = true;
}
public void Initialize(bool found, string rawValue, IShaderField field, IProvider provider, string actualHintKey, IReadOnlyDictionary<string, string> rawHints)
{
if (!found) return;
if (!field.IsInput) { Message = "Expected input parameter."; return; }
if (field.ShaderType.Name != "half" && field.ShaderType.Name != "float")
{
Message = $"Expected floating point scalar, but found '{field.ShaderType.Name}'.";
return;
}
float min = 0;
float max = 1;
if (string.IsNullOrWhiteSpace(rawValue)) { Value = new float[] { min, max }; IsResolved = true; return; }
float[] values = HintUtils.LazyTokenFloat(rawValue);
if (values.Length > 2 || !string.IsNullOrEmpty(rawValue) && values.Length == 0)
{
Message = $"Expected 0, 1, or 2 floating point values, but found '{values.Length}'.";
return;
}
if (values.Length == 1)
{
if (values[0] < 0) { min = values[0]; max = 0; }
else max = values[0];
}
else if (values.Length >= 2)
{
min = Mathf.Min(values);
max = Mathf.Max(values);
}
Value = new float[2] { min, max };
IsResolved = true;
if (min == max)
Message = $"Expected min and max to be different values, but both are '{min}'.";
}
}
[StrongHint]
internal class Dropdown : IStrongHint<IShaderField>
{
public string Key => Param.kDropdown;
public IReadOnlyCollection<string> Conflicts { get; } = new string[] { Param.kCustomEditor };
public IReadOnlyCollection<string> Synonyms { get; } = new string[] { "Enum" };
public object Value { get; private set; }
public string Message { get; private set; }
public bool IsResolved { get; private set; }
// Default constructor — used by TypeCache auto-discovery.
internal Dropdown() { }
// Direct-value constructor.
internal Dropdown(string[] options)
{
Value = options;
IsResolved = true;
}
public void Initialize(bool found, string rawValue, IShaderField field, IProvider provider, string actualHintKey, IReadOnlyDictionary<string, string> rawHints)
{
if (!found) return;
if (!field.IsInput) { Message = "Expected input parameter."; return; }
switch (field.ShaderType.Name)
{
case "int": case "uint": case "float": case "half": break;
default: Message = $"Expected numeric scalar, but found '{field.ShaderType.Name}'."; return;
}
string[] options = HintUtils.LazyTokenString(rawValue);
if (options.Length == 0) { Message = "Expected at least 1 comma separated option, but found none."; return; }
Value = options;
IsResolved = true;
}
}
[StrongHint]
internal class Color : IStrongHint<IShaderField>
{
public string Key => Param.kColor;
public IReadOnlyCollection<string> Conflicts { get; } = new string[] { Param.kCustomEditor };
public object Value { get; private set; }
public string Message { get; private set; }
public bool IsResolved { get; private set; }
// Default constructor — used by TypeCache auto-discovery.
internal Color() { }
// Direct-value constructor (flag present).
internal Color(bool resolved)
{
IsResolved = resolved;
}
public void Initialize(bool found, string rawValue, IShaderField field, IProvider provider, string actualHintKey, IReadOnlyDictionary<string, string> rawHints)
{
if (!found) return;
if (!field.IsInput) { Message = "Expected input parameter."; return; }
switch (field.ShaderType.Name)
{
case "float3": case "float4": case "half3": case "half4":
Value = rawValue; IsResolved = true; break;
default:
Message = $"Expected floating point vector of length 3 or 4, but found '{field.ShaderType.Name}'."; break;
}
}
}
[StrongHint]
internal class Literal : IStrongHint<IShaderField>
{
public string Key => Param.kLiteral;
public object Value { get; private set; }
public string Message { get; private set; }
public bool IsResolved { get; private set; }
// Default constructor — used by TypeCache auto-discovery.
internal Literal() { }
// Direct-value constructor.
internal Literal(bool resolved)
{
IsResolved = resolved;
}
public void Initialize(bool found, string rawValue, IShaderField field, IProvider provider, string actualHintKey, IReadOnlyDictionary<string, string> rawHints)
{
if (!found) return;
switch (field.ShaderType.Name)
{
case "float": case "half": case "int": case "uint":
Value = rawValue; IsResolved = true; break;
default:
Message = $"Expected numeric scalar, but found '{field.ShaderType.Name}'."; break;
}
}
}
[StrongHint]
internal class Static : IStrongHint<IShaderField>
{
public string Key => Param.kStatic;
public IReadOnlyCollection<string> Conflicts { get; } = new string[] { Param.kAccessModifier };
public object Value { get; private set; }
public string Message { get; private set; }
public bool IsResolved { get; private set; }
// Default constructor — used by TypeCache auto-discovery.
internal Static() { }
// Direct-value constructor.
internal Static(bool resolved)
{
IsResolved = resolved;
}
public void Initialize(bool found, string rawValue, IShaderField field, IProvider provider, string actualHintKey, IReadOnlyDictionary<string, string> rawHints)
{
if (!found) return;
if (!field.IsInput) { Message = "Expected input parameter."; return; }
switch (field.ShaderType.Name)
{
case "float": case "half": case "int": case "uint": case "bool":
Value = rawValue; IsResolved = true; break;
case "float3": case "float4": case "half3": case "half4":
if (rawHints?.ContainsKey(Param.kColor) == true) { Value = rawValue; IsResolved = true; break; }
Message = $"Requires '{Param.kColor}' to support '{field.ShaderType.Name}'."; break;
default:
Message = $"Expected '{Param.kColor}' or scalar, but found '{field.ShaderType.Name}'."; break;
}
}
}
[StrongHint]
internal class External : IStrongHint<IShaderField>
{
public string Key => Param.kExternal;
public IReadOnlyCollection<string> Synonyms { get; } = new string[] { "ExternalNamespace" };
public object Value { get; private set; }
public string Message { get; private set; }
public bool IsResolved { get; private set; }
// Default constructor — used by TypeCache auto-discovery.
internal External() { }
// Direct-value constructor.
internal External(string resolvedValue)
{
Value = resolvedValue;
IsResolved = true;
}
public void Initialize(bool found, string rawValue, IShaderField field, IProvider provider, string actualHintKey, IReadOnlyDictionary<string, string> rawHints)
{
Value = rawValue;
IsResolved = true;
}
}
[StrongHint]
internal class Default : IStrongHint<IShaderField>
{
public string Key => Param.kDefault;
public object Value { get; private set; }
public string Message { get; private set; }
public bool IsResolved { get; private set; }
// Default constructor — used by TypeCache auto-discovery.
internal Default() { }
// Direct-value constructor.
internal Default(string resolvedValue)
{
Value = resolvedValue;
IsResolved = true;
}
public void Initialize(bool found, string rawValue, IShaderField field, IProvider provider, string actualHintKey, IReadOnlyDictionary<string, string> rawHints)
{
// TODO(SVFXG-868): Multiple formats to support; pass along raw value for now.
if (!found) return;
if (!field.IsInput) { Message = "Expected input parameter."; return; }
Value = rawValue;
IsResolved = true;
}
}
[StrongHint]
internal class Dynamic : IStrongHint<IShaderField>
{
public string Key => Param.kDynamic;
public IReadOnlyCollection<string> Conflicts { get; } = new string[] { Param.kCustomEditor };
public IReadOnlyCollection<string> Synonyms { get; } = new string[] { "Dynamic" };
public object Value { get; private set; }
public string Message { get; private set; }
public bool IsResolved { get; private set; }
// Default constructor — used by TypeCache auto-discovery.
internal Dynamic() { }
// Direct-value constructor.
internal Dynamic(bool resolved)
{
IsResolved = resolved;
}
public void Initialize(bool found, string rawValue, IShaderField field, IProvider provider, string actualHintKey, IReadOnlyDictionary<string, string> rawHints)
{
if (!found) return;
switch (field.ShaderType.Name)
{
case "float": case "float2": case "float3": case "float4":
case "half": case "half2": case "half3": case "half4":
Value = rawValue; IsResolved = true; break;
default:
Message = $"Expected floating point vector or scalar, but found {field.ShaderType.Name}."; break;
}
}
}
[StrongHint]
internal class Linkage : IStrongHint<IShaderField>
{
public string Key => Param.kLinkage;
public IReadOnlyCollection<string> Conflicts { get; } = new string[] { Param.kAccessModifier };
public object Value { get; private set; }
public string Message { get; private set; }
public bool IsResolved { get; private set; }
// Default constructor — used by TypeCache auto-discovery.
internal Linkage() { }
// Direct-value constructor.
internal Linkage(string resolvedValue)
{
Value = resolvedValue;
IsResolved = true;
}
public void Initialize(bool found, string rawValue, IShaderField field, IProvider provider, string actualHintKey, IReadOnlyDictionary<string, string> rawHints)
{
if (!found) return;
if (!field.IsInput) { Message = "Expected input parameter."; return; }
if (field.ShaderType.Name != "bool") { Message = $"Expected type bool, but found '{field.ShaderType.Name}'."; return; }
if (rawValue == field.Name) { Message = "Cannot check linkage state against itself."; return; }
// Validate the target parameter exists on the enclosing function.
// The provider here is a FieldHintContext whose Definition is the already-built
// unresolved function, so this is safe and non-recursive.
var funcProvider = provider as IProvider<IShaderFunction>;
if (funcProvider?.Definition != null)
{
bool hasMatch = false;
foreach (var param in funcProvider.Definition.Parameters)
if (param.Name == rawValue) { hasMatch = true; break; }
if (!hasMatch) { Message = $"Could not find expected parameter '{rawValue}'."; return; }
}
Value = rawValue;
IsResolved = true;
}
}
[StrongHint]
internal class Referable : IStrongHint<IShaderField>
{
public string Key => Param.kReferable;
static HashSet<string> s_commonReferables;
private static HashSet<string> GetCommonReferables()
{
if (s_commonReferables == null)
{
s_commonReferables = new HashSet<string>() {
Param.Ref.kUV,
Param.Ref.kPosition,
Param.Ref.kTangent,
Param.Ref.kNormal,
Param.Ref.kBitangent,
Param.Ref.kViewDirection,
Param.Ref.kVertexColor,
Param.Ref.kScreenPosition,
};
}
return s_commonReferables;
}
public IReadOnlyCollection<string> Synonyms => GetCommonReferables();
public IReadOnlyCollection<string> Conflicts { get; } = new string[] { Param.kCustomEditor };
public object Value { get; private set; }
public string Message { get; private set; }
public bool IsResolved { get; private set; }
// Default constructor — used by TypeCache auto-discovery.
internal Referable() { }
// Direct-value constructor.
internal Referable(string resolvedValue)
{
Value = resolvedValue;
IsResolved = true;
}
public void Initialize(bool found, string rawValue, IShaderField field, IProvider provider, string actualHintKey, IReadOnlyDictionary<string, string> rawHints)
{
object value = Key == actualHintKey ? rawValue : actualHintKey;
if (!field.IsInput) { Message = "Expected input parameter."; return; }
if (value == null) { Message = "Could not resolve a referable type."; return; }
if (!GetCommonReferables().Contains((string)value)) { Message = $"'{value}' is not a supported referable key."; return; }
switch ((string)value)
{
case Param.Ref.kUV:
if (field.ShaderType.Name != "half2" && field.ShaderType.Name != "float2")
{ Message = $"'{value}' expects floating point vector of length 2, but found '{field.ShaderType.Name}'."; return; }
break;
case Param.Ref.kVertexColor:
case Param.Ref.kScreenPosition:
if (field.ShaderType.Name != "half4" && field.ShaderType.Name != "float4")
{ Message = $"'{value}' expects floating point vector of length 4, but found '{field.ShaderType.Name}'."; return; }
break;
default:
if (field.ShaderType.Name != "half3" && field.ShaderType.Name != "float3")
{ Message = $"'{value}' expects floating point vector of length 3, but found '{field.ShaderType.Name}'."; return; }
break;
}
Value = value;
IsResolved = true;
}
}
[StrongHint]
internal class CustomBinding : IStrongHint<IShaderField>
{
public string Key => Param.kCustomBinding;
public IReadOnlyCollection<string> Conflicts { get; } = new string[] { Param.kCustomEditor };
public object Value { get; private set; }
public string Message { get; private set; }
public bool IsResolved { get; private set; }
// Default constructor — used by TypeCache auto-discovery.
internal CustomBinding() { }
// Direct-value constructor.
internal CustomBinding(string resolvedValue)
{
Value = resolvedValue;
IsResolved = true;
}
public void Initialize(bool found, string rawValue, IShaderField field, IProvider provider, string actualHintKey, IReadOnlyDictionary<string, string> rawHints)
{
if (!found) return;
if (!field.IsInput) { Message = "Expected input parameter."; return; }
if (string.IsNullOrEmpty(rawValue)) { Message = "Invalid value."; return; }
Value = rawValue;
IsResolved = true;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 45a7e22cad240154d8b9f9de72768fa8
@@ -0,0 +1,20 @@
using System.Collections.Generic;
namespace Unity.GraphAuthoring.Editor.ProviderSystem
{
internal interface IShaderType : IDefinition { }
internal interface IShaderField : IDefinition
{
bool IsInput { get; }
bool IsOutput { get; }
IShaderType ShaderType { get; }
}
internal interface IShaderFunction : IDefinition
{
IShaderType ReturnType { get; }
string FunctionBody { get; }
IEnumerable<IShaderField> Parameters { get; }
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: efe6c69ae14849341a7ea16ccb2f88ae
@@ -0,0 +1,176 @@
using System.Collections.Generic;
using System.Text;
namespace Unity.GraphAuthoring.Editor.ProviderSystem
{
internal static class ShaderObjectUtils
{
internal static string QualifyName(IDefinition obj, string separator = "::", bool includeName = true)
{
if (obj == null || !obj.IsValid)
return null;
StringBuilder builder = new();
if (obj.Namespace != null)
foreach (var name in obj.Namespace)
builder.Append($"{name}{separator}");
builder.Append(obj.Name);
return builder.ToString();
}
internal static string QualifySignature(IShaderFunction obj, bool includeNamespace = true, bool forDisplay = false)
{
if (obj == null || !obj.IsValid)
return null;
StringBuilder builder = new();
builder.Append(includeNamespace ? QualifyName(obj) : obj.Name);
builder.Append(forDisplay ? " (" : "(");
bool first = true;
if (obj.Parameters != null)
{
foreach (var param in obj.Parameters)
{
if (!first) builder.Append(forDisplay ? ", " : ",");
first = false;
builder.Append(QualifyName(param.ShaderType));
}
}
builder.Append(")");
return builder.ToString();
}
// Searches the resolved IStrongHint collection for the provider key hint.
internal static string EvaluateProviderKey(IShaderFunction obj, string fromHint = Hints.Func.kProviderKey)
{
if (obj?.IsValid != true)
return null;
if (obj.Hints != null)
foreach (var h in obj.Hints)
if (h.Key == fromHint && h.IsResolved)
return h.Value as string ?? QualifySignature(obj);
return QualifySignature(obj);
}
// Fast provider-key lookup from a raw string dict (used at construction time before hints are resolved).
internal static string EvaluateProviderKey(IReadOnlyDictionary<string, string> rawHints, IShaderFunction contextObj, string fromHint = Hints.Func.kProviderKey)
{
if (rawHints != null && rawHints.TryGetValue(fromHint, out var result) && !string.IsNullOrWhiteSpace(result))
return result;
return QualifySignature(contextObj);
}
private static string DeclareField(IShaderField a)
{
(string typeName, string arraySpec) SplitTypeName(string typeName)
{
int i = typeName.IndexOf('[');
return i == -1 ? (typeName, "") : (typeName.Substring(0, i - 1), typeName.Substring(i));
}
var t = SplitTypeName(a.ShaderType.Name);
string access = a.IsInput && a.IsOutput ? "inout " : a.IsOutput ? "out " : "";
return $"{access}{t.typeName} {a.Name}{t.arraySpec}";
}
private static string HintValueToString(object value)
{
return value switch
{
string s => s,
string[] arr => string.Join(", ", arr),
float[] arr => string.Join(", ", arr),
_ => value?.ToString() ?? ""
};
}
private static string GenerateHints(string closure, IReadOnlyCollection<IStrongHint> hints, string name = null)
{
StringBuilder sb = new();
bool any = false;
foreach (var hint in hints)
{
if (!hint.IsResolved) continue;
if (!any)
{
sb.AppendLine(name != null ? $"/// <{closure} name = \"{name}\">" : $"/// <{closure}>");
any = true;
}
sb.AppendLine($"///\t <{hint.Key}>{HintValueToString(hint.Value)}</{hint.Key}>");
}
if (any) sb.AppendLine($"/// </{closure}>");
return sb.ToString();
}
internal static string GenerateCall(IShaderFunction func, string argList)
{
StringBuilder call = new();
string funcName = func.Name;
foreach (var name in func.Namespace)
call.Append($"{name}::");
call.Append(funcName);
call.Append("(");
call.Append(argList);
call.Append(");");
return call.ToString();
}
internal static string GenerateCode(IShaderFunction func, bool generateHints = true, bool export = true, bool generateNamespace = true)
{
StringBuilder sb = new();
int indent = 0;
void Ln(string text = "") { for (int i = 0; i < indent; i++) sb.Append(" "); sb.AppendLine(text); }
if (generateNamespace)
foreach (var name in func.Namespace)
sb.Append($"namespace {name} {{");
sb.AppendLine();
indent++;
if (generateHints)
{
if (func.Hints.Count > 0)
sb.Append(GenerateHints("funchints", func.Hints));
foreach (var param in func.Parameters)
if (param.Hints.Count > 0)
sb.Append(GenerateHints("paramhints", param.Hints, param.Name));
}
string funcName = func.Name;
string typeName = func.ReturnType.Name;
sb.Append($"{(export ? "UNITY_EXPORT_REFLECTION" : "")} {typeName} {funcName}(");
bool first = true;
foreach (var param in func.Parameters)
{
if (!first) sb.Append(", ");
first = false;
sb.Append(DeclareField(param));
}
Ln(")");
Ln("{");
indent++;
Ln(func.FunctionBody);
indent--;
Ln("}");
indent--;
if (generateNamespace)
foreach (var name in func.Namespace)
sb.Append($"}} /*{name}*/");
return sb.ToString();
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3cd2d0e59bfe3c848951929a7a8a9d91