2
0
Эх сурвалжийг харах

Wip bilinear and partial preview rendering

Krzysztof Krysiński 3 долоо хоног өмнө
parent
commit
2e0b1fe04b
25 өөрчлөгдсөн 185 нэмэгдсэн , 63 устгасан
  1. 1 1
      src/Drawie
  2. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/SceneObjectRenderContext.cs
  3. 3 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CreateImageNode.cs
  4. 5 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Effects/OutlineNode.cs
  5. 2 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FilterNodes/ApplyFilterNode.cs
  6. 26 7
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ImageLayerNode.cs
  7. 1 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/OutputNode.cs
  8. 2 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/RenderNode.cs
  9. 10 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ShaderNode.cs
  10. 1 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/StructureNode.cs
  11. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/TextureCache.cs
  12. 8 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/VectorLayerNode.cs
  13. 2 1
      src/PixiEditor.ChangeableDocument/Changes/NodeGraph/EvaluateGraph_Change.cs
  14. 6 3
      src/PixiEditor.ChangeableDocument/Rendering/DocumentRenderer.cs
  15. 5 3
      src/PixiEditor.ChangeableDocument/Rendering/RenderContext.cs
  16. BIN
      src/PixiEditor.Extensions.Sdk/build/PixiEditor.Api.CGlueMSBuild.dll
  17. 1 1
      src/PixiEditor/Models/DocumentModels/ActionAccumulator.cs
  18. 10 1
      src/PixiEditor/Models/Rendering/AffectedAreasGatherer.cs
  19. 9 4
      src/PixiEditor/Models/Rendering/MemberPreviewUpdater.cs
  20. 40 2
      src/PixiEditor/Models/Rendering/PreviewPainter.cs
  21. 30 16
      src/PixiEditor/Models/Rendering/SceneRenderer.cs
  22. 8 1
      src/PixiEditor/Views/Rendering/Scene.cs
  23. 8 0
      src/PixiEditor/Views/Visuals/PreviewPainterControl.cs
  24. 3 2
      tests/PixiEditor.Tests/BlendingTests.cs
  25. 2 1
      tests/PixiEditor.Tests/RenderTests.cs

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit 1ec31f39432d2a1e94cd72e84d12f5f782e51d79
+Subproject commit 0d235a224a27508a44b8f6d7cf8b6aca1b8f24e6

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

@@ -13,7 +13,7 @@ public class SceneObjectRenderContext : RenderContext
     public RenderOutputProperty TargetPropertyOutput { get; }
 
     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;
         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();
 
-        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();
 

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

@@ -88,7 +88,8 @@ public class OutlineNode : RenderNode, IRenderInput
 
             var ctx = context.Clone();
             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);
 
@@ -124,7 +125,10 @@ public class OutlineNode : RenderNode, IRenderInput
 
     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);
+        renderOn.Canvas.RestoreToCount(saved);
         return true;
     }
 

+ 2 - 4
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FilterNodes/ApplyFilterNode.cs

@@ -42,14 +42,14 @@ public class ApplyFilterNode : RenderNode, IRenderInput
 
         if (!context.ProcessingColorSpace.IsSrgb)
         {
-            var intermediate = Texture.ForProcessing(surface, context.ProcessingColorSpace);
+            using var intermediate = Texture.ForProcessing(surface, context.ProcessingColorSpace);
 
             int saved = surface.Canvas.Save();
             surface.Canvas.SetMatrix(Matrix3X3.Identity);
 
             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);
@@ -57,8 +57,6 @@ public class ApplyFilterNode : RenderNode, IRenderInput
 
             surface.Canvas.DrawSurface(srgbSurface.DrawingSurface, 0, 0);
             surface.Canvas.RestoreToCount(saved);
-            intermediate.Dispose();
-            srgbSurface.Dispose();
         }
         else
         {

+ 26 - 7
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ImageLayerNode.cs

@@ -5,6 +5,7 @@ using PixiEditor.ChangeableDocument.Helpers;
 using PixiEditor.ChangeableDocument.Rendering;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
@@ -130,6 +131,7 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
 
         var sceneSize = GetSceneSize(ctx.FrameTime);
         VecD topLeft = sceneSize / 2f;
+
         if (renderedSurfaceFrame == null || ctx.FullRerender || ctx.FrameTime.Frame != renderedSurfaceFrame)
         {
             GetLayerImageAtFrame(ctx.FrameTime.Frame).DrawMostUpToDateRegionOn(
@@ -139,14 +141,18 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
         }
         else
         {
-            var samplingOptions = SamplingOptions.Default;
-            if (ctx.ChunkResolution != ChunkResolution.Full)
+            if (ctx.DesiredSamplingOptions == SamplingOptions.Default)
             {
-                samplingOptions = new SamplingOptions(FilterMode.Linear, MipmapMode.Linear);
+                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);
             }
-
-            using var snapshot = fullResrenderedSurface.DrawingSurface.Snapshot();
-            workingSurface.Canvas.DrawImage(snapshot, -(float)topLeft.X, -(float)topLeft.Y, samplingOptions, paint);
         }
 
         workingSurface.Canvas.RestoreToCount(saved);
@@ -249,7 +255,20 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
 
         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
         {

+ 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();
+        renderOn.Canvas.Scale((float)context.ChunkResolution.Multiplier());
         Input.Value.Paint(context, renderOn);
 
         renderOn.Canvas.RestoreToCount(saved);

+ 2 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/RenderNode.cs

@@ -85,7 +85,9 @@ public abstract class RenderNode : Node, IPreviewRenderable, IHighDpiRenderNode
     public virtual bool RenderPreview(DrawingSurface renderOn, RenderContext context,
         string elementToRenderName)
     {
+        int saved = renderOn.Canvas.Save();
         OnPaint(context, renderOn);
+        renderOn.Canvas.RestoreToCount(saved);
         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 = 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("iNormalizedTime", new Uniform("iNormalizedTime", (float)context.FrameTime.NormalizedTime));
@@ -123,8 +124,11 @@ public class ShaderNode : RenderNode, IRenderInput, ICustomShaderNode
         int saved = texture.DrawingSurface.Canvas.Save();
         //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);
         texture.DrawingSurface.Canvas.RestoreToCount(saved);
 
@@ -156,8 +160,10 @@ public class ShaderNode : RenderNode, IRenderInput, ICustomShaderNode
 
         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,
-                (VecI)(context.RenderOutputSize * context.ChunkResolution.InvertedMultiplier()),
+                finalSize,
                 ColorSpace.Value == ColorSpaceType.Inherit
                     ? context.ProcessingColorSpace
                     : ColorSpace.Value == ColorSpaceType.Srgb

+ 1 - 2
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,
             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;
         return renderObjectContext;
     }

+ 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 (texture.Size != size || texture.IsDisposed || texture.ColorSpace != processingCs)
+            if (texture.Size != size || texture.IsDisposed || !texture.ColorSpace.Equals(processingCs))
             {
                 texture.Dispose();
                 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;
         }
 
+
+        int savedCount = renderOn.Canvas.Save();
+        renderOn.Canvas.Scale((float)context.ChunkResolution.Multiplier());
         Rasterize(renderOn, paint);
+        renderOn.Canvas.RestoreToCount(savedCount);
 
         return true;
     }
@@ -188,8 +192,9 @@ public class VectorLayerNode : LayerNode, ITransformableObject, IReadOnlyVectorN
     {
         int layer;
         // 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);
         }
@@ -197,7 +202,7 @@ public class VectorLayerNode : LayerNode, ITransformableObject, IReadOnlyVectorN
         {
             layer = surface.Canvas.Save();
         }
-        
+
         RenderableShapeData?.RasterizeTransformed(surface.Canvas);
 
         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.Surfaces;
 using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Graph;
 using PixiEditor.ChangeableDocument.Rendering;
@@ -33,7 +34,7 @@ internal class EvaluateGraph_Change : Change
         using Texture renderTexture = Texture.ForProcessing(target.Size, target.ProcessingColorSpace);
         RenderContext context =
             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)
         {
             nodeToEvaluate.Execute(context);

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

@@ -68,7 +68,7 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
         toRenderOn.Canvas.SetMatrix(Matrix3X3.Identity);
 
         RenderContext context = new(renderTexture.DrawingSurface, frame, resolution, Document.Size, Document.Size,
-            Document.ProcessingColorSpace);
+            Document.ProcessingColorSpace, SamplingOptions.Default);
         context.FullRerender = true;
         IReadOnlyNodeGraph membersOnlyGraph = ConstructMembersOnlyGraph(layersToCombine, Document.NodeGraph);
         try
@@ -114,7 +114,7 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
         toRenderOn.Canvas.SetMatrix(Matrix3X3.Identity);
 
         RenderContext context = new(renderTexture.DrawingSurface, frameTime, resolution, Document.Size, Document.Size,
-            Document.ProcessingColorSpace);
+            Document.ProcessingColorSpace, SamplingOptions.Default);
         context.FullRerender = true;
 
         node.RenderForOutput(context, toRenderOn, null);
@@ -201,8 +201,11 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
         IsBusy = true;
 
         renderOn.Canvas.Clear();
+        int savedCount = renderOn.Canvas.Save();
+        renderOn.Canvas.Scale((float)context.ChunkResolution.Multiplier());
         context.RenderSurface = renderOn;
         Document.NodeGraph.Execute(context);
+        renderOn.Canvas.RestoreToCount(savedCount);
 
         IsBusy = false;
 
@@ -237,7 +240,7 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
 
         RenderContext context =
             new(renderTexture.DrawingSurface, frameTime, ChunkResolution.Full, SolveRenderOutputSize(customOutput, graph, Document.Size),
-                Document.Size, Document.ProcessingColorSpace) { FullRerender = true };
+                Document.Size, Document.ProcessingColorSpace, SamplingOptions.Default) { FullRerender = true };
 
         if (hasCustomOutput)
         {

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

@@ -13,6 +13,7 @@ public class RenderContext
 
     public KeyFrameTime FrameTime { get; }
     public ChunkResolution ChunkResolution { get; set; }
+    public SamplingOptions DesiredSamplingOptions { get; set; } = SamplingOptions.Default;
     public VecI RenderOutputSize { get; set; }
 
     public VecI DocumentSize { get; set; }
@@ -24,7 +25,7 @@ public class RenderContext
 
 
     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;
         FrameTime = frameTime;
@@ -33,6 +34,7 @@ public class RenderContext
         Opacity = opacity;
         ProcessingColorSpace = processingColorSpace;
         DocumentSize = documentSize;
+        DesiredSamplingOptions = desiredSampling;
     }
 
     public static DrawingApiBlendMode GetDrawingBlendMode(BlendMode blendMode)
@@ -63,10 +65,10 @@ public class RenderContext
 
     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,
-            TargetOutput = TargetOutput
+            TargetOutput = TargetOutput,
         };
     }
 }

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


+ 1 - 1
src/PixiEditor/Models/DocumentModels/ActionAccumulator.cs

@@ -147,7 +147,7 @@ internal class ActionAccumulator
                 previewUpdater.UpdatePreviews(
                     affectedAreas.ChangedMembers,
                     affectedAreas.ChangedMasks,
-                    affectedAreas.ChangedNodes, affectedAreas.ChangedKeyFrames);
+                    affectedAreas.ChangedNodes, affectedAreas.ChangedKeyFrames, affectedAreas.IgnoreAnimationPreviews);
 
                 // force refresh viewports for better responsiveness
                 foreach (var (_, value) in internals.State.Viewports)

+ 10 - 1
src/PixiEditor/Models/Rendering/AffectedAreasGatherer.cs

@@ -30,6 +30,7 @@ internal class AffectedAreasGatherer
 
     public AffectedArea MainImageArea { get; private set; } = new();
     public HashSet<Guid> ChangedMembers { get; private set; } = new();
+    public bool IgnoreAnimationPreviews => ignoreAnimationPreviews;
     public HashSet<Guid> ChangedMasks { get; private set; } = new();
     public HashSet<Guid> ChangedKeyFrames { get; private set; } = new();
 
@@ -38,6 +39,7 @@ internal class AffectedAreasGatherer
     public HashSet<Guid> ChangedNodes { get; set; } = new();
 
     private bool alreadyAddedWholeCanvasToEveryImagePreview = false;
+    private bool ignoreAnimationPreviews = false;
 
     public AffectedAreasGatherer(KeyFrameTime activeFrame, DocumentChangeTracker tracker,
         IReadOnlyList<IChangeInfo> changes)
@@ -153,6 +155,7 @@ internal class AffectedAreasGatherer
                     AddWholeCanvasToMainImage();
                     AddWholeCanvasToEveryImagePreview();
                     AddAllNodesToImagePreviews();
+                    ignoreAnimationPreviews = true;
                     break;
                 case KeyFrameLength_ChangeInfo:
                     AddWholeCanvasToMainImage();
@@ -386,7 +389,13 @@ internal class AffectedAreasGatherer
         if (alreadyAddedWholeCanvasToEveryImagePreview)
             return;
 
-        tracker.Document.ForEveryReadonlyMember((member) => AddWholeCanvasToImagePreviews(member.Id));
+        tracker.Document.ForEveryReadonlyMember((member) =>
+        {
+            if (member.KeyFrames.Count > 0)
+            {
+                AddWholeCanvasToImagePreviews(member.Id);
+            }
+        });
         alreadyAddedWholeCanvasToEveryImagePreview = true;
     }
 

+ 9 - 4
src/PixiEditor/Models/Rendering/MemberPreviewUpdater.cs

@@ -26,13 +26,14 @@ internal class MemberPreviewUpdater
     }
 
     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)
     {
         if (!membersToUpdate.Any() && !masksToUpdate.Any() && !nodesToUpdate.Any() &&
             !keyFramesToUpdate.Any())
             return;
 
-        UpdatePreviewPainters(membersToUpdate, masksToUpdate, nodesToUpdate, keyFramesToUpdate);
+        UpdatePreviewPainters(membersToUpdate, masksToUpdate, nodesToUpdate, keyFramesToUpdate, ignoreAnimationPreviews);
     }
 
     /// <summary>
@@ -41,13 +42,16 @@ internal class MemberPreviewUpdater
     /// <param name="members">Members that should be rendered</param>
     /// <param name="masksToUpdate">Masks that should be rendered</param>
     private void UpdatePreviewPainters(HashSet<Guid> members, HashSet<Guid> masksToUpdate,
-        HashSet<Guid> nodesToUpdate, HashSet<Guid> keyFramesToUpdate)
+        HashSet<Guid> nodesToUpdate, HashSet<Guid> keyFramesToUpdate, bool ignoreAnimationPreviews)
     {
         RenderWholeCanvasPreview();
         RenderLayersPreview(members);
         RenderMaskPreviews(masksToUpdate);
 
-        RenderAnimationPreviews(members, keyFramesToUpdate);
+        if (!ignoreAnimationPreviews)
+        {
+            RenderAnimationPreviews(members, keyFramesToUpdate);
+        }
 
         RenderNodePreviews(nodesToUpdate);
     }
@@ -287,6 +291,7 @@ internal class MemberPreviewUpdater
                 nodeVm.ResultPainter = new PreviewPainter(doc.Renderer, renderable,
                     doc.AnimationHandler.ActiveFrameTime,
                     doc.SizeBindable, internals.Tracker.Document.ProcessingColorSpace);
+                nodeVm.ResultPainter.AllowPartialResolutions = false;
                 nodeVm.ResultPainter.Repaint();
             }
             else

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

@@ -23,6 +23,8 @@ public class PreviewPainter : IDisposable
     public VecI DocumentSize { get; set; }
     public DocumentRenderer Renderer { get; set; }
 
+    public bool AllowPartialResolutions { get; set; } = true;
+
     public bool CanRender => canRender;
 
     public event Action<bool>? CanRenderChanged;
@@ -167,11 +169,18 @@ public class PreviewPainter : IDisposable
             renderTexture.DrawingSurface.Canvas.Save();
 
             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.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);
             Renderer.RenderNodePreview(PreviewRenderable, renderTexture.DrawingSurface, context, ElementToRenderName)
@@ -230,6 +239,33 @@ 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)
+    {
+        if (DocumentSize.X / (double)bounds.X > 2.01)
+        {
+            return SamplingOptions.Bilinear;
+        }
+
+        return SamplingOptions.Default;
+    }
+
     public void Dispose()
     {
         foreach (var texture in renderTextures)
@@ -242,6 +278,8 @@ public class PreviewPainter : IDisposable
 public class PainterInstance
 {
     public int RequestId { get; set; }
+    public Func<VecI> RequestRenderBounds;
+
     public Func<Matrix3X3?>? RequestMatrix;
     public Action RequestRepaint;
 }

+ 30 - 16
src/PixiEditor/Models/Rendering/SceneRenderer.cs

@@ -35,7 +35,8 @@ internal class SceneRenderer : IDisposable
         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 ||
             target.DeviceClipBounds.Size.ShortestAxis <= 0) return;
@@ -56,7 +57,7 @@ internal class SceneRenderer : IDisposable
                 cachedTextures[adjustedTargetOutput]?.Dispose();
             }
 
-            var rendered = RenderGraph(target, resolution, targetOutput, finalGraph);
+            var rendered = RenderGraph(target, resolution, samplingOptions, targetOutput, finalGraph);
             cachedTextures[adjustedTargetOutput] = rendered;
             return;
         }
@@ -65,18 +66,21 @@ internal class SceneRenderer : IDisposable
         Matrix3X3 matrixDiff = SolveMatrixDiff(target, cachedTexture);
         int saved = target.Canvas.Save();
         target.Canvas.SetMatrix(matrixDiff);
-        using var img = cachedTexture.DrawingSurface.Snapshot();
-        SamplingOptions samplingOptions = SamplingOptions.Default;
-        if (matrixDiff.ScaleX < 1 || matrixDiff.ScaleY < 1)
+        if (samplingOptions == SamplingOptions.Default)
         {
-            samplingOptions = new SamplingOptions(FilterMode.Linear, MipmapMode.Linear);
+            target.Canvas.DrawSurface(cachedTexture.DrawingSurface, 0, 0);
+        }
+        else
+        {
+            using var img = cachedTexture.DrawingSurface.Snapshot();
+            target.Canvas.DrawImage(img, 0, 0, samplingOptions);
         }
 
-        target.Canvas.DrawImage(img, 0, 0, samplingOptions);
         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)
     {
         DrawingSurface renderTarget = target;
@@ -112,15 +116,22 @@ internal class SceneRenderer : IDisposable
         }
 
         RenderContext context = new(renderTarget, DocumentViewModel.AnimationHandler.ActiveFrameTime,
-            resolution, finalSize, Document.Size, Document.ProcessingColorSpace);
+            resolution, finalSize, Document.Size, Document.ProcessingColorSpace, samplingOptions);
         context.TargetOutput = targetOutput;
         finalGraph.Execute(context);
 
         if (renderTexture != null)
         {
-            using var snapshot = renderTexture.DrawingSurface.Snapshot();
-            SamplingOptions samplingOptions = resolution == ChunkResolution.Full ? SamplingOptions.Default : new SamplingOptions(FilterMode.Linear, MipmapMode.Linear);
-            target.Canvas.DrawImage(snapshot, 0, 0, samplingOptions);
+            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);
         }
 
@@ -269,6 +280,10 @@ internal class SceneRenderer : IDisposable
         var finalGraph = RenderingUtils.SolveFinalNodeGraph(targetOutput, Document);
         var renderOutputSize = SolveRenderOutputSize(targetOutput, finalGraph, Document.Size);
 
+        SamplingOptions samplingOptions = resolution == ChunkResolution.Full
+            ? SamplingOptions.Default
+            : new SamplingOptions(FilterMode.Linear, MipmapMode.Linear);
+
         // Render previous frames'
         for (int i = 1; i <= animationData.OnionFrames; i++)
         {
@@ -280,9 +295,9 @@ internal class SceneRenderer : IDisposable
 
             double finalOpacity = onionOpacity * alphaFalloffMultiplier * (animationData.OnionFrames - i + 1);
 
+
             RenderContext onionContext = new(target, frame, resolution, renderOutputSize, Document.Size,
-                Document.ProcessingColorSpace,
-                finalOpacity);
+                Document.ProcessingColorSpace, samplingOptions, finalOpacity);
             onionContext.TargetOutput = targetOutput;
             finalGraph.Execute(onionContext);
         }
@@ -298,8 +313,7 @@ internal class SceneRenderer : IDisposable
 
             double finalOpacity = onionOpacity * alphaFalloffMultiplier * (animationData.OnionFrames - i + 1);
             RenderContext onionContext = new(target, frame, resolution, renderOutputSize, Document.Size,
-                Document.ProcessingColorSpace,
-                finalOpacity);
+                Document.ProcessingColorSpace, samplingOptions, finalOpacity);
             onionContext.TargetOutput = targetOutput;
             finalGraph.Execute(onionContext);
         }

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

@@ -230,6 +230,13 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         };
     }
 
+    private SamplingOptions CalculateSampling()
+    {
+        return Scale >= 1
+            ? SamplingOptions.Default
+            : SamplingOptions.Bilinear;
+    }
+
     protected override void OnLoaded(RoutedEventArgs e)
     {
         base.OnLoaded(e);
@@ -312,7 +319,7 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         DrawOverlays(renderTexture.DrawingSurface, bounds, OverlayRenderSorting.Background);
         try
         {
-            SceneRenderer.RenderScene(renderTexture.DrawingSurface, CalculateResolution(), renderOutput);
+            SceneRenderer.RenderScene(renderTexture.DrawingSurface, CalculateResolution(), CalculateSampling(), renderOutput);
         }
         catch (Exception e)
         {

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

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

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

@@ -1,12 +1,13 @@
 using ChunkyImageLib.DataHolders;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Graph;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
-using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.ChangeableDocument.Rendering;
+using BlendMode = PixiEditor.ChangeableDocument.Enums.BlendMode;
 
 namespace PixiEditor.Tests;
 
@@ -58,7 +59,7 @@ public class BlendingTests : PixiEditorTest
         secondImageLayer.BlendMode.NonOverridenValue = blendMode;
 
         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);
         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.Bridge;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.Surfaces;
 using Drawie.Numerics;
 using PixiEditor.Models.IO;
 using Xunit.Abstractions;
@@ -89,7 +90,7 @@ public class RenderTests : FullPixiEditorTest
 
         var document = Importer.ImportDocument(pixiFile);
         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;