Files
2026-11-01 23:39:55 +02:00

230 lines
13 KiB
C#

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;
}
}
}