using System; using System.Collections.Generic; using System.Linq; using UnityEditor.VFX; using UnityEngine; using UnityEngine.VFX; using UnityObject = UnityEngine.Object; namespace UnityEditor.VFX { static class VFXReflectionHelper { public static T[] CollectStaticReadOnlyExpression(Type expressionType, System.Reflection.BindingFlags additionnalFlag = System.Reflection.BindingFlags.Public) { var members = expressionType.GetFields(System.Reflection.BindingFlags.Static | additionnalFlag) .Where(m => m.IsInitOnly && m.FieldType == typeof(T)) .ToArray(); var expressions = members.Select(m => (T)m.GetValue(null)).ToArray(); return expressions; } } abstract partial class VFXExpression { public struct Operands { public static readonly int OperandCount = 4; int data0; int data1; int data2; int data3; public Operands(int defaultValue) { data0 = defaultValue; data1 = defaultValue; data2 = defaultValue; data3 = defaultValue; } // This ugly code is for optimization purpose (no garbage created) public int this[int index] { get { switch (index) { case 0: return data0; case 1: return data1; case 2: return data2; case 3: return data3; default: throw new IndexOutOfRangeException(); } } set { switch (index) { case 0: data0 = value; break; case 1: data1 = value; break; case 2: data2 = value; break; case 3: data3 = value; break; default: throw new IndexOutOfRangeException(); } } } public int[] ToArray() { return new int[] { data0, data1, data2, data3 }; } } [Flags] public enum Flags { None = 0, Value = 1 << 0, // Expression is a value, get/set can be called on it Foldable = 1 << 1, // Expression is not a constant but can be folded anyway Constant = 1 << 2, // Expression is a constant, it can be folded InvalidOnGPU = 1 << 3, // Expression can be evaluated on GPU InvalidOnCPU = 1 << 4, // Expression can be evaluated on CPU InvalidConstant = 1 << 5, // Expression can be folded (for UI) but constant folding is forbidden PerElement = 1 << 6, // Expression is per element PerSpawn = 1 << 7, // Expression relies on event attribute or spawn context NotCompilableOnCPU = InvalidOnCPU | PerElement //Helper to filter out invalid expression on CPU } public static bool IsFloatValueType(VFXValueType valueType) { return valueType == VFXValueType.Float || valueType == VFXValueType.Float2 || valueType == VFXValueType.Float3 || valueType == VFXValueType.Float4; } public static bool IsUIntValueType(VFXValueType valueType) { return valueType == VFXValueType.Uint32; } public static bool IsIntValueType(VFXValueType valueType) { return valueType == VFXValueType.Int32; } public static bool IsBoolValueType(VFXValueType valueType) { return valueType == VFXValueType.Boolean; } public static int TypeToSize(VFXValueType type) { return VFXExpressionHelper.GetSizeOfType(type); } public static string TypeToCode(VFXValueType type) { switch (type) { case VFXValueType.Float: return "float"; case VFXValueType.Float2: return "float2"; case VFXValueType.Float3: return "float3"; case VFXValueType.Float4: return "float4"; case VFXValueType.Int32: return "int"; case VFXValueType.Uint32: return "uint"; case VFXValueType.Texture2D: return "Texture2D"; case VFXValueType.Texture2DArray: return "Texture2DArray"; case VFXValueType.Texture3D: return "Texture3D"; case VFXValueType.TextureCube: return "TextureCube"; case VFXValueType.TextureCubeArray: return "TextureCubeArray"; case VFXValueType.Matrix4x4: return "float4x4"; case VFXValueType.Mesh: case VFXValueType.SkinnedMeshRenderer: case VFXValueType.Buffer: return "ByteAddressBuffer"; case VFXValueType.Boolean: return "bool"; } throw new NotImplementedException(type.ToString()); } // As certain type of uniforms are not handled in material, we need to use floats instead public static string TypeToUniformCode(VFXValueType type) { switch (type) { case VFXValueType.Float: return "float"; case VFXValueType.Float2: return "float2"; case VFXValueType.Float3: return "float3"; case VFXValueType.Float4: return "float4"; case VFXValueType.Int32: return "float"; case VFXValueType.Uint32: return "float"; case VFXValueType.Matrix4x4: return "float4x4"; case VFXValueType.Boolean: return "float"; } throw new NotImplementedException(type.ToString()); } public static Type TypeToType(VFXValueType type) { switch (type) { case VFXValueType.Float: return typeof(float); case VFXValueType.Float2: return typeof(Vector2); case VFXValueType.Float3: return typeof(Vector3); case VFXValueType.Float4: return typeof(Vector4); case VFXValueType.Int32: return typeof(int); case VFXValueType.Uint32: return typeof(uint); case VFXValueType.Texture2D: return typeof(Texture); case VFXValueType.Texture2DArray: return typeof(Texture); case VFXValueType.Texture3D: return typeof(Texture); case VFXValueType.TextureCube: return typeof(Texture); case VFXValueType.TextureCubeArray: return typeof(Texture); case VFXValueType.Matrix4x4: return typeof(Matrix4x4); case VFXValueType.Mesh: return typeof(Mesh); case VFXValueType.Curve: return typeof(AnimationCurve); case VFXValueType.ColorGradient: return typeof(Gradient); case VFXValueType.Boolean: return typeof(bool); } throw new NotImplementedException(type.ToString()); } public static bool IsTypeValidOnGPU(VFXValueType type) { switch (type) { case VFXValueType.Float: case VFXValueType.Float2: case VFXValueType.Float3: case VFXValueType.Float4: case VFXValueType.Int32: case VFXValueType.Uint32: case VFXValueType.Texture2D: case VFXValueType.Texture2DArray: case VFXValueType.Texture3D: case VFXValueType.TextureCube: case VFXValueType.TextureCubeArray: case VFXValueType.Matrix4x4: case VFXValueType.Boolean: return true; } return false; } public static bool IsTypeConstantFoldable(VFXValueType type) { switch (type) { //Mesh API can modify the vertex count & layout. //Thus, all mesh related expression should never been constant folded while generating code. // The same goes for textures case VFXValueType.Texture2D: case VFXValueType.Texture2DArray: case VFXValueType.Texture3D: case VFXValueType.TextureCube: case VFXValueType.TextureCubeArray: case VFXValueType.Mesh: case VFXValueType.SkinnedMeshRenderer: return false; } return true; } public static bool IsTexture(VFXValueType type) { switch (type) { case VFXValueType.Texture2D: case VFXValueType.Texture2DArray: case VFXValueType.Texture3D: case VFXValueType.TextureCube: case VFXValueType.TextureCubeArray: return true; } return false; } public static bool IsBufferOnGPU(VFXValueType type) { return type == VFXValueType.Mesh || type == VFXValueType.Buffer; } public static bool IsUniform(VFXValueType type) { switch (type) { case VFXValueType.Float: case VFXValueType.Float2: case VFXValueType.Float3: case VFXValueType.Float4: case VFXValueType.Int32: case VFXValueType.Uint32: case VFXValueType.Matrix4x4: case VFXValueType.Boolean: return true; } return false; } public static Type GetMatchingScalar(Type type) { var vfxType = GetVFXValueTypeFromType(type); if (vfxType == VFXValueType.None) { var affinityFallback = VFXOperatorDynamicOperand.GetTypeAffinityList(type).GetEnumerator(); while (affinityFallback.MoveNext() && vfxType == VFXValueType.None) { vfxType = GetVFXValueTypeFromType(affinityFallback.Current); } } return TypeToType(GetMatchingScalar(vfxType)); } public static VFXValueType GetMatchingScalar(VFXValueType type) { if (IsFloatValueType(type)) return VFXValueType.Float; if (IsUIntValueType(type)) return VFXValueType.Uint32; if (IsIntValueType(type)) return VFXValueType.Int32; return VFXValueType.None; } public static VFXValueType GetVFXValueTypeFromType(Type type) { if (type == typeof(float)) return VFXValueType.Float; if (type == typeof(Vector2)) return VFXValueType.Float2; if (type == typeof(Vector3)) return VFXValueType.Float3; if (type == typeof(Vector4)) return VFXValueType.Float4; if (type == typeof(Color)) return VFXValueType.Float4; if (type == typeof(int)) return VFXValueType.Int32; if (type == typeof(uint)) return VFXValueType.Uint32; if (type == typeof(Texture2D)) return VFXValueType.Texture2D; if (type == typeof(Texture2DArray)) return VFXValueType.Texture2DArray; if (type == typeof(Texture3D)) return VFXValueType.Texture3D; if (type == typeof(Cubemap)) return VFXValueType.TextureCube; if (type == typeof(CubemapArray)) return VFXValueType.TextureCubeArray; if (type == typeof(Matrix4x4)) return VFXValueType.Matrix4x4; if (type == typeof(AnimationCurve)) return VFXValueType.Curve; if (type == typeof(Gradient)) return VFXValueType.ColorGradient; if (type == typeof(Mesh)) return VFXValueType.Mesh; if (type == typeof(SkinnedMeshRenderer)) return VFXValueType.SkinnedMeshRenderer; if (type == typeof(List)) return VFXValueType.Spline; if (type == typeof(bool)) return VFXValueType.Boolean; return VFXValueType.None; } private static Dictionary s_ExpressionCache = new Dictionary(); public static void ClearCache() { s_ExpressionCache.Clear(); } //Ideally, we should use HashSet.TryGetValue https://msdn.microsoft.com/en-us/library/mt829070(v=vs.110).aspx //but it's available only in 4.7, Dictionary is a workaround, sensible same performance but there is a waste of memory private void SimplifyWithCacheParents() { for (int i = 0; i < m_Parents.Length; ++i) { VFXExpression parentEq; if (!s_ExpressionCache.TryGetValue(parents[i], out parentEq)) { s_ExpressionCache.Add(parents[i], parents[i]); } else { m_Parents[i] = parentEq; } } } protected VFXExpression(Flags flags, params VFXExpression[] parents) { if (parents.Length > 4) { throw new System.ArgumentException("An expression can only take up to 4 parent expressions"); } m_Parents = parents; SimplifyWithCacheParents(); m_Flags = flags; PropagateParentsFlags(); } // Only do that when constructing an instance if needed private void Initialize(VFXExpression[] parents) { if (parents.Length > 4) { throw new System.ArgumentException("An expression can only take up to 4 parent expressions"); } m_Parents = parents; SimplifyWithCacheParents(); PropagateParentsFlags(); m_HashCodeCached = false; // as expression is mutated } //Helper using reflection to recreate a concrete type from an abstract class (useful with reduce behavior) private static VFXExpression CreateNewInstance(Type expressionType) { var allconstructors = expressionType.GetConstructors().ToArray(); if (allconstructors.Length == 0) return null; //Only static readonly expression allowed, constructors are private (attribute or builtIn) var constructor = allconstructors .OrderBy(o => o.GetParameters().Count()) //promote simplest (or default) constructors .First(); var param = constructor.GetParameters().Select(o => { var type = o.GetType(); return type.IsValueType ? Activator.CreateInstance(type) : null; }).ToArray(); return (VFXExpression)constructor.Invoke(param); } private VFXExpression CreateNewInstance() { return CreateNewInstance(GetType()); } // Reduce the expression protected virtual VFXExpression Reduce(VFXExpression[] reducedParents) { if (reducedParents.Length == 0) return this; var reduced = CreateNewInstance(); reduced.Initialize(reducedParents); return reduced; } // Evaluate the expression protected virtual VFXExpression Evaluate(VFXExpression[] constParents) { throw new NotImplementedException(); } // Get the HLSL code snippet public virtual string GetCodeString(string[] parents) { throw new NotImplementedException(GetType().ToString()); } // Get the operands for the runtime evaluation public Operands GetOperands(VFXExpressionGraph graph) { var addOperands = additionnalOperands; if (parents.Length + addOperands.Length > 4) throw new Exception("Too many parameters for expression : " + this); var data = new Operands(-1); if (graph != null) for (int i = 0; i < parents.Length; ++i) data[i] = graph.GetFlattenedIndex(parents[i]); for (int i = 0; i < addOperands.Length; ++i) data[Operands.OperandCount - addOperands.Length + i] = addOperands[i]; return data; } public virtual IEnumerable GetNeededAttributes() { return Enumerable.Empty(); } public bool Is(Flags flag) { return (m_Flags & flag) == flag; } public bool IsAny(Flags flag) { return (m_Flags & flag) != 0; } public virtual VFXValueType valueType { get { var data = GetOperands(null); return VFXExpressionHelper.GetTypeOfOperation(operation, data[0], data[1], data[2], data[3]); } } public abstract VFXExpressionOperation operation { get; } public VFXExpression[] parents { get { return m_Parents; } } public override bool Equals(object obj) { if (ReferenceEquals(this, obj)) return true; var other = obj as VFXExpression; if (other == null) return false; if (GetType() != other.GetType()) return false; if (operation != other.operation) return false; if (valueType != other.valueType) return false; if (m_Flags != other.m_Flags) return false; if (GetHashCode() != other.GetHashCode()) return false; var operands = additionnalOperands; var otherOperands = other.additionnalOperands; if (operands.Length != otherOperands.Length) return false; for (int i = 0; i < operands.Length; ++i) if (operands[i] != otherOperands[i]) return false; var thisParents = parents; var otherParents = other.parents; if (thisParents == null && otherParents == null) return true; if (thisParents == null || otherParents == null) return false; if (thisParents.Length != otherParents.Length) return false; for (int i = 0; i < thisParents.Length; ++i) if (!thisParents[i].Equals(otherParents[i])) return false; return true; } public override sealed int GetHashCode() { if (!m_HashCodeCached) { m_HashCode = GetInnerHashCode(); m_HashCodeCached = true; } return m_HashCode; } protected virtual int GetInnerHashCode() { int hash = GetType().GetHashCode(); var parents = this.parents; for (int i = 0; i < parents.Length; ++i) hash = (hash * 397) ^ parents[i].GetHashCode(); // 397 taken from resharper var operands = additionnalOperands; for (int i = 0; i < operands.Length; ++i) hash = (hash * 397) ^ operands[i].GetHashCode(); hash = (hash * 397) ^ m_Flags.GetHashCode(); hash = (hash * 397) ^ valueType.GetHashCode(); hash = (hash * 397) ^ operation.GetHashCode(); return hash; } private static readonly int[] k_EmptyOperands = Enumerable.Empty().ToArray(); protected virtual int[] additionnalOperands { get { return k_EmptyOperands; } } public virtual T Get() { var value = (this as VFXValue); if (value == null) { throw new ArgumentException(string.Format("Get isn't available for {0} with {1}", typeof(T).FullName, GetType().FullName)); } return value.Get(); } public virtual object GetContent() { throw new ArgumentException(string.Format("GetContent isn't available for {0}", GetType().FullName)); } private void PropagateParentsFlags() { if (m_Parents.Length > 0) { bool foldable = true; foreach (var parent in m_Parents) { foldable &= parent.Is(Flags.Foldable); const Flags propagatedFlags = Flags.NotCompilableOnCPU | Flags.InvalidConstant | Flags.PerSpawn; m_Flags |= parent.m_Flags & propagatedFlags; if (parent.IsAny(Flags.NotCompilableOnCPU) && parent.Is(Flags.InvalidOnGPU)) m_Flags |= Flags.InvalidOnGPU; // Only propagate GPU validity for per element expressions if (parent.Is(Flags.InvalidConstant)) m_Flags |= Flags.InvalidConstant; } if (foldable) m_Flags |= Flags.Foldable; else m_Flags &= ~Flags.Foldable; } } public static VFXExpression operator*(VFXExpression a, VFXExpression b) { return new VFXExpressionMul(a, b); } public static VFXExpression operator/(VFXExpression a, VFXExpression b) { return new VFXExpressionDivide(a, b); } public static VFXExpression operator+(VFXExpression a, VFXExpression b) { return new VFXExpressionAdd(a, b); } public static VFXExpression operator-(VFXExpression a, VFXExpression b) { return new VFXExpressionSubtract(a, b); } public static VFXExpression operator|(VFXExpression a, VFXExpression b) { return new VFXExpressionBitwiseOr(a, b); } public static VFXExpression operator&(VFXExpression a, VFXExpression b) { return new VFXExpressionBitwiseAnd(a, b); } public static VFXExpression operator|(VFXExpression a, uint b) { return new VFXExpressionBitwiseOr(a, VFXValue.Constant(b)); } public static VFXExpression operator&(VFXExpression a, uint b) { return new VFXExpressionBitwiseAnd(a, VFXValue.Constant(b)); } public static VFXExpression operator<<(VFXExpression a, int shift) { return new VFXExpressionBitwiseLeftShift(a, VFXValue.Constant((uint)shift)); } public static VFXExpression operator>>(VFXExpression a, int shift) { return new VFXExpressionBitwiseRightShift(a, VFXValue.Constant((uint)shift)); } public VFXExpression this[int index] { get { return new VFXExpressionExtractComponent(this, index); } } public VFXExpression x { get { return new VFXExpressionExtractComponent(this, 0); } } public VFXExpression y { get { return new VFXExpressionExtractComponent(this, 1); } } public VFXExpression z { get { return new VFXExpressionExtractComponent(this, 2); } } public VFXExpression w { get { return new VFXExpressionExtractComponent(this, 3); } } public VFXExpression xxx { get { return new VFXExpressionCombine(x, x, x); } } public VFXExpression yyy { get { return new VFXExpressionCombine(y, y, y); } } public VFXExpression zzz { get { return new VFXExpressionCombine(z, z, z); } } private Flags m_Flags = Flags.None; private VFXExpression[] m_Parents; private int m_HashCode; private bool m_HashCodeCached = false; } }