浏览代码

Added performance settings and fixed all the remaining stuff

Krzysztof Krysiński 3 周之前
父节点
当前提交
b84df2fd5e
共有 24 个文件被更改,包括 242 次插入70 次删除
  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/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ImageLayerNode.cs
  7. 14 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/LayerNode.cs
  8. 5 0
      src/PixiEditor.Extensions.CommonApi/UserPreferences/PreferencesConstants.cs
  9. 42 17
      src/PixiEditor.Extensions.CommonApi/UserPreferences/Settings/PixiEditor/PixiEditorSettings.cs
  10. 5 1
      src/PixiEditor/Data/Localization/Languages/en.json
  11. 17 4
      src/PixiEditor/Models/DocumentModels/ActionAccumulator.cs
  12. 2 1
      src/PixiEditor/Models/Rendering/MemberPreviewUpdater.cs
  13. 6 7
      src/PixiEditor/Models/Rendering/PreviewPainter.cs
  14. 4 8
      src/PixiEditor/Models/Rendering/SceneRenderer.cs
  15. 4 0
      src/PixiEditor/ViewModels/Document/DocumentViewModel.cs
  16. 2 1
      src/PixiEditor/ViewModels/SettingsWindowViewModel.cs
  17. 19 0
      src/PixiEditor/ViewModels/SubViewModels/ViewOptionsViewModel.cs
  18. 21 0
      src/PixiEditor/ViewModels/UserPreferences/Settings/PerformanceSettings.cs
  19. 2 0
      src/PixiEditor/ViewModels/UserPreferences/SettingsViewModel.cs
  20. 1 0
      src/PixiEditor/Views/Dock/DocumentTemplate.axaml
  21. 1 0
      src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml
  22. 8 0
      src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml.cs
  23. 21 4
      src/PixiEditor/Views/Rendering/Scene.cs
  24. 36 0
      src/PixiEditor/Views/Windows/Settings/SettingsWindow.axaml

+ 10 - 2
src/ChunkyImageLib/Chunk.cs

@@ -83,9 +83,17 @@ public class Chunk : IDisposable
     /// </summary>
     /// <param name="pos">The destination for the <paramref name="surface"/></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)

+ 6 - 6
src/ChunkyImageLib/ChunkyImage.cs

@@ -365,7 +365,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
     /// </returns>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
     public bool DrawMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos,
-        Paint? paint = null)
+        Paint? paint = null, SamplingOptions? samplingOptions = null)
     {
         lock (lockObject)
         {
@@ -390,7 +390,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
             {
                 if (committedChunk is null)
                     return false;
-                committedChunk.DrawChunkOn(surface, pos, paint);
+                committedChunk.DrawChunkOn(surface, pos, paint, samplingOptions);
                 return true;
             }
 
@@ -399,7 +399,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
             {
                 if (latestChunk.IsT2)
                 {
-                    latestChunk.AsT2.DrawChunkOn(surface, pos, paint);
+                    latestChunk.AsT2.DrawChunkOn(surface, pos, paint, samplingOptions);
                     return true;
                 }
 
@@ -415,7 +415,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
                 blendModePaint);
             if (lockTransparency)
                 OperationHelper.ClampAlpha(tempChunk.Surface.DrawingSurface, committedChunk.Surface.DrawingSurface);
-            tempChunk.DrawChunkOn(surface, pos, paint);
+            tempChunk.DrawChunkOn(surface, pos, paint, samplingOptions);
 
             return true;
         }
@@ -458,7 +458,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
     public bool DrawCommittedChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos,
-        Paint? paint = null)
+        Paint? paint = null, SamplingOptions? samplingOptions = null)
     {
         lock (lockObject)
         {
@@ -466,7 +466,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
             var chunk = GetCommittedChunk(chunkPos, resolution);
             if (chunk is null)
                 return false;
-            chunk.DrawChunkOn(surface, pos, paint);
+            chunk.DrawChunkOn(surface, pos, paint, samplingOptions);
             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="paint">Paint to use for drawing</param>
     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>
@@ -35,9 +36,9 @@ public static class IReadOnlyChunkyImageEx
     /// <param name="pos">Starting position on the surface</param>
     /// <param name="paint">Paint to use for drawing</param>
     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(
@@ -45,8 +46,8 @@ public static class IReadOnlyChunkyImageEx
         ChunkResolution resolution,
         DrawingSurface surface,
         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();
         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++)
             {
                 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
 {
-    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? FindChunkAlignedCommittedBounds();
     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(
             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(
             topLeft,
             targetChunk.Resolution,
             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;
         if (gridShift.X != 0)
@@ -79,7 +79,7 @@ internal class ChunkyImageOperation : IMirroredDrawOperation
             targetChunk.Resolution,
             targetChunk.Surface.DrawingSurface,
             (VecI)((topRight * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * targetChunk.Resolution.Multiplier()),
-            null);
+            null, null);
         }
         if (gridShift.Y != 0)
         {
@@ -88,7 +88,7 @@ internal class ChunkyImageOperation : IMirroredDrawOperation
             targetChunk.Resolution,
             targetChunk.Surface.DrawingSurface,
             (VecI)((bottomLeft * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * targetChunk.Resolution.Multiplier()),
-            null);
+            null, null);
         }
         if (gridShift.X != 0 && gridShift.Y != 0)
         {
@@ -97,7 +97,7 @@ internal class ChunkyImageOperation : IMirroredDrawOperation
             targetChunk.Resolution,
             targetChunk.Surface.DrawingSurface,
             (VecI)((bottomRight * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * targetChunk.Resolution.Multiplier()),
-            null);
+            null, null);
         }
 
         targetChunk.Surface.DrawingSurface.Canvas.Restore();

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

@@ -275,7 +275,7 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
             img.DrawMostUpToDateRegionOn(
                 new RectI(0, 0, img.LatestSize.X, img.LatestSize.Y),
                 context.ChunkResolution,
-                renderOnto, VecI.Zero, blendPaint);
+                renderOnto, VecI.Zero, blendPaint, context.DesiredSamplingOptions);
         }
 
         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);
 
                 blendPaint.SetFilters(null);
-                DrawWithResolution(tempSurface.DrawingSurface, renderOnto, context.ChunkResolution);
+                DrawWithResolution(tempSurface.DrawingSurface, renderOnto, context.ChunkResolution,
+                    context.DesiredSamplingOptions);
             }
 
             return;
@@ -130,7 +131,8 @@ public abstract class LayerNode : StructureNode, IReadOnlyLayerNode, IClipSource
             blendPaint.SetFilters(null);
         }
 
-        DrawWithResolution(outputWorkingSurface.DrawingSurface, renderOnto, adjustedResolution);
+        DrawWithResolution(outputWorkingSurface.DrawingSurface, renderOnto, adjustedResolution,
+            context.DesiredSamplingOptions);
 
         renderOnto.Canvas.RestoreToCount(saved);
         outputWorkingSurface.DrawingSurface.Canvas.Restore();
@@ -149,13 +151,21 @@ public abstract class LayerNode : StructureNode, IReadOnlyLayerNode, IClipSource
         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();
         float multiplier = (float)resolution.InvertedMultiplier();
         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);
     }

+ 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 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
 {
     private const string PixiEditor = "PixiEditor";
-    
+
     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
@@ -22,16 +23,18 @@ public static class PixiEditorSettings
 
         public static LocalSetting<string> PoEditorApiKey { get; } = new($"{PixiEditor}:POEditor_API_Key");
     }
-    
+
     public static class Tools
     {
         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<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
@@ -39,12 +42,13 @@ public static class PixiEditorSettings
         public static SyncedSetting<int> DefaultNewFileWidth { 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 class StartupWindow
     {
         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<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 SyncedSetting<bool> EnableRichPresence { get; } = SyncedSetting.NonOwned(PixiEditor, true);
@@ -74,12 +79,32 @@ public static class PixiEditorSettings
 
     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);
     }
 }

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

@@ -1103,5 +1103,9 @@
   "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": "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."
 }

+ 17 - 4
src/PixiEditor/Models/DocumentModels/ActionAccumulator.cs

@@ -6,6 +6,7 @@ using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.ChangeableDocument.Actions.Undo;
 using PixiEditor.ChangeableDocument.ChangeInfos;
 using Drawie.Backend.Core.Bridge;
+using PixiEditor.Extensions.CommonApi.UserPreferences.Settings.PixiEditor;
 using PixiEditor.Helpers;
 using PixiEditor.Models.DocumentPassthroughActions;
 using PixiEditor.Models.Handlers;
@@ -144,10 +145,20 @@ internal class ActionAccumulator
                         undoBoundaryPassed || viewportRefreshRequest);
                 }*/
 
-                previewUpdater.UpdatePreviews(
-                    affectedAreas.ChangedMembers,
-                    affectedAreas.ChangedMasks,
-                    affectedAreas.ChangedNodes, affectedAreas.ChangedKeyFrames, affectedAreas.IgnoreAnimationPreviews);
+                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);
+                    }
+                }
 
                 // force refresh viewports for better responsiveness
                 foreach (var (_, value) in internals.State.Viewports)
@@ -175,6 +186,8 @@ internal class ActionAccumulator
         executing = false;
     }
 
+    private const int LiveUpdatePerformanceThreshold = 2048;
+
     private bool AreAllPassthrough(List<(ActionSource, IAction)> actions)
     {
         foreach (var action in actions)

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

@@ -120,7 +120,8 @@ internal class MemberPreviewUpdater
                 {
                     if (!keyFramesGuids.Contains(childFrame.Id))
                     {
-                        if (!memberGuids.Contains(childFrame.LayerGuid) || !IsInFrame(childFrame) || !groupHandler.IsVisible)
+                        if (!memberGuids.Contains(childFrame.LayerGuid) || !IsInFrame(childFrame) ||
+                            !groupHandler.IsVisible)
                             continue;
                     }
 

+ 6 - 7
src/PixiEditor/Models/Rendering/PreviewPainter.cs

@@ -138,7 +138,8 @@ public class PreviewPainter : IDisposable
     {
         var dirtyArray = dirtyTextures.ToArray();
         bool couldRender = canRender;
-        canRender = PreviewRenderable?.GetPreviewBounds(FrameTime.Frame, ElementToRenderName) != null;
+        canRender = PreviewRenderable?.GetPreviewBounds(FrameTime.Frame, ElementToRenderName) != null &&
+                    painterInstances.Count > 0;
         if (couldRender != canRender)
         {
             CanRenderChanged?.Invoke(canRender);
@@ -258,12 +259,10 @@ public class PreviewPainter : IDisposable
 
     private SamplingOptions FindSamplingOptions(VecI bounds)
     {
-        if (DocumentSize.X / (double)bounds.X > 2.01)
-        {
-            return SamplingOptions.Bilinear;
-        }
-
-        return SamplingOptions.Default;
+        var density = DocumentSize.X / (double)bounds.X;
+        return density > 1
+            ? SamplingOptions.Bilinear
+            : SamplingOptions.Default;
     }
 
     public void Dispose()

+ 4 - 8
src/PixiEditor/Models/Rendering/SceneRenderer.cs

@@ -40,7 +40,7 @@ internal class SceneRenderer : IDisposable
     {
         if (Document.Renderer.IsBusy || DocumentViewModel.Busy ||
             target.DeviceClipBounds.Size.ShortestAxis <= 0) return;
-        RenderOnionSkin(target, resolution, targetOutput);
+        RenderOnionSkin(target, resolution, samplingOptions, targetOutput);
 
         string adjustedTargetOutput = targetOutput ?? "";
 
@@ -266,7 +266,7 @@ internal class SceneRenderer : IDisposable
         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;
         if (!DocumentViewModel.AnimationHandler.OnionSkinningEnabledBindable)
@@ -280,10 +280,6 @@ 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++)
         {
@@ -297,7 +293,7 @@ internal class SceneRenderer : IDisposable
 
 
             RenderContext onionContext = new(target, frame, resolution, renderOutputSize, Document.Size,
-                Document.ProcessingColorSpace, samplingOptions, finalOpacity);
+                Document.ProcessingColorSpace, sampling, finalOpacity);
             onionContext.TargetOutput = targetOutput;
             finalGraph.Execute(onionContext);
         }
@@ -313,7 +309,7 @@ internal class SceneRenderer : IDisposable
 
             double finalOpacity = onionOpacity * alphaFalloffMultiplier * (animationData.OnionFrames - i + 1);
             RenderContext onionContext = new(target, frame, resolution, renderOutputSize, Document.Size,
-                Document.ProcessingColorSpace, samplingOptions, finalOpacity);
+                Document.ProcessingColorSpace, sampling, finalOpacity);
             onionContext.TargetOutput = targetOutput;
             finalGraph.Execute(onionContext);
         }

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

@@ -35,6 +35,7 @@ using PixiEditor.Models.Tools;
 using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Workspace;
 using PixiEditor.Models.IO;
+using PixiEditor.Models.Position;
 using PixiEditor.Parser;
 using PixiEditor.Parser.Skia;
 using PixiEditor.UI.Common.Localization;
@@ -374,6 +375,9 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             viewModel.MarkAsSaved();
         }));
 
+        // This also refreshes previews on start. On big documents, previews are empty on start if not this
+        acc.AddActions(new SetActiveFrame_PassthroughAction(1));
+
         foreach (var factory in allFactories)
         {
             factory.ResourceLocator = null;

+ 2 - 1
src/PixiEditor/ViewModels/SettingsWindowViewModel.cs

@@ -284,7 +284,8 @@ internal partial class SettingsWindowViewModel : ViewModelBase
             new("KEY_BINDINGS"),
             new SettingsPage("UPDATES"),
             new("EXPORT"),
-            new SettingsPage("SCENE")
+            new SettingsPage("SCENE"),
+            new("PERFORMANCE")
         };
 
         ILocalizationProvider.Current.OnLanguageChanged += OnLanguageChanged;

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

@@ -1,6 +1,9 @@
 using Avalonia.Input;
+using PixiEditor.Extensions.CommonApi.UserPreferences.Settings.PixiEditor;
 using PixiEditor.Models.Commands.Attributes.Commands;
+using PixiEditor.Models.Preferences;
 using PixiEditor.UI.Common.Fonts;
+using PixiEditor.ViewModels.UserPreferences.Settings;
 
 namespace PixiEditor.ViewModels.SubViewModels;
 #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)
         : 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,

+ 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 PerformanceSettings Performance { get; set; } = new();
+
     public SettingsViewModel(SettingsWindowViewModel owner)
         : base(owner)
     {

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

@@ -45,6 +45,7 @@
         CustomBackgroundScaleY="{Binding CustomBackgroundScaleY, Mode=OneWay}"
         BackgroundBitmap="{Binding BackgroundBitmap, Mode=OneWay}"
         HudVisible="{Binding HudVisible}"
+        MaxBilinearSamplingSize="{Binding ViewportSubViewModel.MaxBilinearSampleSize, Source={viewModels1:MainVM}}"
         Document="{Binding Document}">
     </viewportControls:Viewport>
 </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}"
             CustomBackgroundScaleY="{Binding CustomBackgroundScaleY, 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"
             ui1:RenderOptionsBindable.BitmapInterpolationMode="{Binding Scale, Converter={converters:ScaleToBitmapScalingModeConverter}, RelativeSource={RelativeSource Self}}">
             <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 ViewportOverlays builtInOverlays = new();
+    public static readonly StyledProperty<int> MaxBilinearSamplingSizeProperty
+        = AvaloniaProperty.Register<Viewport, int>("MaxBilinearSamplingSize", 4096);
 
     public static readonly StyledProperty<bool> SnappingEnabledProperty =
         AvaloniaProperty.Register<Viewport, bool>("SnappingEnabled");
@@ -445,6 +447,12 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
         set { SetValue(AvailableRenderOutputsProperty, value); }
     }
 
+    public int MaxBilinearSamplingSize
+    {
+        get { return (int)GetValue(MaxBilinearSamplingSizeProperty); }
+        set { SetValue(MaxBilinearSamplingSizeProperty, value); }
+    }
+
     private void ForceRefreshFinalImage()
     {
         Scene.InvalidateVisual();

+ 21 - 4
src/PixiEditor/Views/Rendering/Scene.cs

@@ -148,6 +148,15 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         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 List<Overlay> mouseOverOverlays = new();
@@ -232,9 +241,16 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
 
     private SamplingOptions CalculateSampling()
     {
-        return Scale >= 1
-            ? SamplingOptions.Default
-            : SamplingOptions.Bilinear;
+        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)
@@ -319,7 +335,8 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         DrawOverlays(renderTexture.DrawingSurface, bounds, OverlayRenderSorting.Background);
         try
         {
-            SceneRenderer.RenderScene(renderTexture.DrawingSurface, CalculateResolution(), CalculateSampling(), renderOutput);
+            SceneRenderer.RenderScene(renderTexture.DrawingSurface, CalculateResolution(), CalculateSampling(),
+                renderOutput);
         }
         catch (Exception e)
         {

+ 36 - 0
src/PixiEditor/Views/Windows/Settings/SettingsWindow.axaml

@@ -423,6 +423,42 @@
                                 ui:Translator.Key="RESET" />
                         </controls:FixedSizeStackPanel>
                     </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">
+
+                            <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>
             </Border>
         </DockPanel>