Browse Source

Merge pull request #887 from PixiEditor/fixes/10.04.2025

Fixes/10.04.2025
Krzysztof Krysiński 4 months ago
parent
commit
8c0f744356
24 changed files with 170 additions and 52 deletions
  1. 17 9
      src/ChunkyImageLib/Chunk.cs
  2. 5 2
      src/ChunkyImageLib/ChunkPool.cs
  3. 1 1
      src/Drawie
  4. 1 5
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ImageLayerNode.cs
  5. 17 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/LayerNode.cs
  6. 4 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/StructureNode.cs
  7. 5 3
      src/PixiEditor.ChangeableDocument/Changes/Drawing/LineBasedPen_UpdateableChange.cs
  8. 35 17
      src/PixiEditor.ChangeableDocument/Rendering/DocumentRenderer.cs
  9. 1 1
      src/PixiEditor/Models/Handlers/IAnimationHandler.cs
  10. 1 1
      src/PixiEditor/Models/Handlers/ICelHandler.cs
  11. 1 1
      src/PixiEditor/Models/Handlers/INodeHandler.cs
  12. 11 1
      src/PixiEditor/Models/Rendering/PreviewPainter.cs
  13. 9 1
      src/PixiEditor/Models/Rendering/SceneRenderer.cs
  14. 2 1
      src/PixiEditor/Models/Serialization/Factories/ChunkyImageSerializationFactory.cs
  15. 8 0
      src/PixiEditor/ViewModels/Document/AnimationDataViewModel.cs
  16. 6 0
      src/PixiEditor/ViewModels/Document/CelViewModel.cs
  17. 10 3
      src/PixiEditor/ViewModels/Document/DocumentViewModel.Serialization.cs
  18. 4 0
      src/PixiEditor/ViewModels/Document/DocumentViewModel.cs
  19. 9 1
      src/PixiEditor/ViewModels/Document/NodeGraphViewModel.cs
  20. 12 4
      src/PixiEditor/ViewModels/Document/Nodes/StructureMemberViewModel.cs
  21. 5 0
      src/PixiEditor/ViewModels/Nodes/NodeViewModel.cs
  22. 4 0
      src/PixiEditor/Views/MainView.axaml.cs
  23. 1 0
      src/PixiEditor/Views/Nodes/NodeGraphView.cs
  24. 1 0
      src/PixiEditor/Views/Rendering/Scene.cs

+ 17 - 9
src/ChunkyImageLib/Chunk.cs

@@ -31,7 +31,7 @@ public class Chunk : IDisposable
             {
                 throw new ObjectDisposedException("Chunk has been disposed");
             }
-            
+
             return internalSurface;
         }
     }
@@ -47,10 +47,11 @@ public class Chunk : IDisposable
     public ChunkResolution Resolution { get; }
 
     public ColorSpace ColorSpace { get; }
-    
+
     public bool Disposed => returned;
 
     private Surface internalSurface;
+
     private Chunk(ChunkResolution resolution, ColorSpace colorSpace)
     {
         int size = resolution.PixelSize();
@@ -66,7 +67,12 @@ public class Chunk : IDisposable
     /// </summary>
     public static Chunk Create(ColorSpace chunkCs, ChunkResolution resolution = ChunkResolution.Full)
     {
-        var chunk = ChunkPool.Instance.Get(resolution, chunkCs) ?? new Chunk(resolution, chunkCs);
+        var chunk = ChunkPool.Instance.Get(resolution, chunkCs);
+        if (chunk == null || chunk.Disposed)
+        {
+            chunk = new Chunk(resolution, chunkCs);
+        }
+
         chunk.returned = false;
         Interlocked.Increment(ref chunkCounter);
         return chunk;
@@ -81,18 +87,20 @@ public class Chunk : IDisposable
     {
         surface.Canvas.DrawSurface(Surface.DrawingSurface, (float)pos.X, (float)pos.Y, paint);
     }
-    
+
     public unsafe RectI? FindPreciseBounds(RectI? passedSearchRegion = null)
     {
         RectI? bounds = null;
-        if (returned) 
+        if (returned)
             return bounds;
 
-        if (passedSearchRegion is not null && !new RectI(VecI.Zero, Surface.Size).ContainsInclusive(passedSearchRegion.Value))
-            throw new ArgumentException("Passed search region lies outside of the chunk's surface", nameof(passedSearchRegion));
+        if (passedSearchRegion is not null &&
+            !new RectI(VecI.Zero, Surface.Size).ContainsInclusive(passedSearchRegion.Value))
+            throw new ArgumentException("Passed search region lies outside of the chunk's surface",
+                nameof(passedSearchRegion));
 
         RectI searchRegion = passedSearchRegion ?? new RectI(VecI.Zero, Surface.Size);
-        
+
         ulong* ptr = (ulong*)Surface.PixelBuffer;
         for (int y = searchRegion.Top; y < searchRegion.Bottom; y++)
         {
@@ -108,7 +116,7 @@ public class Chunk : IDisposable
                 }
             }
         }
-        
+
         return bounds;
     }
 

+ 5 - 2
src/ChunkyImageLib/ChunkPool.cs

@@ -11,6 +11,7 @@ internal class ChunkPool
 
     private static object lockObj = new();
     private static ChunkPool? instance;
+
     /// <summary>
     /// The instance of the <see cref="ChunkPool"/>
     /// </summary>
@@ -25,6 +26,7 @@ internal class ChunkPool
                     instance ??= new ChunkPool();
                 }
             }
+
             return instance;
         }
     }
@@ -39,7 +41,8 @@ internal class ChunkPool
     /// </summary>
     /// <param name="resolution">The resolution for the chunk</param>
     /// <param name="chunkCs"></param>
-    internal Chunk? Get(ChunkResolution resolution, ColorSpace chunkCs) => GetBag(resolution, chunkCs).TryTake(out Chunk? item) ? item : null;
+    internal Chunk? Get(ChunkResolution resolution, ColorSpace chunkCs) =>
+        GetBag(resolution, chunkCs).TryTake(out Chunk? item) ? item : null;
 
     private ConcurrentBag<Chunk> GetBag(ChunkResolution resolution, ColorSpace colorSpace)
     {
@@ -60,7 +63,7 @@ internal class ChunkPool
     {
         var chunks = GetBag(chunk.Resolution, chunk.ColorSpace);
         //a race condition can cause the count to go above 200, but likely not by much
-        if (chunks.Count < 200)
+        if (chunks.Count <= 32)
             chunks.Add(chunk);
         else
             chunk.Surface.Dispose();

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit 676738f1cb90e799f574851ad93171e18e434434
+Subproject commit 252fe53970fb1770a2400eb964fbc214f4b92121

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

@@ -267,11 +267,7 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
     public override void Dispose()
     {
         base.Dispose();
-
-        foreach (var workingSurface in workingSurfaces)
-        {
-            workingSurface.Value.Dispose();
-        }
+        fullResrenderedSurface.Dispose();
     }
 
     IReadOnlyChunkyImage IReadOnlyImageNode.GetLayerImageAtFrame(int frame) => GetLayerImageAtFrame(frame);

+ 17 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/LayerNode.cs

@@ -150,6 +150,11 @@ public abstract class LayerNode : StructureNode, IReadOnlyLayerNode, IClipSource
             DrawWithoutFilters(ctx, targetSurface, finalPaint);
         }
 
+        if (finalPaint != blendPaint)
+        {
+            finalPaint.Dispose();
+        }
+
         if (targetSurface != workingSurface)
         {
             workingSurface.Canvas.DrawSurface(targetSurface, 0, 0, blendPaint);
@@ -185,4 +190,16 @@ public abstract class LayerNode : StructureNode, IReadOnlyLayerNode, IClipSource
     {
         RenderContent(context, drawOnto, false);
     }
+
+    public override void Dispose()
+    {
+        base.Dispose();
+        if (workingSurfaces != null)
+        {
+            foreach (var workingSurface in workingSurfaces.Values)
+            {
+                workingSurface?.Dispose();
+            }
+        }
+    }
 }

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

@@ -348,9 +348,12 @@ public abstract class StructureNode : RenderNode, IReadOnlyStructureNode, IRende
 
     public override void Dispose()
     {
-        Output.Value = null;
         base.Dispose();
+        renderedMask?.Dispose();
+        EmbeddedMask?.Dispose();
+        Output.Value = null;
         maskPaint.Dispose();
         blendPaint.Dispose();
+        maskPreviewPaint.Dispose();
     }
 }

+ 5 - 3
src/PixiEditor.ChangeableDocument/Changes/Drawing/LineBasedPen_UpdateableChange.cs

@@ -21,6 +21,7 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
     private float hardness;
     private float spacing = 1;
     private readonly Paint srcPaint = new Paint() { BlendMode = BlendMode.Src };
+    private Paintable? finalPaintable;
 
     private CommittedChunkStorage? storedChunks;
     private readonly List<VecI> points = new();
@@ -103,7 +104,7 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
 
             lastPos = point;
             var rect = new RectI(point - new VecI((int)(strokeWidth / 2f)), new VecI((int)strokeWidth));
-            Paintable finalPaintable = color;
+            finalPaintable = color;
 
             if (!squareBrush)
             {
@@ -135,7 +136,7 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
         if (points.Count == 1)
         {
             var rect = new RectI(points[0] - new VecI((int)(strokeWidth / 2f)), new VecI((int)strokeWidth));
-            Paintable finalPaintable = color;
+            finalPaintable = color;
 
             if (!squareBrush)
             {
@@ -169,7 +170,7 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
 
             lastPos = points[i];
             var rect = new RectI(points[i] - new VecI((int)(strokeWidth / 2f)), new VecI((int)strokeWidth));
-            Paintable? finalPaintable = color;
+            finalPaintable = color;
 
             if (!squareBrush)
             {
@@ -250,5 +251,6 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
     public override void Dispose()
     {
         storedChunks?.Dispose();
+        srcPaint.Dispose();
     }
 }

+ 35 - 17
src/PixiEditor.ChangeableDocument/Rendering/DocumentRenderer.cs

@@ -13,7 +13,7 @@ using Drawie.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Rendering;
 
-public class DocumentRenderer : IPreviewRenderable
+public class DocumentRenderer : IPreviewRenderable, IDisposable
 {
     private Queue<RenderRequest> renderRequests = new();
     private Texture renderTexture;
@@ -25,9 +25,9 @@ public class DocumentRenderer : IPreviewRenderable
 
     private IReadOnlyDocument Document { get; }
     public bool IsBusy { get; private set; }
-    
+
     private bool isExecuting = false;
-    
+
     public void UpdateChunk(VecI chunkPos, ChunkResolution resolution, KeyFrameTime frameTime)
     {
         try
@@ -109,27 +109,29 @@ public class DocumentRenderer : IPreviewRenderable
         toRenderOn.Canvas.Save();
         toRenderOn.Canvas.SetMatrix(Matrix3X3.Identity);
 
-        RenderContext context = new(renderTexture.DrawingSurface, frameTime, resolution, Document.Size, Document.ProcessingColorSpace);
+        RenderContext context = new(renderTexture.DrawingSurface, frameTime, resolution, Document.Size,
+            Document.ProcessingColorSpace);
         context.FullRerender = true;
 
         node.RenderForOutput(context, toRenderOn, null);
-        
+
         renderTexture.DrawingSurface.Canvas.Restore();
         toRenderOn.Canvas.Restore();
-        
+
         IsBusy = false;
     }
 
-    public async Task RenderNodePreview(IPreviewRenderable previewRenderable, DrawingSurface renderOn, RenderContext context,
+    public async Task RenderNodePreview(IPreviewRenderable previewRenderable, DrawingSurface renderOn,
+        RenderContext context,
         string elementToRenderName)
     {
         if (previewRenderable is Node { IsDisposed: true }) return;
         TaskCompletionSource<bool> tcs = new();
         RenderRequest request = new(tcs, context, renderOn, previewRenderable, elementToRenderName);
-        
+
         renderRequests.Enqueue(request);
         ExecuteRenderRequests();
-        
+
         await tcs.Task;
     }
 
@@ -203,7 +205,8 @@ public class DocumentRenderer : IPreviewRenderable
         return true;
     }
 
-    public void RenderDocument(DrawingSurface toRenderOn, KeyFrameTime frameTime, VecI renderSize, string? customOutput = null)
+    public void RenderDocument(DrawingSurface toRenderOn, KeyFrameTime frameTime, VecI renderSize,
+        string? customOutput = null)
     {
         IsBusy = true;
 
@@ -247,8 +250,8 @@ public class DocumentRenderer : IPreviewRenderable
 
     private void ExecuteRenderRequests()
     {
-        if(isExecuting) return;
-        
+        if (isExecuting) return;
+
         isExecuting = true;
         while (renderRequests.Count > 0)
         {
@@ -273,7 +276,7 @@ public class DocumentRenderer : IPreviewRenderable
                 request.TaskCompletionSource.SetException(e);
             }
         }
-        
+
         isExecuting = false;
     }
 
@@ -315,6 +318,19 @@ public class DocumentRenderer : IPreviewRenderable
 
         return found ?? (membersOnlyGraph.OutputNode as IRenderInput)?.Background;
     }
+
+    public void Dispose()
+    {
+        renderTexture?.Dispose();
+        renderTexture = null;
+
+        foreach (var request in renderRequests)
+        {
+            if (request.TaskCompletionSource == null) continue;
+
+            request.TaskCompletionSource.TrySetCanceled();
+        }
+    }
 }
 
 public struct RenderRequest
@@ -325,16 +341,18 @@ public struct RenderRequest
     public IPreviewRenderable? PreviewRenderable { get; set; }
     public string ElementToRenderName { get; set; }
     public TaskCompletionSource<bool> TaskCompletionSource { get; set; }
-    
-    public RenderRequest(TaskCompletionSource<bool> completionSource, RenderContext context, DrawingSurface renderOn, IReadOnlyNodeGraph nodeGraph)
+
+    public RenderRequest(TaskCompletionSource<bool> completionSource, RenderContext context, DrawingSurface renderOn,
+        IReadOnlyNodeGraph nodeGraph)
     {
         TaskCompletionSource = completionSource;
         Context = context;
         RenderOn = renderOn;
         NodeGraph = nodeGraph;
     }
-    
-    public RenderRequest(TaskCompletionSource<bool> completionSource, RenderContext context, DrawingSurface renderOn, IPreviewRenderable previewRenderable, string elementToRenderName)
+
+    public RenderRequest(TaskCompletionSource<bool> completionSource, RenderContext context, DrawingSurface renderOn,
+        IPreviewRenderable previewRenderable, string elementToRenderName)
     {
         TaskCompletionSource = completionSource;
         Context = context;

+ 1 - 1
src/PixiEditor/Models/Handlers/IAnimationHandler.cs

@@ -2,7 +2,7 @@
 
 namespace PixiEditor.Models.Handlers;
 
-internal interface IAnimationHandler
+internal interface IAnimationHandler : IDisposable
 {
     public IReadOnlyCollection<ICelHandler> KeyFrames { get; }
     public int ActiveFrameBindable { get; set; }

+ 1 - 1
src/PixiEditor/Models/Handlers/ICelHandler.cs

@@ -4,7 +4,7 @@ using PixiEditor.Models.Rendering;
 
 namespace PixiEditor.Models.Handlers;
 
-internal interface ICelHandler
+internal interface ICelHandler : IDisposable
 {
     public PreviewPainter? PreviewPainter { get; set; }
     public int StartFrameBindable { get; }

+ 1 - 1
src/PixiEditor/Models/Handlers/INodeHandler.cs

@@ -11,7 +11,7 @@ using Drawie.Numerics;
 
 namespace PixiEditor.Models.Handlers;
 
-public interface INodeHandler : INotifyPropertyChanged
+public interface INodeHandler : INotifyPropertyChanged, IDisposable
 {
     public Guid Id { get; }
     public string NodeNameBindable { get; set; }

+ 11 - 1
src/PixiEditor/Models/Rendering/PreviewPainter.cs

@@ -14,7 +14,7 @@ using PixiEditor.ChangeableDocument.Rendering;
 
 namespace PixiEditor.Models.Rendering;
 
-public class PreviewPainter
+public class PreviewPainter : IDisposable
 {
     public string ElementToRenderName { get; set; }
     public IPreviewRenderable PreviewRenderable { get; set; }
@@ -126,6 +126,8 @@ public class PreviewPainter
         RepaintDirty();
     }
 
+
+
     private void RepaintDirty()
     {
         var dirtyArray = dirtyTextures.ToArray();
@@ -211,6 +213,14 @@ public class PreviewPainter
                 });
         }
     }
+
+    public void Dispose()
+    {
+        foreach (var texture in renderTextures)
+        {
+            texture.Value.Dispose();
+        }
+    }
 }
 
 public class PainterInstance

+ 9 - 1
src/PixiEditor/Models/Rendering/SceneRenderer.cs

@@ -14,7 +14,7 @@ using PixiEditor.Models.Handlers;
 
 namespace PixiEditor.Models.Rendering;
 
-internal class SceneRenderer
+internal class SceneRenderer : IDisposable
 {
     public const double ZoomDiffToRerender = 20;
     public IReadOnlyDocument Document { get; }
@@ -246,4 +246,12 @@ internal class SceneRenderer
             finalGraph.Execute(onionContext);
         }
     }
+
+    public void Dispose()
+    {
+        foreach (var texture in cachedTextures)
+        {
+            texture.Value?.Dispose();
+        }
+    }
 }

+ 2 - 1
src/PixiEditor/Models/Serialization/Factories/ChunkyImageSerializationFactory.cs

@@ -15,7 +15,7 @@ public class ChunkyImageSerializationFactory : SerializationFactory<byte[], Chun
         var encoder = Config.Encoder;
         surfaceFactory.Config = Config;
 
-        Surface surface = new Surface(original.LatestSize);
+        using Surface surface = new Surface(original.LatestSize);
         original.DrawMostUpToDateRegionOn(
             new RectI(0, 0, original.LatestSize.X,
                 original.LatestSize.Y), ChunkResolution.Full, surface.DrawingSurface, new VecI(0, 0), new Paint());
@@ -38,6 +38,7 @@ public class ChunkyImageSerializationFactory : SerializationFactory<byte[], Chun
             original = new ChunkyImage(surface.Size, Config.ProcessingColorSpace);
             original.EnqueueDrawImage(VecI.Zero, surface);
             original.CommitChanges();
+            surface.Dispose();
             return true;
         }
 

+ 8 - 0
src/PixiEditor/ViewModels/Document/AnimationDataViewModel.cs

@@ -471,4 +471,12 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
 
         return false;
     }
+
+    public void Dispose()
+    {
+        foreach (var cel in allCels)
+        {
+            cel.Dispose();
+        }
+    }
 }

+ 6 - 0
src/PixiEditor/ViewModels/Document/CelViewModel.cs

@@ -140,4 +140,10 @@ internal abstract class CelViewModel : ObservableObject, ICelHandler
     {
         return frame >= StartFrameBindable && frame < StartFrameBindable + DurationBindable;
     }
+
+    public void Dispose()
+    {
+        PreviewPainter?.Dispose();
+        PreviewPainter = null;
+    }
 }

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

@@ -73,6 +73,14 @@ internal partial class DocumentViewModel
 
         AddNodes(doc.NodeGraph, graph, nodeIdMap, keyFrameIdMap, serializationConfig, factories);
 
+        var preview = TryRenderWholeImage(0);
+        byte[]? previewBytes = null;
+        if (preview.IsT1)
+        {
+            previewBytes = preview.AsT1.DrawingSurface.Snapshot().Encode().AsSpan().ToArray();
+            preview.AsT1.Dispose();
+        }
+
         var document = new PixiDocument
         {
             SerializerName = "PixiEditor",
@@ -83,8 +91,7 @@ internal partial class DocumentViewModel
             Swatches = ToCollection(Swatches),
             Palette = ToCollection(Palette),
             Graph = graph,
-            PreviewImage =
-                (TryRenderWholeImage(0).Value as Surface)?.DrawingSurface.Snapshot().Encode().AsSpan().ToArray(),
+            PreviewImage = previewBytes,
             ReferenceLayer = GetReferenceLayer(doc, serializationConfig),
             AnimationData = ToAnimationData(doc.AnimationData, doc.NodeGraph, nodeIdMap, keyFrameIdMap),
             ImageEncoderUsed = encoder.EncodedFormatName,
@@ -584,7 +591,7 @@ internal partial class DocumentViewModel
 
         DrawingSurface surface = null;
 
-        if (bounds != null)
+        if (bounds is { Width: > 0, Height: > 0 })
         {
             surface = DrawingBackendApi.Current.SurfaceImplementation.Create(
                 new ImageInfo(bounds.Value.Width, bounds.Value.Height));

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

@@ -1153,6 +1153,10 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
 
     public void Dispose()
     {
+        NodeGraph.Dispose();
+        Renderer.Dispose();
+        SceneRenderer.Dispose();
+        AnimationDataViewModel.Dispose();
         Internals.ChangeController.TryStopActiveExecutor();
         Internals.Tracker.Dispose();
         Internals.Tracker.Document.Dispose();

+ 9 - 1
src/PixiEditor/ViewModels/Document/NodeGraphViewModel.cs

@@ -16,7 +16,7 @@ using PixiEditor.ViewModels.Nodes;
 
 namespace PixiEditor.ViewModels.Document;
 
-internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler
+internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler, IDisposable
 {
     public DocumentViewModel DocumentViewModel { get; }
     public ObservableCollection<INodeHandler> AllNodes { get; } = new();
@@ -374,4 +374,12 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler
             }
         }
     }
+
+    public void Dispose()
+    {
+        foreach (var node in AllNodes)
+        {
+            node.Dispose();
+        }
+    }
 }

+ 12 - 4
src/PixiEditor/ViewModels/Document/Nodes/StructureMemberViewModel.cs

@@ -19,7 +19,6 @@ internal abstract class StructureMemberViewModel<T> : NodeViewModel<T>, IStructu
 {
     public StructureMemberViewModel()
     {
-        
     }
 
     private bool isVisible;
@@ -44,10 +43,10 @@ internal abstract class StructureMemberViewModel<T> : NodeViewModel<T>, IStructu
 
     public RectD? TightBounds => Internals.Tracker.Document.FindMember(Id)
         ?.GetTightBounds(Document.AnimationDataViewModel.ActiveFrameBindable);
-    
+
     public ShapeCorners TransformationCorners => Internals.Tracker.Document.FindMember(Id)
         ?.GetTransformationCorners(Document.AnimationDataViewModel.ActiveFrameBindable) ?? new ShapeCorners();
-    
+
     public void SetMaskIsVisible(bool maskIsVisible)
     {
         this.maskIsVisible = maskIsVisible;
@@ -114,7 +113,7 @@ internal abstract class StructureMemberViewModel<T> : NodeViewModel<T>, IStructu
     {
         get => hasMask;
     }
-    
+
     private float opacity;
 
     public void SetOpacity(float newOpacity)
@@ -161,6 +160,15 @@ internal abstract class StructureMemberViewModel<T> : NodeViewModel<T>, IStructu
     }
 
     IDocument IStructureMemberHandler.Document => Document;
+
+    public override void Dispose()
+    {
+        base.Dispose();
+        PreviewPainter?.Dispose();
+        MaskPreviewPainter?.Dispose();
+        PreviewPainter = null;
+        MaskPreviewPainter = null;
+    }
 }
 
 public static class StructureMemberViewModel

+ 5 - 0
src/PixiEditor/ViewModels/Nodes/NodeViewModel.cs

@@ -380,6 +380,11 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
         }
     }
 
+    public virtual void Dispose()
+    {
+        ResultPainter?.Dispose();
+    }
+
     public NodePropertyViewModel FindInputProperty(string propName)
     {
         return Inputs.FirstOrDefault(x => x.PropertyName == propName) as NodePropertyViewModel;

+ 4 - 0
src/PixiEditor/Views/MainView.axaml.cs

@@ -46,6 +46,10 @@ public partial class MainView : UserControl
                 OpenGlInitDummy.IsVisible = false;
             });
         }
+        else
+        {
+            OpenGlInitDummy.IsVisible = false;
+        }
 
         if (DataContext is ViewModelMain vm)
         {

+ 1 - 0
src/PixiEditor/Views/Nodes/NodeGraphView.cs

@@ -382,6 +382,7 @@ internal class NodeGraphView : Zoombox.Zoombox
         }
         else
         {
+            ZoomMode = e.GetMouseButton(this) == MouseButton.Middle ? ZoomboxMode.Move : ZoomboxMode.Normal;
             isSelecting = false;
             selectionRectangle.IsVisible = false;
         }

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

@@ -280,6 +280,7 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
 
         RectD operationSurfaceRectToRender = new RectD(0, 0, dirtyBounds.Width, dirtyBounds.Height);
         float checkerScale = (float)ZoomToViewportConverter.ZoomToViewport(16, Scale) * 0.5f;
+        checkerPaint?.Shader?.Dispose();
         checkerPaint?.Dispose();
         checkerPaint = new Paint
         {