using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using JetBrains.Annotations; using UnityEngine; using UnityEditor.Graphing; using UnityEditor.ShaderGraph.Internal; namespace UnityEditor.ShaderGraph { abstract class CodeFunctionNode : AbstractMaterialNode , IGeneratesBodyCode , IGeneratesFunction , IMayRequireNormal , IMayRequireTangent , IMayRequireBitangent , IMayRequireMeshUV , IMayRequireScreenPosition , IMayRequireViewDirection , IMayRequirePosition , IMayRequirePositionPredisplacement , IMayRequireVertexColor { [NonSerialized] private List m_Slots = new List(); public override bool hasPreview { get { return true; } } protected CodeFunctionNode() { UpdateNodeAfterDeserialization(); } protected struct Boolean { } protected struct Vector1 { } protected struct Texture2D { } protected struct Texture2DArray { } protected struct Texture3D { } protected struct SamplerState { } protected struct Gradient { } protected struct DynamicDimensionVector { } protected struct ColorRGBA { } protected struct ColorRGB { } protected struct Matrix3x3 { } protected struct Matrix2x2 { } protected struct DynamicDimensionMatrix { } protected struct PropertyConnectionState { } protected enum Binding { None, ObjectSpaceNormal, ObjectSpaceTangent, ObjectSpaceBitangent, ObjectSpacePosition, ViewSpaceNormal, ViewSpaceTangent, ViewSpaceBitangent, ViewSpacePosition, WorldSpaceNormal, WorldSpaceTangent, WorldSpaceBitangent, WorldSpacePosition, TangentSpaceNormal, TangentSpaceTangent, TangentSpaceBitangent, TangentSpacePosition, MeshUV0, MeshUV1, MeshUV2, MeshUV3, ScreenPosition, ObjectSpaceViewDirection, ViewSpaceViewDirection, WorldSpaceViewDirection, TangentSpaceViewDirection, VertexColor, } [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)] protected class SlotAttribute : Attribute { public int slotId { get; private set; } public Binding binding { get; private set; } public bool hidden { get; private set; } public Vector4? defaultValue { get; private set; } public ShaderStageCapability stageCapability { get; private set; } public SlotAttribute(int mSlotId, Binding mImplicitBinding, ShaderStageCapability mStageCapability = ShaderStageCapability.All) { slotId = mSlotId; binding = mImplicitBinding; defaultValue = null; stageCapability = mStageCapability; } public SlotAttribute(int mSlotId, Binding mImplicitBinding, bool mHidden, ShaderStageCapability mStageCapability = ShaderStageCapability.All) { slotId = mSlotId; binding = mImplicitBinding; hidden = mHidden; defaultValue = null; stageCapability = mStageCapability; } public SlotAttribute(int mSlotId, Binding mImplicitBinding, float defaultX, float defaultY, float defaultZ, float defaultW, ShaderStageCapability mStageCapability = ShaderStageCapability.All) { slotId = mSlotId; binding = mImplicitBinding; defaultValue = new Vector4(defaultX, defaultY, defaultZ, defaultW); stageCapability = mStageCapability; } } protected abstract MethodInfo GetFunctionToConvert(); private static SlotValueType ConvertTypeToSlotValueType(ParameterInfo p) { Type t = p.ParameterType; if (p.ParameterType.IsByRef) t = p.ParameterType.GetElementType(); if (t == typeof(Boolean)) { return SlotValueType.Boolean; } if (t == typeof(Vector1)) { return SlotValueType.Vector1; } if (t == typeof(Vector2)) { return SlotValueType.Vector2; } if (t == typeof(Vector3)) { return SlotValueType.Vector3; } if (t == typeof(Vector4)) { return SlotValueType.Vector4; } if (t == typeof(Color)) { return SlotValueType.Vector4; } if (t == typeof(ColorRGBA)) { return SlotValueType.Vector4; } if (t == typeof(ColorRGB)) { return SlotValueType.Vector3; } if (t == typeof(Texture2D)) { return SlotValueType.Texture2D; } if (t == typeof(Texture2DArray)) { return SlotValueType.Texture2DArray; } if (t == typeof(Texture3D)) { return SlotValueType.Texture3D; } if (t == typeof(Cubemap)) { return SlotValueType.Cubemap; } if (t == typeof(Gradient)) { return SlotValueType.Gradient; } if (t == typeof(SamplerState)) { return SlotValueType.SamplerState; } if (t == typeof(DynamicDimensionVector)) { return SlotValueType.DynamicVector; } if (t == typeof(Matrix4x4)) { return SlotValueType.Matrix4; } if (t == typeof(Matrix3x3)) { return SlotValueType.Matrix3; } if (t == typeof(Matrix2x2)) { return SlotValueType.Matrix2; } if (t == typeof(DynamicDimensionMatrix)) { return SlotValueType.DynamicMatrix; } if (t == typeof(PropertyConnectionState)) { return SlotValueType.PropertyConnectionState; } throw new ArgumentException("Unsupported type " + t); } public sealed override void UpdateNodeAfterDeserialization() { var method = GetFunctionToConvert(); if (method == null) throw new ArgumentException("Mapped method is null on node" + this); if (method.ReturnType != typeof(string)) throw new ArgumentException("Mapped function should return string"); // validate no duplicates var slotAtributes = method.GetParameters().Select(GetSlotAttribute).ToList(); if (slotAtributes.Any(x => x == null)) throw new ArgumentException("Missing SlotAttribute on " + method.Name); if (slotAtributes.GroupBy(x => x.slotId).Any(x => x.Count() > 1)) throw new ArgumentException("Duplicate SlotAttribute on " + method.Name); List slots = new List(); foreach (var par in method.GetParameters()) { var attribute = GetSlotAttribute(par); var name = GraphUtil.ConvertCamelCase(par.Name, true); MaterialSlot s; if (attribute.binding == Binding.None && !par.IsOut && par.ParameterType == typeof(Color)) s = new ColorRGBAMaterialSlot(attribute.slotId, name, par.Name, SlotType.Input, attribute.defaultValue ?? Vector4.zero, stageCapability: attribute.stageCapability, hidden: attribute.hidden); else if (attribute.binding == Binding.None && !par.IsOut && par.ParameterType == typeof(ColorRGBA)) s = new ColorRGBAMaterialSlot(attribute.slotId, name, par.Name, SlotType.Input, attribute.defaultValue ?? Vector4.zero, stageCapability: attribute.stageCapability, hidden: attribute.hidden); else if (attribute.binding == Binding.None && !par.IsOut && par.ParameterType == typeof(ColorRGB)) s = new ColorRGBMaterialSlot(attribute.slotId, name, par.Name, SlotType.Input, attribute.defaultValue ?? Vector4.zero, ColorMode.Default, stageCapability: attribute.stageCapability, hidden: attribute.hidden); else if (attribute.binding == Binding.None || par.IsOut) s = MaterialSlot.CreateMaterialSlot( ConvertTypeToSlotValueType(par), attribute.slotId, name, par.Name, par.IsOut ? SlotType.Output : SlotType.Input, attribute.defaultValue ?? Vector4.zero, shaderStageCapability: attribute.stageCapability, hidden: attribute.hidden); else s = CreateBoundSlot(attribute.binding, attribute.slotId, name, par.Name, attribute.stageCapability, attribute.hidden); slots.Add(s); m_Slots.Add(attribute); } foreach (var slot in slots) { AddSlot(slot); } RemoveSlotsNameNotMatching(slots.Select(x => x.id), true); } private static MaterialSlot CreateBoundSlot(Binding attributeBinding, int slotId, string displayName, string shaderOutputName, ShaderStageCapability shaderStageCapability, bool hidden = false) { switch (attributeBinding) { case Binding.ObjectSpaceNormal: return new NormalMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.Object, shaderStageCapability, hidden); case Binding.ObjectSpaceTangent: return new TangentMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.Object, shaderStageCapability, hidden); case Binding.ObjectSpaceBitangent: return new BitangentMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.Object, shaderStageCapability, hidden); case Binding.ObjectSpacePosition: return new PositionMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.Object, shaderStageCapability, hidden); case Binding.ViewSpaceNormal: return new NormalMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.View, shaderStageCapability, hidden); case Binding.ViewSpaceTangent: return new TangentMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.View, shaderStageCapability, hidden); case Binding.ViewSpaceBitangent: return new BitangentMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.View, shaderStageCapability, hidden); case Binding.ViewSpacePosition: return new PositionMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.View, shaderStageCapability, hidden); case Binding.WorldSpaceNormal: return new NormalMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.World, shaderStageCapability, hidden); case Binding.WorldSpaceTangent: return new TangentMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.World, shaderStageCapability, hidden); case Binding.WorldSpaceBitangent: return new BitangentMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.World, shaderStageCapability, hidden); case Binding.WorldSpacePosition: return new PositionMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.World, shaderStageCapability, hidden); case Binding.TangentSpaceNormal: return new NormalMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.Tangent, shaderStageCapability, hidden); case Binding.TangentSpaceTangent: return new TangentMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.Tangent, shaderStageCapability, hidden); case Binding.TangentSpaceBitangent: return new BitangentMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.Tangent, shaderStageCapability, hidden); case Binding.TangentSpacePosition: return new PositionMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.Tangent, shaderStageCapability, hidden); case Binding.MeshUV0: return new UVMaterialSlot(slotId, displayName, shaderOutputName, UVChannel.UV0, shaderStageCapability, hidden); case Binding.MeshUV1: return new UVMaterialSlot(slotId, displayName, shaderOutputName, UVChannel.UV1, shaderStageCapability, hidden); case Binding.MeshUV2: return new UVMaterialSlot(slotId, displayName, shaderOutputName, UVChannel.UV2, shaderStageCapability, hidden); case Binding.MeshUV3: return new UVMaterialSlot(slotId, displayName, shaderOutputName, UVChannel.UV3, shaderStageCapability, hidden); case Binding.ScreenPosition: return new ScreenPositionMaterialSlot(slotId, displayName, shaderOutputName, ScreenSpaceType.Default, shaderStageCapability, hidden); case Binding.ObjectSpaceViewDirection: return new ViewDirectionMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.Object, shaderStageCapability, hidden); case Binding.ViewSpaceViewDirection: return new ViewDirectionMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.View, shaderStageCapability, hidden); case Binding.WorldSpaceViewDirection: return new ViewDirectionMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.World, shaderStageCapability, hidden); case Binding.TangentSpaceViewDirection: return new ViewDirectionMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.Tangent, shaderStageCapability, hidden); case Binding.VertexColor: return new VertexColorMaterialSlot(slotId, displayName, shaderOutputName, shaderStageCapability, hidden); default: throw new ArgumentOutOfRangeException("attributeBinding", attributeBinding, null); } } public void GenerateNodeCode(ShaderStringBuilder sb, GenerationMode generationMode) { using (var tempSlots = PooledList.Get()) { GetOutputSlots(tempSlots); foreach (var outSlot in tempSlots) { sb.AppendLine(outSlot.concreteValueType.ToShaderString(PrecisionUtil.Token) + " " + GetVariableNameForSlot(outSlot.id) + ";"); } string call = GetFunctionName() + "("; bool first = true; tempSlots.Clear(); GetSlots(tempSlots); tempSlots.Sort((slot1, slot2) => slot1.id.CompareTo(slot2.id)); foreach (var slot in tempSlots) { if (!first) { call += ", "; } first = false; if (slot.isInputSlot) call += GetSlotValue(slot.id, generationMode); else call += GetVariableNameForSlot(slot.id); } call += ");"; sb.AppendLine(call); } } private string GetFunctionName() { var function = GetFunctionToConvert(); return function.Name + (function.IsStatic ? string.Empty : "_" + objectId) + "_$precision" + (this.GetSlots().Select(s => NodeUtils.GetSlotDimension(s.concreteValueType)).FirstOrDefault() ?? "") + (this.GetSlots().Select(s => NodeUtils.GetSlotDimension(s.concreteValueType)).FirstOrDefault() ?? ""); } private string GetFunctionHeader() { string header = "void " + GetFunctionName() + "("; using (var tempSlots = PooledList.Get()) { GetSlots(tempSlots); tempSlots.Sort((slot1, slot2) => slot1.id.CompareTo(slot2.id)); var first = true; foreach (var slot in tempSlots) { if (!first) header += ", "; first = false; if (slot.isOutputSlot) header += "out "; // always use generic precisions for parameters, they will get concretized by the system header += slot.concreteValueType.ToShaderString(PrecisionUtil.Token) + " " + slot.shaderOutputName; } header += ")"; } return header; } private static object GetDefault(Type type) { return type.IsValueType ? Activator.CreateInstance(type) : null; } private string GetFunctionBody(MethodInfo info) { var args = new List(); foreach (var param in info.GetParameters()) args.Add(GetDefault(param.ParameterType)); var result = info.Invoke(this, args.ToArray()) as string; if (string.IsNullOrEmpty(result)) return string.Empty; using (var tempSlots = PooledList.Get()) { GetSlots(tempSlots); foreach (var slot in tempSlots) { var toReplace = string.Format("{{slot{0}dimension}}", slot.id); var replacement = NodeUtils.GetSlotDimension(slot.concreteValueType); result = result.Replace(toReplace, replacement); } } return result; } public virtual void GenerateNodeFunction(FunctionRegistry registry, GenerationMode generationMode) { registry.ProvideFunction(GetFunctionName(), s => { s.AppendLine(GetFunctionHeader()); var functionBody = GetFunctionBody(GetFunctionToConvert()); var lines = functionBody.Trim('\r', '\n', '\t', ' '); s.AppendLines(lines); }); } private static SlotAttribute GetSlotAttribute([NotNull] ParameterInfo info) { var attrs = info.GetCustomAttributes(typeof(SlotAttribute), false).OfType().ToList(); return attrs.FirstOrDefault(); } public NeededCoordinateSpace RequiresNormal(ShaderStageCapability stageCapability) { var binding = NeededCoordinateSpace.None; using (var tempSlots = PooledList.Get()) { GetInputSlots(tempSlots); foreach (var slot in tempSlots) binding |= slot.RequiresNormal(); return binding; } } public NeededCoordinateSpace RequiresViewDirection(ShaderStageCapability stageCapability) { var binding = NeededCoordinateSpace.None; using (var tempSlots = PooledList.Get()) { GetInputSlots(tempSlots); foreach (var slot in tempSlots) binding |= slot.RequiresViewDirection(); return binding; } } public NeededCoordinateSpace RequiresPosition(ShaderStageCapability stageCapability) { using (var tempSlots = PooledList.Get()) { GetInputSlots(tempSlots); var binding = NeededCoordinateSpace.None; foreach (var slot in tempSlots) binding |= slot.RequiresPosition(); return binding; } } public NeededCoordinateSpace RequiresPositionPredisplacement(ShaderStageCapability stageCapability) { using (var tempSlots = PooledList.Get()) { GetInputSlots(tempSlots); var binding = NeededCoordinateSpace.None; foreach (var slot in tempSlots) binding |= slot.RequiresPositionPredisplacement(); return binding; } } public NeededCoordinateSpace RequiresTangent(ShaderStageCapability stageCapability) { using (var tempSlots = PooledList.Get()) { GetInputSlots(tempSlots); var binding = NeededCoordinateSpace.None; foreach (var slot in tempSlots) binding |= slot.RequiresTangent(); return binding; } } public NeededCoordinateSpace RequiresBitangent(ShaderStageCapability stageCapability) { using (var tempSlots = PooledList.Get()) { GetInputSlots(tempSlots); var binding = NeededCoordinateSpace.None; foreach (var slot in tempSlots) binding |= slot.RequiresBitangent(); return binding; } } public bool RequiresMeshUV(UVChannel channel, ShaderStageCapability stageCapability) { using (var tempSlots = PooledList.Get()) { GetInputSlots(tempSlots); foreach (var slot in tempSlots) { if (slot.RequiresMeshUV(channel)) return true; } return false; } } public bool RequiresScreenPosition(ShaderStageCapability stageCapability) { using (var tempSlots = PooledList.Get()) { GetInputSlots(tempSlots); foreach (var slot in tempSlots) { if (slot.RequiresScreenPosition(stageCapability)) return true; } return false; } } public bool RequiresVertexColor(ShaderStageCapability stageCapability) { using (var tempSlots = PooledList.Get()) { GetInputSlots(tempSlots); foreach (var slot in tempSlots) { if (slot.RequiresVertexColor()) return true; } return false; } } } }