601 lines
23 KiB
C#

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<T>(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<Vector3>)) return VFXValueType.Spline;
if (type == typeof(bool)) return VFXValueType.Boolean;
return VFXValueType.None;
}
private static Dictionary<VFXExpression, VFXExpression> s_ExpressionCache = new Dictionary<VFXExpression, VFXExpression>();
public static void ClearCache()
{
s_ExpressionCache.Clear();
}
//Ideally, we should use HashSet<T>.TryGetValue https://msdn.microsoft.com/en-us/library/mt829070(v=vs.110).aspx
//but it's available only in 4.7, Dictionary<T, T> 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<VFXAttributeInfo> GetNeededAttributes()
{
return Enumerable.Empty<VFXAttributeInfo>();
}
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<int>().ToArray();
protected virtual int[] additionnalOperands { get { return k_EmptyOperands; } }
public virtual T Get<T>()
{
var value = (this as VFXValue<T>);
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;
}
}