Przeglądaj źródła

Merge branch 'master' into improved-node-frame

# Conflicts:
#	src/PixiEditor/Models/Rendering/MemberPreviewUpdater.cs
CPKreuz 2 tygodni temu
rodzic
commit
b388d22bbb
54 zmienionych plików z 709 dodań i 180 usunięć
  1. 10 2
      src/ChunkyImageLib/Chunk.cs
  2. 6 6
      src/ChunkyImageLib/ChunkyImage.cs
  3. 8 7
      src/ChunkyImageLib/ChunkyImageEx.cs
  4. 2 2
      src/ChunkyImageLib/IReadOnlyChunkyImage.cs
  5. 5 5
      src/ChunkyImageLib/Operations/ChunkyImageOperation.cs
  6. 1 1
      src/Drawie
  7. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/SceneObjectRenderContext.cs
  8. 3 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CreateImageNode.cs
  9. 5 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Effects/OutlineNode.cs
  10. 84 27
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FilterNodes/ApplyFilterNode.cs
  11. 15 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Image/MaskNode.cs
  12. 29 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ImageLayerNode.cs
  13. 14 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/LayerNode.cs
  14. 1 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/OutputNode.cs
  15. 11 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/RenderNode.cs
  16. 10 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ShaderNode.cs
  17. 7 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/StructureNode.cs
  18. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/TextureCache.cs
  19. 8 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/VectorLayerNode.cs
  20. 2 1
      src/PixiEditor.ChangeableDocument/Changes/NodeGraph/EvaluateGraph_Change.cs
  21. 21 6
      src/PixiEditor.ChangeableDocument/Rendering/DocumentRenderer.cs
  22. 5 3
      src/PixiEditor.ChangeableDocument/Rendering/RenderContext.cs
  23. 5 0
      src/PixiEditor.Extensions.CommonApi/UserPreferences/PreferencesConstants.cs
  24. 42 17
      src/PixiEditor.Extensions.CommonApi/UserPreferences/Settings/PixiEditor/PixiEditorSettings.cs
  25. BIN
      src/PixiEditor.Extensions.Sdk/build/PixiEditor.Api.CGlueMSBuild.dll
  26. 6 1
      src/PixiEditor/Data/Localization/Languages/en.json
  27. 26 7
      src/PixiEditor/Models/DocumentModels/ActionAccumulator.cs
  28. 7 0
      src/PixiEditor/Models/DocumentPassthroughActions/RefreshPreviews_PassthroughAction.cs
  29. 20 0
      src/PixiEditor/Models/EnumTranslations.cs
  30. 2 1
      src/PixiEditor/Models/Handlers/IDocument.cs
  31. 1 0
      src/PixiEditor/Models/Handlers/INodePropertyHandler.cs
  32. 29 11
      src/PixiEditor/Models/Rendering/AffectedAreasGatherer.cs
  33. 43 19
      src/PixiEditor/Models/Rendering/MemberPreviewUpdater.cs
  34. 40 3
      src/PixiEditor/Models/Rendering/PreviewPainter.cs
  35. 35 13
      src/PixiEditor/Models/Rendering/SceneRenderer.cs
  36. 2 2
      src/PixiEditor/Properties/AssemblyInfo.cs
  37. 13 0
      src/PixiEditor/ViewModels/Document/DocumentViewModel.cs
  38. 21 1
      src/PixiEditor/ViewModels/Document/Nodes/FilterNodes/ApplyFilterNodeViewModel.cs
  39. 2 0
      src/PixiEditor/ViewModels/Nodes/NodePropertyViewModel.cs
  40. 16 3
      src/PixiEditor/ViewModels/SettingsWindowViewModel.cs
  41. 19 0
      src/PixiEditor/ViewModels/SubViewModels/ViewOptionsViewModel.cs
  42. 4 3
      src/PixiEditor/ViewModels/SubViewModels/ViewportWindowViewModel.cs
  43. 21 0
      src/PixiEditor/ViewModels/UserPreferences/Settings/PerformanceSettings.cs
  44. 2 0
      src/PixiEditor/ViewModels/UserPreferences/SettingsViewModel.cs
  45. 1 0
      src/PixiEditor/Views/Dock/DocumentTemplate.axaml
  46. 1 0
      src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml
  47. 8 0
      src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml.cs
  48. 15 2
      src/PixiEditor/Views/Nodes/Properties/StringPropertyView.axaml.cs
  49. 2 1
      src/PixiEditor/Views/Overlays/TextOverlay/TextOverlay.cs
  50. 25 1
      src/PixiEditor/Views/Rendering/Scene.cs
  51. 8 0
      src/PixiEditor/Views/Visuals/PreviewPainterControl.cs
  52. 39 2
      src/PixiEditor/Views/Windows/Settings/SettingsWindow.axaml
  53. 3 2
      tests/PixiEditor.Tests/BlendingTests.cs
  54. 2 1
      tests/PixiEditor.Tests/RenderTests.cs

+ 10 - 2
src/ChunkyImageLib/Chunk.cs

@@ -83,9 +83,17 @@ public class Chunk : IDisposable
     /// </summary>
     /// </summary>
     /// <param name="pos">The destination for the <paramref name="surface"/></param>
     /// <param name="pos">The destination for the <paramref name="surface"/></param>
     /// <param name="paint">The paint to use while drawing</param>
     /// <param name="paint">The paint to use while drawing</param>
-    public void DrawChunkOn(DrawingSurface surface, VecD pos, Paint? paint = null)
+    public void DrawChunkOn(DrawingSurface surface, VecD pos, Paint? paint = null, SamplingOptions? samplingOptions = null)
     {
     {
-        surface.Canvas.DrawSurface(Surface.DrawingSurface, (float)pos.X, (float)pos.Y, paint);
+        if (samplingOptions == null || samplingOptions == SamplingOptions.Default)
+        {
+            surface.Canvas.DrawSurface(Surface.DrawingSurface, (float)pos.X, (float)pos.Y, paint);
+        }
+        else
+        {
+            using var snapshot = Surface.DrawingSurface.Snapshot();
+            surface.Canvas.DrawImage(snapshot, (float)pos.X, (float)pos.Y, samplingOptions.Value, paint);
+        }
     }
     }
 
 
     public unsafe RectI? FindPreciseBounds(RectI? passedSearchRegion = null)
     public unsafe RectI? FindPreciseBounds(RectI? passedSearchRegion = null)

+ 6 - 6
src/ChunkyImageLib/ChunkyImage.cs

@@ -365,7 +365,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
     /// </returns>
     /// </returns>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
     public bool DrawMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos,
     public bool DrawMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos,
-        Paint? paint = null)
+        Paint? paint = null, SamplingOptions? samplingOptions = null)
     {
     {
         lock (lockObject)
         lock (lockObject)
         {
         {
@@ -390,7 +390,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
             {
             {
                 if (committedChunk is null)
                 if (committedChunk is null)
                     return false;
                     return false;
-                committedChunk.DrawChunkOn(surface, pos, paint);
+                committedChunk.DrawChunkOn(surface, pos, paint, samplingOptions);
                 return true;
                 return true;
             }
             }
 
 
@@ -399,7 +399,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
             {
             {
                 if (latestChunk.IsT2)
                 if (latestChunk.IsT2)
                 {
                 {
-                    latestChunk.AsT2.DrawChunkOn(surface, pos, paint);
+                    latestChunk.AsT2.DrawChunkOn(surface, pos, paint, samplingOptions);
                     return true;
                     return true;
                 }
                 }
 
 
@@ -415,7 +415,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
                 blendModePaint);
                 blendModePaint);
             if (lockTransparency)
             if (lockTransparency)
                 OperationHelper.ClampAlpha(tempChunk.Surface.DrawingSurface, committedChunk.Surface.DrawingSurface);
                 OperationHelper.ClampAlpha(tempChunk.Surface.DrawingSurface, committedChunk.Surface.DrawingSurface);
-            tempChunk.DrawChunkOn(surface, pos, paint);
+            tempChunk.DrawChunkOn(surface, pos, paint, samplingOptions);
 
 
             return true;
             return true;
         }
         }
@@ -458,7 +458,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
 
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
     public bool DrawCommittedChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos,
     public bool DrawCommittedChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos,
-        Paint? paint = null)
+        Paint? paint = null, SamplingOptions? samplingOptions = null)
     {
     {
         lock (lockObject)
         lock (lockObject)
         {
         {
@@ -466,7 +466,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
             var chunk = GetCommittedChunk(chunkPos, resolution);
             var chunk = GetCommittedChunk(chunkPos, resolution);
             if (chunk is null)
             if (chunk is null)
                 return false;
                 return false;
-            chunk.DrawChunkOn(surface, pos, paint);
+            chunk.DrawChunkOn(surface, pos, paint, samplingOptions);
             return true;
             return true;
         }
         }
     }
     }

+ 8 - 7
src/ChunkyImageLib/ChunkyImageEx.cs

@@ -19,9 +19,10 @@ public static class IReadOnlyChunkyImageEx
     /// <param name="pos">Starting position on the surface</param>
     /// <param name="pos">Starting position on the surface</param>
     /// <param name="paint">Paint to use for drawing</param>
     /// <param name="paint">Paint to use for drawing</param>
     public static void DrawMostUpToDateRegionOn
     public static void DrawMostUpToDateRegionOn
-        (this IReadOnlyChunkyImage image, RectI fullResRegion, ChunkResolution resolution, DrawingSurface surface, VecD pos, Paint? paint = null)
+    (this IReadOnlyChunkyImage image, RectI fullResRegion, ChunkResolution resolution, DrawingSurface surface,
+        VecD pos, Paint? paint = null, SamplingOptions? sampling = null)
     {
     {
-        DrawRegionOn(fullResRegion, resolution, surface, pos, image.DrawMostUpToDateChunkOn, paint);
+        DrawRegionOn(fullResRegion, resolution, surface, pos, image.DrawMostUpToDateChunkOn, paint, sampling);
     }
     }
     
     
     /// <summary>
     /// <summary>
@@ -35,9 +36,9 @@ public static class IReadOnlyChunkyImageEx
     /// <param name="pos">Starting position on the surface</param>
     /// <param name="pos">Starting position on the surface</param>
     /// <param name="paint">Paint to use for drawing</param>
     /// <param name="paint">Paint to use for drawing</param>
     public static void DrawCommittedRegionOn
     public static void DrawCommittedRegionOn
-        (this IReadOnlyChunkyImage image, RectI fullResRegion, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null)
+        (this IReadOnlyChunkyImage image, RectI fullResRegion, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null, SamplingOptions? samplingOptions = null)
     {
     {
-        DrawRegionOn(fullResRegion, resolution, surface, pos, image.DrawCommittedChunkOn, paint);
+        DrawRegionOn(fullResRegion, resolution, surface, pos, image.DrawCommittedChunkOn, paint, samplingOptions);
     }
     }
     
     
     private static void DrawRegionOn(
     private static void DrawRegionOn(
@@ -45,8 +46,8 @@ public static class IReadOnlyChunkyImageEx
         ChunkResolution resolution,
         ChunkResolution resolution,
         DrawingSurface surface,
         DrawingSurface surface,
         VecD pos,
         VecD pos,
-        Func<VecI, ChunkResolution, DrawingSurface, VecD, Paint?, bool> drawingFunc,
-        Paint? paint = null)
+        Func<VecI, ChunkResolution, DrawingSurface, VecD, Paint?, SamplingOptions?, bool> drawingFunc,
+        Paint? paint = null, SamplingOptions? samplingOptions = null)
     {
     {
         int count = surface.Canvas.Save();
         int count = surface.Canvas.Save();
         surface.Canvas.ClipRect(new RectD(pos, fullResRegion.Size));
         surface.Canvas.ClipRect(new RectD(pos, fullResRegion.Size));
@@ -61,7 +62,7 @@ public static class IReadOnlyChunkyImageEx
             for (int i = chunkTopLeft.X; i <= chunkBotRight.X; i++)
             for (int i = chunkTopLeft.X; i <= chunkBotRight.X; i++)
             {
             {
                 var chunkPos = new VecI(i, j);
                 var chunkPos = new VecI(i, j);
-                drawingFunc(chunkPos, resolution, surface, offsetTargetRes + (chunkPos - chunkTopLeft) * resolution.PixelSize() + pos, paint);
+                drawingFunc(chunkPos, resolution, surface, offsetTargetRes + (chunkPos - chunkTopLeft) * resolution.PixelSize() + pos, paint, samplingOptions);
             }
             }
         }
         }
 
 

+ 2 - 2
src/ChunkyImageLib/IReadOnlyChunkyImage.cs

@@ -10,8 +10,8 @@ namespace ChunkyImageLib;
 
 
 public interface IReadOnlyChunkyImage
 public interface IReadOnlyChunkyImage
 {
 {
-    bool DrawMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos, Paint? paint = null);
-    bool DrawCommittedChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos, Paint? paint = null);
+    bool DrawMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos, Paint? paint = null, SamplingOptions? sampling = null);
+    bool DrawCommittedChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos, Paint? paint = null, SamplingOptions? sampling = null);
     RectI? FindChunkAlignedMostUpToDateBounds();
     RectI? FindChunkAlignedMostUpToDateBounds();
     RectI? FindChunkAlignedCommittedBounds();
     RectI? FindChunkAlignedCommittedBounds();
     RectI? FindTightCommittedBounds(ChunkResolution precision = ChunkResolution.Full, bool fallbackToChunkAligned = false);
     RectI? FindTightCommittedBounds(ChunkResolution precision = ChunkResolution.Full, bool fallbackToChunkAligned = false);

+ 5 - 5
src/ChunkyImageLib/Operations/ChunkyImageOperation.cs

@@ -63,13 +63,13 @@ internal class ChunkyImageOperation : IMirroredDrawOperation
         VecI bottomLeft = OperationHelper.GetChunkPos(
         VecI bottomLeft = OperationHelper.GetChunkPos(
             new VecI(chunkCenterOnImage.X - halfChunk.X, chunkCenterOnImage.Y + halfChunk.Y), ChunkyImage.FullChunkSize);
             new VecI(chunkCenterOnImage.X - halfChunk.X, chunkCenterOnImage.Y + halfChunk.Y), ChunkyImage.FullChunkSize);
 
 
-        Func<VecI, ChunkResolution, DrawingSurface, VecD, Paint?, bool> drawMethod = drawUpToDate ? imageToDraw.DrawMostUpToDateChunkOn : imageToDraw.DrawCommittedChunkOn;
+        Func<VecI, ChunkResolution, DrawingSurface, VecD, Paint?, SamplingOptions?, bool> drawMethod = drawUpToDate ? imageToDraw.DrawMostUpToDateChunkOn : imageToDraw.DrawCommittedChunkOn;
         
         
         drawMethod(
         drawMethod(
             topLeft,
             topLeft,
             targetChunk.Resolution,
             targetChunk.Resolution,
             targetChunk.Surface.DrawingSurface,
             targetChunk.Surface.DrawingSurface,
-            (VecI)((topLeft * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * targetChunk.Resolution.Multiplier()), null);
+            (VecI)((topLeft * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * targetChunk.Resolution.Multiplier()), null, null);
 
 
         VecI gridShift = targetPos % ChunkyImage.FullChunkSize;
         VecI gridShift = targetPos % ChunkyImage.FullChunkSize;
         if (gridShift.X != 0)
         if (gridShift.X != 0)
@@ -79,7 +79,7 @@ internal class ChunkyImageOperation : IMirroredDrawOperation
             targetChunk.Resolution,
             targetChunk.Resolution,
             targetChunk.Surface.DrawingSurface,
             targetChunk.Surface.DrawingSurface,
             (VecI)((topRight * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * targetChunk.Resolution.Multiplier()),
             (VecI)((topRight * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * targetChunk.Resolution.Multiplier()),
-            null);
+            null, null);
         }
         }
         if (gridShift.Y != 0)
         if (gridShift.Y != 0)
         {
         {
@@ -88,7 +88,7 @@ internal class ChunkyImageOperation : IMirroredDrawOperation
             targetChunk.Resolution,
             targetChunk.Resolution,
             targetChunk.Surface.DrawingSurface,
             targetChunk.Surface.DrawingSurface,
             (VecI)((bottomLeft * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * targetChunk.Resolution.Multiplier()),
             (VecI)((bottomLeft * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * targetChunk.Resolution.Multiplier()),
-            null);
+            null, null);
         }
         }
         if (gridShift.X != 0 && gridShift.Y != 0)
         if (gridShift.X != 0 && gridShift.Y != 0)
         {
         {
@@ -97,7 +97,7 @@ internal class ChunkyImageOperation : IMirroredDrawOperation
             targetChunk.Resolution,
             targetChunk.Resolution,
             targetChunk.Surface.DrawingSurface,
             targetChunk.Surface.DrawingSurface,
             (VecI)((bottomRight * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * targetChunk.Resolution.Multiplier()),
             (VecI)((bottomRight * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * targetChunk.Resolution.Multiplier()),
-            null);
+            null, null);
         }
         }
 
 
         targetChunk.Surface.DrawingSurface.Canvas.Restore();
         targetChunk.Surface.DrawingSurface.Canvas.Restore();

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit 1be85ac9f4bc6b584e6a3a5a3d0287201c6a5f03
+Subproject commit b6c34c96ac5b01abad69604465445270270270d2

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/SceneObjectRenderContext.cs

@@ -13,7 +13,7 @@ public class SceneObjectRenderContext : RenderContext
     public RenderOutputProperty TargetPropertyOutput { get; }
     public RenderOutputProperty TargetPropertyOutput { get; }
 
 
     public SceneObjectRenderContext(RenderOutputProperty targetPropertyOutput, DrawingSurface surface, RectD localBounds, KeyFrameTime frameTime,
     public SceneObjectRenderContext(RenderOutputProperty targetPropertyOutput, DrawingSurface surface, RectD localBounds, KeyFrameTime frameTime,
-        ChunkResolution chunkResolution, VecI renderOutputSize, VecI documentSize, bool renderSurfaceIsScene, ColorSpace processingColorSpace, double opacity) : base(surface, frameTime, chunkResolution, renderOutputSize, documentSize, processingColorSpace, opacity)
+        ChunkResolution chunkResolution, VecI renderOutputSize, VecI documentSize, bool renderSurfaceIsScene, ColorSpace processingColorSpace, SamplingOptions desiredSampling, double opacity) : base(surface, frameTime, chunkResolution, renderOutputSize, documentSize, processingColorSpace, desiredSampling, opacity)
     {
     {
         TargetPropertyOutput = targetPropertyOutput;
         TargetPropertyOutput = targetPropertyOutput;
         LocalBounds = localBounds;
         LocalBounds = localBounds;

+ 3 - 4
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CreateImageNode.cs

@@ -72,10 +72,9 @@ public class CreateImageNode : Node, IPreviewRenderable
 
 
         int saved = surface.DrawingSurface.Canvas.Save();
         int saved = surface.DrawingSurface.Canvas.Save();
 
 
-        RenderContext ctx = new RenderContext(surface.DrawingSurface, context.FrameTime, context.ChunkResolution,
-            surface.Size, context.DocumentSize, context.ProcessingColorSpace);
-        ctx.FullRerender = context.FullRerender;
-        ctx.TargetOutput = context.TargetOutput;
+        RenderContext ctx = context.Clone();
+        ctx.RenderSurface = surface.DrawingSurface;
+        ctx.RenderOutputSize = surface.Size;
 
 
         float chunkMultiplier = (float)context.ChunkResolution.Multiplier();
         float chunkMultiplier = (float)context.ChunkResolution.Multiplier();
 
 

+ 5 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Effects/OutlineNode.cs

@@ -88,7 +88,8 @@ public class OutlineNode : RenderNode, IRenderInput
 
 
             var ctx = context.Clone();
             var ctx = context.Clone();
             ctx.ChunkResolution = ChunkResolution.Full;
             ctx.ChunkResolution = ChunkResolution.Full;
-            ctx.RenderOutputSize = (VecI)(context.RenderOutputSize * context.ChunkResolution.InvertedMultiplier());
+            bool isAdjusted = context.DocumentSize == context.RenderOutputSize;
+            ctx.RenderOutputSize = isAdjusted ? context.RenderOutputSize : (VecI)(context.RenderOutputSize * context.ChunkResolution.InvertedMultiplier());
 
 
             Background.Value.Paint(ctx, temp.DrawingSurface);
             Background.Value.Paint(ctx, temp.DrawingSurface);
 
 
@@ -124,7 +125,10 @@ public class OutlineNode : RenderNode, IRenderInput
 
 
     public override bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
     public override bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
     {
     {
+        int saved = renderOn.Canvas.Save();
+        renderOn.Canvas.Scale((float)context.ChunkResolution.Multiplier());
         OnPaint(context, renderOn);
         OnPaint(context, renderOn);
+        renderOn.Canvas.RestoreToCount(saved);
         return true;
         return true;
     }
     }
 
 

+ 84 - 27
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FilterNodes/ApplyFilterNode.cs

@@ -11,67 +11,124 @@ using Drawie.Numerics;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
 
 
 [NodeInfo("ApplyFilter")]
 [NodeInfo("ApplyFilter")]
-public class ApplyFilterNode : RenderNode, IRenderInput
+public sealed class ApplyFilterNode : RenderNode, IRenderInput
 {
 {
-    private Paint _paint = new();
+    private readonly Paint _paint = new();
+    private readonly Paint _maskPaint = new()
+    {
+        BlendMode = BlendMode.DstIn,
+        ColorFilter = Filters.MaskFilter
+    };
+
     public InputProperty<Filter?> Filter { get; }
     public InputProperty<Filter?> Filter { get; }
 
 
     public RenderInputProperty Background { get; }
     public RenderInputProperty Background { get; }
 
 
+    public RenderInputProperty Mask { get; }
+    
+    public InputProperty<bool> InvertMask { get; }
+
     public ApplyFilterNode()
     public ApplyFilterNode()
     {
     {
         Background = CreateRenderInput("Input", "IMAGE");
         Background = CreateRenderInput("Input", "IMAGE");
         Filter = CreateInput<Filter>("Filter", "FILTER", null);
         Filter = CreateInput<Filter>("Filter", "FILTER", null);
+        Mask = CreateRenderInput("Mask", "MASK");
+        InvertMask = CreateInput("InvertMask", "INVERT_MASK", false);
         Output.FirstInChain = null;
         Output.FirstInChain = null;
         AllowHighDpiRendering = true;
         AllowHighDpiRendering = true;
     }
     }
 
 
-
     protected override void Paint(RenderContext context, DrawingSurface surface)
     protected override void Paint(RenderContext context, DrawingSurface surface)
     {
     {
         AllowHighDpiRendering = (Background.Connection.Node as RenderNode)?.AllowHighDpiRendering ?? true;
         AllowHighDpiRendering = (Background.Connection.Node as RenderNode)?.AllowHighDpiRendering ?? true;
         base.Paint(context, surface);
         base.Paint(context, surface);
     }
     }
 
 
-    protected override void OnPaint(RenderContext context, DrawingSurface surface)
+    protected override void OnPaint(RenderContext context, DrawingSurface outputSurface)
     {
     {
-        if (_paint == null)
-            return;
+        using var _ = DetermineTargetSurface(context, outputSurface, out var processingSurface);
+
+        DrawWithFilter(context, outputSurface, processingSurface);
+        
+        // If the Mask is null, we already drew to the output surface, otherwise we still need to draw to it (and apply the mask)
+        if (processingSurface != outputSurface)
+        {
+            ApplyWithMask(context, processingSurface, outputSurface);
+        }
+    }
 
 
+    private void DrawWithFilter(RenderContext context, DrawingSurface outputSurface, DrawingSurface processingSurface)
+    {
         _paint.SetFilters(Filter.Value);
         _paint.SetFilters(Filter.Value);
 
 
         if (!context.ProcessingColorSpace.IsSrgb)
         if (!context.ProcessingColorSpace.IsSrgb)
         {
         {
-            var intermediate = Texture.ForProcessing(surface, context.ProcessingColorSpace);
+            HandleNonSrgbContext(context, outputSurface, processingSurface);
+            return;
+        }
+
+        var layer = processingSurface.Canvas.SaveLayer(_paint);
+        Background.Value?.Paint(context, processingSurface);
+        processingSurface.Canvas.RestoreToCount(layer);
+    }
 
 
-            int saved = surface.Canvas.Save();
-            surface.Canvas.SetMatrix(Matrix3X3.Identity);
+    private void HandleNonSrgbContext(RenderContext context, DrawingSurface surface, DrawingSurface targetSurface)
+    {
+        using var intermediate = Texture.ForProcessing(surface, context.ProcessingColorSpace);
 
 
-            Background.Value?.Paint(context, intermediate.DrawingSurface);
+        Background.Value?.Paint(context, intermediate.DrawingSurface);
 
 
-            var srgbSurface = Texture.ForProcessing(intermediate.Size, ColorSpace.CreateSrgb());
+        using var srgbSurface = Texture.ForProcessing(intermediate.Size, ColorSpace.CreateSrgb());
 
 
-            srgbSurface.DrawingSurface.Canvas.SaveLayer(_paint);
-            srgbSurface.DrawingSurface.Canvas.DrawSurface(intermediate.DrawingSurface, 0, 0);
-            srgbSurface.DrawingSurface.Canvas.Restore();
+        srgbSurface.DrawingSurface.Canvas.SaveLayer(_paint);
+        srgbSurface.DrawingSurface.Canvas.DrawSurface(intermediate.DrawingSurface, 0, 0);
+        srgbSurface.DrawingSurface.Canvas.Restore();
 
 
-            surface.Canvas.DrawSurface(srgbSurface.DrawingSurface, 0, 0);
-            surface.Canvas.RestoreToCount(saved);
-            intermediate.Dispose();
-            srgbSurface.Dispose();
-        }
-        else
-        {
-            int layer = surface.Canvas.SaveLayer(_paint);
-            Background.Value?.Paint(context, surface);
-            surface.Canvas.RestoreToCount(layer);
-        }
+        var saved = targetSurface.Canvas.Save();
+        targetSurface.Canvas.SetMatrix(Matrix3X3.Identity);
+
+        targetSurface.Canvas.DrawSurface(srgbSurface.DrawingSurface, 0, 0);
+        targetSurface.Canvas.RestoreToCount(saved);
+    }
+
+    private Texture? DetermineTargetSurface(RenderContext context, DrawingSurface outputSurface, out DrawingSurface targetSurface)
+    {
+        targetSurface = outputSurface;
+        
+        if (Mask.Value == null)
+            return null;
+        
+        Background.Value?.Paint(context, outputSurface);
+        var texture = Texture.ForProcessing(outputSurface, context.ProcessingColorSpace);
+        targetSurface = texture.DrawingSurface;
+        
+        return texture;
     }
     }
 
 
-    public override RectD? GetPreviewBounds(int frame, string elementToRenderName = "")
+    private void ApplyWithMask(RenderContext context, DrawingSurface processedSurface, DrawingSurface finalSurface)
     {
     {
-        return PreviewUtils.FindPreviewBounds(Background.Connection, frame, elementToRenderName);
+        _maskPaint.BlendMode = !InvertMask.Value ? BlendMode.DstIn : BlendMode.DstOut;
+        var maskLayer = processedSurface.Canvas.SaveLayer(_maskPaint);
+        Mask.Value?.Paint(context, processedSurface);
+        processedSurface.Canvas.RestoreToCount(maskLayer);
+
+        var saved = finalSurface.Canvas.Save();
+        finalSurface.Canvas.SetMatrix(Matrix3X3.Identity);
+
+        finalSurface.Canvas.DrawSurface(processedSurface, 0, 0);
+        finalSurface.Canvas.RestoreToCount(saved);
     }
     }
 
 
+    public override RectD? GetPreviewBounds(int frame, string elementToRenderName = "") =>
+        PreviewUtils.FindPreviewBounds(Background.Connection, frame, elementToRenderName);
+
     public override Node CreateCopy() => new ApplyFilterNode();
     public override Node CreateCopy() => new ApplyFilterNode();
+
+    public override void Dispose()
+    {
+        base.Dispose();
+        
+        _paint.Dispose();
+        _maskPaint.Dispose();
+    }
 }
 }

+ 15 - 4
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Image/MaskNode.cs

@@ -6,20 +6,23 @@ using PixiEditor.ChangeableDocument.Rendering;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Image;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Image;
 
 
 [NodeInfo("Mask")]
 [NodeInfo("Mask")]
-public class MaskNode : RenderNode, IRenderInput
+public sealed class MaskNode : RenderNode, IRenderInput
 {
 {
     public RenderInputProperty Background { get; }
     public RenderInputProperty Background { get; }
     public RenderInputProperty Mask { get; }
     public RenderInputProperty Mask { get; }
+    public InputProperty<bool> Invert { get; }
 
 
-    protected Paint maskPaint = new Paint()
+    private readonly Paint maskPaint = new()
     {
     {
-        BlendMode = Drawie.Backend.Core.Surfaces.BlendMode.DstIn, ColorFilter = Nodes.Filters.MaskFilter
+        BlendMode = BlendMode.DstIn,
+        ColorFilter = Filters.MaskFilter
     };
     };
 
 
     public MaskNode()
     public MaskNode()
     {
     {
         Background = CreateRenderInput("Background", "INPUT");
         Background = CreateRenderInput("Background", "INPUT");
         Mask = CreateRenderInput("Mask", "MASK");
         Mask = CreateRenderInput("Mask", "MASK");
+        Invert = CreateInput("Invert", "INVERT", false);
         AllowHighDpiRendering = true;
         AllowHighDpiRendering = true;
         Output.FirstInChain = null;
         Output.FirstInChain = null;
     }
     }
@@ -38,14 +41,22 @@ public class MaskNode : RenderNode, IRenderInput
             return;
             return;
         }
         }
 
 
+        maskPaint.BlendMode = !Invert.Value ? BlendMode.DstIn : BlendMode.DstOut;
+
         int layer = surface.Canvas.SaveLayer(maskPaint);
         int layer = surface.Canvas.SaveLayer(maskPaint);
         Mask.Value.Paint(context, surface);
         Mask.Value.Paint(context, surface);
         surface.Canvas.RestoreToCount(layer);
         surface.Canvas.RestoreToCount(layer);
     }
     }
 
 
-
     public override Node CreateCopy()
     public override Node CreateCopy()
     {
     {
         return new MaskNode();
         return new MaskNode();
     }
     }
+
+    public override void Dispose()
+    {
+        base.Dispose();
+        
+        maskPaint.Dispose();
+    }
 }
 }

+ 29 - 3
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ImageLayerNode.cs

@@ -5,6 +5,7 @@ using PixiEditor.ChangeableDocument.Helpers;
 using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.ChangeableDocument.Rendering;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
@@ -130,6 +131,7 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
 
 
         var sceneSize = GetSceneSize(ctx.FrameTime);
         var sceneSize = GetSceneSize(ctx.FrameTime);
         VecD topLeft = sceneSize / 2f;
         VecD topLeft = sceneSize / 2f;
+
         if (renderedSurfaceFrame == null || ctx.FullRerender || ctx.FrameTime.Frame != renderedSurfaceFrame)
         if (renderedSurfaceFrame == null || ctx.FullRerender || ctx.FrameTime.Frame != renderedSurfaceFrame)
         {
         {
             GetLayerImageAtFrame(ctx.FrameTime.Frame).DrawMostUpToDateRegionOn(
             GetLayerImageAtFrame(ctx.FrameTime.Frame).DrawMostUpToDateRegionOn(
@@ -139,7 +141,18 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
         }
         }
         else
         else
         {
         {
-            workingSurface.Canvas.DrawSurface(fullResrenderedSurface.DrawingSurface, -topLeft, paint);
+            if (ctx.DesiredSamplingOptions == SamplingOptions.Default)
+            {
+                workingSurface.Canvas.DrawSurface(
+                    fullResrenderedSurface.DrawingSurface, -(float)topLeft.X, -(float)topLeft.Y, paint);
+            }
+            else
+            {
+                using var snapshot = fullResrenderedSurface.DrawingSurface.Snapshot();
+                workingSurface.Canvas.DrawImage(snapshot, -(float)topLeft.X, -(float)topLeft.Y,
+                    ctx.DesiredSamplingOptions,
+                    paint);
+            }
         }
         }
 
 
         workingSurface.Canvas.RestoreToCount(saved);
         workingSurface.Canvas.RestoreToCount(saved);
@@ -242,14 +255,27 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
 
 
         if (renderedSurfaceFrame == cacheFrame)
         if (renderedSurfaceFrame == cacheFrame)
         {
         {
-            renderOnto.Canvas.DrawSurface(fullResrenderedSurface.DrawingSurface, VecI.Zero, blendPaint);
+            int saved = renderOnto.Canvas.Save();
+            renderOnto.Canvas.Scale((float)context.ChunkResolution.Multiplier());
+            if (context.DesiredSamplingOptions == SamplingOptions.Default)
+            {
+                renderOnto.Canvas.DrawSurface(
+                    fullResrenderedSurface.DrawingSurface, 0, 0, blendPaint);
+            }
+            else
+            {
+                using var snapshot = fullResrenderedSurface.DrawingSurface.Snapshot();
+                renderOnto.Canvas.DrawImage(snapshot, 0, 0, context.DesiredSamplingOptions, blendPaint);
+            }
+
+            renderOnto.Canvas.RestoreToCount(saved);
         }
         }
         else
         else
         {
         {
             img.DrawMostUpToDateRegionOn(
             img.DrawMostUpToDateRegionOn(
                 new RectI(0, 0, img.LatestSize.X, img.LatestSize.Y),
                 new RectI(0, 0, img.LatestSize.X, img.LatestSize.Y),
                 context.ChunkResolution,
                 context.ChunkResolution,
-                renderOnto, VecI.Zero, blendPaint);
+                renderOnto, VecI.Zero, blendPaint, context.DesiredSamplingOptions);
         }
         }
 
 
         return true;
         return true;

+ 14 - 4
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/LayerNode.cs

@@ -62,7 +62,8 @@ public abstract class LayerNode : StructureNode, IReadOnlyLayerNode, IClipSource
                 DrawLayerOnTexture(context, tempSurface.DrawingSurface, context.ChunkResolution, useFilters, targetPaint);
                 DrawLayerOnTexture(context, tempSurface.DrawingSurface, context.ChunkResolution, useFilters, targetPaint);
 
 
                 blendPaint.SetFilters(null);
                 blendPaint.SetFilters(null);
-                DrawWithResolution(tempSurface.DrawingSurface, renderOnto, context.ChunkResolution);
+                DrawWithResolution(tempSurface.DrawingSurface, renderOnto, context.ChunkResolution,
+                    context.DesiredSamplingOptions);
             }
             }
 
 
             return;
             return;
@@ -130,7 +131,8 @@ public abstract class LayerNode : StructureNode, IReadOnlyLayerNode, IClipSource
             blendPaint.SetFilters(null);
             blendPaint.SetFilters(null);
         }
         }
 
 
-        DrawWithResolution(outputWorkingSurface.DrawingSurface, renderOnto, adjustedResolution);
+        DrawWithResolution(outputWorkingSurface.DrawingSurface, renderOnto, adjustedResolution,
+            context.DesiredSamplingOptions);
 
 
         renderOnto.Canvas.RestoreToCount(saved);
         renderOnto.Canvas.RestoreToCount(saved);
         outputWorkingSurface.DrawingSurface.Canvas.Restore();
         outputWorkingSurface.DrawingSurface.Canvas.Restore();
@@ -149,13 +151,21 @@ public abstract class LayerNode : StructureNode, IReadOnlyLayerNode, IClipSource
         workingSurface.Canvas.RestoreToCount(scaled);
         workingSurface.Canvas.RestoreToCount(scaled);
     }
     }
 
 
-    private void DrawWithResolution(DrawingSurface source, DrawingSurface target, ChunkResolution resolution)
+    private void DrawWithResolution(DrawingSurface source, DrawingSurface target, ChunkResolution resolution, SamplingOptions sampling)
     {
     {
         int scaled = target.Canvas.Save();
         int scaled = target.Canvas.Save();
         float multiplier = (float)resolution.InvertedMultiplier();
         float multiplier = (float)resolution.InvertedMultiplier();
         target.Canvas.Scale(multiplier, multiplier);
         target.Canvas.Scale(multiplier, multiplier);
 
 
-        target.Canvas.DrawSurface(source, 0, 0, blendPaint);
+        if (sampling == SamplingOptions.Default)
+        {
+            target.Canvas.DrawSurface(source, 0, 0, blendPaint);
+        }
+        else
+        {
+            using var snapshot = source.Snapshot();
+            target.Canvas.DrawImage(snapshot, 0, 0, sampling, blendPaint);
+        }
 
 
         target.Canvas.RestoreToCount(scaled);
         target.Canvas.RestoreToCount(scaled);
     }
     }

+ 1 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/OutputNode.cs

@@ -56,6 +56,7 @@ public class OutputNode : Node, IRenderInput, IPreviewRenderable
         }
         }
 
 
         int saved = renderOn.Canvas.Save();
         int saved = renderOn.Canvas.Save();
+        renderOn.Canvas.Scale((float)context.ChunkResolution.Multiplier());
         Input.Value.Paint(context, renderOn);
         Input.Value.Paint(context, renderOn);
 
 
         renderOn.Canvas.RestoreToCount(saved);
         renderOn.Canvas.RestoreToCount(saved);

+ 11 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/RenderNode.cs

@@ -66,7 +66,15 @@ public abstract class RenderNode : Node, IPreviewRenderable, IHighDpiRenderNode
                 surface.Canvas.Scale((float)context.ChunkResolution.InvertedMultiplier());
                 surface.Canvas.Scale((float)context.ChunkResolution.InvertedMultiplier());
             }
             }
 
 
-            surface.Canvas.DrawSurface(target, 0, 0);
+            if (context.DesiredSamplingOptions != SamplingOptions.Default)
+            {
+                using var snapshot = target.Snapshot();
+                surface.Canvas.DrawImage(snapshot, 0, 0, context.DesiredSamplingOptions);
+            }
+            else
+            {
+                surface.Canvas.DrawSurface(target, 0, 0);
+            }
 
 
             if (RendersInAbsoluteCoordinates)
             if (RendersInAbsoluteCoordinates)
             {
             {
@@ -85,7 +93,9 @@ public abstract class RenderNode : Node, IPreviewRenderable, IHighDpiRenderNode
     public virtual bool RenderPreview(DrawingSurface renderOn, RenderContext context,
     public virtual bool RenderPreview(DrawingSurface renderOn, RenderContext context,
         string elementToRenderName)
         string elementToRenderName)
     {
     {
+        int saved = renderOn.Canvas.Save();
         OnPaint(context, renderOn);
         OnPaint(context, renderOn);
+        renderOn.Canvas.RestoreToCount(saved);
         return true;
         return true;
     }
     }
 
 

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

@@ -104,7 +104,8 @@ public class ShaderNode : RenderNode, IRenderInput, ICustomShaderNode
         Uniforms uniforms;
         Uniforms uniforms;
         uniforms = new Uniforms();
         uniforms = new Uniforms();
 
 
-        VecI finalSize = (VecI)(context.RenderOutputSize * context.ChunkResolution.InvertedMultiplier());
+        bool isAdjusted = context.RenderOutputSize == context.DocumentSize;
+        VecI finalSize = isAdjusted ? context.RenderOutputSize : (VecI)(context.RenderOutputSize * context.ChunkResolution.InvertedMultiplier());
 
 
         uniforms.Add("iResolution", new Uniform("iResolution", (VecD)finalSize));
         uniforms.Add("iResolution", new Uniform("iResolution", (VecD)finalSize));
         uniforms.Add("iNormalizedTime", new Uniform("iNormalizedTime", (float)context.FrameTime.NormalizedTime));
         uniforms.Add("iNormalizedTime", new Uniform("iNormalizedTime", (float)context.FrameTime.NormalizedTime));
@@ -123,8 +124,11 @@ public class ShaderNode : RenderNode, IRenderInput, ICustomShaderNode
         int saved = texture.DrawingSurface.Canvas.Save();
         int saved = texture.DrawingSurface.Canvas.Save();
         //texture.DrawingSurface.Canvas.Scale((float)context.ChunkResolution.Multiplier(), (float)context.ChunkResolution.Multiplier());
         //texture.DrawingSurface.Canvas.Scale((float)context.ChunkResolution.Multiplier(), (float)context.ChunkResolution.Multiplier());
 
 
-        var ctx = new RenderContext(texture.DrawingSurface, context.FrameTime, ChunkResolution.Full, finalSize,
-            context.DocumentSize, context.ProcessingColorSpace, context.Opacity);
+        var ctx = context.Clone();
+        ctx.RenderSurface = texture.DrawingSurface;
+        ctx.RenderOutputSize = finalSize;
+        ctx.ChunkResolution = ChunkResolution.Full;
+
         Background.Value.Paint(ctx, texture.DrawingSurface);
         Background.Value.Paint(ctx, texture.DrawingSurface);
         texture.DrawingSurface.Canvas.RestoreToCount(saved);
         texture.DrawingSurface.Canvas.RestoreToCount(saved);
 
 
@@ -156,8 +160,10 @@ public class ShaderNode : RenderNode, IRenderInput, ICustomShaderNode
 
 
         if (context.ChunkResolution != ChunkResolution.Full)
         if (context.ChunkResolution != ChunkResolution.Full)
         {
         {
+            bool isAdjusted = context.RenderOutputSize == context.DocumentSize;
+            VecI finalSize = isAdjusted ? context.RenderOutputSize : (VecI)(context.RenderOutputSize * context.ChunkResolution.InvertedMultiplier());
             var intermediateSurface = RequestTexture(51,
             var intermediateSurface = RequestTexture(51,
-                (VecI)(context.RenderOutputSize * context.ChunkResolution.InvertedMultiplier()),
+                finalSize,
                 ColorSpace.Value == ColorSpaceType.Inherit
                 ColorSpace.Value == ColorSpaceType.Inherit
                     ? context.ProcessingColorSpace
                     ? context.ProcessingColorSpace
                     : ColorSpace.Value == ColorSpaceType.Srgb
                     : ColorSpace.Value == ColorSpaceType.Srgb

+ 7 - 4
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/StructureNode.cs

@@ -184,8 +184,7 @@ public abstract class StructureNode : RenderNode, IReadOnlyStructureNode, IRende
 
 
         SceneObjectRenderContext renderObjectContext = new SceneObjectRenderContext(output, renderTarget, localBounds,
         SceneObjectRenderContext renderObjectContext = new SceneObjectRenderContext(output, renderTarget, localBounds,
             context.FrameTime, context.ChunkResolution, context.RenderOutputSize, context.DocumentSize, renderTarget == context.RenderSurface,
             context.FrameTime, context.ChunkResolution, context.RenderOutputSize, context.DocumentSize, renderTarget == context.RenderSurface,
-            context.ProcessingColorSpace,
-            context.Opacity);
+            context.ProcessingColorSpace, context.DesiredSamplingOptions, context.Opacity);
         renderObjectContext.FullRerender = context.FullRerender;
         renderObjectContext.FullRerender = context.FullRerender;
         return renderObjectContext;
         return renderObjectContext;
     }
     }
@@ -247,7 +246,12 @@ public abstract class StructureNode : RenderNode, IReadOnlyStructureNode, IRende
 
 
         VecI targetSize = img.LatestSize;
         VecI targetSize = img.LatestSize;
 
 
-        var ctx = DrawingBackendApi.Current.RenderingDispatcher.EnsureContext();
+        if (targetSize.X <= 0 || targetSize.Y <= 0)
+        {
+            return;
+        }
+
+        using var ctx = DrawingBackendApi.Current.RenderingDispatcher.EnsureContext();
         renderSurface = RequestTexture(textureId, targetSize, processingColorSpace, false);
         renderSurface = RequestTexture(textureId, targetSize, processingColorSpace, false);
 
 
         int saved = renderSurface.DrawingSurface.Canvas.Save();
         int saved = renderSurface.DrawingSurface.Canvas.Save();
@@ -265,7 +269,6 @@ public abstract class StructureNode : RenderNode, IReadOnlyStructureNode, IRende
         }
         }
 
 
         renderSurface.DrawingSurface.Canvas.RestoreToCount(saved);
         renderSurface.DrawingSurface.Canvas.RestoreToCount(saved);
-        ctx?.Dispose();
     }
     }
 
 
     protected void ApplyRasterClip(DrawingSurface toClip, DrawingSurface clipSource)
     protected void ApplyRasterClip(DrawingSurface toClip, DrawingSurface clipSource)

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

@@ -14,7 +14,7 @@ public class TextureCache : IDisposable
     {
     {
         if (_managedTextures.TryGetValue(id, out var texture))
         if (_managedTextures.TryGetValue(id, out var texture))
         {
         {
-            if (texture.Size != size || texture.IsDisposed || texture.ColorSpace != processingCs)
+            if (texture.Size != size || texture.IsDisposed || !texture.ColorSpace.Equals(processingCs))
             {
             {
                 texture.Dispose();
                 texture.Dispose();
                 texture = new Texture(CreateImageInfo(size, processingCs));
                 texture = new Texture(CreateImageInfo(size, processingCs));

+ 8 - 3
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/VectorLayerNode.cs

@@ -132,7 +132,11 @@ public class VectorLayerNode : LayerNode, ITransformableObject, IReadOnlyVectorN
             return false;
             return false;
         }
         }
 
 
+
+        int savedCount = renderOn.Canvas.Save();
+        renderOn.Canvas.Scale((float)context.ChunkResolution.Multiplier());
         Rasterize(renderOn, paint);
         Rasterize(renderOn, paint);
+        renderOn.Canvas.RestoreToCount(savedCount);
 
 
         return true;
         return true;
     }
     }
@@ -188,8 +192,9 @@ public class VectorLayerNode : LayerNode, ITransformableObject, IReadOnlyVectorN
     {
     {
         int layer;
         int layer;
         // TODO: This can be further optimized by passing opacity, blend mode and filters directly to the rasterization method
         // TODO: This can be further optimized by passing opacity, blend mode and filters directly to the rasterization method
-        if (paint != null && (paint.Color.A < 255 || paint.ColorFilter != null || paint.ImageFilter != null || paint.Shader != null ||
-            paint.BlendMode != Drawie.Backend.Core.Surfaces.BlendMode.SrcOver))
+        if (paint != null && (paint.Color.A < 255 || paint.ColorFilter != null || paint.ImageFilter != null ||
+                              paint.Shader != null ||
+                              paint.BlendMode != Drawie.Backend.Core.Surfaces.BlendMode.SrcOver))
         {
         {
             layer = surface.Canvas.SaveLayer(paint);
             layer = surface.Canvas.SaveLayer(paint);
         }
         }
@@ -197,7 +202,7 @@ public class VectorLayerNode : LayerNode, ITransformableObject, IReadOnlyVectorN
         {
         {
             layer = surface.Canvas.Save();
             layer = surface.Canvas.Save();
         }
         }
-        
+
         RenderableShapeData?.RasterizeTransformed(surface.Canvas);
         RenderableShapeData?.RasterizeTransformed(surface.Canvas);
 
 
         surface.Canvas.RestoreToCount(layer);
         surface.Canvas.RestoreToCount(layer);

+ 2 - 1
src/PixiEditor.ChangeableDocument/Changes/NodeGraph/EvaluateGraph_Change.cs

@@ -1,4 +1,5 @@
 using Drawie.Backend.Core;
 using Drawie.Backend.Core;
+using Drawie.Backend.Core.Surfaces;
 using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Graph;
 using PixiEditor.ChangeableDocument.Changeables.Graph;
 using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.ChangeableDocument.Rendering;
@@ -33,7 +34,7 @@ internal class EvaluateGraph_Change : Change
         using Texture renderTexture = Texture.ForProcessing(target.Size, target.ProcessingColorSpace);
         using Texture renderTexture = Texture.ForProcessing(target.Size, target.ProcessingColorSpace);
         RenderContext context =
         RenderContext context =
             new(renderTexture.DrawingSurface, frameTime, ChunkResolution.Full, target.Size, target.Size,
             new(renderTexture.DrawingSurface, frameTime, ChunkResolution.Full, target.Size, target.Size,
-                target.ProcessingColorSpace) { FullRerender = true };
+                target.ProcessingColorSpace, SamplingOptions.Default) { FullRerender = true };
         foreach (var nodeToEvaluate in queue)
         foreach (var nodeToEvaluate in queue)
         {
         {
             nodeToEvaluate.Execute(context);
             nodeToEvaluate.Execute(context);

+ 21 - 6
src/PixiEditor.ChangeableDocument/Rendering/DocumentRenderer.cs

@@ -21,6 +21,7 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
 {
 {
     private Queue<RenderRequest> renderRequests = new();
     private Queue<RenderRequest> renderRequests = new();
     private Texture renderTexture;
     private Texture renderTexture;
+    private int lastExecutedGraphFrame = -1;
 
 
     public DocumentRenderer(IReadOnlyDocument document)
     public DocumentRenderer(IReadOnlyDocument document)
     {
     {
@@ -68,7 +69,7 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
         toRenderOn.Canvas.SetMatrix(Matrix3X3.Identity);
         toRenderOn.Canvas.SetMatrix(Matrix3X3.Identity);
 
 
         RenderContext context = new(renderTexture.DrawingSurface, frame, resolution, Document.Size, Document.Size,
         RenderContext context = new(renderTexture.DrawingSurface, frame, resolution, Document.Size, Document.Size,
-            Document.ProcessingColorSpace);
+            Document.ProcessingColorSpace, SamplingOptions.Default);
         context.FullRerender = true;
         context.FullRerender = true;
         IReadOnlyNodeGraph membersOnlyGraph = ConstructMembersOnlyGraph(layersToCombine, Document.NodeGraph);
         IReadOnlyNodeGraph membersOnlyGraph = ConstructMembersOnlyGraph(layersToCombine, Document.NodeGraph);
         try
         try
@@ -114,7 +115,7 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
         toRenderOn.Canvas.SetMatrix(Matrix3X3.Identity);
         toRenderOn.Canvas.SetMatrix(Matrix3X3.Identity);
 
 
         RenderContext context = new(renderTexture.DrawingSurface, frameTime, resolution, Document.Size, Document.Size,
         RenderContext context = new(renderTexture.DrawingSurface, frameTime, resolution, Document.Size, Document.Size,
-            Document.ProcessingColorSpace);
+            Document.ProcessingColorSpace, SamplingOptions.Default);
         context.FullRerender = true;
         context.FullRerender = true;
 
 
         node.RenderForOutput(context, toRenderOn, null);
         node.RenderForOutput(context, toRenderOn, null);
@@ -134,7 +135,7 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
         RenderRequest request = new(tcs, context, renderOn, previewRenderable, elementToRenderName);
         RenderRequest request = new(tcs, context, renderOn, previewRenderable, elementToRenderName);
 
 
         renderRequests.Enqueue(request);
         renderRequests.Enqueue(request);
-        ExecuteRenderRequests();
+        ExecuteRenderRequests(context.FrameTime);
 
 
         return await tcs.Task;
         return await tcs.Task;
     }
     }
@@ -201,8 +202,12 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
         IsBusy = true;
         IsBusy = true;
 
 
         renderOn.Canvas.Clear();
         renderOn.Canvas.Clear();
+        int savedCount = renderOn.Canvas.Save();
+        renderOn.Canvas.Scale((float)context.ChunkResolution.Multiplier());
         context.RenderSurface = renderOn;
         context.RenderSurface = renderOn;
         Document.NodeGraph.Execute(context);
         Document.NodeGraph.Execute(context);
+        lastExecutedGraphFrame = context.FrameTime.Frame;
+        renderOn.Canvas.RestoreToCount(savedCount);
 
 
         IsBusy = false;
         IsBusy = false;
 
 
@@ -236,8 +241,9 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
             : Document.NodeGraph;
             : Document.NodeGraph;
 
 
         RenderContext context =
         RenderContext context =
-            new(renderTexture.DrawingSurface, frameTime, ChunkResolution.Full, SolveRenderOutputSize(customOutput, graph, Document.Size),
-                Document.Size, Document.ProcessingColorSpace) { FullRerender = true };
+            new(renderTexture.DrawingSurface, frameTime, ChunkResolution.Full,
+                SolveRenderOutputSize(customOutput, graph, Document.Size),
+                Document.Size, Document.ProcessingColorSpace, SamplingOptions.Default) { FullRerender = true };
 
 
         if (hasCustomOutput)
         if (hasCustomOutput)
         {
         {
@@ -264,19 +270,28 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
         renderTexture.DrawingSurface.Canvas.Restore();
         renderTexture.DrawingSurface.Canvas.Restore();
         toRenderOn.Canvas.Restore();
         toRenderOn.Canvas.Restore();
 
 
+        lastExecutedGraphFrame = frameTime.Frame;
+
         IsBusy = false;
         IsBusy = false;
     }
     }
 
 
-    private void ExecuteRenderRequests()
+    private void ExecuteRenderRequests(KeyFrameTime frameTime)
     {
     {
         if (isExecuting) return;
         if (isExecuting) return;
 
 
         isExecuting = true;
         isExecuting = true;
         using var ctx = DrawingBackendApi.Current?.RenderingDispatcher.EnsureContext();
         using var ctx = DrawingBackendApi.Current?.RenderingDispatcher.EnsureContext();
+
         while (renderRequests.Count > 0)
         while (renderRequests.Count > 0)
         {
         {
             RenderRequest request = renderRequests.Dequeue();
             RenderRequest request = renderRequests.Dequeue();
 
 
+            if (frameTime.Frame != lastExecutedGraphFrame && request.PreviewRenderable != this)
+            {
+                using Texture executeSurface = Texture.ForDisplay(new VecI(1));
+                RenderDocument(executeSurface.DrawingSurface, frameTime, VecI.One);
+            }
+
             try
             try
             {
             {
                 bool result = true;
                 bool result = true;

+ 5 - 3
src/PixiEditor.ChangeableDocument/Rendering/RenderContext.cs

@@ -13,6 +13,7 @@ public class RenderContext
 
 
     public KeyFrameTime FrameTime { get; }
     public KeyFrameTime FrameTime { get; }
     public ChunkResolution ChunkResolution { get; set; }
     public ChunkResolution ChunkResolution { get; set; }
+    public SamplingOptions DesiredSamplingOptions { get; set; } = SamplingOptions.Default;
     public VecI RenderOutputSize { get; set; }
     public VecI RenderOutputSize { get; set; }
 
 
     public VecI DocumentSize { get; set; }
     public VecI DocumentSize { get; set; }
@@ -24,7 +25,7 @@ public class RenderContext
 
 
 
 
     public RenderContext(DrawingSurface renderSurface, KeyFrameTime frameTime, ChunkResolution chunkResolution,
     public RenderContext(DrawingSurface renderSurface, KeyFrameTime frameTime, ChunkResolution chunkResolution,
-        VecI renderOutputSize, VecI documentSize, ColorSpace processingColorSpace, double opacity = 1)
+        VecI renderOutputSize, VecI documentSize, ColorSpace processingColorSpace, SamplingOptions desiredSampling, double opacity = 1)
     {
     {
         RenderSurface = renderSurface;
         RenderSurface = renderSurface;
         FrameTime = frameTime;
         FrameTime = frameTime;
@@ -33,6 +34,7 @@ public class RenderContext
         Opacity = opacity;
         Opacity = opacity;
         ProcessingColorSpace = processingColorSpace;
         ProcessingColorSpace = processingColorSpace;
         DocumentSize = documentSize;
         DocumentSize = documentSize;
+        DesiredSamplingOptions = desiredSampling;
     }
     }
 
 
     public static DrawingApiBlendMode GetDrawingBlendMode(BlendMode blendMode)
     public static DrawingApiBlendMode GetDrawingBlendMode(BlendMode blendMode)
@@ -63,10 +65,10 @@ public class RenderContext
 
 
     public RenderContext Clone()
     public RenderContext Clone()
     {
     {
-        return new RenderContext(RenderSurface, FrameTime, ChunkResolution, RenderOutputSize, DocumentSize, ProcessingColorSpace, Opacity)
+        return new RenderContext(RenderSurface, FrameTime, ChunkResolution, RenderOutputSize, DocumentSize, ProcessingColorSpace, DesiredSamplingOptions, Opacity)
         {
         {
             FullRerender = FullRerender,
             FullRerender = FullRerender,
-            TargetOutput = TargetOutput
+            TargetOutput = TargetOutput,
         };
         };
     }
     }
 }
 }

+ 5 - 0
src/PixiEditor.Extensions.CommonApi/UserPreferences/PreferencesConstants.cs

@@ -50,4 +50,9 @@ public static class PreferencesConstants
     public const string SecondaryBackgroundColorDefault = "#353535";
     public const string SecondaryBackgroundColorDefault = "#353535";
     public const string SecondaryBackgroundColor = "SecondaryBackgroundColor";
     public const string SecondaryBackgroundColor = "SecondaryBackgroundColor";
 
 
+    public const string MaxBilinearSampleSize = "MaxBilinearSampleSize";
+    public const int MaxBilinearSampleSizeDefault = 4096;
+
+    public const string DisablePreviews = "DisablePreviews";
+    public const bool DisablePreviewsDefault = false;
 }
 }

+ 42 - 17
src/PixiEditor.Extensions.CommonApi/UserPreferences/Settings/PixiEditor/PixiEditorSettings.cs

@@ -3,10 +3,11 @@
 public static class PixiEditorSettings
 public static class PixiEditorSettings
 {
 {
     private const string PixiEditor = "PixiEditor";
     private const string PixiEditor = "PixiEditor";
-    
+
     public static class Palettes
     public static class Palettes
     {
     {
-        public static LocalSetting<IEnumerable<string>> FavouritePalettes { get; } = LocalSetting.NonOwned<IEnumerable<string>>(PixiEditor);
+        public static LocalSetting<IEnumerable<string>> FavouritePalettes { get; } =
+            LocalSetting.NonOwned<IEnumerable<string>>(PixiEditor);
     }
     }
 
 
     public static class Update
     public static class Update
@@ -22,16 +23,18 @@ public static class PixiEditorSettings
 
 
         public static LocalSetting<string> PoEditorApiKey { get; } = new($"{PixiEditor}:POEditor_API_Key");
         public static LocalSetting<string> PoEditorApiKey { get; } = new($"{PixiEditor}:POEditor_API_Key");
     }
     }
-    
+
     public static class Tools
     public static class Tools
     {
     {
         public static SyncedSetting<bool> EnableSharedToolbar { get; } = SyncedSetting.NonOwned<bool>(PixiEditor);
         public static SyncedSetting<bool> EnableSharedToolbar { get; } = SyncedSetting.NonOwned<bool>(PixiEditor);
 
 
-        public static SyncedSetting<RightClickMode> RightClickMode { get; } = SyncedSetting.NonOwned<RightClickMode>(PixiEditor);
-        
+        public static SyncedSetting<RightClickMode> RightClickMode { get; } =
+            SyncedSetting.NonOwned<RightClickMode>(PixiEditor);
+
         public static SyncedSetting<bool> IsPenModeEnabled { get; } = SyncedSetting.NonOwned<bool>(PixiEditor);
         public static SyncedSetting<bool> IsPenModeEnabled { get; } = SyncedSetting.NonOwned<bool>(PixiEditor);
 
 
-        public static SyncedSetting<string> PrimaryToolset { get; } = SyncedSetting.NonOwned<string>(PixiEditor, "PAINT_TOOLSET");
+        public static SyncedSetting<string> PrimaryToolset { get; } =
+            SyncedSetting.NonOwned<string>(PixiEditor, "PAINT_TOOLSET");
     }
     }
 
 
     public static class File
     public static class File
@@ -39,12 +42,13 @@ public static class PixiEditorSettings
         public static SyncedSetting<int> DefaultNewFileWidth { get; } = SyncedSetting.NonOwned(PixiEditor, 64);
         public static SyncedSetting<int> DefaultNewFileWidth { get; } = SyncedSetting.NonOwned(PixiEditor, 64);
 
 
         public static SyncedSetting<int> DefaultNewFileHeight { get; } = SyncedSetting.NonOwned(PixiEditor, 64);
         public static SyncedSetting<int> DefaultNewFileHeight { get; } = SyncedSetting.NonOwned(PixiEditor, 64);
-        
-        public static LocalSetting<IEnumerable<string>> RecentlyOpened { get; } = LocalSetting.NonOwned<IEnumerable<string>>(PixiEditor, []);
-    
+
+        public static LocalSetting<IEnumerable<string>> RecentlyOpened { get; } =
+            LocalSetting.NonOwned<IEnumerable<string>>(PixiEditor, []);
+
         public static SyncedSetting<int> MaxOpenedRecently { get; } = SyncedSetting.NonOwned(PixiEditor, 8);
         public static SyncedSetting<int> MaxOpenedRecently { get; } = SyncedSetting.NonOwned(PixiEditor, 8);
     }
     }
-    
+
     public static class StartupWindow
     public static class StartupWindow
     {
     {
         public static SyncedSetting<bool> ShowStartupWindow { get; } = SyncedSetting.NonOwned(PixiEditor, true);
         public static SyncedSetting<bool> ShowStartupWindow { get; } = SyncedSetting.NonOwned(PixiEditor, true);
@@ -53,9 +57,10 @@ public static class PixiEditorSettings
 
 
         public static SyncedSetting<bool> NewsPanelCollapsed { get; } = SyncedSetting.NonOwned<bool>(PixiEditor);
         public static SyncedSetting<bool> NewsPanelCollapsed { get; } = SyncedSetting.NonOwned<bool>(PixiEditor);
 
 
-        public static SyncedSetting<IEnumerable<int>> LastCheckedNewsIds { get; } = SyncedSetting.NonOwned<IEnumerable<int>>(PixiEditor);
+        public static SyncedSetting<IEnumerable<int>> LastCheckedNewsIds { get; } =
+            SyncedSetting.NonOwned<IEnumerable<int>>(PixiEditor);
     }
     }
-    
+
     public static class Discord
     public static class Discord
     {
     {
         public static SyncedSetting<bool> EnableRichPresence { get; } = SyncedSetting.NonOwned(PixiEditor, true);
         public static SyncedSetting<bool> EnableRichPresence { get; } = SyncedSetting.NonOwned(PixiEditor, true);
@@ -74,12 +79,32 @@ public static class PixiEditorSettings
 
 
     public static class Scene
     public static class Scene
     {
     {
-        public static SyncedSetting<bool> AutoScaleBackground { get; } = SyncedSetting.NonOwned(PixiEditor, PreferencesConstants.AutoScaleBackgroundDefault, PreferencesConstants.AutoScaleBackground);
-        public static SyncedSetting<double> CustomBackgroundScaleX { get; } = SyncedSetting.NonOwned(PixiEditor, PreferencesConstants.CustomBackgroundScaleDefault, PreferencesConstants.CustomBackgroundScaleX);
-        public static SyncedSetting<double> CustomBackgroundScaleY { get; } = SyncedSetting.NonOwned(PixiEditor, PreferencesConstants.CustomBackgroundScaleDefault, PreferencesConstants.CustomBackgroundScaleY);
+        public static SyncedSetting<bool> AutoScaleBackground { get; } = SyncedSetting.NonOwned(PixiEditor,
+            PreferencesConstants.AutoScaleBackgroundDefault, PreferencesConstants.AutoScaleBackground);
+
+        public static SyncedSetting<double> CustomBackgroundScaleX { get; } = SyncedSetting.NonOwned(PixiEditor,
+            PreferencesConstants.CustomBackgroundScaleDefault, PreferencesConstants.CustomBackgroundScaleX);
+
+        public static SyncedSetting<double> CustomBackgroundScaleY { get; } = SyncedSetting.NonOwned(PixiEditor,
+            PreferencesConstants.CustomBackgroundScaleDefault, PreferencesConstants.CustomBackgroundScaleY);
 
 
-        public static SyncedSetting<string> PrimaryBackgroundColor { get; } = SyncedSetting.NonOwned(PixiEditor, PreferencesConstants.PrimaryBackgroundColorDefault, PreferencesConstants.PrimaryBackgroundColor);
-        public static SyncedSetting<string> SecondaryBackgroundColor { get; } = SyncedSetting.NonOwned(PixiEditor, PreferencesConstants.SecondaryBackgroundColorDefault, PreferencesConstants.SecondaryBackgroundColor);
+        public static SyncedSetting<string> PrimaryBackgroundColor { get; } = SyncedSetting.NonOwned(PixiEditor,
+            PreferencesConstants.PrimaryBackgroundColorDefault, PreferencesConstants.PrimaryBackgroundColor);
 
 
+        public static SyncedSetting<string> SecondaryBackgroundColor { get; } = SyncedSetting.NonOwned(PixiEditor,
+            PreferencesConstants.SecondaryBackgroundColorDefault, PreferencesConstants.SecondaryBackgroundColor);
+    }
+
+    public static class Performance
+    {
+        public static SyncedSetting<int> MaxBilinearSampleSize { get; } = SyncedSetting.NonOwned(
+            PixiEditor,
+            PreferencesConstants.MaxBilinearSampleSizeDefault,
+            PreferencesConstants.MaxBilinearSampleSize);
+
+        public static SyncedSetting<bool> DisablePreviews { get; } = SyncedSetting.NonOwned(
+            PixiEditor,
+            PreferencesConstants.DisablePreviewsDefault,
+            PreferencesConstants.DisablePreviews);
     }
     }
 }
 }

BIN
src/PixiEditor.Extensions.Sdk/build/PixiEditor.Api.CGlueMSBuild.dll


+ 6 - 1
src/PixiEditor/Data/Localization/Languages/en.json

@@ -1111,5 +1111,10 @@
   "ERROR_GPU_RESOURCES_CREATION": "Failed to create resources: Try updating your GPU drivers or try setting different rendering api in settings. \nError: '{0}'",
   "ERROR_GPU_RESOURCES_CREATION": "Failed to create resources: Try updating your GPU drivers or try setting different rendering api in settings. \nError: '{0}'",
   "ERROR_SAVING_PREFERENCES_DESC": "Failed to save preferences with error: '{0}'. Please check if you have write permissions to the PixiEditor data folder.",
   "ERROR_SAVING_PREFERENCES_DESC": "Failed to save preferences with error: '{0}'. Please check if you have write permissions to the PixiEditor data folder.",
   "ERROR_SAVING_PREFERENCES": "Failed to save preferences",
   "ERROR_SAVING_PREFERENCES": "Failed to save preferences",
-  "PREFERRED_RENDERER": "Preferred Render Api"
+  "PREFERRED_RENDERER": "Preferred Render Api",
+  "PERFORMANCE": "Performance",
+  "DISABLE_PREVIEWS": "Disable Previews",
+  "MAX_BILINEAR_CANVAS_SIZE": "Max Bilinear Canvas Size",
+  "MAX_BILINEAR_CANVAS_SIZE_DESC": "Maximum canvas size for bilinear filtering. Set to 0 to disable bilinear filtering. Bilinear filtering improves the quality of the canvas, but can cause performance issues on large canvases.",
+  "INVERT_MASK": "Invert mask"
 }
 }

+ 26 - 7
src/PixiEditor/Models/DocumentModels/ActionAccumulator.cs

@@ -6,6 +6,7 @@ using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.ChangeableDocument.Actions.Undo;
 using PixiEditor.ChangeableDocument.Actions.Undo;
 using PixiEditor.ChangeableDocument.ChangeInfos;
 using PixiEditor.ChangeableDocument.ChangeInfos;
 using Drawie.Backend.Core.Bridge;
 using Drawie.Backend.Core.Bridge;
+using PixiEditor.Extensions.CommonApi.UserPreferences.Settings.PixiEditor;
 using PixiEditor.Helpers;
 using PixiEditor.Helpers;
 using PixiEditor.Models.DocumentPassthroughActions;
 using PixiEditor.Models.DocumentPassthroughActions;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers;
@@ -104,7 +105,8 @@ internal class ActionAccumulator
                 queuedActions = new();
                 queuedActions = new();
 
 
                 List<IChangeInfo?> changes;
                 List<IChangeInfo?> changes;
-                if (AreAllPassthrough(toExecute))
+                bool allPassthrough = AreAllPassthrough(toExecute);
+                if (allPassthrough)
                 {
                 {
                     changes = toExecute.Select(a => (IChangeInfo?)a.action).ToList();
                     changes = toExecute.Select(a => (IChangeInfo?)a.action).ToList();
                 }
                 }
@@ -119,6 +121,8 @@ internal class ActionAccumulator
                         action.action is ChangeBoundary_Action or Redo_Action or Undo_Action);
                         action.action is ChangeBoundary_Action or Redo_Action or Undo_Action);
                 bool viewportRefreshRequest =
                 bool viewportRefreshRequest =
                     toExecute.Any(static action => action.action is RefreshViewport_PassthroughAction);
                     toExecute.Any(static action => action.action is RefreshViewport_PassthroughAction);
+                bool refreshPreviewsRequest =
+                    toExecute.Any(static action => action.action is RefreshPreviews_PassthroughAction);
                 bool changeFrameRequest =
                 bool changeFrameRequest =
                     toExecute.Any(static action => action.action is SetActiveFrame_PassthroughAction);
                     toExecute.Any(static action => action.action is SetActiveFrame_PassthroughAction);
 
 
@@ -130,10 +134,12 @@ internal class ActionAccumulator
                 if (undoBoundaryPassed)
                 if (undoBoundaryPassed)
                     internals.Updater.AfterUndoBoundaryPassed();
                     internals.Updater.AfterUndoBoundaryPassed();
 
 
+
                 var affectedAreas = new AffectedAreasGatherer(document.AnimationHandler.ActiveFrameTime,
                 var affectedAreas = new AffectedAreasGatherer(document.AnimationHandler.ActiveFrameTime,
                     internals.Tracker,
                     internals.Tracker,
-                    optimizedChanges);
-                if (DrawingBackendApi.Current.IsHardwareAccelerated)
+                    optimizedChanges, refreshPreviewsRequest);
+
+                if (DrawingBackendApi.Current.IsHardwareAccelerated && !allPassthrough)
                 {
                 {
                     canvasUpdater.UpdateGatheredChunksSync(affectedAreas,
                     canvasUpdater.UpdateGatheredChunksSync(affectedAreas,
                         undoBoundaryPassed || viewportRefreshRequest);
                         undoBoundaryPassed || viewportRefreshRequest);
@@ -144,10 +150,21 @@ internal class ActionAccumulator
                         undoBoundaryPassed || viewportRefreshRequest);
                         undoBoundaryPassed || viewportRefreshRequest);
                 }*/
                 }*/
 
 
-                previewUpdater.UpdatePreviews(
-                    affectedAreas.ChangedMembers,
-                    affectedAreas.ChangedMasks,
-                    affectedAreas.ChangedNodes, affectedAreas.ChangedKeyFrames);
+                bool previewsDisabled = PixiEditorSettings.Performance.DisablePreviews.Value;
+
+                if (!previewsDisabled)
+                {
+                    if (undoBoundaryPassed || viewportRefreshRequest || changeFrameRequest ||
+                        document.SizeBindable.LongestAxis <= LiveUpdatePerformanceThreshold)
+                    {
+                        previewUpdater.UpdatePreviews(
+                            affectedAreas.ChangedMembers,
+                            affectedAreas.ChangedMasks,
+                            affectedAreas.ChangedNodes, affectedAreas.ChangedKeyFrames,
+                            affectedAreas.IgnoreAnimationPreviews,
+                            undoBoundaryPassed || refreshPreviewsRequest);
+                    }
+                }
 
 
                 // force refresh viewports for better responsiveness
                 // force refresh viewports for better responsiveness
                 foreach (var (_, value) in internals.State.Viewports)
                 foreach (var (_, value) in internals.State.Viewports)
@@ -175,6 +192,8 @@ internal class ActionAccumulator
         executing = false;
         executing = false;
     }
     }
 
 
+    private const int LiveUpdatePerformanceThreshold = 2048;
+
     private bool AreAllPassthrough(List<(ActionSource, IAction)> actions)
     private bool AreAllPassthrough(List<(ActionSource, IAction)> actions)
     {
     {
         foreach (var action in actions)
         foreach (var action in actions)

+ 7 - 0
src/PixiEditor/Models/DocumentPassthroughActions/RefreshPreviews_PassthroughAction.cs

@@ -0,0 +1,7 @@
+using PixiEditor.ChangeableDocument.Actions;
+using PixiEditor.ChangeableDocument.ChangeInfos;
+using PixiEditor.Models.Position;
+
+namespace PixiEditor.Models.DocumentPassthroughActions;
+
+internal record class RefreshPreviews_PassthroughAction() : IAction, IChangeInfo;

+ 20 - 0
src/PixiEditor/Models/EnumTranslations.cs

@@ -11,6 +11,7 @@ using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Effects;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Matrix;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Matrix;
 using PixiEditor.Helpers;
 using PixiEditor.Helpers;
+using BlendMode = PixiEditor.ChangeableDocument.Enums.BlendMode;
 
 
 [assembly: LocalizeEnum<StrokeCap>(StrokeCap.Butt, "BUTT_STROKE_CAP")]
 [assembly: LocalizeEnum<StrokeCap>(StrokeCap.Butt, "BUTT_STROKE_CAP")]
 [assembly: LocalizeEnum<StrokeCap>(StrokeCap.Round, "ROUND_STROKE_CAP")]
 [assembly: LocalizeEnum<StrokeCap>(StrokeCap.Round, "ROUND_STROKE_CAP")]
@@ -98,3 +99,22 @@ using PixiEditor.Helpers;
 [assembly: LocalizeEnum<VoronoiFeature>(VoronoiFeature.F1, "F1_VORONOI_FEATURE")]
 [assembly: LocalizeEnum<VoronoiFeature>(VoronoiFeature.F1, "F1_VORONOI_FEATURE")]
 [assembly: LocalizeEnum<VoronoiFeature>(VoronoiFeature.F2, "F2_VORONOI_FEATURE")]
 [assembly: LocalizeEnum<VoronoiFeature>(VoronoiFeature.F2, "F2_VORONOI_FEATURE")]
 [assembly: LocalizeEnum<VoronoiFeature>(VoronoiFeature.F2MinusF1, "F2_MINUS_F1_VORONOI_FEATURE")]
 [assembly: LocalizeEnum<VoronoiFeature>(VoronoiFeature.F2MinusF1, "F2_MINUS_F1_VORONOI_FEATURE")]
+
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Normal, "NORMAL_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Darken, "DARKEN_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Lighten, "LIGHTEN_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Multiply, "MULTIPLY_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Screen, "SCREEN_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Overlay, "OVERLAY_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.HardLight, "HARD_LIGHT_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.SoftLight, "SOFT_LIGHT_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.ColorDodge, "COLOR_DODGE_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.ColorBurn, "COLOR_BURN_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Hue, "HUE_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Saturation, "SATURATION_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Color, "COLOR_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Luminosity, "LUMINOSITY_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Difference, "DIFFERENCE_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Exclusion, "EXCLUSION_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Erase, "ERASE_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.LinearDodge, "LINEAR_DODGE_BLEND_MODE")]

+ 2 - 1
src/PixiEditor/Models/Handlers/IDocument.cs

@@ -33,7 +33,7 @@ internal interface IDocument : IHandler, Extensions.CommonApi.Documents.IDocumen
     public VectorPath SelectionPathBindable { get; }
     public VectorPath SelectionPathBindable { get; }
     public INodeGraphHandler NodeGraphHandler { get; }
     public INodeGraphHandler NodeGraphHandler { get; }
     public DocumentStructureModule StructureHelper { get; }
     public DocumentStructureModule StructureHelper { get; }
-    public PreviewPainter PreviewPainter { get; set; }
+    public PreviewPainter? PreviewPainter { get; set; }
     public bool AllChangesSaved { get; }
     public bool AllChangesSaved { get; }
     public string CoordinatesString { get; set; }
     public string CoordinatesString { get; set; }
     public IReadOnlyCollection<IStructureMemberHandler> SoftSelectedStructureMembers { get; }
     public IReadOnlyCollection<IStructureMemberHandler> SoftSelectedStructureMembers { get; }
@@ -50,6 +50,7 @@ internal interface IDocument : IHandler, Extensions.CommonApi.Documents.IDocumen
     public DocumentRenderer Renderer { get; }
     public DocumentRenderer Renderer { get; }
     public ISnappingHandler SnappingHandler { get; }
     public ISnappingHandler SnappingHandler { get; }
     public IReadOnlyCollection<Guid> SelectedMembers { get; }
     public IReadOnlyCollection<Guid> SelectedMembers { get; }
+    public PreviewPainter? MiniPreviewPainter { get; set; }
     public void RemoveSoftSelectedMember(IStructureMemberHandler member);
     public void RemoveSoftSelectedMember(IStructureMemberHandler member);
     public void ClearSoftSelectedMembers();
     public void ClearSoftSelectedMembers();
     public void AddSoftSelectedMember(IStructureMemberHandler member);
     public void AddSoftSelectedMember(IStructureMemberHandler member);

+ 1 - 0
src/PixiEditor/Models/Handlers/INodePropertyHandler.cs

@@ -15,6 +15,7 @@ public interface INodePropertyHandler
     public ObservableCollection<INodePropertyHandler> ConnectedInputs { get; }
     public ObservableCollection<INodePropertyHandler> ConnectedInputs { get; }
 
 
     public event NodePropertyValueChanged ValueChanged;
     public event NodePropertyValueChanged ValueChanged;
+    public event EventHandler ConnectedOutputChanged;
     public INodeHandler Node { get; set; }
     public INodeHandler Node { get; set; }
     public Type PropertyType { get; }
     public Type PropertyType { get; }
     public void UpdateComputedValue();
     public void UpdateComputedValue();

+ 29 - 11
src/PixiEditor/Models/Rendering/AffectedAreasGatherer.cs

@@ -30,6 +30,7 @@ internal class AffectedAreasGatherer
 
 
     public AffectedArea MainImageArea { get; private set; } = new();
     public AffectedArea MainImageArea { get; private set; } = new();
     public HashSet<Guid> ChangedMembers { get; private set; } = new();
     public HashSet<Guid> ChangedMembers { get; private set; } = new();
+    public bool IgnoreAnimationPreviews { get; private set; }
     public HashSet<Guid> ChangedMasks { get; private set; } = new();
     public HashSet<Guid> ChangedMasks { get; private set; } = new();
     public HashSet<Guid> ChangedKeyFrames { get; private set; } = new();
     public HashSet<Guid> ChangedKeyFrames { get; private set; } = new();
 
 
@@ -40,10 +41,20 @@ internal class AffectedAreasGatherer
     private bool alreadyAddedWholeCanvasToEveryImagePreview = false;
     private bool alreadyAddedWholeCanvasToEveryImagePreview = false;
 
 
     public AffectedAreasGatherer(KeyFrameTime activeFrame, DocumentChangeTracker tracker,
     public AffectedAreasGatherer(KeyFrameTime activeFrame, DocumentChangeTracker tracker,
-        IReadOnlyList<IChangeInfo> changes)
+        IReadOnlyList<IChangeInfo> changes, bool refreshAllPreviews)
     {
     {
         this.tracker = tracker;
         this.tracker = tracker;
         ActiveFrame = activeFrame;
         ActiveFrame = activeFrame;
+
+        if (refreshAllPreviews)
+        {
+            AddWholeCanvasToMainImage();
+            AddWholeCanvasToEveryImagePreview(false);
+            AddWholeCanvasToEveryMaskPreview();
+            AddAllNodesToImagePreviews();
+            return;
+        }
+
         ProcessChanges(changes);
         ProcessChanges(changes);
 
 
         var outputNode = tracker.Document.NodeGraph.OutputNode;
         var outputNode = tracker.Document.NodeGraph.OutputNode;
@@ -102,7 +113,7 @@ internal class AffectedAreasGatherer
                     break;
                     break;
                 case Size_ChangeInfo:
                 case Size_ChangeInfo:
                     AddWholeCanvasToMainImage();
                     AddWholeCanvasToMainImage();
-                    AddWholeCanvasToEveryImagePreview();
+                    AddWholeCanvasToEveryImagePreview(false);
                     AddWholeCanvasToEveryMaskPreview();
                     AddWholeCanvasToEveryMaskPreview();
                     break;
                     break;
                 case StructureMemberMask_ChangeInfo info:
                 case StructureMemberMask_ChangeInfo info:
@@ -151,24 +162,25 @@ internal class AffectedAreasGatherer
                     break;
                     break;
                 case SetActiveFrame_PassthroughAction:
                 case SetActiveFrame_PassthroughAction:
                     AddWholeCanvasToMainImage();
                     AddWholeCanvasToMainImage();
-                    AddWholeCanvasToEveryImagePreview();
+                    AddWholeCanvasToEveryImagePreview(true);
                     AddAllNodesToImagePreviews();
                     AddAllNodesToImagePreviews();
+                    IgnoreAnimationPreviews = true;
                     break;
                     break;
                 case KeyFrameLength_ChangeInfo:
                 case KeyFrameLength_ChangeInfo:
                     AddWholeCanvasToMainImage();
                     AddWholeCanvasToMainImage();
-                    AddWholeCanvasToEveryImagePreview();
+                    AddWholeCanvasToEveryImagePreview(true);
                     break;
                     break;
                 case DeleteKeyFrame_ChangeInfo:
                 case DeleteKeyFrame_ChangeInfo:
                     AddWholeCanvasToMainImage();
                     AddWholeCanvasToMainImage();
-                    AddWholeCanvasToEveryImagePreview();
+                    AddWholeCanvasToEveryImagePreview(true);
                     break;
                     break;
                 case KeyFrameVisibility_ChangeInfo:
                 case KeyFrameVisibility_ChangeInfo:
                     AddWholeCanvasToMainImage();
                     AddWholeCanvasToMainImage();
-                    AddWholeCanvasToEveryImagePreview();
+                    AddWholeCanvasToEveryImagePreview(true);
                     break;
                     break;
                 case ConnectProperty_ChangeInfo info:
                 case ConnectProperty_ChangeInfo info:
                     AddWholeCanvasToMainImage();
                     AddWholeCanvasToMainImage();
-                    AddWholeCanvasToEveryImagePreview();
+                    AddWholeCanvasToEveryImagePreview(false);
                     AddToNodePreviews(info.InputNodeId);
                     AddToNodePreviews(info.InputNodeId);
                     if (info.OutputNodeId.HasValue)
                     if (info.OutputNodeId.HasValue)
                     {
                     {
@@ -178,7 +190,7 @@ internal class AffectedAreasGatherer
                     break;
                     break;
                 case PropertyValueUpdated_ChangeInfo info:
                 case PropertyValueUpdated_ChangeInfo info:
                     AddWholeCanvasToMainImage();
                     AddWholeCanvasToMainImage();
-                    AddWholeCanvasToEveryImagePreview();
+                    AddWholeCanvasToEveryImagePreview(false);
                     AddToNodePreviews(info.NodeId);
                     AddToNodePreviews(info.NodeId);
                     break;
                     break;
                 case ToggleOnionSkinning_PassthroughAction:
                 case ToggleOnionSkinning_PassthroughAction:
@@ -194,7 +206,7 @@ internal class AffectedAreasGatherer
                     break;
                     break;
                 case ProcessingColorSpace_ChangeInfo:
                 case ProcessingColorSpace_ChangeInfo:
                     AddWholeCanvasToMainImage();
                     AddWholeCanvasToMainImage();
-                    AddWholeCanvasToEveryImagePreview();
+                    AddWholeCanvasToEveryImagePreview(false);
                     AddWholeCanvasToEveryMaskPreview();
                     AddWholeCanvasToEveryMaskPreview();
                     break;
                     break;
             }
             }
@@ -381,12 +393,18 @@ internal class AffectedAreasGatherer
         }
         }
     }
     }
 
 
-    private void AddWholeCanvasToEveryImagePreview()
+    private void AddWholeCanvasToEveryImagePreview(bool onlyWithKeyFrames)
     {
     {
         if (alreadyAddedWholeCanvasToEveryImagePreview)
         if (alreadyAddedWholeCanvasToEveryImagePreview)
             return;
             return;
 
 
-        tracker.Document.ForEveryReadonlyMember((member) => AddWholeCanvasToImagePreviews(member.Id));
+        tracker.Document.ForEveryReadonlyMember((member) =>
+        {
+            if (!onlyWithKeyFrames || member.KeyFrames.Count > 0)
+            {
+                AddWholeCanvasToImagePreviews(member.Id);
+            }
+        });
         alreadyAddedWholeCanvasToEveryImagePreview = true;
         alreadyAddedWholeCanvasToEveryImagePreview = true;
     }
     }
 
 

+ 43 - 19
src/PixiEditor/Models/Rendering/MemberPreviewUpdater.cs

@@ -9,6 +9,7 @@ using PixiEditor.Models.DocumentModels;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers;
 using Drawie.Numerics;
 using Drawie.Numerics;
 using PixiEditor.ViewModels.Nodes;
 using PixiEditor.ViewModels.Nodes;
+using PixiEditor.ChangeableDocument.Rendering;
 
 
 namespace PixiEditor.Models.Rendering;
 namespace PixiEditor.Models.Rendering;
 
 
@@ -27,13 +28,15 @@ internal class MemberPreviewUpdater
     }
     }
 
 
     public void UpdatePreviews(HashSet<Guid> membersToUpdate,
     public void UpdatePreviews(HashSet<Guid> membersToUpdate,
-        HashSet<Guid> masksToUpdate, HashSet<Guid> nodesToUpdate, HashSet<Guid> keyFramesToUpdate)
+        HashSet<Guid> masksToUpdate, HashSet<Guid> nodesToUpdate, HashSet<Guid> keyFramesToUpdate,
+        bool ignoreAnimationPreviews, bool renderMiniPreviews)
     {
     {
         if (!membersToUpdate.Any() && !masksToUpdate.Any() && !nodesToUpdate.Any() &&
         if (!membersToUpdate.Any() && !masksToUpdate.Any() && !nodesToUpdate.Any() &&
             !keyFramesToUpdate.Any())
             !keyFramesToUpdate.Any())
             return;
             return;
 
 
-        UpdatePreviewPainters(membersToUpdate, masksToUpdate, nodesToUpdate, keyFramesToUpdate);
+        UpdatePreviewPainters(membersToUpdate, masksToUpdate, nodesToUpdate, keyFramesToUpdate, ignoreAnimationPreviews,
+            renderMiniPreviews);
     }
     }
 
 
     /// <summary>
     /// <summary>
@@ -42,13 +45,20 @@ internal class MemberPreviewUpdater
     /// <param name="members">Members that should be rendered</param>
     /// <param name="members">Members that should be rendered</param>
     /// <param name="masksToUpdate">Masks that should be rendered</param>
     /// <param name="masksToUpdate">Masks that should be rendered</param>
     private void UpdatePreviewPainters(HashSet<Guid> members, HashSet<Guid> masksToUpdate,
     private void UpdatePreviewPainters(HashSet<Guid> members, HashSet<Guid> masksToUpdate,
-        HashSet<Guid> nodesToUpdate, HashSet<Guid> keyFramesToUpdate)
+        HashSet<Guid> nodesToUpdate, HashSet<Guid> keyFramesToUpdate, bool ignoreAnimationPreviews,
+        bool renderLowPriorityPreviews)
     {
     {
-        RenderWholeCanvasPreview();
-        RenderLayersPreview(members);
-        RenderMaskPreviews(masksToUpdate);
+        RenderWholeCanvasPreview(renderLowPriorityPreviews);
+        if (renderLowPriorityPreviews)
+        {
+            RenderLayersPreview(members);
+            RenderMaskPreviews(masksToUpdate);
+        }
 
 
-        RenderAnimationPreviews(members, keyFramesToUpdate);
+        if (!ignoreAnimationPreviews)
+        {
+            RenderAnimationPreviews(members, keyFramesToUpdate);
+        }
 
 
         RenderNodePreviews(nodesToUpdate);
         RenderNodePreviews(nodesToUpdate);
     }
     }
@@ -56,21 +66,33 @@ internal class MemberPreviewUpdater
     /// <summary>
     /// <summary>
     /// Re-renders the preview of the whole canvas which is shown as the tab icon
     /// Re-renders the preview of the whole canvas which is shown as the tab icon
     /// </summary>
     /// </summary>
-    private void RenderWholeCanvasPreview()
+    /// <param name="renderMiniPreviews">Decides whether to re-render mini previews for the document</param>
+    private void RenderWholeCanvasPreview(bool renderMiniPreviews)
     {
     {
         var previewSize = StructureHelpers.CalculatePreviewSize(internals.Tracker.Document.Size);
         var previewSize = StructureHelpers.CalculatePreviewSize(internals.Tracker.Document.Size);
         //float scaling = (float)previewSize.X / doc.SizeBindable.X;
         //float scaling = (float)previewSize.X / doc.SizeBindable.X;
 
 
-        if (doc.PreviewPainter == null)
-        {
-            doc.PreviewPainter = new PreviewPainter(doc.Renderer, doc.Renderer, doc.AnimationHandler.ActiveFrameTime,
-                doc.SizeBindable, internals.Tracker.Document.ProcessingColorSpace);
-        }
+        doc.PreviewPainter ??= new PreviewPainter(doc.Renderer, doc.Renderer, doc.AnimationHandler.ActiveFrameTime,
+            doc.SizeBindable, internals.Tracker.Document.ProcessingColorSpace);
+
+        UpdateDocPreviewPainter(doc.PreviewPainter);
 
 
-        doc.PreviewPainter.DocumentSize = doc.SizeBindable;
-        doc.PreviewPainter.ProcessingColorSpace = internals.Tracker.Document.ProcessingColorSpace;
-        doc.PreviewPainter.FrameTime = doc.AnimationHandler.ActiveFrameTime;
-        doc.PreviewPainter.Repaint();
+        if (!renderMiniPreviews)
+            return;
+
+        doc.MiniPreviewPainter ??= new PreviewPainter(doc.Renderer, doc.Renderer,
+            doc.AnimationHandler.ActiveFrameTime,
+            doc.SizeBindable, internals.Tracker.Document.ProcessingColorSpace);
+
+        UpdateDocPreviewPainter(doc.MiniPreviewPainter);
+    }
+
+    private void UpdateDocPreviewPainter(PreviewPainter painter)
+    {
+        painter.DocumentSize = doc.SizeBindable;
+        painter.ProcessingColorSpace = internals.Tracker.Document.ProcessingColorSpace;
+        painter.FrameTime = doc.AnimationHandler.ActiveFrameTime;
+        painter.Repaint();
     }
     }
 
 
     private void RenderLayersPreview(HashSet<Guid> memberGuids)
     private void RenderLayersPreview(HashSet<Guid> memberGuids)
@@ -117,7 +139,8 @@ internal class MemberPreviewUpdater
                 {
                 {
                     if (!keyFramesGuids.Contains(childFrame.Id))
                     if (!keyFramesGuids.Contains(childFrame.Id))
                     {
                     {
-                        if (!memberGuids.Contains(childFrame.LayerGuid) || !IsInFrame(childFrame))
+                        if (!memberGuids.Contains(childFrame.LayerGuid) || !IsInFrame(childFrame) ||
+                            !groupHandler.IsVisible)
                             continue;
                             continue;
                     }
                     }
 
 
@@ -135,7 +158,7 @@ internal class MemberPreviewUpdater
     private bool IsInFrame(ICelHandler cel)
     private bool IsInFrame(ICelHandler cel)
     {
     {
         return cel.StartFrameBindable <= doc.AnimationHandler.ActiveFrameBindable &&
         return cel.StartFrameBindable <= doc.AnimationHandler.ActiveFrameBindable &&
-               cel.StartFrameBindable + cel.DurationBindable >= doc.AnimationHandler.ActiveFrameBindable;
+               cel.StartFrameBindable + cel.DurationBindable > doc.AnimationHandler.ActiveFrameBindable;
     }
     }
 
 
     private void RenderFramePreview(ICelHandler cel)
     private void RenderFramePreview(ICelHandler cel)
@@ -288,6 +311,7 @@ internal class MemberPreviewUpdater
                 nodeVm.ResultPainter = new PreviewPainter(doc.Renderer, renderable,
                 nodeVm.ResultPainter = new PreviewPainter(doc.Renderer, renderable,
                     doc.AnimationHandler.ActiveFrameTime,
                     doc.AnimationHandler.ActiveFrameTime,
                     doc.SizeBindable, internals.Tracker.Document.ProcessingColorSpace);
                     doc.SizeBindable, internals.Tracker.Document.ProcessingColorSpace);
+                nodeVm.ResultPainter.AllowPartialResolutions = false;
                 nodeVm.ResultPainter.Repaint();
                 nodeVm.ResultPainter.Repaint();
             }
             }
             else
             else

+ 40 - 3
src/PixiEditor/Models/Rendering/PreviewPainter.cs

@@ -23,6 +23,8 @@ public class PreviewPainter : IDisposable
     public VecI DocumentSize { get; set; }
     public VecI DocumentSize { get; set; }
     public DocumentRenderer Renderer { get; set; }
     public DocumentRenderer Renderer { get; set; }
 
 
+    public bool AllowPartialResolutions { get; set; } = true;
+
     public bool CanRender => canRender;
     public bool CanRender => canRender;
 
 
     public event Action<bool>? CanRenderChanged;
     public event Action<bool>? CanRenderChanged;
@@ -136,7 +138,8 @@ public class PreviewPainter : IDisposable
     {
     {
         var dirtyArray = dirtyTextures.ToArray();
         var dirtyArray = dirtyTextures.ToArray();
         bool couldRender = canRender;
         bool couldRender = canRender;
-        canRender = PreviewRenderable?.GetPreviewBounds(FrameTime.Frame, ElementToRenderName) != null;
+        canRender = PreviewRenderable?.GetPreviewBounds(FrameTime.Frame, ElementToRenderName) != null &&
+                    painterInstances.Count > 0;
         if (couldRender != canRender)
         if (couldRender != canRender)
         {
         {
             CanRenderChanged?.Invoke(canRender);
             CanRenderChanged?.Invoke(canRender);
@@ -167,11 +170,18 @@ public class PreviewPainter : IDisposable
             renderTexture.DrawingSurface.Canvas.Save();
             renderTexture.DrawingSurface.Canvas.Save();
 
 
             Matrix3X3? matrix = painterInstance.RequestMatrix?.Invoke();
             Matrix3X3? matrix = painterInstance.RequestMatrix?.Invoke();
+            VecI bounds = painterInstance.RequestRenderBounds?.Invoke() ?? VecI.Zero;
+
+            ChunkResolution finalResolution = FindResolution(bounds);
+            SamplingOptions samplingOptions = FindSamplingOptions(bounds);
 
 
             renderTexture.DrawingSurface.Canvas.SetMatrix(matrix ?? Matrix3X3.Identity);
             renderTexture.DrawingSurface.Canvas.SetMatrix(matrix ?? Matrix3X3.Identity);
+            renderTexture.DrawingSurface.Canvas.Scale((float)finalResolution.InvertedMultiplier());
 
 
-            RenderContext context = new(renderTexture.DrawingSurface, FrameTime, ChunkResolution.Full, DocumentSize, DocumentSize,
-                ProcessingColorSpace);
+            RenderContext context = new(renderTexture.DrawingSurface, FrameTime, finalResolution,
+                DocumentSize,
+                DocumentSize,
+                ProcessingColorSpace, samplingOptions);
 
 
             dirtyTextures.Remove(texture);
             dirtyTextures.Remove(texture);
             Renderer.RenderNodePreview(PreviewRenderable, renderTexture.DrawingSurface, context, ElementToRenderName)
             Renderer.RenderNodePreview(PreviewRenderable, renderTexture.DrawingSurface, context, ElementToRenderName)
@@ -230,6 +240,31 @@ public class PreviewPainter : IDisposable
         }
         }
     }
     }
 
 
+    private ChunkResolution FindResolution(VecI bounds)
+    {
+        if (bounds.X <= 0 || bounds.Y <= 0 || !AllowPartialResolutions)
+        {
+            return ChunkResolution.Full;
+        }
+
+        double density = DocumentSize.X / (double)bounds.X;
+        if (density > 8.01)
+            return ChunkResolution.Eighth;
+        if (density > 4.01)
+            return ChunkResolution.Quarter;
+        if (density > 2.01)
+            return ChunkResolution.Half;
+        return ChunkResolution.Full;
+    }
+
+    private SamplingOptions FindSamplingOptions(VecI bounds)
+    {
+        var density = DocumentSize.X / (double)bounds.X;
+        return density > 1
+            ? SamplingOptions.Bilinear
+            : SamplingOptions.Default;
+    }
+
     public void Dispose()
     public void Dispose()
     {
     {
         foreach (var texture in renderTextures)
         foreach (var texture in renderTextures)
@@ -242,6 +277,8 @@ public class PreviewPainter : IDisposable
 public class PainterInstance
 public class PainterInstance
 {
 {
     public int RequestId { get; set; }
     public int RequestId { get; set; }
+    public Func<VecI> RequestRenderBounds;
+
     public Func<Matrix3X3?>? RequestMatrix;
     public Func<Matrix3X3?>? RequestMatrix;
     public Action RequestRepaint;
     public Action RequestRepaint;
 }
 }

+ 35 - 13
src/PixiEditor/Models/Rendering/SceneRenderer.cs

@@ -4,6 +4,7 @@ using Drawie.Backend.Core.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.ChangeableDocument.Rendering;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Numerics;
 using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
@@ -34,11 +35,12 @@ internal class SceneRenderer : IDisposable
         DocumentViewModel = documentViewModel;
         DocumentViewModel = documentViewModel;
     }
     }
 
 
-    public void RenderScene(DrawingSurface target, ChunkResolution resolution, string? targetOutput = null)
+    public void RenderScene(DrawingSurface target, ChunkResolution resolution, SamplingOptions samplingOptions,
+        string? targetOutput = null)
     {
     {
         if (Document.Renderer.IsBusy || DocumentViewModel.Busy ||
         if (Document.Renderer.IsBusy || DocumentViewModel.Busy ||
             target.DeviceClipBounds.Size.ShortestAxis <= 0) return;
             target.DeviceClipBounds.Size.ShortestAxis <= 0) return;
-        RenderOnionSkin(target, resolution, targetOutput);
+        RenderOnionSkin(target, resolution, samplingOptions, targetOutput);
 
 
         string adjustedTargetOutput = targetOutput ?? "";
         string adjustedTargetOutput = targetOutput ?? "";
 
 
@@ -55,7 +57,7 @@ internal class SceneRenderer : IDisposable
                 cachedTextures[adjustedTargetOutput]?.Dispose();
                 cachedTextures[adjustedTargetOutput]?.Dispose();
             }
             }
 
 
-            var rendered = RenderGraph(target, resolution, targetOutput, finalGraph);
+            var rendered = RenderGraph(target, resolution, samplingOptions, targetOutput, finalGraph);
             cachedTextures[adjustedTargetOutput] = rendered;
             cachedTextures[adjustedTargetOutput] = rendered;
             return;
             return;
         }
         }
@@ -64,11 +66,21 @@ internal class SceneRenderer : IDisposable
         Matrix3X3 matrixDiff = SolveMatrixDiff(target, cachedTexture);
         Matrix3X3 matrixDiff = SolveMatrixDiff(target, cachedTexture);
         int saved = target.Canvas.Save();
         int saved = target.Canvas.Save();
         target.Canvas.SetMatrix(matrixDiff);
         target.Canvas.SetMatrix(matrixDiff);
-        target.Canvas.DrawSurface(cachedTexture.DrawingSurface, 0, 0);
+        if (samplingOptions == SamplingOptions.Default)
+        {
+            target.Canvas.DrawSurface(cachedTexture.DrawingSurface, 0, 0);
+        }
+        else
+        {
+            using var img = cachedTexture.DrawingSurface.Snapshot();
+            target.Canvas.DrawImage(img, 0, 0, samplingOptions);
+        }
+
         target.Canvas.RestoreToCount(saved);
         target.Canvas.RestoreToCount(saved);
     }
     }
 
 
-    private Texture RenderGraph(DrawingSurface target, ChunkResolution resolution, string? targetOutput,
+    private Texture RenderGraph(DrawingSurface target, ChunkResolution resolution, SamplingOptions samplingOptions,
+        string? targetOutput,
         IReadOnlyNodeGraph finalGraph)
         IReadOnlyNodeGraph finalGraph)
     {
     {
         DrawingSurface renderTarget = target;
         DrawingSurface renderTarget = target;
@@ -104,13 +116,22 @@ internal class SceneRenderer : IDisposable
         }
         }
 
 
         RenderContext context = new(renderTarget, DocumentViewModel.AnimationHandler.ActiveFrameTime,
         RenderContext context = new(renderTarget, DocumentViewModel.AnimationHandler.ActiveFrameTime,
-            resolution, finalSize, Document.Size, Document.ProcessingColorSpace);
+            resolution, finalSize, Document.Size, Document.ProcessingColorSpace, samplingOptions);
         context.TargetOutput = targetOutput;
         context.TargetOutput = targetOutput;
         finalGraph.Execute(context);
         finalGraph.Execute(context);
 
 
         if (renderTexture != null)
         if (renderTexture != null)
         {
         {
-            target.Canvas.DrawSurface(renderTexture.DrawingSurface, 0, 0);
+            if (samplingOptions == SamplingOptions.Default)
+            {
+                target.Canvas.DrawSurface(renderTexture.DrawingSurface, 0, 0);
+            }
+            else
+            {
+                using var snapshot = renderTexture.DrawingSurface.Snapshot();
+                target.Canvas.DrawImage(snapshot, 0, 0, samplingOptions);
+            }
+
             target.Canvas.RestoreToCount(restoreCanvasTo);
             target.Canvas.RestoreToCount(restoreCanvasTo);
         }
         }
 
 
@@ -168,7 +189,9 @@ internal class SceneRenderer : IDisposable
         }
         }
 
 
         bool renderInDocumentSize = RenderInOutputSize(finalGraph);
         bool renderInDocumentSize = RenderInOutputSize(finalGraph);
-        VecI compareSize = renderInDocumentSize ? (VecI)(Document.Size * resolution.Multiplier()) : target.DeviceClipBounds.Size;
+        VecI compareSize = renderInDocumentSize
+            ? (VecI)(Document.Size * resolution.Multiplier())
+            : target.DeviceClipBounds.Size;
 
 
         if (cachedTexture.DrawingSurface.DeviceClipBounds.Size != compareSize)
         if (cachedTexture.DrawingSurface.DeviceClipBounds.Size != compareSize)
         {
         {
@@ -243,7 +266,7 @@ internal class SceneRenderer : IDisposable
         return highDpiRenderNodePresent;
         return highDpiRenderNodePresent;
     }
     }
 
 
-    private void RenderOnionSkin(DrawingSurface target, ChunkResolution resolution, string? targetOutput)
+    private void RenderOnionSkin(DrawingSurface target, ChunkResolution resolution, SamplingOptions sampling, string? targetOutput)
     {
     {
         var animationData = Document.AnimationData;
         var animationData = Document.AnimationData;
         if (!DocumentViewModel.AnimationHandler.OnionSkinningEnabledBindable)
         if (!DocumentViewModel.AnimationHandler.OnionSkinningEnabledBindable)
@@ -268,9 +291,9 @@ internal class SceneRenderer : IDisposable
 
 
             double finalOpacity = onionOpacity * alphaFalloffMultiplier * (animationData.OnionFrames - i + 1);
             double finalOpacity = onionOpacity * alphaFalloffMultiplier * (animationData.OnionFrames - i + 1);
 
 
+
             RenderContext onionContext = new(target, frame, resolution, renderOutputSize, Document.Size,
             RenderContext onionContext = new(target, frame, resolution, renderOutputSize, Document.Size,
-                Document.ProcessingColorSpace,
-                finalOpacity);
+                Document.ProcessingColorSpace, sampling, finalOpacity);
             onionContext.TargetOutput = targetOutput;
             onionContext.TargetOutput = targetOutput;
             finalGraph.Execute(onionContext);
             finalGraph.Execute(onionContext);
         }
         }
@@ -286,8 +309,7 @@ internal class SceneRenderer : IDisposable
 
 
             double finalOpacity = onionOpacity * alphaFalloffMultiplier * (animationData.OnionFrames - i + 1);
             double finalOpacity = onionOpacity * alphaFalloffMultiplier * (animationData.OnionFrames - i + 1);
             RenderContext onionContext = new(target, frame, resolution, renderOutputSize, Document.Size,
             RenderContext onionContext = new(target, frame, resolution, renderOutputSize, Document.Size,
-                Document.ProcessingColorSpace,
-                finalOpacity);
+                Document.ProcessingColorSpace, sampling, finalOpacity);
             onionContext.TargetOutput = targetOutput;
             onionContext.TargetOutput = targetOutput;
             finalGraph.Execute(onionContext);
             finalGraph.Execute(onionContext);
         }
         }

+ 2 - 2
src/PixiEditor/Properties/AssemblyInfo.cs

@@ -43,5 +43,5 @@ using System.Runtime.InteropServices;
 // You can specify all the values or you can default the Build and Revision Numbers
 // You can specify all the values or you can default the Build and Revision Numbers
 // by using the '*' as shown below:
 // by using the '*' as shown below:
 // [assembly: AssemblyVersion("1.0.*")]
 // [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("2.0.1.9")]
-[assembly: AssemblyFileVersion("2.0.1.9")]
+[assembly: AssemblyVersion("2.0.1.10")]
+[assembly: AssemblyFileVersion("2.0.1.10")]

+ 13 - 0
src/PixiEditor/ViewModels/Document/DocumentViewModel.cs

@@ -35,6 +35,7 @@ using PixiEditor.Models.Tools;
 using Drawie.Numerics;
 using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Workspace;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Workspace;
 using PixiEditor.Models.IO;
 using PixiEditor.Models.IO;
+using PixiEditor.Models.Position;
 using PixiEditor.Parser;
 using PixiEditor.Parser;
 using PixiEditor.Parser.Skia;
 using PixiEditor.Parser.Skia;
 using PixiEditor.UI.Common.Localization;
 using PixiEditor.UI.Common.Localization;
@@ -184,6 +185,16 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
 
 
     public IStructureMemberHandler? SelectedStructureMember { get; private set; } = null;
     public IStructureMemberHandler? SelectedStructureMember { get; private set; } = null;
 
 
+    private PreviewPainter miniPreviewPainter;
+
+    public PreviewPainter MiniPreviewPainter
+    {
+        get => miniPreviewPainter;
+        set
+        {
+            SetProperty(ref miniPreviewPainter, value);
+        }
+    }
 
 
     private PreviewPainter previewSurface;
     private PreviewPainter previewSurface;
 
 
@@ -374,6 +385,8 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             viewModel.MarkAsSaved();
             viewModel.MarkAsSaved();
         }));
         }));
 
 
+        acc.AddActions(new SetActiveFrame_PassthroughAction(1), new RefreshPreviews_PassthroughAction());
+
         foreach (var factory in allFactories)
         foreach (var factory in allFactories)
         {
         {
             factory.ResourceLocator = null;
             factory.ResourceLocator = null;

+ 21 - 1
src/PixiEditor/ViewModels/Document/Nodes/FilterNodes/ApplyFilterNodeViewModel.cs

@@ -1,3 +1,4 @@
+using System.Collections.Specialized;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
 using PixiEditor.UI.Common.Fonts;
 using PixiEditor.UI.Common.Fonts;
 using PixiEditor.ViewModels.Nodes;
 using PixiEditor.ViewModels.Nodes;
@@ -5,4 +6,23 @@ using PixiEditor.ViewModels.Nodes;
 namespace PixiEditor.ViewModels.Document.Nodes.FilterNodes;
 namespace PixiEditor.ViewModels.Document.Nodes.FilterNodes;
 
 
 [NodeViewModel("APPLY_FILTER_NODE", "FILTERS", PixiPerfectIcons.Magic)]
 [NodeViewModel("APPLY_FILTER_NODE", "FILTERS", PixiPerfectIcons.Magic)]
-internal class ApplyFilterNodeViewModel : NodeViewModel<ApplyFilterNode>;
+internal class ApplyFilterNodeViewModel : NodeViewModel<ApplyFilterNode>
+{
+    private NodePropertyViewModel MaskInput { get; set; }
+    
+    private NodePropertyViewModel MaskInvertInput { get; set; }
+
+    public override void OnInitialized()
+    {
+        MaskInput = FindInputProperty("Mask");
+        MaskInvertInput = FindInputProperty("InvertMask");
+        
+        UpdateInvertVisible();
+        MaskInput.ConnectedOutputChanged += (_, _) => UpdateInvertVisible();
+    }
+
+    private void UpdateInvertVisible()
+    {
+        MaskInvertInput.IsVisible = MaskInput.ConnectedOutput != null;
+    }
+}

+ 2 - 0
src/PixiEditor/ViewModels/Nodes/NodePropertyViewModel.cs

@@ -27,6 +27,7 @@ internal abstract class NodePropertyViewModel : ViewModelBase, INodePropertyHand
     private INodePropertyHandler? connectedOutput;
     private INodePropertyHandler? connectedOutput;
 
 
     public event NodePropertyValueChanged? ValueChanged;
     public event NodePropertyValueChanged? ValueChanged;
+    public event EventHandler? ConnectedOutputChanged;
 
 
     public string DisplayName
     public string DisplayName
     {
     {
@@ -115,6 +116,7 @@ internal abstract class NodePropertyViewModel : ViewModelBase, INodePropertyHand
             if (SetProperty(ref connectedOutput, value))
             if (SetProperty(ref connectedOutput, value))
             {
             {
                 OnPropertyChanged(nameof(ShowInputField));
                 OnPropertyChanged(nameof(ShowInputField));
+                ConnectedOutputChanged?.Invoke(this, EventArgs.Empty);
             }
             }
         }
         }
     }
     }

+ 16 - 3
src/PixiEditor/ViewModels/SettingsWindowViewModel.cs

@@ -170,11 +170,23 @@ internal partial class SettingsWindowViewModel : ViewModelBase
         {
         {
             Patterns = fileTypes.SelectMany(a => a.Patterns).ToList(),
             Patterns = fileTypes.SelectMany(a => a.Patterns).ToList(),
         });
         });
-        
+
+        IStorageFolder? suggestedLocation = null;
+        try
+        {
+            suggestedLocation =
+                await MainWindow.Current!.StorageProvider.TryGetWellKnownFolderAsync(WellKnownFolder.Documents);
+        }
+        catch (Exception)
+        {
+            // If we can't get the documents folder, we will just use the default location
+            // This is not a critical error, so we can ignore it
+        }
+
         IReadOnlyList<IStorageFile> files = await MainWindow.Current!.StorageProvider.OpenFilePickerAsync(new()
         IReadOnlyList<IStorageFile> files = await MainWindow.Current!.StorageProvider.OpenFilePickerAsync(new()
         {
         {
             AllowMultiple = false,
             AllowMultiple = false,
-            SuggestedStartLocation = await MainWindow.Current!.StorageProvider.TryGetWellKnownFolderAsync(WellKnownFolder.Documents),
+            SuggestedStartLocation = suggestedLocation,
             FileTypeFilter = fileTypes,
             FileTypeFilter = fileTypes,
         });
         });
         
         
@@ -272,7 +284,8 @@ internal partial class SettingsWindowViewModel : ViewModelBase
             new("KEY_BINDINGS"),
             new("KEY_BINDINGS"),
             new SettingsPage("UPDATES"),
             new SettingsPage("UPDATES"),
             new("EXPORT"),
             new("EXPORT"),
-            new SettingsPage("SCENE")
+            new SettingsPage("SCENE"),
+            new("PERFORMANCE")
         };
         };
 
 
         ILocalizationProvider.Current.OnLanguageChanged += OnLanguageChanged;
         ILocalizationProvider.Current.OnLanguageChanged += OnLanguageChanged;

+ 19 - 0
src/PixiEditor/ViewModels/SubViewModels/ViewOptionsViewModel.cs

@@ -1,6 +1,9 @@
 using Avalonia.Input;
 using Avalonia.Input;
+using PixiEditor.Extensions.CommonApi.UserPreferences.Settings.PixiEditor;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.Models.Commands.Attributes.Commands;
+using PixiEditor.Models.Preferences;
 using PixiEditor.UI.Common.Fonts;
 using PixiEditor.UI.Common.Fonts;
+using PixiEditor.ViewModels.UserPreferences.Settings;
 
 
 namespace PixiEditor.ViewModels.SubViewModels;
 namespace PixiEditor.ViewModels.SubViewModels;
 #nullable enable
 #nullable enable
@@ -36,9 +39,25 @@ internal class ViewOptionsViewModel : SubViewModel<ViewModelMain>
         }
         }
     }
     }
 
 
+    private int maxBilinearSampleSize = 4096;
+    public int MaxBilinearSampleSize
+    {
+        get => maxBilinearSampleSize;
+        set
+        {
+            SetProperty(ref maxBilinearSampleSize, value);
+        }
+    }
+
     public ViewOptionsViewModel(ViewModelMain owner)
     public ViewOptionsViewModel(ViewModelMain owner)
         : base(owner)
         : base(owner)
     {
     {
+        MaxBilinearSampleSize = PixiEditorSettings.Performance.MaxBilinearSampleSize.Value;
+
+        PixiEditorSettings.Performance.MaxBilinearSampleSize.ValueChanged += (s, e) =>
+        {
+            MaxBilinearSampleSize = PixiEditorSettings.Performance.MaxBilinearSampleSize.Value;
+        };
     }
     }
 
 
     [Command.Basic("PixiEditor.View.ToggleGrid", "TOGGLE_GRIDLINES", "TOGGLE_GRIDLINES", Key = Key.OemTilde,
     [Command.Basic("PixiEditor.View.ToggleGrid", "TOGGLE_GRIDLINES", "TOGGLE_GRIDLINES", Key = Key.OemTilde,

+ 4 - 3
src/PixiEditor/ViewModels/SubViewModels/ViewportWindowViewModel.cs

@@ -175,7 +175,8 @@ internal class ViewportWindowViewModel : SubViewModel<WindowViewModel>, IDockabl
         PixiEditorSettings.Scene.PrimaryBackgroundColor.ValueChanged += UpdateBackgroundBitmap;
         PixiEditorSettings.Scene.PrimaryBackgroundColor.ValueChanged += UpdateBackgroundBitmap;
         PixiEditorSettings.Scene.SecondaryBackgroundColor.ValueChanged += UpdateBackgroundBitmap;
         PixiEditorSettings.Scene.SecondaryBackgroundColor.ValueChanged += UpdateBackgroundBitmap;
 
 
-        previewPainterControl = new PreviewPainterControl(Document.PreviewPainter,
+        previewPainterControl = new PreviewPainterControl(
+            Document.MiniPreviewPainter,
             Document.AnimationDataViewModel.ActiveFrameTime.Frame);
             Document.AnimationDataViewModel.ActiveFrameTime.Frame);
         TabCustomizationSettings.Icon = previewPainterControl;
         TabCustomizationSettings.Icon = previewPainterControl;
     }
     }
@@ -187,9 +188,9 @@ internal class ViewportWindowViewModel : SubViewModel<WindowViewModel>, IDockabl
         {
         {
             OnPropertyChanged(nameof(Title));
             OnPropertyChanged(nameof(Title));
         }
         }
-        else if (e.PropertyName == nameof(DocumentViewModel.PreviewPainter))
+        else if (e.PropertyName == nameof(DocumentViewModel.MiniPreviewPainter))
         {
         {
-            previewPainterControl.PreviewPainter = Document.PreviewPainter;
+            previewPainterControl.PreviewPainter = Document.MiniPreviewPainter;
             previewPainterControl.FrameToRender = Document.AnimationDataViewModel.ActiveFrameTime.Frame;
             previewPainterControl.FrameToRender = Document.AnimationDataViewModel.ActiveFrameTime.Frame;
         }
         }
         else if (e.PropertyName == nameof(DocumentViewModel.AllChangesSaved))
         else if (e.PropertyName == nameof(DocumentViewModel.AllChangesSaved))

+ 21 - 0
src/PixiEditor/ViewModels/UserPreferences/Settings/PerformanceSettings.cs

@@ -0,0 +1,21 @@
+using PixiEditor.Extensions.CommonApi.UserPreferences;
+
+namespace PixiEditor.ViewModels.UserPreferences.Settings;
+
+internal class PerformanceSettings : SettingsGroup
+{
+    private bool disablePreviews = GetPreference(PreferencesConstants.DisablePreviews, PreferencesConstants.DisablePreviewsDefault);
+    private int maxBilinearSize = GetPreference(PreferencesConstants.MaxBilinearSampleSize, PreferencesConstants.MaxBilinearSampleSizeDefault);
+
+    public bool DisablePreviews
+    {
+        get => disablePreviews;
+        set => RaiseAndUpdatePreference(ref disablePreviews, value, PreferencesConstants.DisablePreviews);
+    }
+
+    public int MaxBilinearSize
+    {
+        get => maxBilinearSize;
+        set => RaiseAndUpdatePreference(ref maxBilinearSize, value, PreferencesConstants.MaxBilinearSampleSize);
+    }
+}

+ 2 - 0
src/PixiEditor/ViewModels/UserPreferences/SettingsViewModel.cs

@@ -17,6 +17,8 @@ internal class SettingsViewModel : SubViewModel<SettingsWindowViewModel>
 
 
     public SceneSettings Scene { get; set; } = new();
     public SceneSettings Scene { get; set; } = new();
 
 
+    public PerformanceSettings Performance { get; set; } = new();
+
     public SettingsViewModel(SettingsWindowViewModel owner)
     public SettingsViewModel(SettingsWindowViewModel owner)
         : base(owner)
         : base(owner)
     {
     {

+ 1 - 0
src/PixiEditor/Views/Dock/DocumentTemplate.axaml

@@ -45,6 +45,7 @@
         CustomBackgroundScaleY="{Binding CustomBackgroundScaleY, Mode=OneWay}"
         CustomBackgroundScaleY="{Binding CustomBackgroundScaleY, Mode=OneWay}"
         BackgroundBitmap="{Binding BackgroundBitmap, Mode=OneWay}"
         BackgroundBitmap="{Binding BackgroundBitmap, Mode=OneWay}"
         HudVisible="{Binding HudVisible}"
         HudVisible="{Binding HudVisible}"
+        MaxBilinearSamplingSize="{Binding ViewportSubViewModel.MaxBilinearSampleSize, Source={viewModels1:MainVM}}"
         Document="{Binding Document}">
         Document="{Binding Document}">
     </viewportControls:Viewport>
     </viewportControls:Viewport>
 </UserControl>
 </UserControl>

+ 1 - 0
src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml

@@ -257,6 +257,7 @@
             CustomBackgroundScaleX="{Binding CustomBackgroundScaleX, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=OneWay}"
             CustomBackgroundScaleX="{Binding CustomBackgroundScaleX, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=OneWay}"
             CustomBackgroundScaleY="{Binding CustomBackgroundScaleY, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=OneWay}"
             CustomBackgroundScaleY="{Binding CustomBackgroundScaleY, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=OneWay}"
             BackgroundBitmap="{Binding BackgroundBitmap, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=OneWay}"
             BackgroundBitmap="{Binding BackgroundBitmap, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=OneWay}"
+            MaxBilinearSamplingSize="{Binding MaxBilinearSamplingSize, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=OneWay}"
             PointerPressed="Scene_OnContextMenuOpening"
             PointerPressed="Scene_OnContextMenuOpening"
             ui1:RenderOptionsBindable.BitmapInterpolationMode="{Binding Scale, Converter={converters:ScaleToBitmapScalingModeConverter}, RelativeSource={RelativeSource Self}}">
             ui1:RenderOptionsBindable.BitmapInterpolationMode="{Binding Scale, Converter={converters:ScaleToBitmapScalingModeConverter}, RelativeSource={RelativeSource Self}}">
             <rendering:Scene.ContextFlyout>
             <rendering:Scene.ContextFlyout>

+ 8 - 0
src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml.cs

@@ -382,6 +382,8 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
 
 
     private MouseUpdateController? mouseUpdateController;
     private MouseUpdateController? mouseUpdateController;
     private ViewportOverlays builtInOverlays = new();
     private ViewportOverlays builtInOverlays = new();
+    public static readonly StyledProperty<int> MaxBilinearSamplingSizeProperty
+        = AvaloniaProperty.Register<Viewport, int>("MaxBilinearSamplingSize", 4096);
 
 
     public static readonly StyledProperty<bool> SnappingEnabledProperty =
     public static readonly StyledProperty<bool> SnappingEnabledProperty =
         AvaloniaProperty.Register<Viewport, bool>("SnappingEnabled");
         AvaloniaProperty.Register<Viewport, bool>("SnappingEnabled");
@@ -445,6 +447,12 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
         set { SetValue(AvailableRenderOutputsProperty, value); }
         set { SetValue(AvailableRenderOutputsProperty, value); }
     }
     }
 
 
+    public int MaxBilinearSamplingSize
+    {
+        get { return (int)GetValue(MaxBilinearSamplingSizeProperty); }
+        set { SetValue(MaxBilinearSamplingSizeProperty, value); }
+    }
+
     private void ForceRefreshFinalImage()
     private void ForceRefreshFinalImage()
     {
     {
         Scene.InvalidateVisual();
         Scene.InvalidateVisual();

+ 15 - 2
src/PixiEditor/Views/Nodes/Properties/StringPropertyView.axaml.cs

@@ -17,14 +17,16 @@ namespace PixiEditor.Views.Nodes.Properties;
 
 
 public partial class StringPropertyView : NodePropertyView
 public partial class StringPropertyView : NodePropertyView
 {
 {
-    public static readonly StyledProperty<ICommand> OpenInDefaultAppCommandProperty = AvaloniaProperty.Register<StringPropertyView, ICommand>(
-        nameof(OpenInDefaultAppCommand));
+    public static readonly StyledProperty<ICommand> OpenInDefaultAppCommandProperty =
+        AvaloniaProperty.Register<StringPropertyView, ICommand>(
+            nameof(OpenInDefaultAppCommand));
 
 
     public ICommand OpenInDefaultAppCommand
     public ICommand OpenInDefaultAppCommand
     {
     {
         get => GetValue(OpenInDefaultAppCommandProperty);
         get => GetValue(OpenInDefaultAppCommandProperty);
         set => SetValue(OpenInDefaultAppCommandProperty, value);
         set => SetValue(OpenInDefaultAppCommandProperty, value);
     }
     }
+
     public StringPropertyView()
     public StringPropertyView()
     {
     {
         InitializeComponent();
         InitializeComponent();
@@ -33,7 +35,18 @@ public partial class StringPropertyView : NodePropertyView
     protected override void OnLoaded(RoutedEventArgs e)
     protected override void OnLoaded(RoutedEventArgs e)
     {
     {
         base.OnLoaded(e);
         base.OnLoaded(e);
+        if (smallTextBox is null)
+        {
+            return;
+        }
+
         ScrollViewer scroll = smallTextBox.FindDescendantOfType<ScrollViewer>();
         ScrollViewer scroll = smallTextBox.FindDescendantOfType<ScrollViewer>();
+
+        if (scroll is null)
+        {
+            return;
+        }
+
         scroll.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled;
         scroll.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled;
         scroll.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled;
         scroll.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled;
     }
     }

+ 2 - 1
src/PixiEditor/Views/Overlays/TextOverlay/TextOverlay.cs

@@ -260,7 +260,8 @@ internal class TextOverlay : Overlay
         caret.GlyphWidths = glyphWidths;
         caret.GlyphWidths = glyphWidths;
         caret.Offset = Position;
         caret.Offset = Position;
 
 
-        caret.CaretWidth = 2f / (float)ZoomScale;
+        caret.CaretWidth = (2f / (float)ZoomScale) / Matrix.ScaleX;
+
         caret.Render(context);
         caret.Render(context);
     }
     }
 
 

+ 25 - 1
src/PixiEditor/Views/Rendering/Scene.cs

@@ -148,6 +148,15 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         set { SetValue(RenderOutputProperty, value); }
         set { SetValue(RenderOutputProperty, value); }
     }
     }
 
 
+    public static readonly StyledProperty<int> MaxBilinearSamplingSizeProperty = AvaloniaProperty.Register<Scene, int>(
+        nameof(MaxBilinearSamplingSize), 4096);
+
+    public int MaxBilinearSamplingSize
+    {
+        get => GetValue(MaxBilinearSamplingSizeProperty);
+        set => SetValue(MaxBilinearSamplingSizeProperty, value);
+    }
+
     private Overlay? capturedOverlay;
     private Overlay? capturedOverlay;
 
 
     private List<Overlay> mouseOverOverlays = new();
     private List<Overlay> mouseOverOverlays = new();
@@ -230,6 +239,20 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         };
         };
     }
     }
 
 
+    private SamplingOptions CalculateSampling()
+    {
+        if (Document.SizeBindable.LongestAxis > MaxBilinearSamplingSize)
+        {
+            return SamplingOptions.Default;
+        }
+
+        VecD densityVec = Dimensions.Divide(RealDimensions);
+        double density = Math.Min(densityVec.X, densityVec.Y);
+        return density > 1
+            ? SamplingOptions.Bilinear
+            : SamplingOptions.Default;
+    }
+
     protected override void OnLoaded(RoutedEventArgs e)
     protected override void OnLoaded(RoutedEventArgs e)
     {
     {
         base.OnLoaded(e);
         base.OnLoaded(e);
@@ -312,7 +335,8 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         DrawOverlays(renderTexture.DrawingSurface, bounds, OverlayRenderSorting.Background);
         DrawOverlays(renderTexture.DrawingSurface, bounds, OverlayRenderSorting.Background);
         try
         try
         {
         {
-            SceneRenderer.RenderScene(renderTexture.DrawingSurface, CalculateResolution(), renderOutput);
+            SceneRenderer.RenderScene(renderTexture.DrawingSurface, CalculateResolution(), CalculateSampling(),
+                renderOutput);
         }
         }
         catch (Exception e)
         catch (Exception e)
         {
         {

+ 8 - 0
src/PixiEditor/Views/Visuals/PreviewPainterControl.cs

@@ -68,6 +68,7 @@ public class PreviewPainterControl : DrawieControl
             PreviewPainter.RemovePainterInstance(painterInstance.RequestId);
             PreviewPainter.RemovePainterInstance(painterInstance.RequestId);
             painterInstance.RequestMatrix = null;
             painterInstance.RequestMatrix = null;
             painterInstance.RequestRepaint = null;
             painterInstance.RequestRepaint = null;
+            painterInstance.RequestRenderBounds = null;
             painterInstance = null;
             painterInstance = null;
         }
         }
     }
     }
@@ -86,6 +87,7 @@ public class PreviewPainterControl : DrawieControl
 
 
             painterInstance.RequestMatrix = OnPainterRequestMatrix;
             painterInstance.RequestMatrix = OnPainterRequestMatrix;
             painterInstance.RequestRepaint = OnPainterRenderRequest;
             painterInstance.RequestRepaint = OnPainterRenderRequest;
+            painterInstance.RequestRenderBounds = OnPainterRequestBounds;
 
 
             PreviewPainter.RepaintFor(painterInstance.RequestId);
             PreviewPainter.RepaintFor(painterInstance.RequestId);
         }
         }
@@ -116,6 +118,7 @@ public class PreviewPainterControl : DrawieControl
 
 
             sender.painterInstance.RequestMatrix = sender.OnPainterRequestMatrix;
             sender.painterInstance.RequestMatrix = sender.OnPainterRequestMatrix;
             sender.painterInstance.RequestRepaint = sender.OnPainterRenderRequest;
             sender.painterInstance.RequestRepaint = sender.OnPainterRenderRequest;
+            sender.painterInstance.RequestRenderBounds = sender.OnPainterRequestBounds;
 
 
             args.NewValue.Value.RepaintFor(sender.painterInstance.RequestId);
             args.NewValue.Value.RepaintFor(sender.painterInstance.RequestId);
         }
         }
@@ -216,4 +219,9 @@ public class PreviewPainterControl : DrawieControl
 
 
         return UniformScale(x, y, previewBounds.Value);
         return UniformScale(x, y, previewBounds.Value);
     }
     }
+
+    private VecI OnPainterRequestBounds()
+    {
+        return GetFinalSize();
+    }
 }
 }

+ 39 - 2
src/PixiEditor/Views/Windows/Settings/SettingsWindow.axaml

@@ -369,7 +369,7 @@
                         <!--Background="{StaticResource AccentColor}"-->
                         <!--Background="{StaticResource AccentColor}"-->
                         <controls:FixedSizeStackPanel Orientation="Vertical" ChildSize="32"
                         <controls:FixedSizeStackPanel Orientation="Vertical" ChildSize="32"
                                                       VerticalChildrenAlignment="Center" Margin="12">
                                                       VerticalChildrenAlignment="Center" Margin="12">
-
+                            <TextBlock ui:Translator.Key="EXPORT" Classes="h5" />
                             <CheckBox Classes="leftOffset" Width="200" HorizontalAlignment="Left"
                             <CheckBox Classes="leftOffset" Width="200" HorizontalAlignment="Left"
                                       ui:Translator.Key="OPEN_DIRECTORY_ON_EXPORT"
                                       ui:Translator.Key="OPEN_DIRECTORY_ON_EXPORT"
                                       IsChecked="{Binding SettingsSubViewModel.File.OpenDirectoryOnExport}" />
                                       IsChecked="{Binding SettingsSubViewModel.File.OpenDirectoryOnExport}" />
@@ -387,7 +387,7 @@
                         <controls:FixedSizeStackPanel Orientation="Vertical" ChildSize="32"
                         <controls:FixedSizeStackPanel Orientation="Vertical" ChildSize="32"
                                                       VerticalChildrenAlignment="Center" Margin="12">
                                                       VerticalChildrenAlignment="Center" Margin="12">
 
 
-                            <TextBlock ui:Translator.Key="BACKGROUND" Classes="h4" />
+                            <TextBlock ui:Translator.Key="BACKGROUND" Classes="h5" />
 
 
                             <CheckBox Classes="leftOffset" Width="200" HorizontalAlignment="Left"
                             <CheckBox Classes="leftOffset" Width="200" HorizontalAlignment="Left"
                                       ui:Translator.Key="AUTO_SCALE_BACKGROUND"
                                       ui:Translator.Key="AUTO_SCALE_BACKGROUND"
@@ -423,6 +423,43 @@
                                 ui:Translator.Key="RESET" />
                                 ui:Translator.Key="RESET" />
                         </controls:FixedSizeStackPanel>
                         </controls:FixedSizeStackPanel>
                     </ScrollViewer>
                     </ScrollViewer>
+                    <ScrollViewer>
+                        <ScrollViewer.IsVisible>
+                            <Binding Path="CurrentPage" Converter="{converters:IsEqualConverter}">
+                                <Binding.ConverterParameter>
+                                    <sys:Int32>6</sys:Int32>
+                                </Binding.ConverterParameter>
+                            </Binding>
+                        </ScrollViewer.IsVisible>
+                        <!--Background="{StaticResource AccentColor}"-->
+                        <controls:FixedSizeStackPanel Orientation="Vertical" ChildSize="32"
+                                                      VerticalChildrenAlignment="Center" Margin="12">
+                            <TextBlock ui:Translator.Key="PERFORMANCE" Classes="h5" />
+
+                            <CheckBox Classes="leftOffset" Width="200" HorizontalAlignment="Left"
+                                      ui:Translator.Key="DISABLE_PREVIEWS"
+                                      IsChecked="{Binding SettingsSubViewModel.Performance.DisablePreviews}" />
+
+                            <StackPanel Orientation="Horizontal" Classes="leftOffset">
+                                <Label d:Content="Height" ui:Translator.Key="MAX_BILINEAR_CANVAS_SIZE" />
+                                <controls:SizeInput
+                                    Size="{Binding SettingsSubViewModel.Performance.MaxBilinearSize, Mode=TwoWay}"
+                                    MinSize="0"
+                                    MaxSize="99999" HorizontalAlignment="Left" />
+                                <TextBlock Cursor="Help"
+                                           Classes="pixi-icon"
+                                           Text="{DynamicResource icon-help}"
+                                           FontSize="24"
+                                           Margin="5, 0, 0, 0"
+                                           VerticalAlignment="Center"
+                                           ToolTip.ShowDelay="0">
+                                    <ToolTip.Tip>
+                                        <TextBlock TextWrapping="Wrap" MaxWidth="200" ui:Translator.Key="MAX_BILINEAR_CANVAS_SIZE_DESC" />
+                                    </ToolTip.Tip>
+                                </TextBlock>
+                            </StackPanel>
+                        </controls:FixedSizeStackPanel>
+                    </ScrollViewer>
                 </Grid>
                 </Grid>
             </Border>
             </Border>
         </DockPanel>
         </DockPanel>

+ 3 - 2
tests/PixiEditor.Tests/BlendingTests.cs

@@ -1,12 +1,13 @@
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.DataHolders;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Numerics;
 using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Graph;
 using PixiEditor.ChangeableDocument.Changeables.Graph;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
-using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.ChangeableDocument.Rendering;
+using BlendMode = PixiEditor.ChangeableDocument.Enums.BlendMode;
 
 
 namespace PixiEditor.Tests;
 namespace PixiEditor.Tests;
 
 
@@ -58,7 +59,7 @@ public class BlendingTests : PixiEditorTest
         secondImageLayer.BlendMode.NonOverridenValue = blendMode;
         secondImageLayer.BlendMode.NonOverridenValue = blendMode;
 
 
         Surface output = Surface.ForProcessing(VecI.One, ColorSpace.CreateSrgbLinear());
         Surface output = Surface.ForProcessing(VecI.One, ColorSpace.CreateSrgbLinear());
-        graph.Execute(new RenderContext(output.DrawingSurface, 0, ChunkResolution.Full, VecI.One, VecI.One, ColorSpace.CreateSrgbLinear(), 1));
+        graph.Execute(new RenderContext(output.DrawingSurface, 0, ChunkResolution.Full, VecI.One, VecI.One, ColorSpace.CreateSrgbLinear(), SamplingOptions.Default, 1));
 
 
         Color result = output.GetSrgbPixel(VecI.Zero);
         Color result = output.GetSrgbPixel(VecI.Zero);
         Assert.Equal(expectedColor, result.ToRgbHex());
         Assert.Equal(expectedColor, result.ToRgbHex());

+ 2 - 1
tests/PixiEditor.Tests/RenderTests.cs

@@ -4,6 +4,7 @@ using ChunkyImageLib.DataHolders;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core.Bridge;
 using Drawie.Backend.Core.Bridge;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.Surfaces;
 using Drawie.Numerics;
 using Drawie.Numerics;
 using PixiEditor.Models.IO;
 using PixiEditor.Models.IO;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
@@ -89,7 +90,7 @@ public class RenderTests : FullPixiEditorTest
 
 
         var document = Importer.ImportDocument(pixiFile);
         var document = Importer.ImportDocument(pixiFile);
         using Surface output = Surface.ForDisplay(document.SizeBindable);
         using Surface output = Surface.ForDisplay(document.SizeBindable);
-        document.SceneRenderer.RenderScene(output.DrawingSurface, ChunkResolution.Half);
+        document.SceneRenderer.RenderScene(output.DrawingSurface, ChunkResolution.Half, SamplingOptions.Default);
 
 
         Color expectedColor = Colors.Yellow;
         Color expectedColor = Colors.Yellow;