using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using JetBrains.Annotations; using UnityEngine; using UnityEditor.Graphing; namespace UnityEditor.ShaderGraph { public abstract class CodeFunctionNode : AbstractMaterialNode , IGeneratesBodyCode , IGeneratesFunction , IMayRequireNormal , IMayRequireTangent , IMayRequireBitangent , IMayRequireMeshUV , IMayRequireScreenPosition , IMayRequireViewDirection , IMayRequirePosition , IMayRequireVertexColor { [NonSerialized] private List m_Slots = new List(); public override bool hasPreview { get { return true; } } protected CodeFunctionNode() { UpdateNodeAfterDeserialization(); } protected struct Vector1 {} protected struct Texture2D {} protected struct SamplerState {} protected struct DynamicDimensionVector {} 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 SlotAttribute(int mSlotId, Binding mImplicitBinding) { slotId = mSlotId; binding = mImplicitBinding; defaultValue = null; } public SlotAttribute(int mSlotId, Binding mImplicitBinding, bool mHidden) { slotId = mSlotId; binding = mImplicitBinding; hidden = mHidden; defaultValue = null; } public SlotAttribute(int mSlotId, Binding mImplicitBinding, float defaultX, float defaultY, float defaultZ, float defaultW) { slotId = mSlotId; binding = mImplicitBinding; defaultValue = new Vector4(defaultX, defaultY, defaultZ, defaultW); } } 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(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(Texture2D)) { return SlotValueType.Texture2D; } if (t == typeof(Cubemap)) { return SlotValueType.Cubemap; } if (t == typeof(SamplerState)) { return SlotValueType.SamplerState; } if (t == typeof(DynamicDimensionVector)) { return SlotValueType.Dynamic; } if (t == typeof(Matrix4x4)) { return SlotValueType.Matrix4; } 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); MaterialSlot s; if (attribute.binding == Binding.None && !par.IsOut && par.ParameterType == typeof(Color)) s = new ColorMaterialSlot(attribute.slotId, par.Name, par.Name, SlotType.Input, attribute.defaultValue ?? Vector4.zero, hidden: attribute.hidden); else if (attribute.binding == Binding.None || par.IsOut) s = MaterialSlot.CreateMaterialSlot( ConvertTypeToSlotValueType(par), attribute.slotId, par.Name, par.Name, par.IsOut ? SlotType.Output : SlotType.Input, attribute.defaultValue ?? Vector4.zero, hidden: attribute.hidden); else s = CreateBoundSlot(attribute.binding, attribute.slotId, par.Name, par.Name, attribute.hidden); slots.Add(s); m_Slots.Add(attribute); } foreach (var slot in slots) { AddSlot(slot); } RemoveSlotsNameNotMatching(slots.Select(x => x.id)); } private static MaterialSlot CreateBoundSlot(Binding attributeBinding, int slotId, string displayName, string shaderOutputName, bool hidden) { switch (attributeBinding) { case Binding.ObjectSpaceNormal: return new NormalMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.Object); case Binding.ObjectSpaceTangent: return new TangentMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.Object); case Binding.ObjectSpaceBitangent: return new BitangentMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.Object); case Binding.ObjectSpacePosition: return new PositionMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.Object); case Binding.ViewSpaceNormal: return new NormalMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.View); case Binding.ViewSpaceTangent: return new TangentMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.View); case Binding.ViewSpaceBitangent: return new BitangentMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.View); case Binding.ViewSpacePosition: return new PositionMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.View); case Binding.WorldSpaceNormal: return new NormalMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.World); case Binding.WorldSpaceTangent: return new TangentMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.World); case Binding.WorldSpaceBitangent: return new BitangentMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.World); case Binding.WorldSpacePosition: return new PositionMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.World); case Binding.TangentSpaceNormal: return new NormalMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.Tangent); case Binding.TangentSpaceTangent: return new TangentMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.Tangent); case Binding.TangentSpaceBitangent: return new BitangentMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.Tangent); case Binding.TangentSpacePosition: return new PositionMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.Tangent); case Binding.MeshUV0: return new UVMaterialSlot(slotId, displayName, shaderOutputName, UVChannel.uv0); case Binding.MeshUV1: return new UVMaterialSlot(slotId, displayName, shaderOutputName, UVChannel.uv1); case Binding.MeshUV2: return new UVMaterialSlot(slotId, displayName, shaderOutputName, UVChannel.uv2); case Binding.MeshUV3: return new UVMaterialSlot(slotId, displayName, shaderOutputName, UVChannel.uv3); case Binding.ScreenPosition: return new ScreenPositionMaterialSlot(slotId, displayName, shaderOutputName); case Binding.ObjectSpaceViewDirection: return new ViewDirectionMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.Object); case Binding.ViewSpaceViewDirection: return new ViewDirectionMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.View); case Binding.WorldSpaceViewDirection: return new ViewDirectionMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.World); case Binding.TangentSpaceViewDirection: return new ViewDirectionMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.Tangent); case Binding.VertexColor: return new VertexColorMaterialSlot(slotId, displayName, shaderOutputName); default: throw new ArgumentOutOfRangeException("attributeBinding", attributeBinding, null); } } public void GenerateNodeCode(ShaderGenerator visitor, GenerationMode generationMode) { foreach (var outSlot in GetOutputSlots()) { visitor.AddShaderChunk(GetParamTypeName(outSlot) + " " + GetVariableNameForSlot(outSlot.id) + ";", true); } string call = GetFunctionName() + "("; bool first = true; foreach (var slot in GetSlots().OrderBy(x => x.id)) { if (!first) { call += ", "; } first = false; if (slot.isInputSlot) call += GetSlotValue(slot.id, generationMode); else call += GetVariableNameForSlot(slot.id); } call += ");"; visitor.AddShaderChunk(call, true); } private string GetParamTypeName(MaterialSlot slot) { return ConvertConcreteSlotValueTypeToString(precision, slot.concreteValueType); } private string GetFunctionName() { var function = GetFunctionToConvert(); return function.Name + "_" + (function.IsStatic ? string.Empty : GuidEncoder.Encode(guid) + "_") + precision; } private string GetFunctionHeader() { string header = "void " + GetFunctionName() + "("; var first = true; foreach (var slot in GetSlots().OrderBy(x => x.id)) { if (!first) header += ", "; first = false; if (slot.isOutputSlot) header += "out "; header += GetParamTypeName(slot) + " " + 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; result = result.Replace("{precision}", precision.ToString()); foreach (var slot in GetSlots()) { var toReplace = string.Format("{{slot{0}dimension}}", slot.id); var replacement = GetSlotDimension(slot.concreteValueType); result = result.Replace(toReplace, replacement); } return result; } public virtual void GenerateNodeFunction(ShaderGenerator visitor, GenerationMode generationMode) { string function = GetFunctionHeader() + GetFunctionBody(GetFunctionToConvert()); visitor.AddShaderChunk(function, true); } private static SlotAttribute GetSlotAttribute([NotNull] ParameterInfo info) { var attrs = info.GetCustomAttributes(typeof(SlotAttribute), false).OfType().ToList(); return attrs.FirstOrDefault(); } public NeededCoordinateSpace RequiresNormal() { var binding = NeededCoordinateSpace.None; foreach (var slot in GetInputSlots().OfType()) binding |= slot.RequiresNormal(); return binding; } public NeededCoordinateSpace RequiresViewDirection() { var binding = NeededCoordinateSpace.None; foreach (var slot in GetInputSlots().OfType()) binding |= slot.RequiresViewDirection(); return binding; } public NeededCoordinateSpace RequiresPosition() { var binding = NeededCoordinateSpace.None; foreach (var slot in GetInputSlots().OfType()) binding |= slot.RequiresPosition(); return binding; } public NeededCoordinateSpace RequiresTangent() { var binding = NeededCoordinateSpace.None; foreach (var slot in GetInputSlots().OfType()) binding |= slot.RequiresTangent(); return binding; } public NeededCoordinateSpace RequiresBitangent() { var binding = NeededCoordinateSpace.None; foreach (var slot in GetInputSlots().OfType()) binding |= slot.RequiresBitangent(); return binding; } public bool RequiresMeshUV(UVChannel channel) { foreach (var slot in GetInputSlots().OfType()) { if (slot.RequiresMeshUV(channel)) return true; } return false; } public bool RequiresScreenPosition() { foreach (var slot in GetInputSlots().OfType()) { if (slot.RequiresScreenPosition()) return true; } return false; } public bool RequiresVertexColor() { foreach (var slot in GetInputSlots().OfType()) { if (slot.RequiresVertexColor()) return true; } return false; } } }