Browse Source

wip repeat node

Krzysztof Krysiński 1 day ago
parent
commit
9bed67b9ff
32 changed files with 674 additions and 560 deletions
  1. 1 363
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Context/FuncContext.cs
  2. 14 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Context/RepeatFuncContext.cs
  3. 378 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Context/ShaderFuncContext.cs
  4. 5 5
      src/PixiEditor.ChangeableDocument/Changeables/Graph/FuncInputProperty.cs
  5. 2 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/FuncOutputProperty.cs
  6. 6 6
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Animable/EasingNode.cs
  7. 13 13
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Calculations/RemapNode.cs
  8. 5 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ColorNode.cs
  9. 14 14
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/CombineColorNode.cs
  10. 8 8
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/CombineVecDNode.cs
  11. 7 7
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/CombineVecINode.cs
  12. 14 14
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/SeparateColorNode.cs
  13. 6 6
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/SeparateVecDNode.cs
  14. 7 6
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/SeparateVecINode.cs
  15. 9 9
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/LerpColorNode.cs
  16. 10 10
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/MathNode.cs
  17. 23 23
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Matrix/ComposeMatrixNode.cs
  18. 21 20
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Matrix/DecomposeMatrixNode.cs
  19. 6 6
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Matrix/Matrix3X3BaseNode.cs
  20. 3 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Matrix/OffsetNode.cs
  21. 5 5
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Matrix/RotateNode.cs
  22. 5 5
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Matrix/ScaleNode.cs
  23. 3 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Matrix/SkewNode.cs
  24. 6 6
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Matrix/TransformNode.cs
  25. 5 5
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ModifyImageLeftNode.cs
  26. 8 7
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ModifyImageRightNode.cs
  27. 5 5
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Node.cs
  28. 5 5
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/SampleImageNode.cs
  29. 18 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Utility/RepeatNodeEnd.cs
  30. 42 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Utility/RepeatNodeStart.cs
  31. 10 0
      src/PixiEditor/ViewModels/Document/Nodes/Utility/RepeatNodeViewModelEnd.cs
  32. 10 0
      src/PixiEditor/ViewModels/Document/Nodes/Utility/RepeatNodeViewModelStart.cs

+ 1 - 363
src/PixiEditor.ChangeableDocument/Changeables/Graph/Context/FuncContext.cs

@@ -17,23 +17,7 @@ public class FuncContext
 {
     public static FuncContext NoContext { get; } = new();
 
-    /// <summary>
-    ///     Original position of the pixel in the image. This is the input argument of the main function.
-    /// </summary>
-    public Float2 OriginalPosition { get; private set; }
-
-    /// <summary>
-    ///     Modified position of the pixel. This should be used to sample the texture, unless you want to sample the texture at the original position only.
-    /// </summary>
-    public Float2 SamplePosition { get; private set; }
-
-    public VecI Size { get; private set; }
-    public bool HasContext { get; private set; }
-    public RenderContext RenderContext { get; set; }
-
-    public ShaderBuilder Builder { get; set; }
-
-    private Dictionary<IFuncInputProperty, ShaderExpressionVariable> _cachedValues = new();
+    public bool HasContext { get; protected set; }
 
     public void ThrowOnMissingContext()
     {
@@ -42,350 +26,4 @@ public class FuncContext
             throw new NoNodeFuncContextException();
         }
     }
-
-    public FuncContext()
-    {
-    }
-
-    public FuncContext(RenderContext renderContext, ShaderBuilder builder)
-    {
-        RenderContext = renderContext;
-        Builder = builder;
-        HasContext = true;
-        OriginalPosition = new Float2("coords"); // input argument 'half4 main(float2 coords)'
-        SamplePosition = Builder.ConstructFloat2(OriginalPosition.X, OriginalPosition.Y);
-    }
-
-    public Half4 SampleSurface(DrawingSurface surface, Expression pos, ColorSampleMode sampleMode, bool normalizedCoordinates)
-    {
-        SurfaceSampler texName = Builder.AddOrGetSurface(surface, sampleMode);
-        return Builder.Sample(texName, pos, normalizedCoordinates);
-    }
-
-    public Float2 NewFloat2(Expression x, Expression y)
-    {
-        if (!HasContext && x is Float1 firstFloat && y is Float1 secondFloat)
-        {
-            Float2 constantFloat = new Float2("");
-            constantFloat.ConstantValue = new VecD(firstFloat.ConstantValue, secondFloat.ConstantValue);
-            return constantFloat;
-        }
-
-        return Builder.ConstructFloat2(x, y);
-    }
-
-    public Float1 NewFloat1(Expression result)
-    {
-        if (!HasContext && result is Float1 float1)
-        {
-            Float1 constantFloat = new Float1("");
-            constantFloat.ConstantValue = float1.ConstantValue;
-            return constantFloat;
-        }
-
-        return Builder.ConstructFloat1(result);
-    }
-
-
-    public Int2 NewInt2(Expression first, Expression second)
-    {
-        if (!HasContext && first is Int1 firstInt && second is Int1 secondInt)
-        {
-            Int2 constantInt = new Int2("");
-            constantInt.ConstantValue = new VecI(firstInt.ConstantValue, secondInt.ConstantValue);
-            return constantInt;
-        }
-
-        return Builder.ConstructInt2(first, second);
-    }
-
-    public Half4 NewHalf4(Expression r, Expression g, Expression b, Expression a)
-    {
-        if (!HasContext && r is Float1 firstFloat && g is Float1 secondFloat && b is Float1 thirdFloat &&
-            a is Float1 fourthFloat)
-        {
-            Half4 constantHalf4 = new Half4("");
-            byte rByte = firstFloat.AsConstantColorByte();
-            byte gByte = secondFloat.AsConstantColorByte();
-            byte bByte = thirdFloat.AsConstantColorByte();
-            byte aByte = fourthFloat.AsConstantColorByte();
-            constantHalf4.ConstantValue = new Color(rByte, gByte, bByte, aByte);
-            return constantHalf4;
-        }
-
-        return Builder.ConstructHalf4(r, g, b, a);
-    }
-
-    public Half4 HsvaToRgba(Expression h, Expression s, Expression v, Expression a)
-    {
-        if (!HasContext && h is Float1 firstFloat && s is Float1 secondFloat && v is Float1 thirdFloat &&
-            a is Float1 fourthFloat)
-        {
-            Half4 constantHalf4 = new Half4("");
-            var hValue = firstFloat.ConstantValue * 360;
-            var sValue = secondFloat.ConstantValue * 100;
-            var vValue = thirdFloat.ConstantValue * 100;
-            byte aByte = fourthFloat.AsConstantColorByte();
-            constantHalf4.ConstantValue = Color.FromHsv((float)hValue, (float)sValue, (float)vValue, aByte);
-            return constantHalf4;
-        }
-
-        return Builder.AssignNewHalf4(Builder.Functions.GetHsvToRgb(h, s, v, a));
-    }
-
-    public Half4 HslaToRgba(Expression h, Expression s, Expression l, Expression a)
-    {
-        if (!HasContext && h is Float1 firstFloat && s is Float1 secondFloat && l is Float1 thirdFloat &&
-            a is Float1 fourthFloat)
-        {
-            Half4 constantHalf4 = new Half4("");
-            var hValue = firstFloat.ConstantValue * 360;
-            var sValue = secondFloat.ConstantValue * 100;
-            var lValue = thirdFloat.ConstantValue * 100;
-            byte aByte = fourthFloat.AsConstantColorByte();
-            constantHalf4.ConstantValue = Color.FromHsl((float)hValue, (float)sValue, (float)lValue, aByte);
-            return constantHalf4;
-        }
-
-        return Builder.AssignNewHalf4(Builder.Functions.GetHslToRgb(h, s, l, a));
-    }
-
-    public Half4 RgbaToHsva(Expression color)
-    {
-        if (!HasContext && color is Half4 constantColor)
-        {
-            var variable = new Half4(string.Empty);
-            constantColor.ConstantValue.ToHsv(out float h, out float s, out float l);
-            variable.ConstantValue = new Color((byte)(h * 255), (byte)(s * 255), (byte)(l * 255),
-                constantColor.ConstantValue.A);
-
-            return variable;
-        }
-
-        return Builder.AssignNewHalf4(Builder.Functions.GetRgbToHsv(color));
-    }
-
-    public Half4 RgbaToHsla(Expression color)
-    {
-        if (!HasContext && color is Half4 constantColor)
-        {
-            var variable = new Half4(string.Empty);
-            constantColor.ConstantValue.ToHsl(out float h, out float s, out float l);
-            variable.ConstantValue = new Color((byte)(h * 255), (byte)(s * 255), (byte)(l * 255),
-                constantColor.ConstantValue.A);
-
-            return variable;
-        }
-
-        return Builder.AssignNewHalf4(Builder.Functions.GetRgbToHsl(color));
-    }
-
-    public Half4 NewHalf4(Expression assignment)
-    {
-        if (!HasContext && assignment is Half4 half4)
-        {
-            Half4 constantHalf4 = new Half4("");
-            constantHalf4.ConstantValue = half4.ConstantValue;
-            return constantHalf4;
-        }
-
-        return Builder.AssignNewHalf4(assignment);
-    }
-
-    public Float1 GetValue(FuncInputProperty<Float1> getFrom)
-    {
-        if (HasContext)
-        {
-            if (getFrom.Connection == null || !IsFuncType(getFrom))
-            {
-                string uniformName = $"float_{Builder.GetUniqueNameNumber()}";
-                Builder.AddUniform(uniformName, (float)getFrom.Value(this).ConstantValue);
-                return new Float1(uniformName);
-            }
-
-            if (_cachedValues.TryGetValue(getFrom, out ShaderExpressionVariable cachedValue))
-            {
-                if (cachedValue is Float1 float1)
-                {
-                    return float1;
-                }
-            }
-        }
-
-
-        var val = getFrom.Value(this);
-        _cachedValues[getFrom] = val;
-
-        return val;
-    }
-
-    public Int1 GetValue(FuncInputProperty<Int1> getFrom)
-    {
-        if (HasContext)
-        {
-            if (getFrom.Connection == null || !IsFuncType(getFrom))
-            {
-                string uniformName = $"int_{Builder.GetUniqueNameNumber()}";
-                Builder.AddUniform(uniformName, (int)getFrom.Value(this).ConstantValue);
-                return new Int1(uniformName);
-            }
-
-            if (_cachedValues.TryGetValue(getFrom, out ShaderExpressionVariable cachedValue))
-            {
-                if (cachedValue is Int1 int1)
-                {
-                    return int1;
-                }
-            }
-        }
-
-        var val = getFrom.Value(this);
-        _cachedValues[getFrom] = val;
-
-        return val;
-    }
-
-    private static bool IsFuncType<T>(FuncInputProperty<T> getFrom)
-    {
-        return getFrom.Connection.ValueType.IsAssignableTo(typeof(Delegate));
-    }
-
-    public Half4 GetValue(FuncInputProperty<Half4> getFrom)
-    {
-        if (HasContext)
-        {
-            if (getFrom.Connection == null || !IsFuncType(getFrom))
-            {
-                Half4 color = getFrom.Value(this);
-                color.VariableName = $"color_{Builder.GetUniqueNameNumber()}";
-                Builder.AddUniform(color.VariableName, color.ConstantValue);
-                return color;
-            }
-
-            if (_cachedValues.TryGetValue(getFrom, out ShaderExpressionVariable cachedValue))
-            {
-                if (cachedValue is Half4 half4)
-                {
-                    return half4;
-                }
-            }
-        }
-
-        var val = getFrom.Value(this);
-        _cachedValues[getFrom] = val;
-
-        return val;
-    }
-
-    public Float2 GetValue(FuncInputProperty<Float2> getFrom)
-    {
-        if (HasContext)
-        {
-            if (getFrom.Connection == null || !IsFuncType(getFrom))
-            {
-                Float2 value = getFrom.Value(this);
-                value.VariableName = $"float2_{Builder.GetUniqueNameNumber()}";
-                Builder.AddUniform(value.VariableName, value.ConstantValue);
-                return value;
-            }
-
-            if (_cachedValues.TryGetValue(getFrom, out ShaderExpressionVariable cachedValue))
-            {
-                if (cachedValue is Float2 float2)
-                {
-                    return float2;
-                }
-            }
-        }
-
-        var val = getFrom.Value(this);
-        _cachedValues[getFrom] = val;
-
-        return val;
-    }
-
-    public Int2 GetValue(FuncInputProperty<Int2>? getFrom)
-    {
-        if (HasContext)
-        {
-            if (getFrom?.Connection == null || !IsFuncType(getFrom))
-            {
-                Int2 value = getFrom.Value(this);
-                value.VariableName = $"int2_{Builder.GetUniqueNameNumber()}";
-                Builder.AddUniform(value.VariableName, value.ConstantValue);
-                return value;
-            }
-
-            if (_cachedValues.TryGetValue(getFrom, out ShaderExpressionVariable cachedValue))
-            {
-                if (cachedValue is Int2 int2)
-                {
-                    return int2;
-                }
-            }
-        }
-
-        var val = getFrom.Value(this);
-        _cachedValues[getFrom] = val;
-
-        return val;
-    }
-
-    public Float3x3 GetValue(FuncInputProperty<Float3x3> getFrom)
-    {
-        if (HasContext)
-        {
-            if (getFrom.Connection == null || !IsFuncType(getFrom))
-            {
-                Float3x3 value = getFrom.Value(this);
-                value.VariableName = $"float3x3_{Builder.GetUniqueNameNumber()}";
-                Builder.AddUniform(value.VariableName, value.ConstantValue);
-                return value;
-            }
-
-            if (_cachedValues.TryGetValue(getFrom, out ShaderExpressionVariable cachedValue))
-            {
-                if (cachedValue is Float3x3 float3x3)
-                {
-                    return float3x3;
-                }
-            }
-        }
-
-        var val = getFrom.Value(this);
-        _cachedValues[getFrom] = val;
-
-        return val;
-    }
-
-    public Float3x3 NewFloat3x3(Expression m00, Expression m01, Expression m02,
-        Expression m10, Expression m11, Expression m12,
-        Expression m20, Expression m21, Expression m22)
-    {
-        if (!HasContext && m00 is Float1 firstFloat && m01 is Float1 secondFloat && m02 is Float1 thirdFloat &&
-            m10 is Float1 fourthFloat && m11 is Float1 fifthFloat && m12 is Float1 sixthFloat &&
-            m20 is Float1 seventhFloat && m21 is Float1 eighthFloat && m22 is Float1 ninthFloat)
-        {
-            Float3x3 constantMatrix = new Float3x3("");
-            constantMatrix.ConstantValue = new Matrix3X3(
-                (float)firstFloat.ConstantValue, (float)secondFloat.ConstantValue, (float)thirdFloat.ConstantValue,
-                (float)fourthFloat.ConstantValue, (float)fifthFloat.ConstantValue, (float)sixthFloat.ConstantValue,
-                (float)seventhFloat.ConstantValue, (float)eighthFloat.ConstantValue, (float)ninthFloat.ConstantValue);
-            return constantMatrix;
-        }
-
-        return Builder.ConstructFloat3x3(m00, m01, m02, m10, m11, m12, m20, m21, m22);
-    }
-
-    public Float3x3 NewFloat3x3(Expression matrixExpression)
-    {
-        if (!HasContext && matrixExpression is Float3x3 float3x3)
-        {
-            Float3x3 constantMatrix = new Float3x3("");
-            constantMatrix.ConstantValue = float3x3.ConstantValue;
-            return constantMatrix;
-        }
-
-        return Builder.AssignNewFloat3x3(matrixExpression);
-    }
 }

+ 14 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Context/RepeatFuncContext.cs

@@ -0,0 +1,14 @@
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Context;
+
+public class RepeatFuncContext : FuncContext
+{
+    public int CurrentIteration { get; set; }
+
+    public int TotalIterations { get; }
+
+    public RepeatFuncContext(int totalIterations)
+    {
+        HasContext = true;
+        TotalIterations = totalIterations;
+    }
+}

+ 378 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Context/ShaderFuncContext.cs

@@ -0,0 +1,378 @@
+using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Shaders.Generation;
+using Drawie.Backend.Core.Shaders.Generation.Expressions;
+using Drawie.Backend.Core.Surfaces;
+using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Rendering;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Context;
+
+public class ShaderFuncContext : FuncContext
+{
+    public static ShaderFuncContext NoContext { get; } = new();
+    /// <summary>
+    ///     Original position of the pixel in the image. This is the input argument of the main function.
+    /// </summary>
+    public Float2 OriginalPosition { get; private set; }
+
+    /// <summary>
+    ///     Modified position of the pixel. This should be used to sample the texture, unless you want to sample the texture at the original position only.
+    /// </summary>
+    public Float2 SamplePosition { get; private set; }
+
+    public VecI Size { get; private set; }
+    public RenderContext RenderContext { get; set; }
+
+    public ShaderBuilder Builder { get; set; }
+
+    private Dictionary<IFuncInputProperty, ShaderExpressionVariable> _cachedValues = new();
+
+
+    public ShaderFuncContext(RenderContext renderContext, ShaderBuilder builder)
+    {
+        RenderContext = renderContext;
+        Builder = builder;
+        HasContext = true;
+        OriginalPosition = new Float2("coords"); // input argument 'half4 main(float2 coords)'
+        SamplePosition = Builder.ConstructFloat2(OriginalPosition.X, OriginalPosition.Y);
+    }
+
+    public ShaderFuncContext()
+    {
+
+    }
+
+     public Half4 SampleSurface(DrawingSurface surface, Expression pos, ColorSampleMode sampleMode, bool normalizedCoordinates)
+    {
+        SurfaceSampler texName = Builder.AddOrGetSurface(surface, sampleMode);
+        return Builder.Sample(texName, pos, normalizedCoordinates);
+    }
+
+    public Float2 NewFloat2(Expression x, Expression y)
+    {
+        if (!HasContext && x is Float1 firstFloat && y is Float1 secondFloat)
+        {
+            Float2 constantFloat = new Float2("");
+            constantFloat.ConstantValue = new VecD(firstFloat.ConstantValue, secondFloat.ConstantValue);
+            return constantFloat;
+        }
+
+        return Builder.ConstructFloat2(x, y);
+    }
+
+    public Float1 NewFloat1(Expression result)
+    {
+        if (!HasContext && result is Float1 float1)
+        {
+            Float1 constantFloat = new Float1("");
+            constantFloat.ConstantValue = float1.ConstantValue;
+            return constantFloat;
+        }
+
+        return Builder.ConstructFloat1(result);
+    }
+
+
+    public Int2 NewInt2(Expression first, Expression second)
+    {
+        if (!HasContext && first is Int1 firstInt && second is Int1 secondInt)
+        {
+            Int2 constantInt = new Int2("");
+            constantInt.ConstantValue = new VecI(firstInt.ConstantValue, secondInt.ConstantValue);
+            return constantInt;
+        }
+
+        return Builder.ConstructInt2(first, second);
+    }
+
+    public Half4 NewHalf4(Expression r, Expression g, Expression b, Expression a)
+    {
+        if (!HasContext && r is Float1 firstFloat && g is Float1 secondFloat && b is Float1 thirdFloat &&
+            a is Float1 fourthFloat)
+        {
+            Half4 constantHalf4 = new Half4("");
+            byte rByte = firstFloat.AsConstantColorByte();
+            byte gByte = secondFloat.AsConstantColorByte();
+            byte bByte = thirdFloat.AsConstantColorByte();
+            byte aByte = fourthFloat.AsConstantColorByte();
+            constantHalf4.ConstantValue = new Color(rByte, gByte, bByte, aByte);
+            return constantHalf4;
+        }
+
+        return Builder.ConstructHalf4(r, g, b, a);
+    }
+
+    public Half4 HsvaToRgba(Expression h, Expression s, Expression v, Expression a)
+    {
+        if (!HasContext && h is Float1 firstFloat && s is Float1 secondFloat && v is Float1 thirdFloat &&
+            a is Float1 fourthFloat)
+        {
+            Half4 constantHalf4 = new Half4("");
+            var hValue = firstFloat.ConstantValue * 360;
+            var sValue = secondFloat.ConstantValue * 100;
+            var vValue = thirdFloat.ConstantValue * 100;
+            byte aByte = fourthFloat.AsConstantColorByte();
+            constantHalf4.ConstantValue = Color.FromHsv((float)hValue, (float)sValue, (float)vValue, aByte);
+            return constantHalf4;
+        }
+
+        return Builder.AssignNewHalf4(Builder.Functions.GetHsvToRgb(h, s, v, a));
+    }
+
+    public Half4 HslaToRgba(Expression h, Expression s, Expression l, Expression a)
+    {
+        if (!HasContext && h is Float1 firstFloat && s is Float1 secondFloat && l is Float1 thirdFloat &&
+            a is Float1 fourthFloat)
+        {
+            Half4 constantHalf4 = new Half4("");
+            var hValue = firstFloat.ConstantValue * 360;
+            var sValue = secondFloat.ConstantValue * 100;
+            var lValue = thirdFloat.ConstantValue * 100;
+            byte aByte = fourthFloat.AsConstantColorByte();
+            constantHalf4.ConstantValue = Color.FromHsl((float)hValue, (float)sValue, (float)lValue, aByte);
+            return constantHalf4;
+        }
+
+        return Builder.AssignNewHalf4(Builder.Functions.GetHslToRgb(h, s, l, a));
+    }
+
+    public Half4 RgbaToHsva(Expression color)
+    {
+        if (!HasContext && color is Half4 constantColor)
+        {
+            var variable = new Half4(string.Empty);
+            constantColor.ConstantValue.ToHsv(out float h, out float s, out float l);
+            variable.ConstantValue = new Color((byte)(h * 255), (byte)(s * 255), (byte)(l * 255),
+                constantColor.ConstantValue.A);
+
+            return variable;
+        }
+
+        return Builder.AssignNewHalf4(Builder.Functions.GetRgbToHsv(color));
+    }
+
+    public Half4 RgbaToHsla(Expression color)
+    {
+        if (!HasContext && color is Half4 constantColor)
+        {
+            var variable = new Half4(string.Empty);
+            constantColor.ConstantValue.ToHsl(out float h, out float s, out float l);
+            variable.ConstantValue = new Color((byte)(h * 255), (byte)(s * 255), (byte)(l * 255),
+                constantColor.ConstantValue.A);
+
+            return variable;
+        }
+
+        return Builder.AssignNewHalf4(Builder.Functions.GetRgbToHsl(color));
+    }
+
+    public Half4 NewHalf4(Expression assignment)
+    {
+        if (!HasContext && assignment is Half4 half4)
+        {
+            Half4 constantHalf4 = new Half4("");
+            constantHalf4.ConstantValue = half4.ConstantValue;
+            return constantHalf4;
+        }
+
+        return Builder.AssignNewHalf4(assignment);
+    }
+
+    public Float1 GetValue(FuncInputProperty<Float1, ShaderFuncContext> getFrom)
+    {
+        if (HasContext)
+        {
+            if (getFrom.Connection == null || !IsFuncType(getFrom))
+            {
+                string uniformName = $"float_{Builder.GetUniqueNameNumber()}";
+                Builder.AddUniform(uniformName, (float)getFrom.Value(this).ConstantValue);
+                return new Float1(uniformName);
+            }
+
+            if (_cachedValues.TryGetValue(getFrom, out ShaderExpressionVariable cachedValue))
+            {
+                if (cachedValue is Float1 float1)
+                {
+                    return float1;
+                }
+            }
+        }
+
+
+        var val = getFrom.Value(this);
+        _cachedValues[getFrom] = val;
+
+        return val;
+    }
+
+    public Int1 GetValue(FuncInputProperty<Int1, ShaderFuncContext> getFrom)
+    {
+        if (HasContext)
+        {
+            if (getFrom.Connection == null || !IsFuncType(getFrom))
+            {
+                string uniformName = $"int_{Builder.GetUniqueNameNumber()}";
+                Builder.AddUniform(uniformName, (int)getFrom.Value(this).ConstantValue);
+                return new Int1(uniformName);
+            }
+
+            if (_cachedValues.TryGetValue(getFrom, out ShaderExpressionVariable cachedValue))
+            {
+                if (cachedValue is Int1 int1)
+                {
+                    return int1;
+                }
+            }
+        }
+
+        var val = getFrom.Value(this);
+        _cachedValues[getFrom] = val;
+
+        return val;
+    }
+
+    private static bool IsFuncType<T>(FuncInputProperty<T, ShaderFuncContext> getFrom)
+    {
+        return getFrom.Connection.ValueType.IsAssignableTo(typeof(Delegate));
+    }
+
+    public Half4 GetValue(FuncInputProperty<Half4, ShaderFuncContext> getFrom)
+    {
+        if (HasContext)
+        {
+            if (getFrom.Connection == null || !IsFuncType(getFrom))
+            {
+                Half4 color = getFrom.Value(this);
+                color.VariableName = $"color_{Builder.GetUniqueNameNumber()}";
+                Builder.AddUniform(color.VariableName, color.ConstantValue);
+                return color;
+            }
+
+            if (_cachedValues.TryGetValue(getFrom, out ShaderExpressionVariable cachedValue))
+            {
+                if (cachedValue is Half4 half4)
+                {
+                    return half4;
+                }
+            }
+        }
+
+        var val = getFrom.Value(this);
+        _cachedValues[getFrom] = val;
+
+        return val;
+    }
+
+    public Float2 GetValue(FuncInputProperty<Float2, ShaderFuncContext> getFrom)
+    {
+        if (HasContext)
+        {
+            if (getFrom.Connection == null || !IsFuncType(getFrom))
+            {
+                Float2 value = getFrom.Value(this);
+                value.VariableName = $"float2_{Builder.GetUniqueNameNumber()}";
+                Builder.AddUniform(value.VariableName, value.ConstantValue);
+                return value;
+            }
+
+            if (_cachedValues.TryGetValue(getFrom, out ShaderExpressionVariable cachedValue))
+            {
+                if (cachedValue is Float2 float2)
+                {
+                    return float2;
+                }
+            }
+        }
+
+        var val = getFrom.Value(this);
+        _cachedValues[getFrom] = val;
+
+        return val;
+    }
+
+    public Int2 GetValue(FuncInputProperty<Int2, ShaderFuncContext>? getFrom)
+    {
+        if (HasContext)
+        {
+            if (getFrom?.Connection == null || !IsFuncType(getFrom))
+            {
+                Int2 value = getFrom.Value(this);
+                value.VariableName = $"int2_{Builder.GetUniqueNameNumber()}";
+                Builder.AddUniform(value.VariableName, value.ConstantValue);
+                return value;
+            }
+
+            if (_cachedValues.TryGetValue(getFrom, out ShaderExpressionVariable cachedValue))
+            {
+                if (cachedValue is Int2 int2)
+                {
+                    return int2;
+                }
+            }
+        }
+
+        var val = getFrom.Value(this);
+        _cachedValues[getFrom] = val;
+
+        return val;
+    }
+
+    public Float3x3 GetValue(FuncInputProperty<Float3x3, ShaderFuncContext> getFrom)
+    {
+        if (HasContext)
+        {
+            if (getFrom.Connection == null || !IsFuncType(getFrom))
+            {
+                Float3x3 value = getFrom.Value(this);
+                value.VariableName = $"float3x3_{Builder.GetUniqueNameNumber()}";
+                Builder.AddUniform(value.VariableName, value.ConstantValue);
+                return value;
+            }
+
+            if (_cachedValues.TryGetValue(getFrom, out ShaderExpressionVariable cachedValue))
+            {
+                if (cachedValue is Float3x3 float3x3)
+                {
+                    return float3x3;
+                }
+            }
+        }
+
+        var val = getFrom.Value(this);
+        _cachedValues[getFrom] = val;
+
+        return val;
+    }
+
+    public Float3x3 NewFloat3x3(Expression m00, Expression m01, Expression m02,
+        Expression m10, Expression m11, Expression m12,
+        Expression m20, Expression m21, Expression m22)
+    {
+        if (!HasContext && m00 is Float1 firstFloat && m01 is Float1 secondFloat && m02 is Float1 thirdFloat &&
+            m10 is Float1 fourthFloat && m11 is Float1 fifthFloat && m12 is Float1 sixthFloat &&
+            m20 is Float1 seventhFloat && m21 is Float1 eighthFloat && m22 is Float1 ninthFloat)
+        {
+            Float3x3 constantMatrix = new Float3x3("");
+            constantMatrix.ConstantValue = new Matrix3X3(
+                (float)firstFloat.ConstantValue, (float)secondFloat.ConstantValue, (float)thirdFloat.ConstantValue,
+                (float)fourthFloat.ConstantValue, (float)fifthFloat.ConstantValue, (float)sixthFloat.ConstantValue,
+                (float)seventhFloat.ConstantValue, (float)eighthFloat.ConstantValue, (float)ninthFloat.ConstantValue);
+            return constantMatrix;
+        }
+
+        return Builder.ConstructFloat3x3(m00, m01, m02, m10, m11, m12, m20, m21, m22);
+    }
+
+    public Float3x3 NewFloat3x3(Expression matrixExpression)
+    {
+        if (!HasContext && matrixExpression is Float3x3 float3x3)
+        {
+            Float3x3 constantMatrix = new Float3x3("");
+            constantMatrix.ConstantValue = float3x3.ConstantValue;
+            return constantMatrix;
+        }
+
+        return Builder.AssignNewFloat3x3(matrixExpression);
+    }
+}

+ 5 - 5
src/PixiEditor.ChangeableDocument/Changeables/Graph/FuncInputProperty.cs

@@ -8,7 +8,7 @@ using Drawie.Backend.Core.Shaders.Generation.Expressions;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph;
 
-public class FuncInputProperty<T> : InputProperty<Func<FuncContext, T>>, IFuncInputProperty
+public class FuncInputProperty<T, TContext> : InputProperty<Func<TContext, T>>, IFuncInputProperty where TContext : FuncContext
 {
     private T? constantNonOverrideValue;
     private int lastConstantHashCode;
@@ -20,14 +20,14 @@ public class FuncInputProperty<T> : InputProperty<Func<FuncContext, T>>, IFuncIn
         NonOverridenValue = _ => constantNonOverrideValue;
     }
 
-    protected override void NonOverridenValueSet(Func<FuncContext, T> value)
+    protected override void NonOverridenValueSet(Func<TContext, T> value)
     {
-        constantNonOverrideValue = value(FuncContext.NoContext);
+        constantNonOverrideValue = value(null);
     }
 
     protected internal override object FuncFactory(object toReturn)
     {
-        Func<FuncContext, T> func = _ =>
+        Func<TContext, T> func = _ =>
         {
             if (typeof(T).IsAssignableTo(typeof(ShaderExpressionVariable)))
             {
@@ -43,7 +43,7 @@ public class FuncInputProperty<T> : InputProperty<Func<FuncContext, T>>, IFuncIn
 
     protected override object FuncFactoryDelegate(Delegate delegateToCast)
     {
-        Func<FuncContext, T> func = f =>
+        Func<TContext, T> func = f =>
         {
             Type targetType = typeof(T);
             bool isShaderExpression = false;

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changeables/Graph/FuncOutputProperty.cs

@@ -5,9 +5,9 @@ using Drawie.Backend.Core.Shaders;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph;
 
-public class FuncOutputProperty<T> : OutputProperty<Func<FuncContext, T>>
+public class FuncOutputProperty<T, TContext> : OutputProperty<Func<TContext, T>> where TContext : FuncContext
 {
-    internal FuncOutputProperty(Node node, string internalName, string displayName, Func<FuncContext, T> defaultValue) : base(node, internalName, displayName, defaultValue)
+    internal FuncOutputProperty(Node node, string internalName, string displayName, Func<TContext, T> defaultValue) : base(node, internalName, displayName, defaultValue)
     {
         
     }

+ 6 - 6
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Animable/EasingNode.cs

@@ -7,22 +7,22 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Animable;
 [NodeInfo("Easing")]
 public class EasingNode : Node
 {
-    public FuncInputProperty<Float1> Value { get; }
+    public FuncInputProperty<Float1, ShaderFuncContext> Value { get; }
     public InputProperty<EasingType> Easing { get; }
-    public FuncOutputProperty<Float1> Output { get; }
+    public FuncOutputProperty<Float1, ShaderFuncContext> Output { get; }
 
     public EasingNode()
     {
-        Value = CreateFuncInput<Float1>("Value", "VALUE", 0.0);
+        Value = CreateFuncInput<Float1, ShaderFuncContext>("Value", "VALUE", 0.0);
         Easing = CreateInput("EasingType", "EASING_TYPE", EasingType.Linear);
-        Output = CreateFuncOutput<Float1>("Output", "OUTPUT", Evaluate);
+        Output = CreateFuncOutput<Float1, ShaderFuncContext>("Output", "OUTPUT", Evaluate);
     }
 
     protected override void OnExecute(RenderContext context)
     {
     }
 
-    public Float1 Evaluate(FuncContext context)
+    public Float1 Evaluate(ShaderFuncContext context)
     {
         var x = context.GetValue(Value);
         if (!context.HasContext)
@@ -121,7 +121,7 @@ public class EasingNode : Node
         return n1 * (x -= 2.625 / d1) * x + 0.984375;
     }
 
-    private Expression? GetExpression(FuncContext ctx)
+    private Expression? GetExpression(ShaderFuncContext ctx)
     {
         Float1 x = ctx.GetValue(Value);
         return Easing.Value switch

+ 13 - 13
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Calculations/RemapNode.cs

@@ -8,27 +8,27 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Calculations;
 [NodeInfo("Remap")]
 public class RemapNode : Node
 {
-    public FuncInputProperty<Float1> OldMin { get; }
-    public FuncInputProperty<Float1> OldMax { get; }
-    public FuncInputProperty<Float1> NewMin { get; }
-    public FuncInputProperty<Float1> NewMax { get; }
+    public FuncInputProperty<Float1, ShaderFuncContext> OldMin { get; }
+    public FuncInputProperty<Float1, ShaderFuncContext> OldMax { get; }
+    public FuncInputProperty<Float1, ShaderFuncContext> NewMin { get; }
+    public FuncInputProperty<Float1, ShaderFuncContext> NewMax { get; }
 
-    public FuncInputProperty<Float1> Value { get; }
+    public FuncInputProperty<Float1, ShaderFuncContext> Value { get; }
 
-    public FuncOutputProperty<Float1> Result { get; }
+    public FuncOutputProperty<Float1, ShaderFuncContext> Result { get; }
 
     public RemapNode()
     {
-        OldMin = CreateFuncInput<Float1>("OldMin", "OLD_MIN", 0.0);
-        OldMax = CreateFuncInput<Float1>("OldMax", "OLD_MAX", 1.0);
-        NewMin = CreateFuncInput<Float1>("NewMin", "NEW_MIN", 0.0);
-        NewMax = CreateFuncInput<Float1>("NewMax", "NEW_MAX", 1.0);
-        Value = CreateFuncInput<Float1>("Value", "VALUE", 0.5);
+        OldMin = CreateFuncInput<Float1, ShaderFuncContext>("OldMin", "OLD_MIN", 0.0);
+        OldMax = CreateFuncInput<Float1, ShaderFuncContext>("OldMax", "OLD_MAX", 1.0);
+        NewMin = CreateFuncInput<Float1, ShaderFuncContext>("NewMin", "NEW_MIN", 0.0);
+        NewMax = CreateFuncInput<Float1, ShaderFuncContext>("NewMax", "NEW_MAX", 1.0);
+        Value = CreateFuncInput<Float1, ShaderFuncContext>("Value", "VALUE", 0.5);
 
-        Result = CreateFuncOutput<Float1>("Result", "RESULT", Remap);
+        Result = CreateFuncOutput<Float1, ShaderFuncContext>("Result", "RESULT", Remap);
     }
 
-    private Float1 Remap(FuncContext context)
+    private Float1 Remap(ShaderFuncContext context)
     {
         if (context.HasContext)
         {

+ 5 - 4
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ColorNode.cs

@@ -1,5 +1,6 @@
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.Shaders.Generation.Expressions;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Context;
 using PixiEditor.ChangeableDocument.Rendering;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
@@ -7,13 +8,13 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 [NodeInfo("Color")]
 public class ColorNode : Node
 {
-    public FuncInputProperty<Half4> InputColor { get; }
-    public FuncOutputProperty<Half4> Color { get; }
+    public FuncInputProperty<Half4, ShaderFuncContext> InputColor { get; }
+    public FuncOutputProperty<Half4, ShaderFuncContext> Color { get; }
     
     public ColorNode()
     {
-        InputColor = CreateFuncInput<Half4>("InputColor", "COLOR", Colors.White);
-        Color = CreateFuncOutput<Half4>("OutputColor", "COLOR", ctx => ctx.GetValue(InputColor));
+        InputColor = CreateFuncInput<Half4, ShaderFuncContext>("InputColor", "COLOR", Colors.White);
+        Color = CreateFuncOutput<Half4, ShaderFuncContext>("OutputColor", "COLOR", ctx => ctx.GetValue(InputColor));
     }
     
     protected override void OnExecute(RenderContext context)

+ 14 - 14
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/CombineColorNode.cs

@@ -9,39 +9,39 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
 [NodeInfo("CombineColor")]
 public class CombineColorNode : Node
 {
-    public FuncOutputProperty<Half4> Color { get; }
+    public FuncOutputProperty<Half4, ShaderFuncContext> Color { get; }
 
     public InputProperty<CombineSeparateColorMode> Mode { get; }
 
     /// <summary>
     /// Represents either Red 'R' or Hue 'H' depending on <see cref="Mode"/>
     /// </summary>
-    public FuncInputProperty<Float1> V1 { get; }
+    public FuncInputProperty<Float1, ShaderFuncContext> V1 { get; }
 
     /// <summary>
     /// Represents either Green 'G' or Saturation 'S' depending on <see cref="Mode"/>
     /// </summary>
-    public FuncInputProperty<Float1> V2 { get; }
+    public FuncInputProperty<Float1, ShaderFuncContext> V2 { get; }
 
     /// <summary>
     /// Represents either Blue 'B', Value 'V' or Lightness 'L' depending on <see cref="Mode"/>
     /// </summary>
-    public FuncInputProperty<Float1> V3 { get; }
+    public FuncInputProperty<Float1, ShaderFuncContext> V3 { get; }
 
-    public FuncInputProperty<Float1> A { get; }
+    public FuncInputProperty<Float1, ShaderFuncContext> A { get; }
 
     public CombineColorNode()
     {
-        Color = CreateFuncOutput(nameof(Color), "COLOR", GetColor);
+        Color = CreateFuncOutput<Half4, ShaderFuncContext>(nameof(Color), "COLOR", GetColor);
         Mode = CreateInput("Mode", "MODE", CombineSeparateColorMode.RGB);
 
-        V1 = CreateFuncInput<Float1>("R", "R", 0d);
-        V2 = CreateFuncInput<Float1>("G", "G", 0d);
-        V3 = CreateFuncInput<Float1>("B", "B", 0d);
-        A = CreateFuncInput<Float1>("A", "A", 0d);
+        V1 = CreateFuncInput<Float1, ShaderFuncContext>("R", "R", 0d);
+        V2 = CreateFuncInput<Float1, ShaderFuncContext>("G", "G", 0d);
+        V3 = CreateFuncInput<Float1, ShaderFuncContext>("B", "B", 0d);
+        A = CreateFuncInput<Float1, ShaderFuncContext>("A", "A", 0d);
     }
 
-    private Half4 GetColor(FuncContext ctx) =>
+    private Half4 GetColor(ShaderFuncContext ctx) =>
         Mode.Value switch
         {
             CombineSeparateColorMode.RGB => GetRgb(ctx),
@@ -49,7 +49,7 @@ public class CombineColorNode : Node
             CombineSeparateColorMode.HSL => GetHsl(ctx)
         };
 
-    private Half4 GetRgb(FuncContext ctx)
+    private Half4 GetRgb(ShaderFuncContext ctx)
     {
         var r = ctx.GetValue(V1);
         var g = ctx.GetValue(V2);
@@ -59,7 +59,7 @@ public class CombineColorNode : Node
         return ctx.NewHalf4(r, g, b, a); 
     }
 
-    private Half4 GetHsv(FuncContext ctx)
+    private Half4 GetHsv(ShaderFuncContext ctx)
     {
         var h = ctx.GetValue(V1);
         var s = ctx.GetValue(V2);
@@ -69,7 +69,7 @@ public class CombineColorNode : Node
         return ctx.HsvaToRgba(h, s, v, a);
     }
     
-    private Half4 GetHsl(FuncContext ctx)
+    private Half4 GetHsl(ShaderFuncContext ctx)
     {
         var h = ctx.GetValue(V1);
         var s = ctx.GetValue(V2);

+ 8 - 8
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/CombineVecDNode.cs

@@ -10,23 +10,23 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
 [NodeInfo("CombineVecD")]
 public class CombineVecDNode : Node
 {
-    public FuncOutputProperty<Float2> Vector { get; }
+    public FuncOutputProperty<Float2, ShaderFuncContext> Vector { get; }
     
-    public FuncInputProperty<Float1> X { get; }
+    public FuncInputProperty<Float1, ShaderFuncContext> X { get; }
     
-    public FuncInputProperty<Float1> Y { get; }
+    public FuncInputProperty<Float1, ShaderFuncContext> Y { get; }
     
     public CombineVecDNode()
     {
-        Vector = CreateFuncOutput(nameof(Vector), "VECTOR", GetVector);
+        Vector = CreateFuncOutput<Float2, ShaderFuncContext>(nameof(Vector), "VECTOR", GetVector);
 
-        X = CreateFuncInput<Float1>(nameof(X), "X", 0);
-        Y = CreateFuncInput<Float1>(nameof(Y), "Y", 0);
+        X = CreateFuncInput<Float1, ShaderFuncContext>(nameof(X), "X", 0);
+        Y = CreateFuncInput<Float1, ShaderFuncContext>(nameof(Y), "Y", 0);
     }
     
-    private Float2 GetVector(FuncContext ctx)
+    private Float2 GetVector(ShaderFuncContext ctx)
     {
-        var x = ctx.GetValue(X); 
+        var x = ctx.GetValue(X);
         var y = ctx.GetValue(Y);
 
         return ctx.NewFloat2(x, y); 

+ 7 - 7
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/CombineVecINode.cs

@@ -9,21 +9,21 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
 [NodeInfo("CombineVecI")]
 public class CombineVecINode : Node
 {
-    public FuncOutputProperty<Int2> Vector { get; }
+    public FuncOutputProperty<Int2, ShaderFuncContext> Vector { get; }
     
-    public FuncInputProperty<Int1> X { get; }
+    public FuncInputProperty<Int1, ShaderFuncContext> X { get; }
     
-    public FuncInputProperty<Int1> Y { get; }
+    public FuncInputProperty<Int1, ShaderFuncContext> Y { get; }
 
     public CombineVecINode()
     {
-        Vector = CreateFuncOutput(nameof(Vector), "VECTOR", GetVector);
+        Vector = CreateFuncOutput<Int2, ShaderFuncContext>(nameof(Vector), "VECTOR", GetVector);
 
-        X = CreateFuncInput<Int1>(nameof(X), "X", 0);
-        Y = CreateFuncInput<Int1>(nameof(Y), "Y", 0);
+        X = CreateFuncInput<Int1, ShaderFuncContext>(nameof(X), "X", 0);
+        Y = CreateFuncInput<Int1, ShaderFuncContext>(nameof(Y), "Y", 0);
     }
 
-    private Int2 GetVector(FuncContext ctx)
+    private Int2 GetVector(ShaderFuncContext ctx)
     {
         var x = ctx.GetValue(X);
         var y = ctx.GetValue(Y);

+ 14 - 14
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/SeparateColorNode.cs

@@ -12,42 +12,42 @@ public class SeparateColorNode : Node
 {
     private readonly NodeVariableAttachments contextVariables = new();
     
-    public FuncInputProperty<Half4> Color { get; }
+    public FuncInputProperty<Half4, ShaderFuncContext> Color { get; }
     
     public InputProperty<CombineSeparateColorMode> Mode { get; }
 
     /// <summary>
     /// Represents either Red 'R' or Hue 'H' depending on <see cref="Mode"/>
     /// </summary>
-    public FuncOutputProperty<Float1> V1 { get; }
+    public FuncOutputProperty<Float1, ShaderFuncContext> V1 { get; }
     
     /// <summary>
     /// Represents either Green 'G' or Saturation 'S' depending on <see cref="Mode"/>
     /// </summary>
-    public FuncOutputProperty<Float1> V2 { get; }
+    public FuncOutputProperty<Float1, ShaderFuncContext> V2 { get; }
     
     /// <summary>
     /// Represents either Blue 'B', Value 'V' or Lightness 'L' depending on <see cref="Mode"/>
     /// </summary>
-    public FuncOutputProperty<Float1> V3 { get; }
+    public FuncOutputProperty<Float1, ShaderFuncContext> V3 { get; }
     
-    public FuncOutputProperty<Float1> A { get; }
+    public FuncOutputProperty<Float1, ShaderFuncContext> A { get; }
     
     public SeparateColorNode()
     {
-        V1 = CreateFuncOutput<Float1>("R", "R", ctx => GetColor(ctx).R);
-        V2 = CreateFuncOutput<Float1>("G", "G", ctx => GetColor(ctx).G);
-        V3 = CreateFuncOutput<Float1>("B", "B", ctx => GetColor(ctx).B);
-        A = CreateFuncOutput<Float1>("A", "A", ctx => GetColor(ctx).A);
+        V1 = CreateFuncOutput<Float1, ShaderFuncContext>("R", "R", ctx => GetColor(ctx).R);
+        V2 = CreateFuncOutput<Float1, ShaderFuncContext>("G", "G", ctx => GetColor(ctx).G);
+        V3 = CreateFuncOutput<Float1, ShaderFuncContext>("B", "B", ctx => GetColor(ctx).B);
+        A = CreateFuncOutput<Float1, ShaderFuncContext>("A", "A", ctx => GetColor(ctx).A);
         Mode = CreateInput("Mode", "MODE", CombineSeparateColorMode.RGB);
-        Color = CreateFuncInput<Half4>(nameof(Color), "COLOR", new Color());
+        Color = CreateFuncInput<Half4, ShaderFuncContext>(nameof(Color), "COLOR", new Color());
     }
 
     protected override void OnExecute(RenderContext context)
     {
     }
     
-    private Half4 GetColor(FuncContext ctx) =>
+    private Half4 GetColor(ShaderFuncContext ctx) =>
         Mode.Value switch
         {
             CombineSeparateColorMode.RGB => GetRgba(ctx),
@@ -55,13 +55,13 @@ public class SeparateColorNode : Node
             CombineSeparateColorMode.HSL => GetHsla(ctx)
         };
 
-    private Half4 GetRgba(FuncContext ctx) => 
+    private Half4 GetRgba(ShaderFuncContext ctx) =>
         ctx.HasContext ? contextVariables.GetOrAttachNew(ctx, Color, () => ctx.GetValue(Color)) : ctx.GetValue(Color);
 
-    private Half4 GetHsva(FuncContext ctx) =>
+    private Half4 GetHsva(ShaderFuncContext ctx) =>
         ctx.HasContext ? contextVariables.GetOrAttachNew(ctx, Color, () => ctx.RgbaToHsva(ctx.GetValue(Color))) : ctx.RgbaToHsva(ctx.GetValue(Color));
 
-    private Half4 GetHsla(FuncContext ctx) =>
+    private Half4 GetHsla(ShaderFuncContext ctx) =>
         ctx.HasContext ? contextVariables.GetOrAttachNew(ctx, Color, () => ctx.RgbaToHsla(ctx.GetValue(Color))) : ctx.RgbaToHsla(ctx.GetValue(Color));
 
     public override Node CreateCopy() => new SeparateColorNode();

+ 6 - 6
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/SeparateVecDNode.cs

@@ -9,17 +9,17 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
 [NodeInfo("SeparateVecD")]
 public class SeparateVecDNode : Node
 {
-    public FuncInputProperty<Float2> Vector { get; }
+    public FuncInputProperty<Float2, ShaderFuncContext> Vector { get; }
     
-    public FuncOutputProperty<Float1> X { get; }
+    public FuncOutputProperty<Float1, ShaderFuncContext> X { get; }
     
-    public FuncOutputProperty<Float1> Y { get; }
+    public FuncOutputProperty<Float1, ShaderFuncContext> Y { get; }
     
     public SeparateVecDNode()
     {
-        X = CreateFuncOutput<Float1>("X", "X", ctx => ctx.GetValue(Vector).X);
-        Y = CreateFuncOutput("Y", "Y", ctx => ctx.GetValue(Vector).Y);
-        Vector = CreateFuncInput<Float2>("Vector", "VECTOR", VecD.Zero);
+        X = CreateFuncOutput<Float1, ShaderFuncContext>("X", "X", ctx => ctx.GetValue(Vector).X);
+        Y = CreateFuncOutput<Float1, ShaderFuncContext>("Y", "Y", ctx => ctx.GetValue(Vector).Y);
+        Vector = CreateFuncInput<Float2, ShaderFuncContext>("Vector", "VECTOR", VecD.Zero);
     }
 
     protected override void OnExecute(RenderContext context)

+ 7 - 6
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/SeparateVecINode.cs

@@ -2,23 +2,24 @@
 using Drawie.Backend.Core;
 using Drawie.Backend.Core.Shaders.Generation.Expressions;
 using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Context;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
 
 [NodeInfo("SeparateVecI")]
 public class SeparateVecINode : Node
 {
-    public FuncInputProperty<Int2> Vector { get; }
+    public FuncInputProperty<Int2, ShaderFuncContext> Vector { get; }
     
-    public FuncOutputProperty<Int1> X { get; }
+    public FuncOutputProperty<Int1, ShaderFuncContext> X { get; }
     
-    public FuncOutputProperty<Int1> Y { get; }
+    public FuncOutputProperty<Int1, ShaderFuncContext> Y { get; }
     
     public SeparateVecINode()
     {
-        X = CreateFuncOutput<Int1>("X", "X", ctx => ctx.GetValue(Vector).X);
-        Y = CreateFuncOutput<Int1>("Y", "Y", ctx => ctx.GetValue(Vector).Y);
-        Vector = CreateFuncInput<Int2>("Vector", "VECTOR", new VecI(0, 0));
+        X = CreateFuncOutput<Int1, ShaderFuncContext>("X", "X", ctx => ctx.GetValue(Vector).X);
+        Y = CreateFuncOutput<Int1, ShaderFuncContext>("Y", "Y", ctx => ctx.GetValue(Vector).Y);
+        Vector = CreateFuncInput<Int2, ShaderFuncContext>("Vector", "VECTOR", new VecI(0, 0));
     }
 
     protected override void OnExecute(RenderContext context)

+ 9 - 9
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/LerpColorNode.cs

@@ -10,20 +10,20 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 [NodeInfo("Lerp")]
 public class LerpColorNode : Node // TODO: ILerpable as inputs? 
 {
-    public FuncOutputProperty<Half4> Result { get; }
-    public FuncInputProperty<Half4> From { get; }
-    public FuncInputProperty<Half4> To { get; }
-    public FuncInputProperty<Float1> Time { get; }
+    public FuncOutputProperty<Half4, ShaderFuncContext> Result { get; }
+    public FuncInputProperty<Half4, ShaderFuncContext> From { get; }
+    public FuncInputProperty<Half4, ShaderFuncContext> To { get; }
+    public FuncInputProperty<Float1, ShaderFuncContext> Time { get; }
 
     public LerpColorNode()
     {
-        Result = CreateFuncOutput<Half4>("Result", "RESULT", Lerp);
-        From = CreateFuncInput<Half4>("From", "FROM", Colors.Black);
-        To = CreateFuncInput<Half4>("To", "TO", Colors.White);
-        Time = CreateFuncInput<Float1>("Time", "TIME", 0.5);
+        Result = CreateFuncOutput<Half4, ShaderFuncContext>("Result", "RESULT", Lerp);
+        From = CreateFuncInput<Half4, ShaderFuncContext>("From", "FROM", Colors.Black);
+        To = CreateFuncInput<Half4, ShaderFuncContext>("To", "TO", Colors.White);
+        Time = CreateFuncInput<Float1, ShaderFuncContext>("Time", "TIME", 0.5);
     }
 
-    private Half4 Lerp(FuncContext arg)
+    private Half4 Lerp(ShaderFuncContext arg)
     {
         var from = arg.GetValue(From);
         var to = arg.GetValue(To);

+ 10 - 10
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/MathNode.cs

@@ -13,29 +13,29 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 [NodeInfo("Math")]
 public class MathNode : Node
 {
-    public FuncOutputProperty<Float1> Result { get; }
+    public FuncOutputProperty<Float1, ShaderFuncContext> Result { get; }
 
     public InputProperty<MathNodeMode> Mode { get; }
     
     public InputProperty<bool> Clamp { get; }
 
-    public FuncInputProperty<Float1> X { get; }
+    public FuncInputProperty<Float1, ShaderFuncContext> X { get; }
     
-    public FuncInputProperty<Float1> Y { get; }
+    public FuncInputProperty<Float1, ShaderFuncContext> Y { get; }
     
-    public FuncInputProperty<Float1> Z { get; }
+    public FuncInputProperty<Float1, ShaderFuncContext> Z { get; }
     
     public MathNode()
     {
-        Result = CreateFuncOutput<Float1>(nameof(Result), "RESULT", Calculate);
+        Result = CreateFuncOutput<Float1, ShaderFuncContext>(nameof(Result), "RESULT", Calculate);
         Mode = CreateInput(nameof(Mode), "MATH_MODE", MathNodeMode.Add);
         Clamp = CreateInput(nameof(Clamp), "CLAMP", false);
-        X = CreateFuncInput<Float1>(nameof(X), "X", 0d);
-        Y = CreateFuncInput<Float1>(nameof(Y), "Y", 0d);
-        Z = CreateFuncInput<Float1>(nameof(Z), "Z", 0d);
+        X = CreateFuncInput<Float1, ShaderFuncContext>(nameof(X), "X", 0d);
+        Y = CreateFuncInput<Float1, ShaderFuncContext>(nameof(Y), "Y", 0d);
+        Z = CreateFuncInput<Float1, ShaderFuncContext>(nameof(Z), "Z", 0d);
     }
 
-    private Float1 Calculate(FuncContext context)
+    private Float1 Calculate(ShaderFuncContext context)
     {
         var (x, y, z) = GetValues(context);
 
@@ -125,7 +125,7 @@ public class MathNode : Node
         return new Float1(string.Empty) { ConstantValue = constValue };
     }
 
-    private (Float1 xConst, Float1 y, Float1 z) GetValues(FuncContext context)
+    private (Float1 xConst, Float1 y, Float1 z) GetValues(ShaderFuncContext context)
     {
         return (context.GetValue(X), context.GetValue(Y), context.GetValue(Z));
     }

+ 23 - 23
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Matrix/ComposeMatrixNode.cs

@@ -9,38 +9,38 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Matrix;
 [NodeInfo("ComposeMatrix")]
 public class ComposeMatrixNode : Node
 {
-    public FuncInputProperty<Float3x3> MatrixInput { get; }
-    public FuncInputProperty<Float1> ScaleX { get; }
-    public FuncInputProperty<Float1> SkewX { get; }
-    public FuncInputProperty<Float1> TransX { get; }
-    public FuncInputProperty<Float1> SkewY { get; }
-    public FuncInputProperty<Float1> ScaleY { get; }
-    public FuncInputProperty<Float1> TransY { get; }
-    public FuncInputProperty<Float1> Persp0 { get; }
-    public FuncInputProperty<Float1> Persp1 { get; }
-    public FuncInputProperty<Float1> Persp2 { get; }
+    public FuncInputProperty<Float3x3, ShaderFuncContext> MatrixInput { get; }
+    public FuncInputProperty<Float1, ShaderFuncContext> ScaleX { get; }
+    public FuncInputProperty<Float1, ShaderFuncContext> SkewX { get; }
+    public FuncInputProperty<Float1, ShaderFuncContext> TransX { get; }
+    public FuncInputProperty<Float1, ShaderFuncContext> SkewY { get; }
+    public FuncInputProperty<Float1, ShaderFuncContext> ScaleY { get; }
+    public FuncInputProperty<Float1, ShaderFuncContext> TransY { get; }
+    public FuncInputProperty<Float1, ShaderFuncContext> Persp0 { get; }
+    public FuncInputProperty<Float1, ShaderFuncContext> Persp1 { get; }
+    public FuncInputProperty<Float1, ShaderFuncContext> Persp2 { get; }
 
-    public FuncOutputProperty<Float3x3> Matrix { get; }
+    public FuncOutputProperty<Float3x3, ShaderFuncContext> Matrix { get; }
 
     public ComposeMatrixNode()
     {
-        MatrixInput = CreateFuncInput<Float3x3>("MatrixInput", "INPUT_MATRIX",
+        MatrixInput = CreateFuncInput<Float3x3, ShaderFuncContext>("MatrixInput", "INPUT_MATRIX",
             new Float3x3("") { ConstantValue = Matrix3X3.Identity });
 
-        ScaleX = CreateFuncInput<Float1>("ScaleX", "SCALE_X", 1.0f);
-        ScaleY = CreateFuncInput<Float1>("ScaleY", "SCALE_Y", 1.0f);
-        SkewX = CreateFuncInput<Float1>("SkewX", "SKEW_X", 0.0f);
-        SkewY = CreateFuncInput<Float1>("SkewY", "SKEW_Y", 0.0f);
-        TransX = CreateFuncInput<Float1>("TranslateX", "TRANSLATE_X", 0.0f);
-        TransY = CreateFuncInput<Float1>("TranslateY", "TRANSLATE_Y", 0.0f);
-        Persp0 = CreateFuncInput<Float1>("Perspective0", "PERSPECTIVE_0", 0.0f);
-        Persp1 = CreateFuncInput<Float1>("Perspective1", "PERSPECTIVE_1", 0.0f);
-        Persp2 = CreateFuncInput<Float1>("Perspective2", "PERSPECTIVE_2", 1.0f);
+        ScaleX = CreateFuncInput<Float1, ShaderFuncContext>("ScaleX", "SCALE_X", 1.0f);
+        ScaleY = CreateFuncInput<Float1, ShaderFuncContext>("ScaleY", "SCALE_Y", 1.0f);
+        SkewX = CreateFuncInput<Float1, ShaderFuncContext>("SkewX", "SKEW_X", 0.0f);
+        SkewY = CreateFuncInput<Float1, ShaderFuncContext>("SkewY", "SKEW_Y", 0.0f);
+        TransX = CreateFuncInput<Float1, ShaderFuncContext>("TranslateX", "TRANSLATE_X", 0.0f);
+        TransY = CreateFuncInput<Float1, ShaderFuncContext>("TranslateY", "TRANSLATE_Y", 0.0f);
+        Persp0 = CreateFuncInput<Float1, ShaderFuncContext>("Perspective0", "PERSPECTIVE_0", 0.0f);
+        Persp1 = CreateFuncInput<Float1, ShaderFuncContext>("Perspective1", "PERSPECTIVE_1", 0.0f);
+        Persp2 = CreateFuncInput<Float1, ShaderFuncContext>("Perspective2", "PERSPECTIVE_2", 1.0f);
 
-        Matrix = CreateFuncOutput<Float3x3>("Matrix", "MATRIX", ComposeMatrix);
+        Matrix = CreateFuncOutput<Float3x3, ShaderFuncContext>("Matrix", "MATRIX", ComposeMatrix);
     }
 
-    private Float3x3 ComposeMatrix(FuncContext context)
+    private Float3x3 ComposeMatrix(ShaderFuncContext context)
     {
         if (context.HasContext)
         {

+ 21 - 20
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Matrix/DecomposeMatrixNode.cs

@@ -1,6 +1,7 @@
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Shaders.Generation.Expressions;
 using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Context;
 using PixiEditor.ChangeableDocument.Rendering;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Matrix;
@@ -8,31 +9,31 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Matrix;
 [NodeInfo("DecomposeMatrix")]
 public class DecomposeMatrixNode : Node
 {
-    public FuncInputProperty<Float3x3> Matrix { get; }
+    public FuncInputProperty<Float3x3, ShaderFuncContext> Matrix { get; }
 
-    public FuncOutputProperty<Float1> ScaleX { get; }
-    public FuncOutputProperty<Float1> SkewX { get; }
-    public FuncOutputProperty<Float1> TransX { get; }
-    public FuncOutputProperty<Float1> SkewY { get; }
-    public FuncOutputProperty<Float1> ScaleY { get; }
-    public FuncOutputProperty<Float1> TransY { get; }
-    public FuncOutputProperty<Float1> Persp0 { get; }
-    public FuncOutputProperty<Float1> Persp1 { get; }
-    public FuncOutputProperty<Float1> Persp2 { get; }
+    public FuncOutputProperty<Float1, ShaderFuncContext> ScaleX { get; }
+    public FuncOutputProperty<Float1, ShaderFuncContext> SkewX { get; }
+    public FuncOutputProperty<Float1, ShaderFuncContext> TransX { get; }
+    public FuncOutputProperty<Float1, ShaderFuncContext> SkewY { get; }
+    public FuncOutputProperty<Float1, ShaderFuncContext> ScaleY { get; }
+    public FuncOutputProperty<Float1, ShaderFuncContext> TransY { get; }
+    public FuncOutputProperty<Float1, ShaderFuncContext> Persp0 { get; }
+    public FuncOutputProperty<Float1, ShaderFuncContext> Persp1 { get; }
+    public FuncOutputProperty<Float1, ShaderFuncContext> Persp2 { get; }
 
     public DecomposeMatrixNode()
     {
-        Matrix = CreateFuncInput<Float3x3>("Matrix", "MATRIX",
+        Matrix = CreateFuncInput<Float3x3, ShaderFuncContext>("Matrix", "MATRIX",
             new Float3x3("") { ConstantValue = Matrix3X3.Identity });
-        ScaleX = CreateFuncOutput<Float1>("ScaleX", "SCALE_X", context => context.GetValue(Matrix).M11);
-        ScaleY = CreateFuncOutput<Float1>("ScaleY", "SCALE_Y", context => context.GetValue(Matrix).M22);
-        SkewX = CreateFuncOutput<Float1>("SkewX", "SKEW_X", context => context.GetValue(Matrix).M12);
-        SkewY = CreateFuncOutput<Float1>("SkewY", "SKEW_Y", context => context.GetValue(Matrix).M21);
-        TransX = CreateFuncOutput<Float1>("TranslateX", "TRANSLATE_X", context => context.GetValue(Matrix).M13);
-        TransY = CreateFuncOutput<Float1>("TranslateY", "TRANSLATE_Y", context => context.GetValue(Matrix).M23);
-        Persp0 = CreateFuncOutput<Float1>("Perspective0", "PERSPECTIVE_0", context => context.GetValue(Matrix).M31);
-        Persp1 = CreateFuncOutput<Float1>("Perspective1", "PERSPECTIVE_1", context => context.GetValue(Matrix).M32);
-        Persp2 = CreateFuncOutput<Float1>("Perspective2", "PERSPECTIVE_2", context => context.GetValue(Matrix).M33);
+        ScaleX = CreateFuncOutput<Float1, ShaderFuncContext>("ScaleX", "SCALE_X", context => context.GetValue(Matrix).M11);
+        ScaleY = CreateFuncOutput<Float1, ShaderFuncContext>("ScaleY", "SCALE_Y", context => context.GetValue(Matrix).M22);
+        SkewX = CreateFuncOutput<Float1, ShaderFuncContext>("SkewX", "SKEW_X", context => context.GetValue(Matrix).M12);
+        SkewY = CreateFuncOutput<Float1, ShaderFuncContext>("SkewY", "SKEW_Y", context => context.GetValue(Matrix).M21);
+        TransX = CreateFuncOutput<Float1, ShaderFuncContext>("TranslateX", "TRANSLATE_X", context => context.GetValue(Matrix).M13);
+        TransY = CreateFuncOutput<Float1, ShaderFuncContext>("TranslateY", "TRANSLATE_Y", context => context.GetValue(Matrix).M23);
+        Persp0 = CreateFuncOutput<Float1, ShaderFuncContext>("Perspective0", "PERSPECTIVE_0", context => context.GetValue(Matrix).M31);
+        Persp1 = CreateFuncOutput<Float1, ShaderFuncContext>("Perspective1", "PERSPECTIVE_1", context => context.GetValue(Matrix).M32);
+        Persp2 = CreateFuncOutput<Float1, ShaderFuncContext>("Perspective2", "PERSPECTIVE_2", context => context.GetValue(Matrix).M33);
     }
 
     protected override void OnExecute(RenderContext context)

+ 6 - 6
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Matrix/Matrix3X3BaseNode.cs

@@ -12,15 +12,15 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Matrix;
 public abstract class Matrix3X3BaseNode : RenderNode, IRenderInput
 {
     public RenderInputProperty Background { get; }
-    public FuncInputProperty<Float3x3> Input { get; }
-    public FuncOutputProperty<Float3x3> Matrix { get; }
+    public FuncInputProperty<Float3x3, ShaderFuncContext> Input { get; }
+    public FuncOutputProperty<Float3x3, ShaderFuncContext> Matrix { get; }
 
     public Matrix3X3BaseNode()
     {
         Background = CreateRenderInput("Background", "IMAGE");
-        Input = CreateFuncInput<Float3x3>("Input", "INPUT_MATRIX",
+        Input = CreateFuncInput<Float3x3, ShaderFuncContext>("Input", "INPUT_MATRIX",
             new Float3x3("") { ConstantValue = Matrix3X3.Identity });
-        Matrix = CreateFuncOutput<Float3x3>("Matrix", "OUTPUT_MATRIX",
+        Matrix = CreateFuncOutput<Float3x3, ShaderFuncContext>("Matrix", "OUTPUT_MATRIX",
             (c) => CalculateMatrix(c, c.GetValue(Input)));
         Output.FirstInChain = null;
         AllowHighDpiRendering = true;
@@ -38,7 +38,7 @@ public abstract class Matrix3X3BaseNode : RenderNode, IRenderInput
     {
         int layer = surface.Canvas.Save();
 
-        Float3x3 mtx = Matrix.Value.Invoke(FuncContext.NoContext);
+        Float3x3 mtx = Matrix.Value.Invoke(ShaderFuncContext.NoContext);
 
         surface.Canvas.SetMatrix(
             surface.Canvas.TotalMatrix.Concat(mtx.GetConstant() as Matrix3X3? ?? Matrix3X3.Identity));
@@ -66,5 +66,5 @@ public abstract class Matrix3X3BaseNode : RenderNode, IRenderInput
         return base.RenderPreview(renderOn, context, elementToRenderName);
     }
 
-    protected abstract Float3x3 CalculateMatrix(FuncContext ctx, Float3x3 input);
+    protected abstract Float3x3 CalculateMatrix(ShaderFuncContext ctx, Float3x3 input);
 }

+ 3 - 3
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Matrix/OffsetNode.cs

@@ -8,14 +8,14 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Matrix;
 [NodeInfo("Offset")]
 public class OffsetNode : Matrix3X3BaseNode
 {
-    public FuncInputProperty<Float2> Translation { get; }
+    public FuncInputProperty<Float2, ShaderFuncContext> Translation { get; }
 
     public OffsetNode()
     {
-        Translation = CreateFuncInput<Float2>("Offset", "OFFSET", VecD.Zero);
+        Translation = CreateFuncInput<Float2, ShaderFuncContext>("Offset", "OFFSET", VecD.Zero);
     }
 
-    protected override Float3x3 CalculateMatrix(FuncContext ctx, Float3x3 input)
+    protected override Float3x3 CalculateMatrix(ShaderFuncContext ctx, Float3x3 input)
     {
         Float2 translation = ctx.GetValue(Translation);
 

+ 5 - 5
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Matrix/RotateNode.cs

@@ -9,17 +9,17 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Matrix;
 public class RotateNode : Matrix3X3BaseNode
 {
     public InputProperty<RotationType> RotationType { get; }
-    public FuncInputProperty<Float1> Angle { get; }
-    public FuncInputProperty<Float2> Center { get; }
+    public FuncInputProperty<Float1, ShaderFuncContext> Angle { get; }
+    public FuncInputProperty<Float2, ShaderFuncContext> Center { get; }
 
     public RotateNode()
     {
         RotationType = CreateInput("RotationType", "UNIT", Nodes.Matrix.RotationType.Degrees);
-        Angle = CreateFuncInput<Float1>("Angle", "ANGLE", 0.0);
-        Center = CreateFuncInput<Float2>("Center", "CENTER", new VecD(0, 0));
+        Angle = CreateFuncInput<Float1, ShaderFuncContext>("Angle", "ANGLE", 0.0);
+        Center = CreateFuncInput<Float2, ShaderFuncContext>("Center", "CENTER", new VecD(0, 0));
     }
 
-    protected override Float3x3 CalculateMatrix(FuncContext ctx, Float3x3 input)
+    protected override Float3x3 CalculateMatrix(ShaderFuncContext ctx, Float3x3 input)
     {
         Float1 angle = ctx.GetValue(Angle);
 

+ 5 - 5
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Matrix/ScaleNode.cs

@@ -8,16 +8,16 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Matrix;
 [NodeInfo("Scale")]
 public class ScaleNode : Matrix3X3BaseNode
 {
-    public FuncInputProperty<Float2> Scale { get; }
-    public FuncInputProperty<Float2> Center { get; }
+    public FuncInputProperty<Float2, ShaderFuncContext> Scale { get; }
+    public FuncInputProperty<Float2, ShaderFuncContext> Center { get; }
 
     public ScaleNode()
     {
-        Scale = CreateFuncInput<Float2>("Scale", "SCALE", new VecD(1, 1));
-        Center = CreateFuncInput<Float2>("Center", "CENTER", new VecD(0, 0));
+        Scale = CreateFuncInput<Float2, ShaderFuncContext>("Scale", "SCALE", new VecD(1, 1));
+        Center = CreateFuncInput<Float2, ShaderFuncContext>("Center", "CENTER", new VecD(0, 0));
     }
 
-    protected override Float3x3 CalculateMatrix(FuncContext ctx, Float3x3 input)
+    protected override Float3x3 CalculateMatrix(ShaderFuncContext ctx, Float3x3 input)
     {
         Float2 scale = ctx.GetValue(Scale);
         Float2 center = ctx.GetValue(Center);

+ 3 - 3
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Matrix/SkewNode.cs

@@ -8,14 +8,14 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Matrix;
 [NodeInfo("Skew")]
 public class SkewNode : Matrix3X3BaseNode
 {
-    public FuncInputProperty<Float2> Skew { get; }
+    public FuncInputProperty<Float2, ShaderFuncContext> Skew { get; }
 
     public SkewNode()
     {
-        Skew = CreateFuncInput<Float2>("Skew", "SKEW", VecD.Zero);
+        Skew = CreateFuncInput<Float2, ShaderFuncContext>("Skew", "SKEW", VecD.Zero);
     }
 
-    protected override Float3x3 CalculateMatrix(FuncContext ctx, Float3x3 input)
+    protected override Float3x3 CalculateMatrix(ShaderFuncContext ctx, Float3x3 input)
     {
         Float2 skew = ctx.GetValue(Skew);
 

+ 6 - 6
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Matrix/TransformNode.cs

@@ -7,17 +7,17 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Matrix;
 [NodeInfo("Transform")]
 public class TransformNode : Matrix3X3BaseNode
 {
-    public FuncInputProperty<Float2> Position { get; }
-    public FuncOutputProperty<Float2> TransformedPosition { get; }
+    public FuncInputProperty<Float2, ShaderFuncContext> Position { get; }
+    public FuncOutputProperty<Float2, ShaderFuncContext> TransformedPosition { get; }
 
     public TransformNode()
     {
-        Position = CreateFuncInput<Float2>("Position", "POSITION", VecD.Zero);
+        Position = CreateFuncInput<Float2, ShaderFuncContext>("Position", "POSITION", VecD.Zero);
         TransformedPosition =
-            CreateFuncOutput<Float2>("TransformedPosition", "TRANSFORMED_POSITION", TransformPosition);
+            CreateFuncOutput<Float2, ShaderFuncContext>("TransformedPosition", "TRANSFORMED_POSITION", TransformPosition);
     }
 
-    private Float2 TransformPosition(FuncContext arg)
+    private Float2 TransformPosition(ShaderFuncContext arg)
     {
         if (arg.HasContext)
         {
@@ -39,7 +39,7 @@ public class TransformNode : Matrix3X3BaseNode
         return new TransformNode();
     }
 
-    protected override Float3x3 CalculateMatrix(FuncContext ctx, Float3x3 input)
+    protected override Float3x3 CalculateMatrix(ShaderFuncContext ctx, Float3x3 input)
     {
         return input;
     }

+ 5 - 5
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ModifyImageLeftNode.cs

@@ -16,9 +16,9 @@ public class ModifyImageLeftNode : Node, IPairNode, IPreviewRenderable
 {
     public InputProperty<Texture?> Image { get; }
 
-    public FuncOutputProperty<Float2> Coordinate { get; }
+    public FuncOutputProperty<Float2, ShaderFuncContext> Coordinate { get; }
 
-    public FuncOutputProperty<Half4> Color { get; }
+    public FuncOutputProperty<Half4, ShaderFuncContext> Color { get; }
     
     public InputProperty<ColorSampleMode> SampleMode { get; }
     public InputProperty<bool> NormalizeCoordinates { get; }
@@ -28,13 +28,13 @@ public class ModifyImageLeftNode : Node, IPairNode, IPreviewRenderable
     public ModifyImageLeftNode()
     {
         Image = CreateInput<Texture?>("Surface", "IMAGE", null);
-        Coordinate = CreateFuncOutput("Coordinate", "UV", ctx => ctx.OriginalPosition ?? new Float2(""));
-        Color = CreateFuncOutput("Color", "COLOR", GetColor);
+        Coordinate = CreateFuncOutput<Float2, ShaderFuncContext>("Coordinate", "UV", ctx => ctx.OriginalPosition ?? new Float2(""));
+        Color = CreateFuncOutput<Half4, ShaderFuncContext>("Color", "COLOR", GetColor);
         SampleMode = CreateInput("SampleMode", "COLOR_SAMPLE_MODE", ColorSampleMode.ColorManaged);
         NormalizeCoordinates = CreateInput("NormalizeCoordinates", "NORMALIZE_COORDINATES", true);
     }
     
-    private Half4 GetColor(FuncContext context)
+    private Half4 GetColor(ShaderFuncContext context)
     {
         context.ThrowOnMissingContext();
         

+ 8 - 7
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ModifyImageRightNode.cs

@@ -2,6 +2,7 @@
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Rendering;
 using Drawie.Backend.Core;
+using Drawie.Backend.Core.Shaders;
 using Drawie.Backend.Core.Shaders.Generation;
 using Drawie.Backend.Core.Shaders.Generation.Expressions;
 using Drawie.Backend.Core.Surfaces;
@@ -18,8 +19,8 @@ public class ModifyImageRightNode : RenderNode, IPairNode, ICustomShaderNode
 
     private Paint drawingPaint = new Paint() { BlendMode = BlendMode.SrcOver };
 
-    public FuncInputProperty<Float2> Coordinate { get; }
-    public FuncInputProperty<Half4> Color { get; }
+    public FuncInputProperty<Float2, ShaderFuncContext> Coordinate { get; }
+    public FuncInputProperty<Half4, ShaderFuncContext> Color { get; }
 
 
     private string _lastSksl;
@@ -31,8 +32,8 @@ public class ModifyImageRightNode : RenderNode, IPairNode, ICustomShaderNode
 
     public ModifyImageRightNode()
     {
-        Coordinate = CreateFuncInput(nameof(Coordinate), "UV", new Float2("coords"));
-        Color = CreateFuncInput(nameof(Color), "COLOR", new Half4(""));
+        Coordinate = CreateFuncInput<Float2, ShaderFuncContext>(nameof(Coordinate), "UV", new Float2("coords"));
+        Color = CreateFuncInput<Half4, ShaderFuncContext>(nameof(Color), "COLOR", new Half4(""));
 
         RendersInAbsoluteCoordinates = true;
     }
@@ -64,7 +65,7 @@ public class ModifyImageRightNode : RenderNode, IPairNode, ICustomShaderNode
         size = imgSize;
 
         ShaderBuilder builder = new(size.Value, startNode.NormalizeCoordinates.Value);
-        FuncContext context = new(renderContext, builder);
+        ShaderFuncContext context = new(renderContext, builder);
 
         if (Coordinate.Connection != null)
         {
@@ -80,7 +81,7 @@ public class ModifyImageRightNode : RenderNode, IPairNode, ICustomShaderNode
         }
         else
         {
-            var constCoords = Coordinate.NonOverridenValue(FuncContext.NoContext);
+            var constCoords = Coordinate.NonOverridenValue(ShaderFuncContext.NoContext);
             constCoords.VariableName = "constCords";
             builder.AddUniform(constCoords.VariableName, constCoords.ConstantValue);
             builder.Set(context.SamplePosition, constCoords);
@@ -92,7 +93,7 @@ public class ModifyImageRightNode : RenderNode, IPairNode, ICustomShaderNode
         }
         else
         {
-            Half4 color = Color.NonOverridenValue(FuncContext.NoContext);
+            Half4 color = Color.NonOverridenValue(ShaderFuncContext.NoContext);
             color.VariableName = "color";
             builder.AddUniform(color.VariableName, color.ConstantValue);
             builder.ReturnVar(color, false); // Do not premultiply, since we are modifying already premultiplied image

+ 5 - 5
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Node.cs

@@ -364,9 +364,9 @@ public abstract class Node : IReadOnlyNode, IDisposable
     }
 
 
-    protected FuncInputProperty<T> CreateFuncInput<T>(string propName, string displayName, T defaultValue)
+    protected FuncInputProperty<T, TContext> CreateFuncInput<T, TContext>(string propName, string displayName, T defaultValue) where TContext : FuncContext
     {
-        var property = new FuncInputProperty<T>(this, propName, displayName, defaultValue);
+        var property = new FuncInputProperty<T, TContext>(this, propName, displayName, defaultValue);
         if (InputProperties.Any(x => x.InternalPropertyName == propName))
         {
             throw new InvalidOperationException($"Input with name {propName} already exists.");
@@ -390,10 +390,10 @@ public abstract class Node : IReadOnlyNode, IDisposable
         return property;
     }
 
-    protected FuncOutputProperty<T> CreateFuncOutput<T>(string propName, string displayName,
-        Func<FuncContext, T> defaultFunc)
+    protected FuncOutputProperty<T, TContext> CreateFuncOutput<T, TContext>(string propName, string displayName,
+        Func<TContext, T> defaultFunc) where TContext : FuncContext
     {
-        var property = new FuncOutputProperty<T>(this, propName, displayName, defaultFunc);
+        var property = new FuncOutputProperty<T, TContext>(this, propName, displayName, defaultFunc);
         outputs.Add(property);
         return property;
     }

+ 5 - 5
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/SampleImageNode.cs

@@ -14,9 +14,9 @@ public class SampleImageNode : Node
 {
     public InputProperty<Texture?> Image { get; }
 
-    public FuncInputProperty<Float2> Coordinate { get; }
+    public FuncInputProperty<Float2, ShaderFuncContext> Coordinate { get; }
 
-    public FuncOutputProperty<Half4> Color { get; }
+    public FuncOutputProperty<Half4, ShaderFuncContext> Color { get; }
 
     public InputProperty<ColorSampleMode> SampleMode { get; }
 
@@ -25,13 +25,13 @@ public class SampleImageNode : Node
     public SampleImageNode()
     {
         Image = CreateInput<Texture>("Texture", "IMAGE", null);
-        Coordinate = CreateFuncInput<Float2>("Coordinate", "UV", VecD.Zero);
-        Color = CreateFuncOutput("Color", "COLOR", GetColor);
+        Coordinate = CreateFuncInput<Float2, ShaderFuncContext>("Coordinate", "UV", VecD.Zero);
+        Color = CreateFuncOutput<Half4, ShaderFuncContext>("Color", "COLOR", GetColor);
         SampleMode = CreateInput("SampleMode", "COLOR_SAMPLE_MODE", ColorSampleMode.ColorManaged);
         NormalizedCoordinates = CreateInput("NormalizedCoordinates", "NORMALIZE_COORDINATES", true);
     }
 
-    private Half4 GetColor(FuncContext context)
+    private Half4 GetColor(ShaderFuncContext context)
     {
         if (Image.Value is null || Image.Value.IsDisposed)
         {

+ 18 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Utility/RepeatNodeEnd.cs

@@ -0,0 +1,18 @@
+using PixiEditor.ChangeableDocument.Rendering;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Utility;
+
+[NodeInfo("RepeatEnd")]
+[PairNode(typeof(RepeatNodeStart), "RepeatZone", false)]
+public class RepeatNodeEnd : Node
+{
+    protected override void OnExecute(RenderContext context)
+    {
+        throw new NotImplementedException();
+    }
+
+    public override Node CreateCopy()
+    {
+        throw new NotImplementedException();
+    }
+}

+ 42 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Utility/RepeatNodeStart.cs

@@ -0,0 +1,42 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Context;
+using PixiEditor.ChangeableDocument.Rendering;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Utility;
+
+[NodeInfo("RepeatStart")]
+[PairNode(typeof(RepeatNodeEnd), "RepeatZone", true)]
+public class RepeatNodeStart : Node
+{
+    public FuncInputProperty<int, RepeatFuncContext> Iterations { get; }
+    public FuncInputProperty<object, RepeatFuncContext> Input { get; }
+    public FuncOutputProperty<int, RepeatFuncContext> CurrentIteration { get; }
+    public FuncOutputProperty<object, RepeatFuncContext> Output { get; }
+
+    public RepeatNodeStart()
+    {
+        Iterations = CreateFuncInput<int, RepeatFuncContext>("Iterations", "ITERATIONS", 1);
+        Input = CreateFuncInput<object, RepeatFuncContext>("Input", "INPUT", null);
+        CurrentIteration = CreateFuncOutput<int, RepeatFuncContext>("CurrentIteration", "CURRENT_ITERATION", GetIteration);
+        Output = CreateFuncOutput<object, RepeatFuncContext>("Output", "OUTPUT", GetOutput);
+    }
+
+    protected override void OnExecute(RenderContext context)
+    {
+        RepeatFuncContext funcContext = new RepeatFuncContext(1);
+    }
+
+    private int GetIteration(RepeatFuncContext context)
+    {
+        return context.CurrentIteration;
+    }
+
+    private object GetOutput(RepeatFuncContext context)
+    {
+        return null;
+    }
+
+    public override Node CreateCopy()
+    {
+        return new RepeatNodeStart();
+    }
+}

+ 10 - 0
src/PixiEditor/ViewModels/Document/Nodes/Utility/RepeatNodeViewModelEnd.cs

@@ -0,0 +1,10 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Utility;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes.Utility;
+
+[NodeViewModel("REPEAT_END", "UTILITY", null)]
+internal class RepeatNodeViewModelEnd : NodeViewModel<RepeatNodeEnd>, IPairNodeEndViewModel
+{
+
+}

+ 10 - 0
src/PixiEditor/ViewModels/Document/Nodes/Utility/RepeatNodeViewModelStart.cs

@@ -0,0 +1,10 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Utility;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes.Utility;
+
+[NodeViewModel("REPEAT_START", "UTILITY", null)]
+internal class RepeatNodeViewModelStart : NodeViewModel<RepeatNodeStart>, IPairNodeStartViewModel
+{
+
+}