Browse Source

Converted a lot of nodes to use shader builder

flabbet 1 year ago
parent
commit
2b0454d59a
31 changed files with 493 additions and 141 deletions
  1. 22 6
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Context/FuncContext.cs
  2. 16 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/FuncInputProperty.cs
  3. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/NodeGraph.cs
  4. 16 15
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/CombineColorNode.cs
  5. 10 9
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/CombineVecD.cs
  6. 33 11
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/SeparateColorNode.cs
  7. 7 6
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/SeparateVecDNode.cs
  8. 12 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ImageLayerNode.cs
  9. 18 17
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/MathNode.cs
  10. 26 9
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ModifyImageRightNode.cs
  11. 29 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/TextureCache.cs
  12. 7 7
      src/PixiEditor.ChangeableDocument/Rendering/DocumentRenderer.cs
  13. 1 1
      src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IPaintImplementation.cs
  14. 1 0
      src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IShaderImplementation.cs
  15. 1 1
      src/PixiEditor.DrawingApi.Core/Exceptions/ShaderCompilationException.cs
  16. 16 0
      src/PixiEditor.DrawingApi.Core/Shaders/Generation/Expressions/Expression.cs
  17. 15 0
      src/PixiEditor.DrawingApi.Core/Shaders/Generation/Expressions/Float1.cs
  18. 19 0
      src/PixiEditor.DrawingApi.Core/Shaders/Generation/Expressions/Float2.cs
  19. 8 0
      src/PixiEditor.DrawingApi.Core/Shaders/Generation/Expressions/Half4.cs
  20. 46 0
      src/PixiEditor.DrawingApi.Core/Shaders/Generation/Expressions/ShaderMath.cs
  21. 70 5
      src/PixiEditor.DrawingApi.Core/Shaders/Generation/ShaderBuilder.cs
  22. 9 1
      src/PixiEditor.DrawingApi.Core/Shaders/Generation/ShaderExpressionVariable.cs
  23. 12 2
      src/PixiEditor.DrawingApi.Core/Shaders/Shader.cs
  24. 5 0
      src/PixiEditor.DrawingApi.Core/Shaders/Uniform.cs
  25. 1 1
      src/PixiEditor.DrawingApi.Core/Surfaces/PaintImpl/Paint.cs
  26. 2 0
      src/PixiEditor.DrawingApi.Core/Texture.cs
  27. 25 5
      src/PixiEditor.DrawingApi.Skia/Implementations/SkiaPaintImplementation.cs
  28. 28 0
      src/PixiEditor.DrawingApi.Skia/Implementations/SkiaShaderImplementation.cs
  29. 31 37
      src/PixiEditor/Models/Rendering/CanvasUpdater.cs
  30. 3 1
      src/PixiEditor/Models/Rendering/MemberPreviewUpdater.cs
  31. 3 3
      src/PixiEditor/ViewModels/Document/DocumentViewModel.cs

+ 22 - 6
src/PixiEditor.ChangeableDocument/Changeables/Graph/Context/FuncContext.cs

@@ -1,22 +1,24 @@
-using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using System.Linq.Expressions;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.Shaders.Generation;
 using PixiEditor.DrawingApi.Core.Shaders.Generation.Expressions;
 using PixiEditor.Numerics;
+using Expression = PixiEditor.DrawingApi.Core.Shaders.Generation.Expressions.Expression;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Context;
 
 public class FuncContext
 {
     public static FuncContext NoContext { get; } = new();
-    
+
     public Float2 Position { get; private set; }
     public VecI Size { get; private set; }
     public bool HasContext { get; private set; }
     public RenderingContext RenderingContext { get; set; }
-    
+
     public ShaderBuilder Builder { get; set; }
 
     public void ThrowOnMissingContext()
@@ -29,9 +31,8 @@ public class FuncContext
 
     public FuncContext()
     {
-        
     }
-    
+
     public FuncContext(RenderingContext renderingContext, ShaderBuilder builder)
     {
         RenderingContext = renderingContext;
@@ -42,7 +43,22 @@ public class FuncContext
 
     public Half4 SampleTexture(Texture? imageValue, Float2 pos)
     {
-        TextureSampler texName = Builder.AddTexture(imageValue);
+        TextureSampler texName = Builder.AddOrGetTexture(imageValue);
         return Builder.Sample(texName, pos);
     }
+
+    public Float2 NewFloat2(Expression x, Expression y)
+    {
+        return Builder.ConstructFloat2(x, y);
+    }
+
+    public Float1 NewFloat1(Expression result)
+    {
+        return Builder.ConstructFloat1(result);
+    }
+
+    public Half4 NewHalf4(Expression r, Expression g, Expression b, Expression a)
+    {
+        return Builder.ConstructHalf4(r, g, b, a);
+    }
 }

+ 16 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/FuncInputProperty.cs

@@ -27,7 +27,22 @@ public class FuncInputProperty<T> : InputProperty<Func<FuncContext, T>>, IFuncIn
     {
         Func<FuncContext, T> func = f =>
         {
-            ConversionTable.TryConvert(delegateToCast.DynamicInvoke(f), typeof(T), out var result);
+            Type targetType = typeof(T);
+            bool isShaderExpression = false;
+            if(typeof(T).IsAssignableTo(typeof(ShaderExpressionVariable)))
+            {
+                targetType = targetType.BaseType.GenericTypeArguments[0];
+                isShaderExpression = true;
+            }
+            
+            ConversionTable.TryConvert(delegateToCast.DynamicInvoke(f), targetType, out var result);
+            if (isShaderExpression)
+            {
+                var toReturn = Activator.CreateInstance(typeof(T), ""); 
+                ((ShaderExpressionVariable)toReturn).SetConstantValue(result, ConversionTable.Convert);
+                return (T)toReturn;
+            }
+            
             return result == null ? default : (T)result; 
         };
         return func;

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/NodeGraph.cs

@@ -123,7 +123,7 @@ public class NodeGraph : IReadOnlyNodeGraph, IDisposable
         if (OutputNode == null) return null;
 
         var queue = CalculateExecutionQueue(OutputNode);
-
+        
         while (queue.Count > 0)
         {
             var node = queue.Dequeue();

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

@@ -2,21 +2,22 @@
 using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Shaders.Generation.Expressions;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
 
 [NodeInfo("CombineColor")]
 public class CombineColorNode : Node
 {
-    public FuncOutputProperty<Color> Color { get; }
+    public FuncOutputProperty<Half4> Color { get; }
 
-    public FuncInputProperty<double> R { get; }
+    public FuncInputProperty<Float1> R { get; }
 
-    public FuncInputProperty<double> G { get; }
+    public FuncInputProperty<Float1> G { get; }
 
-    public FuncInputProperty<double> B { get; }
+    public FuncInputProperty<Float1> B { get; }
 
-    public FuncInputProperty<double> A { get; }
+    public FuncInputProperty<Float1> A { get; }
 
     public override string DisplayName { get; set; } = "COMBINE_COLOR_NODE";
 
@@ -24,20 +25,20 @@ public class CombineColorNode : Node
     {
         Color = CreateFuncOutput(nameof(Color), "COLOR", GetColor);
 
-        R = CreateFuncInput("R", "R", 0d);
-        G = CreateFuncInput("G", "G", 0d);
-        B = CreateFuncInput("B", "B", 0d);
-        A = CreateFuncInput("A", "A", 0d);
+        R = CreateFuncInput<Float1>("R", "R", 0d);
+        G = CreateFuncInput<Float1>("G", "G", 0d);
+        B = CreateFuncInput<Float1>("B", "B", 0d);
+        A = CreateFuncInput<Float1>("A", "A", 0d);
     }
 
-    private Color GetColor(FuncContext ctx)
+    private Half4 GetColor(FuncContext ctx)
     {
-        var r = R.Value(ctx) * 255;
-        var g = G.Value(ctx) * 255;
-        var b = B.Value(ctx) * 255;
-        var a = A.Value(ctx) * 255;
+        var r = R.Value(ctx);
+        var g = G.Value(ctx);
+        var b = B.Value(ctx);
+        var a = A.Value(ctx);
 
-        return new Color((byte)r, (byte)g, (byte)b, (byte)a);
+        return ctx.NewHalf4(r, g, b, a); 
     }
 
 

+ 10 - 9
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/CombineVecD.cs

@@ -2,6 +2,7 @@
 using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Shaders.Generation.Expressions;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
@@ -9,11 +10,11 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
 [NodeInfo("CombineVecD")]
 public class CombineVecD : Node
 {
-    public FuncOutputProperty<VecD> Vector { get; }
+    public FuncOutputProperty<Float2> Vector { get; }
     
-    public FuncInputProperty<double> X { get; }
+    public FuncInputProperty<Float1> X { get; }
     
-    public FuncInputProperty<double> Y { get; }
+    public FuncInputProperty<Float1> Y { get; }
     
     
     public override string DisplayName { get; set; } = "COMBINE_VECD_NODE";
@@ -22,16 +23,16 @@ public class CombineVecD : Node
     {
         Vector = CreateFuncOutput(nameof(Vector), "VECTOR", GetVector);
 
-        X = CreateFuncInput(nameof(X), "X", 0d);
-        Y = CreateFuncInput(nameof(Y), "Y", 0d);
+        X = CreateFuncInput<Float1>(nameof(X), "X", 0);
+        Y = CreateFuncInput<Float1>(nameof(Y), "Y", 0);
     }
     
-    private VecD GetVector(FuncContext ctx)
+    private Float2 GetVector(FuncContext ctx)
     {
-        var r = X.Value(ctx);
-        var g = Y.Value(ctx);
+        var x = X.Value(ctx);
+        var y = Y.Value(ctx);
 
-        return new VecD(r, g);
+        return ctx.NewFloat2(x, y); 
     }
 
     protected override Texture? OnExecute(RenderingContext context)

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

@@ -1,37 +1,59 @@
-using PixiEditor.ChangeableDocument.Rendering;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Context;
+using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Shaders.Generation.Expressions;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
 
 [NodeInfo("SeparateColor")]
 public class SeparateColorNode : Node
 {
-    public FuncInputProperty<Color> Color { get; }
+    public FuncInputProperty<Half4> Color { get; }
     
-    public FuncOutputProperty<double> R { get; }
+    public FuncOutputProperty<Float1> R { get; }
     
-    public FuncOutputProperty<double> G { get; }
+    public FuncOutputProperty<Float1> G { get; }
     
-    public FuncOutputProperty<double> B { get; }
+    public FuncOutputProperty<Float1> B { get; }
     
-    public FuncOutputProperty<double> A { get; }
+    public FuncOutputProperty<Float1> A { get; }
     
     public override string DisplayName { get; set; } = "SEPARATE_COLOR_NODE";
+    
+    private FuncContext lastContext;
+    private Half4 lastColor;
 
     public SeparateColorNode()
     {
-        Color = CreateFuncInput(nameof(Color), "COLOR", new Color());
-        R = CreateFuncOutput(nameof(R), "R", ctx => Color.Value(ctx).R / 255d);
-        G = CreateFuncOutput(nameof(G), "G", ctx => Color.Value(ctx).G / 255d);
-        B = CreateFuncOutput(nameof(B), "B", ctx => Color.Value(ctx).B / 255d);
-        A = CreateFuncOutput(nameof(A), "A", ctx => Color.Value(ctx).A / 255d);
+        Color = CreateFuncInput<Half4>(nameof(Color), "COLOR", new Color());
+        R = CreateFuncOutput<Float1>(nameof(R), "R", ctx => GetColor(ctx).R);
+        G = CreateFuncOutput<Float1>(nameof(G), "G", ctx => GetColor(ctx).G);
+        B = CreateFuncOutput<Float1>(nameof(B), "B", ctx => GetColor(ctx).B);
+        A = CreateFuncOutput<Float1>(nameof(A), "A", ctx => GetColor(ctx).A);
     }
 
     protected override Texture? OnExecute(RenderingContext context)
     {
         return null;
     }
+    
+    private Half4 GetColor(FuncContext ctx)
+    {
+        Half4 target = null;
+        if (lastContext == ctx)
+        {
+            target = lastColor;
+        }
+        else
+        {
+            target = Color.Value(ctx);
+        }
+        
+        lastColor = target;
+        lastContext = ctx;
+        return lastColor;
+    }
 
 
     public override Node CreateCopy() => new SeparateColorNode();

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

@@ -1,5 +1,7 @@
-using PixiEditor.ChangeableDocument.Rendering;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Context;
+using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.DrawingApi.Core;
+using PixiEditor.DrawingApi.Core.Shaders.Generation.Expressions;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
@@ -7,11 +9,11 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
 [NodeInfo("SeparateVecD")]
 public class SeparateVecDNode : Node
 {
-    public FuncInputProperty<VecD> Vector { get; }
+    public FuncInputProperty<Float2> Vector { get; }
     
-    public FuncOutputProperty<double> X { get; }
+    public FuncOutputProperty<Float1> X { get; }
     
-    public FuncOutputProperty<double> Y { get; }
+    public FuncOutputProperty<Float1> Y { get; }
     
     public override string DisplayName { get; set; } = "SEPARATE_VECD_NODE";
 
@@ -19,10 +21,9 @@ public class SeparateVecDNode : Node
     {
         X = CreateFuncOutput("X", "X", ctx => Vector.Value(ctx).X);
         Y = CreateFuncOutput("Y", "Y", ctx => Vector.Value(ctx).Y);
-        Vector = CreateFuncInput("Vector", "VECTOR", new VecD(0, 0));
+        Vector = CreateFuncInput<Float2>("Vector", "VECTOR", VecD.Zero);
     }
 
-
     protected override Texture? OnExecute(RenderingContext context)
     {
         return null;

+ 12 - 2
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ImageLayerNode.cs

@@ -207,7 +207,7 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
                 clearPaint);
         }
         
-        using Surface tempSurface = new Surface(tempSizeInChunks * context.ChunkResolution.PixelSize());
+        using Texture tempSurface = new Texture(tempSizeInChunks * context.ChunkResolution.PixelSize());
 
         if (requiresTopLeft)
         {
@@ -256,7 +256,7 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
         workingSurface.DrawingSurface.Canvas.DrawSurface(tempSurface.DrawingSurface, VecI.Zero, blendPaint);
     }
 
-    private void DrawChunk(ChunkyImage frameImage, RenderingContext context, Surface tempSurface, VecI vecI)
+    private void DrawChunk(ChunkyImage frameImage, RenderingContext context, Texture tempSurface, VecI vecI)
     {
         VecI chunkPos = context.ChunkToUpdate + vecI;
         if (frameImage.LatestOrCommittedChunkExists(chunkPos))
@@ -311,6 +311,16 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
         };
     }
 
+    public override void Dispose()
+    {
+        base.Dispose();
+
+        foreach (var workingSurface in workingSurfaces)
+        {
+            workingSurface.Value.Dispose();
+        }
+    }
+
 
     IReadOnlyChunkyImage IReadOnlyImageNode.GetLayerImageAtFrame(int frame) => GetLayerImageAtFrame(frame);
 

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

@@ -4,58 +4,59 @@ using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.DrawingApi.Core;
+using PixiEditor.DrawingApi.Core.Shaders.Generation.Expressions;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 [NodeInfo("Math")]
 public class MathNode : Node
 {
-    public FuncOutputProperty<double> Result { get; }
+    public FuncOutputProperty<Float1> Result { get; }
 
     public InputProperty<MathNodeMode> Mode { get; }
     
     public InputProperty<bool> Clamp { get; }
 
-    public FuncInputProperty<double> X { get; }
+    public FuncInputProperty<Float1> X { get; }
     
-    public FuncInputProperty<double> Y { get; }
+    public FuncInputProperty<Float1> Y { get; }
     
     
     public override string DisplayName { get; set; } = "MATH_NODE";
     
     public MathNode()
     {
-        Result = CreateFuncOutput(nameof(Result), "RESULT", Calculate);
+        Result = CreateFuncOutput<Float1>(nameof(Result), "RESULT", Calculate);
         Mode = CreateInput(nameof(Mode), "MATH_MODE", MathNodeMode.Add);
         Clamp = CreateInput(nameof(Clamp), "CLAMP", false);
-        X = CreateFuncInput(nameof(X), "X", 0d);
-        Y = CreateFuncInput(nameof(Y), "Y", 0d);
+        X = CreateFuncInput<Float1>(nameof(X), "X", 0d);
+        Y = CreateFuncInput<Float1>(nameof(Y), "Y", 0d);
     }
 
-    private double Calculate(FuncContext context)
+    private Float1 Calculate(FuncContext context)
     {
         var (x, y) = GetValues(context);
 
         var result = Mode.Value switch
         {
-            MathNodeMode.Add => x + y,
-            MathNodeMode.Subtract => x - y,
-            MathNodeMode.Multiply => x * y,
-            MathNodeMode.Divide => x / y,
-            MathNodeMode.Sin => Math.Sin(x),
-            MathNodeMode.Cos => Math.Cos(x),
-            MathNodeMode.Tan => Math.Tan(x),
+            MathNodeMode.Add => ShaderMath.Add(x, y),
+            MathNodeMode.Subtract => ShaderMath.Subtract(x, y),
+            MathNodeMode.Multiply => ShaderMath.Multiply(x, y),
+            MathNodeMode.Divide => ShaderMath.Divide(x, y),
+            MathNodeMode.Sin => ShaderMath.Sin(x),
+            MathNodeMode.Cos => ShaderMath.Cos(x),
+            MathNodeMode.Tan => ShaderMath.Tan(x),
         };
 
         if (Clamp.Value)
         {
-            result = Math.Clamp(result, 0, 1);
+            result = ShaderMath.Clamp(result, (Float1)0, (Float1)1);
         }
         
-        return result;
+        return context.NewFloat1(result);
     }
 
-    private (double x, double y) GetValues(FuncContext context) => (X.Value(context), Y.Value(context));
+    private (Float1 x, Float1 y) GetValues(FuncContext context) => (X.Value(context), Y.Value(context));
 
 
     protected override Texture? OnExecute(RenderingContext context)

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

@@ -29,8 +29,9 @@ public class ModifyImageRightNode : Node, IPairNodeEnd
 
     public override string DisplayName { get; set; } = "MODIFY_IMAGE_RIGHT_NODE";
 
-    private Texture surface;
     private string _lastSksl;
+    
+    private TextureCache _textureCache = new(); 
 
     public ModifyImageRightNode()
     {
@@ -59,12 +60,7 @@ public class ModifyImageRightNode : Node, IPairNodeEnd
         var width = size.X;
         var height = size.Y;
 
-        if (surface == null || surface.Size != size)
-        {
-            surface?.Dispose();
-            surface = new Texture(size);
-            surface.DrawingSurface.Canvas.Clear();
-        }
+        Texture surface = _textureCache.GetTexture(renderingContext.ChunkResolution, size); 
 
         if (!surface.IsHardwareAccelerated)
         {
@@ -83,7 +79,15 @@ public class ModifyImageRightNode : Node, IPairNodeEnd
 
             if (Coordinate.Connection != null)
             {
-                builder.Set(context.Position, Coordinate.Value(context));
+                var coordinate = Coordinate.Value(context);
+                if (string.IsNullOrEmpty(coordinate.UniformName))
+                {
+                    builder.SetConstant(context.Position, coordinate);
+                }
+                else
+                {
+                    builder.Set(context.Position, Coordinate.Value(context));
+                }
             }
             else
             {
@@ -103,12 +107,18 @@ public class ModifyImageRightNode : Node, IPairNodeEnd
             if (sksl != _lastSksl)
             {
                 _lastSksl = sksl;
+                drawingPaint?.Shader?.Dispose();
                 drawingPaint.Shader = builder.BuildShader();
             }
+            else
+            {
+                drawingPaint.Shader = drawingPaint.Shader.WithUpdatedUniforms(builder.Uniforms);
+            }
 
             surface.DrawingSurface.Canvas.DrawPaint(drawingPaint);
+            builder.Dispose();
         }
-
+        
         Output.Value = surface;
 
         return Output.Value;
@@ -152,6 +162,13 @@ public class ModifyImageRightNode : Node, IPairNodeEnd
         });*/
     }
 
+    public override void Dispose()
+    {
+        base.Dispose();
+        _textureCache.Dispose(); 
+        drawingPaint?.Dispose();
+    }
+
     private void FindStartNode()
     {
         TraverseBackwards(node =>

+ 29 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/TextureCache.cs

@@ -0,0 +1,29 @@
+using PixiEditor.DrawingApi.Core;
+using PixiEditor.Numerics;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+
+public class TextureCache
+{
+    private Dictionary<ChunkResolution, Texture> _cachedTextures = new();
+    
+    public Texture GetTexture(ChunkResolution resolution, VecI size)
+    {
+        if (_cachedTextures.TryGetValue(resolution, out var texture) && texture.Size == size)
+        {
+            return texture;
+        }
+
+        texture = new Texture(size);
+        _cachedTextures[resolution] = texture;
+        return texture;
+    }
+
+    public void Dispose()
+    {
+        foreach (var texture in _cachedTextures.Values)
+        {
+            texture.Dispose();
+        }
+    }
+}

+ 7 - 7
src/PixiEditor.ChangeableDocument/Rendering/DocumentRenderer.cs

@@ -17,7 +17,7 @@ public class DocumentRenderer
 
     private IReadOnlyDocument Document { get; }
 
-    public OneOf<Chunk, EmptyChunk> RenderChunk(VecI chunkPos, ChunkResolution resolution, KeyFrameTime frameTime,
+    public OneOf<Texture, EmptyChunk> RenderChunk(VecI chunkPos, ChunkResolution resolution, KeyFrameTime frameTime,
         RectI? globalClippingRect = null)
     {
         RenderingContext context = new(frameTime, chunkPos, resolution, Document.Size);
@@ -31,14 +31,14 @@ public class DocumentRenderer
                 return new EmptyChunk();
             }
 
-            Chunk chunk = Chunk.Create(resolution);
+            Texture chunk = new Texture(new VecI(resolution.PixelSize()));
 
-            chunk.Surface.DrawingSurface.Canvas.Save();
-            chunk.Surface.DrawingSurface.Canvas.Clear();
+            chunk.DrawingSurface.Canvas.Save();
+            chunk.DrawingSurface.Canvas.Clear();
 
             if (transformedClippingRect is not null)
             {
-                chunk.Surface.DrawingSurface.Canvas.ClipRect((RectD)transformedClippingRect);
+                chunk.DrawingSurface.Canvas.ClipRect((RectD)transformedClippingRect);
             }
 
             VecD pos = chunkPos;
@@ -63,9 +63,9 @@ public class DocumentRenderer
             
             if(context.IsDisposed) return new EmptyChunk();
 
-            chunk.Surface.DrawingSurface.Canvas.DrawImage(chunkSnapshot, 0, 0, context.ReplacingPaintWithOpacity);
+            chunk.DrawingSurface.Canvas.DrawImage(chunkSnapshot, 0, 0, context.ReplacingPaintWithOpacity);
 
-            chunk.Surface.DrawingSurface.Canvas.Restore();
+            chunk.DrawingSurface.Canvas.Restore();
 
             return chunk;
         }

+ 1 - 1
src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IPaintImplementation.cs

@@ -33,7 +33,7 @@ namespace PixiEditor.DrawingApi.Core.Bridge.NativeObjectsImpl
         public void SetImageFilter(Paint paint, ImageFilter value);
         
         public object GetNativePaint(IntPtr objectPointer);
-        public Shader GetShader(Paint paint);
+        public Shader? GetShader(Paint paint);
         public void SetShader(Paint paint, Shader shader);
     }
 }

+ 1 - 0
src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IShaderImplementation.cs

@@ -16,4 +16,5 @@ public interface IShaderImplementation
     public Shader CreatePerlinNoiseTurbulence(float baseFrequencyX, float baseFrequencyY, int numOctaves, float seed);
     public Shader CreatePerlinFractalNoise(float baseFrequencyX, float baseFrequencyY, int numOctaves, float seed);
     public object GetNativeShader(IntPtr objectPointer);
+    public Shader WithUpdatedUniforms(IntPtr objectPointer, Uniforms uniforms);
 }

+ 1 - 1
src/PixiEditor.DrawingApi.Core/Exceptions/ShaderCompilationException.cs

@@ -4,7 +4,7 @@ namespace PixiEditor.DrawingApi.Core.Exceptions;
 
 public class ShaderCompilationException : Exception
 {
-    public ShaderCompilationException(string errors) : base(errors)
+    public ShaderCompilationException(string errors, string sksl) : base($"Shader compilation failed: {errors}\n\n{sksl}")
     {
     }
 }

+ 16 - 0
src/PixiEditor.DrawingApi.Core/Shaders/Generation/Expressions/Expression.cs

@@ -0,0 +1,16 @@
+namespace PixiEditor.DrawingApi.Core.Shaders.Generation.Expressions;
+
+public class Expression
+{
+    public virtual string ExpressionValue { get; }
+
+    public Expression()
+    {
+        
+    }
+    
+    public Expression(string expressionValue)
+    {
+        ExpressionValue = expressionValue;
+    }
+}

+ 15 - 0
src/PixiEditor.DrawingApi.Core/Shaders/Generation/Expressions/Float1.cs

@@ -0,0 +1,15 @@
+namespace PixiEditor.DrawingApi.Core.Shaders.Generation.Expressions;
+
+/// <summary>
+///     This is a shader type that represents a high precision floating point value. For medium precision see Short type.
+/// </summary>
+/// <param name="name">Name of the variable in shader code</param>
+/// <param name="constant">Constant value of the variable.</param>
+public class Float1(string name, double constant) : ShaderExpressionVariable<double>(name, constant)
+{
+    public override string ConstantValueString => ConstantValue.ToString(System.Globalization.CultureInfo.InvariantCulture);
+    
+    public static implicit operator Float1(double value) => new Float1("", value);
+    
+    public static explicit operator double(Float1 value) => value.ConstantValue;
+}

+ 19 - 0
src/PixiEditor.DrawingApi.Core/Shaders/Generation/Expressions/Float2.cs

@@ -17,4 +17,23 @@ public class Float2(string name, VecD constant) : ShaderExpressionVariable<VecD>
             return $"float2({x}, {y})";
         }
     }
+
+    public Float1 X
+    {
+        get
+        {
+            return new Float1($"{UniformName}.x", ConstantValue.X);
+        }
+    }
+    
+    public Float1 Y
+    {
+        get
+        {
+            return new Float1($"{UniformName}.y", ConstantValue.Y);
+        }
+    }
+    
+    public static implicit operator Float2(VecD value) => new Float2("", value);
+    public static explicit operator VecD(Float2 value) => value.ConstantValue;
 }

+ 8 - 0
src/PixiEditor.DrawingApi.Core/Shaders/Generation/Expressions/Half4.cs

@@ -9,4 +9,12 @@ public class Half4(string name, Color constant) : ShaderExpressionVariable<Color
     }
 
     public override string ConstantValueString => $"half4({ConstantValue.R}, {ConstantValue.G}, {ConstantValue.B}, {ConstantValue.A})";
+    
+    public Float1 R => new Float1($"{UniformName}.r", ConstantValue.R);
+    public Float1 G => new Float1($"{UniformName}.g", ConstantValue.G);
+    public Float1 B => new Float1($"{UniformName}.b", ConstantValue.B);
+    public Float1 A => new Float1($"{UniformName}.a", ConstantValue.A);
+    
+    public static implicit operator Half4(Color value) => new Half4("", value);
+    public static explicit operator Color(Half4 value) => value.ConstantValue;
 }

+ 46 - 0
src/PixiEditor.DrawingApi.Core/Shaders/Generation/Expressions/ShaderMath.cs

@@ -0,0 +1,46 @@
+using System.Linq.Expressions;
+
+namespace PixiEditor.DrawingApi.Core.Shaders.Generation.Expressions;
+
+public static class ShaderMath
+{
+    public static Expression Add(Expression a, Expression b)
+    {
+        return new Expression($"{a.ExpressionValue} + {b.ExpressionValue}");
+    }
+    
+    public static Expression Subtract(ShaderExpressionVariable a, ShaderExpressionVariable b)
+    {
+        return new Expression($"{a.VarOrConst()} - {b.VarOrConst()}");
+    }
+    
+    public static Expression Multiply(ShaderExpressionVariable a, ShaderExpressionVariable b)
+    {
+        return new Expression($"{a.VarOrConst()} * {b.VarOrConst()}");
+    }
+    
+    public static Expression Divide(ShaderExpressionVariable a, ShaderExpressionVariable b)
+    {
+        return new Expression($"{a.VarOrConst()} / {b.VarOrConst()}");
+    }
+
+    public static Expression Clamp(Expression value, Expression min, Expression max)
+    {
+        return new Expression($"clamp({value.ExpressionValue}, {min.ExpressionValue}, {max.ExpressionValue})");
+    }
+
+    public static Expression Sin(Expression x)
+    {
+        return new Expression($"sin({x.ExpressionValue})");
+    }
+    
+    public static Expression Cos(Expression x)
+    {
+        return new Expression($"cos({x.ExpressionValue})");
+    }
+    
+    public static Expression Tan(Expression x)
+    {
+        return new Expression($"tan({x.ExpressionValue})");
+    }
+}

+ 70 - 5
src/PixiEditor.DrawingApi.Core/Shaders/Generation/ShaderBuilder.cs

@@ -1,4 +1,5 @@
-using System.Text;
+using System.Collections.Generic;
+using System.Text;
 using PixiEditor.DrawingApi.Core.Shaders.Generation.Expressions;
 using PixiEditor.Numerics;
 
@@ -9,6 +10,10 @@ public class ShaderBuilder
     public Uniforms Uniforms { get; } = new Uniforms();
 
     private StringBuilder _bodyBuilder = new StringBuilder();
+    
+    private List<ShaderExpressionVariable> _variables = new List<ShaderExpressionVariable>();
+    
+    private Dictionary<Texture, TextureSampler> _samplers = new Dictionary<Texture, TextureSampler>();
 
     public Shader BuildShader()
     {
@@ -36,17 +41,27 @@ public class ShaderBuilder
         }
     }
 
-    public TextureSampler AddTexture(Texture texture)
+    public TextureSampler AddOrGetTexture(Texture texture)
     {
+        if (_samplers.TryGetValue(texture, out var sampler))
+        {
+            return sampler;
+        }
+        
         string name = $"texture_{Uniforms.Count}";
-        Uniforms[name] = new Uniform(name, texture.DrawingSurface.Snapshot().ToShader());
-        return new TextureSampler(name);
+        using var snapshot = texture.DrawingSurface.Snapshot();
+        Uniforms[name] = new Uniform(name, snapshot.ToShader());
+        var newSampler = new TextureSampler(name);
+        _samplers[texture] = newSampler;
+        
+        return newSampler;
     }
     
     public Half4 Sample(TextureSampler texName, Float2 pos)
     {
-        string resultName = $"color_{Uniforms.Count}";
+        string resultName = $"color_{_variables.Count}";
         Half4 result = new Half4(resultName);
+        _variables.Add(result);
         _bodyBuilder.AppendLine($"half4 {resultName} = sample({texName.UniformName}, {pos.UniformName});"); 
         return result;
     }
@@ -70,4 +85,54 @@ public class ShaderBuilder
     {
         _bodyBuilder.AppendLine($"{contextPosition.UniformName} = {constantValueVar.ConstantValueString};"); 
     }
+
+    public Float2 ConstructFloat2(Expression x, Expression y)
+    {
+        string name = $"vec2_{_variables.Count}";
+        Float2 result = new Float2(name);
+        _variables.Add(result);
+
+        string xExpression = x.ExpressionValue; 
+        string yExpression = y.ExpressionValue;
+        
+        _bodyBuilder.AppendLine($"float2 {name} = float2({xExpression}, {yExpression});");
+        return result;
+    }
+
+    public Float1 ConstructFloat1(Expression assignment)
+    {
+        string name = $"float_{_variables.Count}";
+        Float1 result = new Float1(name, 0);
+        _variables.Add(result);
+        
+        _bodyBuilder.AppendLine($"float {name} = {assignment.ExpressionValue};");
+        return result;
+    }
+
+    public Half4 ConstructHalf4(Expression r, Expression g, Expression b, Expression a)
+    {
+        string name = $"color_{_variables.Count}";
+        Half4 result = new Half4(name);
+        _variables.Add(result);
+        
+        string rExpression = r.ExpressionValue;
+        string gExpression = g.ExpressionValue;
+        string bExpression = b.ExpressionValue;
+        string aExpression = a.ExpressionValue;
+        
+        _bodyBuilder.AppendLine($"half4 {name} = half4({rExpression}, {gExpression}, {bExpression}, {aExpression});");
+        return result;
+    }
+
+    public void Dispose()
+    {
+        _bodyBuilder.Clear();
+        _variables.Clear();
+        _samplers.Clear();
+        
+        foreach (var uniform in Uniforms)
+        {
+            uniform.Value.Dispose();
+        }
+    }
 }

+ 9 - 1
src/PixiEditor.DrawingApi.Core/Shaders/Generation/ShaderExpressionVariable.cs

@@ -1,8 +1,9 @@
 using System;
+using PixiEditor.DrawingApi.Core.Shaders.Generation.Expressions;
 
 namespace PixiEditor.DrawingApi.Core.Shaders.Generation;
 
-public abstract class ShaderExpressionVariable(string name)
+public abstract class ShaderExpressionVariable(string name) : Expression
 {
     public string UniformName { get; set; } = name;
     public abstract string ConstantValueString { get; }
@@ -12,7 +13,14 @@ public abstract class ShaderExpressionVariable(string name)
         return UniformName;
     }
 
+    public override string ExpressionValue => VarOrConst();
+
     public abstract void SetConstantValue(object? value, Func<object, Type, object> convertFunc);
+    
+    public string VarOrConst()
+    {
+        return string.IsNullOrEmpty(UniformName) ? ConstantValueString : UniformName;
+    }
 }
 
 public abstract class ShaderExpressionVariable<TConstant>(string name, TConstant constant)

+ 12 - 2
src/PixiEditor.DrawingApi.Core/Shaders/Shader.cs

@@ -19,9 +19,19 @@ public class Shader : NativeObject
     {
         if (!string.IsNullOrEmpty(errors))
         {
-            throw new ShaderCompilationException(errors);
+            throw new ShaderCompilationException(errors, sksl);
         }
     }
+    
+    /// <summary>
+    ///     Creates updated version of shader with new uniforms. THIS FUNCTION DISPOSES OLD SHADER.
+    /// </summary>
+    /// <param name="uniforms"></param>
+    /// <returns></returns>
+    public Shader WithUpdatedUniforms(Uniforms uniforms)
+    {
+        return DrawingBackendApi.Current.ShaderImplementation.WithUpdatedUniforms(ObjectPointer, uniforms);
+    }
 
     public static Shader? CreateFromSksl(string sksl, bool isOpaque, out string errors)
     {
@@ -30,7 +40,7 @@ public class Shader : NativeObject
 
     public override void Dispose()
     {
-        DrawingBackendApi.Current.PaintImplementation.Dispose(ObjectPointer);
+        DrawingBackendApi.Current.ShaderImplementation.Dispose(ObjectPointer);
     }
 
     public static Shader CreateLinearGradient(VecI p1, VecI p2, Color[] colors)

+ 5 - 0
src/PixiEditor.DrawingApi.Core/Shaders/Uniform.cs

@@ -44,6 +44,11 @@ public struct Uniform
         DataType = UniformValueType.Shader;
         UniformName = "shader";
     }
+
+    public void Dispose()
+    {
+        ShaderValue.Dispose();
+    }
 }
 
 public enum UniformValueType

+ 1 - 1
src/PixiEditor.DrawingApi.Core/Surfaces/PaintImpl/Paint.cs

@@ -78,7 +78,7 @@ namespace PixiEditor.DrawingApi.Core.Surfaces.PaintImpl
             }
         }
 
-        public Shader Shader
+        public Shader? Shader
         {
             get => shader ??= DrawingBackendApi.Current.PaintImplementation.GetShader(this);
             set

+ 2 - 0
src/PixiEditor.DrawingApi.Core/Texture.cs

@@ -194,6 +194,8 @@ public class Texture : IDisposable
         IsDisposed = true;
         DrawingSurface.Changed -= DrawingSurfaceOnChanged;
         DrawingSurface.Dispose();
+        pixmap?.Dispose();
+        nearestNeighborReplacingPaint.Dispose();
     }
 
     public static Texture FromExisting(DrawingSurface drawingSurface)

+ 25 - 5
src/PixiEditor.DrawingApi.Skia/Implementations/SkiaPaintImplementation.cs

@@ -36,14 +36,25 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
 
         public void Dispose(IntPtr paintObjPointer)
         {
-            if (!ManagedInstances.ContainsKey(paintObjPointer)) return;
-            SKPaint paint = ManagedInstances[paintObjPointer];
+            if (!ManagedInstances.TryGetValue(paintObjPointer, out var paint)) return;
 
             if (paint.ColorFilter != null)
             {
                 paint.ColorFilter.Dispose();
                 colorFilterImplementation.ManagedInstances.TryRemove(paint.ColorFilter.Handle, out _);
             }
+            
+            if (paint.ImageFilter != null)
+            {
+                paint.ImageFilter.Dispose();
+                imageFilterImplementation.ManagedInstances.TryRemove(paint.ImageFilter.Handle, out _);
+            }
+            
+            if (paint.Shader != null)
+            {
+                paint.Shader.Dispose();
+                shaderImplementation.ManagedInstances.TryRemove(paint.Shader.Handle, out _);
+            }
 
             paint.Dispose();
             ManagedInstances.TryRemove(paintObjPointer, out _);
@@ -164,10 +175,19 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
             skPaint.ImageFilter = value == null ? null : imageFilterImplementation[value.ObjectPointer];
         }
 
-        public Shader GetShader(Paint paint)
+        public Shader? GetShader(Paint paint)
         {
-            SKPaint skPaint = ManagedInstances[paint.ObjectPointer];
-            return new Shader(skPaint.Shader.Handle);
+            if(ManagedInstances.TryGetValue(paint.ObjectPointer, out var skPaint))
+            {
+                if (skPaint.Shader == null)
+                {
+                    return null;
+                }
+                
+                return new Shader(skPaint.Shader.Handle);
+            }
+            
+            return null;
         }
         
         public void SetShader(Paint paint, Shader? shader)

+ 28 - 0
src/PixiEditor.DrawingApi.Skia/Implementations/SkiaShaderImplementation.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using PixiEditor.DrawingApi.Core.Bridge.NativeObjectsImpl;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.Shaders;
@@ -10,6 +11,7 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
 {
     public class SkiaShaderImplementation : SkObjectImplementation<SKShader>, IShaderImplementation
     {
+        private Dictionary<IntPtr, SKRuntimeEffect> runtimeEffects = new();
         public SkiaShaderImplementation()
         {
         }
@@ -30,6 +32,7 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
                 SKRuntimeEffectChildren effectChildren = UniformsToSkChildren(uniforms, effect);
                 SKShader shader = effect.ToShader(isOpaque, effectUniforms, effectChildren);
                 ManagedInstances[shader.Handle] = shader;
+                runtimeEffects[shader.Handle] = effect;
                 return new Shader(shader.Handle);
             }
             
@@ -93,6 +96,31 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
             return ManagedInstances[objectPointer]; 
         }
 
+        public Shader WithUpdatedUniforms(IntPtr objectPointer, Uniforms uniforms)
+        {
+            if (!ManagedInstances.TryGetValue(objectPointer, out var shader))
+            {
+                 throw new InvalidOperationException("Shader does not exist");
+            }
+            if (!runtimeEffects.TryGetValue(objectPointer, out var effect))
+            {
+                throw new InvalidOperationException("Shader is not a runtime effect shader");
+            }
+            
+            SKRuntimeEffectUniforms effectUniforms = UniformsToSkUniforms(uniforms, effect);
+            SKRuntimeEffectChildren effectChildren = UniformsToSkChildren(uniforms, effect);
+            
+            shader.Dispose();
+            ManagedInstances.TryRemove(objectPointer, out _);
+            runtimeEffects.Remove(objectPointer);
+            
+            var newShader = effect.ToShader(false, effectUniforms, effectChildren);
+            ManagedInstances[newShader.Handle] = newShader;
+            runtimeEffects[newShader.Handle] = effect;
+            
+            return new Shader(newShader.Handle);
+        }
+
         public void Dispose(IntPtr shaderObjPointer)
         {
             if (!ManagedInstances.TryGetValue(shaderObjPointer, out var shader)) return;

+ 31 - 37
src/PixiEditor/Models/Rendering/CanvasUpdater.cs

@@ -224,47 +224,41 @@ internal class CanvasUpdater
 
         doc.Renderer.RenderChunk(chunkPos, resolution, doc.AnimationHandler.ActiveFrameTime, globalClippingRectangle)
             .Switch(
-                (Chunk chunk) =>
+                (Texture chunk) =>
                 {
-                    /*Dispatcher.UIThread.Post(() =>
-                    {*/
-                        if (screenSurface.IsDisposed) return;
-
-                        if (globalScaledClippingRectangle is not null)
-                        {
-                            screenSurface.DrawingSurface.Canvas.Save();
-                            screenSurface.DrawingSurface.Canvas.ClipRect((RectD)globalScaledClippingRectangle);
-                        }
-                        
-                        screenSurface.DrawingSurface.Canvas.DrawSurface(
-                            chunk.Surface.DrawingSurface,
-                            chunkPos.Multiply(chunk.PixelSize), ReplacingPaint);
-                        chunk.Dispose();
-
-
-                        if (globalScaledClippingRectangle is not null)
-                            screenSurface.DrawingSurface.Canvas.Restore();
-                    /*});*/
+                    if (screenSurface.IsDisposed) return;
+
+                    if (globalScaledClippingRectangle is not null)
+                    {
+                        screenSurface.DrawingSurface.Canvas.Save();
+                        screenSurface.DrawingSurface.Canvas.ClipRect((RectD)globalScaledClippingRectangle);
+                    }
+
+                    screenSurface.DrawingSurface.Canvas.DrawSurface(
+                        chunk.DrawingSurface,
+                        chunkPos.Multiply(new VecI(resolution.PixelSize())), ReplacingPaint);
+                    chunk.Dispose();
+
+
+                    if (globalScaledClippingRectangle is not null)
+                        screenSurface.DrawingSurface.Canvas.Restore();
                 },
                 (EmptyChunk _) =>
                 {
-                    /*Dispatcher.UIThread.Post(() =>
-                    {*/
-                        if (screenSurface.IsDisposed) return;
-
-                        if (globalScaledClippingRectangle is not null)
-                        {
-                            screenSurface.DrawingSurface.Canvas.Save();
-                            screenSurface.DrawingSurface.Canvas.ClipRect((RectD)globalScaledClippingRectangle);
-                        }
-
-                        var pos = chunkPos * resolution.PixelSize();
-                        screenSurface.DrawingSurface.Canvas.DrawRect(pos.X, pos.Y, resolution.PixelSize(),
-                            resolution.PixelSize(), ClearPaint);
-                        
-                        if (globalScaledClippingRectangle is not null)
-                            screenSurface.DrawingSurface.Canvas.Restore();
-                    /*});*/
+                    if (screenSurface.IsDisposed) return;
+
+                    if (globalScaledClippingRectangle is not null)
+                    {
+                        screenSurface.DrawingSurface.Canvas.Save();
+                        screenSurface.DrawingSurface.Canvas.ClipRect((RectD)globalScaledClippingRectangle);
+                    }
+
+                    var pos = chunkPos * resolution.PixelSize();
+                    screenSurface.DrawingSurface.Canvas.DrawRect(pos.X, pos.Y, resolution.PixelSize(),
+                        resolution.PixelSize(), ClearPaint);
+
+                    if (globalScaledClippingRectangle is not null)
+                        screenSurface.DrawingSurface.Canvas.Restore();
                 });
     }
 }

+ 3 - 1
src/PixiEditor/Models/Rendering/MemberPreviewUpdater.cs

@@ -448,7 +448,9 @@ internal class MemberPreviewUpdater
             else if (rendered.IsT0)
             {
                 using var renderedChunk = rendered.AsT0;
-                renderedChunk.DrawChunkOn(doc.PreviewSurface.DrawingSurface, pos, SmoothReplacingPaint);
+                
+                //TODO: Check if this is correct
+                doc.PreviewSurface.DrawingSurface.Canvas.DrawSurface(renderedChunk.DrawingSurface, pos, SmoothReplacingPaint);
             }
 
             doc.PreviewSurface.DrawingSurface.Canvas.Restore();

+ 3 - 3
src/PixiEditor/ViewModels/Document/DocumentViewModel.cs

@@ -510,9 +510,9 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
                     var maybeChunk = Renderer.RenderChunk(new(i, j), ChunkResolution.Full, frameTime);
                     if (maybeChunk.IsT1)
                         continue;
-                    using Chunk chunk = maybeChunk.AsT0;
+                    using Texture chunk = maybeChunk.AsT0;
                     finalSurface.DrawingSurface.Canvas.DrawSurface(
-                        chunk.Surface.DrawingSurface,
+                        chunk.DrawingSurface,
                         i * ChunkyImage.FullChunkSize, j * ChunkyImage.FullChunkSize);
                 }
             }
@@ -656,7 +656,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
                         chunk =>
                         {
                             VecI posOnChunk = pos - chunkPos * ChunkyImage.FullChunkSize;
-                            var color = chunk.Surface.GetSRGBPixel(posOnChunk);
+                            var color = chunk.GetSRGBPixel(posOnChunk);
                             chunk.Dispose();
                             return color;
                         },