Browse Source

Merge pull request #765 from PixiEditor/fixes/28.02.2025

Fixes/28.02.2025
Krzysztof Krysiński 5 months ago
parent
commit
ff5ed8e5c3
23 changed files with 271 additions and 71 deletions
  1. 1 1
      src/Drawie
  2. 15 0
      src/PixiEditor.ChangeableDocument/Changeables/Animations/KeyFrameData.cs
  3. 18 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/InputProperty.cs
  4. 4 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/INodeProperty.cs
  5. 2 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyNode.cs
  6. 2 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyNodeGraph.cs
  7. 11 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/NodeGraph.cs
  8. 14 10
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Effects/OutlineNode.cs
  9. 1 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/MergeNode.cs
  10. 31 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Node.cs
  11. 5 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Painter.cs
  12. 2 7
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/EllipseVectorData.cs
  13. 5 7
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/LineVectorData.cs
  14. 6 6
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/PathVectorData.cs
  15. 4 7
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/PointsVectorData.cs
  16. 2 7
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/RectangleVectorData.cs
  17. 16 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/ShapeVectorData.cs
  18. 11 7
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/TextVectorData.cs
  19. 11 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/OutputProperty.cs
  20. 4 2
      src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyKeyFrameData.cs
  21. 4 1
      src/PixiEditor/Data/Localization/Languages/en.json
  22. 101 8
      src/PixiEditor/Models/Rendering/SceneRenderer.cs
  23. 1 1
      src/PixiEditor/ViewModels/SubViewModels/UpdateViewModel.cs

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit 1843368cc81ac00bf5bd9e096a1a1da1c500ebcc
+Subproject commit 135600d52af3806ab9bf654d783594683bc756e4

+ 15 - 0
src/PixiEditor.ChangeableDocument/Changeables/Animations/KeyFrameData.cs

@@ -72,4 +72,19 @@ public class KeyFrameData : IDisposable, IReadOnlyKeyFrameData
             IsVisible = IsVisible
         };
     }
+
+    public int GetCacheHash()
+    {
+        HashCode hash = new();
+        hash.Add(StartFrame);
+        hash.Add(Duration);
+        hash.Add(KeyFrameGuid);
+        hash.Add(AffectedElement);
+        if (Data != null)
+        {
+            hash.Add(Data is ICacheable cacheable ? cacheable.GetCacheHash() : Data.GetHashCode());
+        }
+
+        return hash.ToHashCode();
+    }
 }

+ 18 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/InputProperty.cs

@@ -211,6 +211,24 @@ public class InputProperty : IInputProperty
         Node = node;
         ValueType = valueType;
     }
+
+    public int GetCacheHash()
+    {
+        HashCode hash = new();
+        hash.Add(InternalPropertyName);
+        hash.Add(ValueType);
+        if(Value is ICacheable cacheable)
+        {
+            hash.Add(cacheable.GetCacheHash());
+        }
+        else
+        {
+            hash.Add(Value?.GetHashCode() ?? 0);
+        }
+
+        hash.Add(Connection?.GetCacheHash() ?? 0);
+        return hash.ToHashCode();
+    }
 }
 
 public class InputProperty<T> : InputProperty, IInputProperty<T>

+ 4 - 2
src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/INodeProperty.cs

@@ -1,6 +1,8 @@
-namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.Common;
 
-public interface INodeProperty
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+
+public interface INodeProperty : ICacheable
 {
     public string InternalPropertyName { get; }
     public string DisplayName { get; }

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

@@ -3,10 +3,11 @@ using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Rendering;
 using Drawie.Backend.Core;
 using Drawie.Numerics;
+using PixiEditor.Common;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 
-public interface IReadOnlyNode
+public interface IReadOnlyNode : ICacheable
 {
     public Guid Id { get; }
     public IReadOnlyList<IInputProperty> InputProperties { get; }

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

@@ -1,9 +1,10 @@
 using PixiEditor.ChangeableDocument.Rendering;
 using Drawie.Backend.Core;
+using PixiEditor.Common;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 
-public interface IReadOnlyNodeGraph
+public interface IReadOnlyNodeGraph : ICacheable
 {
     public IReadOnlyCollection<IReadOnlyNode> AllNodes { get; }
     public IReadOnlyNode OutputNode { get; }

+ 11 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/NodeGraph.cs

@@ -116,4 +116,15 @@ public class NodeGraph : IReadOnlyNodeGraph, IDisposable
     {
         cachedExecutionList = null;
     }
+
+    public int GetCacheHash()
+    {
+        HashCode hash = new();
+        foreach (var node in Nodes)
+        {
+            hash.Add(node.GetCacheHash());
+        }
+
+        return hash.ToHashCode();
+    }
 }

+ 14 - 10
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Effects/OutlineNode.cs

@@ -1,5 +1,6 @@
 using Drawie.Backend.Core;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Shaders;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
@@ -52,10 +53,6 @@ public class OutlineNode : RenderNode, IRenderInput
     {
         base.OnExecute(context);
         lastDocumentSize = context.DocumentSize;
-        if(lastType == Type.Value)
-        {
-            return;
-        }
 
         Kernel finalKernel = Type.Value switch
         {
@@ -86,20 +83,27 @@ public class OutlineNode : RenderNode, IRenderInput
             paint.ImageFilter = filter;
             paint.ColorFilter = ColorFilter.CreateBlendMode(Color.Value, BlendMode.SrcIn);
 
-            int saved = surface.Canvas.SaveLayer(paint);
+            using Texture temp = Texture.ForProcessing(surface, context.ProcessingColorSpace);
+            int saved = temp.DrawingSurface.Canvas.SaveLayer(paint);
 
-            Background.Value.Paint(context, surface);
+            Background.Value.Paint(context, temp.DrawingSurface);
 
-            surface.Canvas.RestoreToCount(saved);
+            temp.DrawingSurface.Canvas.RestoreToCount(saved);
 
             for (int i = 1; i < (int)Thickness.Value; i++)
             {
-                saved = surface.Canvas.SaveLayer(paint);
+                saved = temp.DrawingSurface.Canvas.SaveLayer(paint);
 
-                surface.Canvas.DrawSurface(surface, 0, 0);
+                temp.DrawingSurface.Canvas.DrawSurface(temp.DrawingSurface, 0, 0);
 
-                surface.Canvas.RestoreToCount(saved);
+                temp.DrawingSurface.Canvas.RestoreToCount(saved);
             }
+
+            saved = surface.Canvas.Save();
+            surface.Canvas.SetMatrix(Matrix3X3.Identity);
+            surface.Canvas.DrawSurface(temp.DrawingSurface, 0, 0);
+
+            surface.Canvas.RestoreToCount(saved);
         }
 
         Background.Value.Paint(context, surface);

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

@@ -55,6 +55,7 @@ public class MergeNode : RenderNode
         {
             int saved = target.Canvas.SaveLayer();
             Bottom.Value?.Paint(context, target);
+            target.Canvas.RestoreToCount(saved);
 
             paint.BlendMode = RenderContext.GetDrawingBlendMode(BlendMode.Value);
             target.Canvas.SaveLayer(paint);

+ 31 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Node.cs

@@ -48,6 +48,11 @@ public abstract class Node : IReadOnlyNode, IDisposable
     protected internal bool IsDisposed => _isDisposed;
     private bool _isDisposed;
 
+    protected virtual int GetContentCacheHash()
+    {
+        return 0;
+    }
+
     public void Execute(RenderContext context)
     {
         ExecuteInternal(context);
@@ -551,4 +556,30 @@ public abstract class Node : IReadOnlyNode, IDisposable
 
         return default;
     }
+
+    public int GetCacheHash()
+    {
+        HashCode hash = new();
+        hash.Add(GetType());
+        hash.Add(DisplayName);
+        hash.Add(Position);
+        foreach (var input in inputs)
+        {
+            hash.Add(input.GetCacheHash());
+        }
+
+        foreach (var output in outputs)
+        {
+            hash.Add(output.GetCacheHash());
+        }
+
+        foreach (var frame in keyFrames)
+        {
+            hash.Add(frame.GetCacheHash());
+        }
+
+        hash.Add(GetContentCacheHash());
+
+        return hash.ToHashCode();
+    }
 }

+ 5 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Painter.cs

@@ -6,4 +6,9 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 public class Painter(Action<RenderContext, DrawingSurface> paint)
 {
     public Action<RenderContext, DrawingSurface> Paint { get; } = paint;
+
+    public override int GetHashCode()
+    {
+        return Paint.GetHashCode();
+    }
 }

+ 2 - 7
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/EllipseVectorData.cs

@@ -78,14 +78,9 @@ public class EllipseVectorData : ShapeVectorData, IReadOnlyEllipseData
         return Radius is { X: > 0, Y: > 0 };
     }
 
-    public override int CalculateHash()
+    protected override int GetSpecificHash()
     {
-        return HashCode.Combine(Center, Radius, StrokeColor, FillColor, StrokeWidth, TransformationMatrix);
-    }
-
-    public override int GetCacheHash()
-    {
-        return CalculateHash();
+        return HashCode.Combine(Center, Radius);
     }
 
     protected override void AdjustCopy(ShapeVectorData copy)

+ 5 - 7
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/LineVectorData.cs

@@ -100,14 +100,12 @@ public class LineVectorData : ShapeVectorData, IReadOnlyLineData
         return Start != End;
     }
 
-    public override int GetCacheHash()
+    protected override int GetSpecificHash()
     {
-        return HashCode.Combine(Start, End, StrokeColor, StrokeWidth, TransformationMatrix);
-    }
-
-    public override int CalculateHash()
-    {
-        return GetCacheHash();
+        HashCode hash = new();
+        hash.Add(Start);
+        hash.Add(End);
+        return hash.ToHashCode();
     }
 
     public override VectorPath ToPath()

+ 6 - 6
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/PathVectorData.cs

@@ -87,14 +87,14 @@ public class PathVectorData : ShapeVectorData, IReadOnlyPathData
         return Path is { IsEmpty: false };
     }
 
-    public override int GetCacheHash()
+    protected override int GetSpecificHash()
     {
-        return Path.GetHashCode();
-    }
+        HashCode hash = new();
+        hash.Add(Path);
+        hash.Add(StrokeLineCap);
+        hash.Add(StrokeLineJoin);
 
-    public override int CalculateHash()
-    {
-        return Path.GetHashCode();
+        return hash.ToHashCode();
     }
 
     protected override void AdjustCopy(ShapeVectorData copy)

+ 4 - 7
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/PointsVectorData.cs

@@ -61,14 +61,11 @@ public class PointsVectorData : ShapeVectorData
         return Points.Count > 0;
     }
 
-    public override int GetCacheHash()
+    protected override int GetSpecificHash()
     {
-        return CalculateHash();
-    }
-
-    public override int CalculateHash()
-    {
-        return Points.GetHashCode();
+        HashCode hash = new();
+        hash.Add(Points);
+        return hash.ToHashCode();
     }
 
     protected override void AdjustCopy(ShapeVectorData copy)

+ 2 - 7
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/RectangleVectorData.cs

@@ -90,14 +90,9 @@ public class RectangleVectorData : ShapeVectorData, IReadOnlyRectangleData
         return Size is { X: > 0, Y: > 0 };
     }
 
-    public override int CalculateHash()
+    protected override int GetSpecificHash()
     {
-        return HashCode.Combine(Center, Size, StrokeColor, FillColor, StrokeWidth, TransformationMatrix);
-    }
-
-    public override int GetCacheHash()
-    {
-        return CalculateHash();
+        return HashCode.Combine(Center, Size);
     }
 
     public override VectorPath ToPath()

+ 16 - 3
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/ShapeVectorData.cs

@@ -48,8 +48,21 @@ public abstract class ShapeVectorData : ICacheable, ICloneable, IReadOnlyShapeVe
     public abstract void RasterizeGeometry(Canvas canvas);
     public abstract void RasterizeTransformed(Canvas canvas);
     public abstract bool IsValid();
-    public abstract int GetCacheHash();
-    public abstract int CalculateHash();
+
+    public int GetCacheHash()
+    {
+        HashCode hash = new();
+        hash.Add(TransformationMatrix);
+        hash.Add(StrokeColor);
+        hash.Add(FillColor);
+        hash.Add(StrokeWidth);
+        hash.Add(Fill);
+        hash.Add(GetSpecificHash());
+
+        return hash.ToHashCode();
+    }
+
+    protected abstract int GetSpecificHash();
 
     public object Clone()
     {
@@ -64,7 +77,7 @@ public abstract class ShapeVectorData : ICacheable, ICloneable, IReadOnlyShapeVe
 
     public override int GetHashCode()
     {
-        return CalculateHash();
+        return GetCacheHash();
     }
 
     public abstract VectorPath ToPath();

+ 11 - 7
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/TextVectorData.cs

@@ -204,11 +204,6 @@ public class TextVectorData : ShapeVectorData, IReadOnlyTextData
         return !string.IsNullOrEmpty(Text);
     }
 
-    public override int GetCacheHash()
-    {
-        return HashCode.Combine(Text, Position, Font, StrokeColor, FillColor, StrokeWidth, TransformationMatrix);
-    }
-
     protected override void AdjustCopy(ShapeVectorData copy)
     {
         if (copy is TextVectorData textData)
@@ -224,8 +219,17 @@ public class TextVectorData : ShapeVectorData, IReadOnlyTextData
         }
     }
 
-    public override int CalculateHash()
+    protected override int GetSpecificHash()
     {
-        return GetCacheHash();
+        HashCode hash = new();
+        hash.Add(Text);
+        hash.Add(Position);
+        hash.Add(Font);
+        hash.Add(Spacing);
+        hash.Add(AntiAlias);
+        hash.Add(MissingFontFamily);
+        hash.Add(MissingFontText);
+
+        return hash.ToHashCode();
     }
 }

+ 11 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/OutputProperty.cs

@@ -1,5 +1,6 @@
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.Common;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph;
 
@@ -65,6 +66,16 @@ public class OutputProperty : IOutputProperty
 
         Disconnected?.Invoke(property, this);
     }
+
+    public int GetCacheHash()
+    {
+        if (Value is ICacheable cacheable)
+        {
+            return cacheable.GetCacheHash();
+        }
+
+        return 0;
+    }
 }
 
 public class OutputProperty<T> : OutputProperty, INodeProperty<T>

+ 4 - 2
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyKeyFrameData.cs

@@ -1,6 +1,8 @@
-namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.Common;
 
-public interface IReadOnlyKeyFrameData
+namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
+
+public interface IReadOnlyKeyFrameData : ICacheable
 {
     int StartFrame { get; }
     int Duration { get; }

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

@@ -905,5 +905,8 @@
   "BRIGHTNESS_VALUE": "Brightness",
   "CONTRAST_VALUE": "Contrast",
   "TEMPERATURE_VALUE": "Temperature",
-  "TINT_VALUE": "Tint"
+  "TINT_VALUE": "Tint",
+  "FAILED_DOWNLOADING_UPDATE_TITLE": "Failed to download update",
+  "FAILED_DOWNLOADING_UPDATE": "Failed to download the update. Try again later."
+
 }

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

@@ -5,6 +5,8 @@ using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Rendering;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.ImageData;
+using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Graph;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
@@ -12,12 +14,17 @@ using PixiEditor.Models.Handlers;
 
 namespace PixiEditor.Models.Rendering;
 
-internal class SceneRenderer 
+internal class SceneRenderer
 {
+    public const double ZoomDiffToRerender = 20;
     public IReadOnlyDocument Document { get; }
     public IDocument DocumentViewModel { get; }
     public bool HighResRendering { get; set; } = true;
 
+    private Dictionary<string, Texture> cachedTextures = new();
+    private bool lastHighResRendering = true;
+    private int lastGraphCacheHash = -1;
+    private KeyFrameTime lastFrameTime;
 
     public SceneRenderer(IReadOnlyDocument trackerDocument, IDocument documentViewModel)
     {
@@ -29,16 +36,39 @@ internal class SceneRenderer
     {
         if (Document.Renderer.IsBusy || DocumentViewModel.Busy) return;
         RenderOnionSkin(target, resolution, targetOutput);
-        RenderGraph(target, resolution, targetOutput);
+
+        string adjustedTargetOutput = targetOutput ?? "";
+
+        IReadOnlyNodeGraph finalGraph = SolveFinalNodeGraph(targetOutput);
+        bool shouldRerender = ShouldRerender(target, resolution, adjustedTargetOutput, finalGraph);
+        if (shouldRerender)
+        {
+            if (cachedTextures.ContainsKey(adjustedTargetOutput))
+            {
+                cachedTextures[adjustedTargetOutput]?.Dispose();
+            }
+
+            var rendered = RenderGraph(target, resolution, targetOutput, finalGraph);
+            cachedTextures[adjustedTargetOutput] = rendered;
+        }
+        else
+        {
+            var cachedTexture = cachedTextures[adjustedTargetOutput];
+            Matrix3X3 matrixDiff = SolveMatrixDiff(target, cachedTexture);
+            int saved = target.Canvas.Save();
+            target.Canvas.SetMatrix(matrixDiff);
+            target.Canvas.DrawSurface(cachedTexture.DrawingSurface, 0, 0);
+            target.Canvas.RestoreToCount(saved);
+        }
     }
 
-    private void RenderGraph(DrawingSurface target, ChunkResolution resolution, string? targetOutput)
+    private Texture RenderGraph(DrawingSurface target, ChunkResolution resolution, string? targetOutput, IReadOnlyNodeGraph finalGraph)
     {
         DrawingSurface renderTarget = target;
         Texture? renderTexture = null;
         bool restoreCanvas = false;
 
-        if (!HighResRendering || !HighDpiRenderNodePresent(Document.NodeGraph))
+        if (RenderInDocumentSize())
         {
             renderTexture = Texture.ForProcessing(Document.Size, Document.ProcessingColorSpace);
             renderTarget = renderTexture.DrawingSurface;
@@ -47,10 +77,10 @@ internal class SceneRenderer
         {
             renderTexture = Texture.ForProcessing(renderTarget.DeviceClipBounds.Size, Document.ProcessingColorSpace);
             renderTarget = renderTexture.DrawingSurface;
-            
+
             target.Canvas.Save();
             renderTarget.Canvas.Save();
-            
+
             renderTarget.Canvas.SetMatrix(target.Canvas.TotalMatrix);
             target.Canvas.SetMatrix(Matrix3X3.Identity);
             restoreCanvas = true;
@@ -59,18 +89,81 @@ internal class SceneRenderer
         RenderContext context = new(renderTarget, DocumentViewModel.AnimationHandler.ActiveFrameTime,
             resolution, Document.Size, Document.ProcessingColorSpace);
         context.TargetOutput = targetOutput;
-        SolveFinalNodeGraph(context.TargetOutput).Execute(context);
+        finalGraph.Execute(context);
 
         if (renderTexture != null)
         {
             target.Canvas.DrawSurface(renderTexture.DrawingSurface, 0, 0);
-            renderTexture.Dispose();
 
             if (restoreCanvas)
             {
                 target.Canvas.Restore();
             }
         }
+
+        return renderTexture;
+    }
+
+    private bool RenderInDocumentSize()
+    {
+        return !HighResRendering || !HighDpiRenderNodePresent(Document.NodeGraph);
+    }
+
+    private bool ShouldRerender(DrawingSurface target, ChunkResolution resolution, string? targetOutput,
+        IReadOnlyNodeGraph finalGraph)
+    {
+        if (!cachedTextures.TryGetValue(targetOutput ?? "", out var cachedTexture) || cachedTexture == null ||
+            cachedTexture.IsDisposed)
+        {
+            return true;
+        }
+
+        if (lastHighResRendering != HighResRendering)
+        {
+            lastHighResRendering = HighResRendering;
+            return true;
+        }
+
+        bool renderInDocumentSize = RenderInDocumentSize();
+        VecI compareSize = renderInDocumentSize ? Document.Size : target.DeviceClipBounds.Size;
+
+        if (cachedTexture.DrawingSurface.DeviceClipBounds.Size != compareSize)
+        {
+            return true;
+        }
+
+        if(lastFrameTime.Frame != DocumentViewModel.AnimationHandler.ActiveFrameTime.Frame)
+        {
+            lastFrameTime = DocumentViewModel.AnimationHandler.ActiveFrameTime;
+            return true;
+        }
+
+        if (!renderInDocumentSize)
+        {
+            double lengthDiff = target.LocalClipBounds.Size.Length - cachedTexture.DrawingSurface.LocalClipBounds.Size.Length;
+            if (lengthDiff > 0 || target.LocalClipBounds.Pos != cachedTexture.DrawingSurface.LocalClipBounds.Pos || lengthDiff < -ZoomDiffToRerender)
+            {
+                return true;
+            }
+        }
+
+        int currentGraphCacheHash = finalGraph.GetCacheHash();
+        if (lastGraphCacheHash != currentGraphCacheHash)
+        {
+            lastGraphCacheHash = currentGraphCacheHash;
+            return true;
+        }
+
+        return false;
+    }
+
+    private Matrix3X3 SolveMatrixDiff(DrawingSurface target, Texture cachedTexture)
+    {
+        Matrix3X3 old = cachedTexture.DrawingSurface.Canvas.TotalMatrix;
+        Matrix3X3 current = target.Canvas.TotalMatrix;
+
+        Matrix3X3 solveMatrixDiff = current.Concat(old.Invert());
+        return solveMatrixDiff;
     }
 
     private IReadOnlyNodeGraph SolveFinalNodeGraph(string? targetOutput)

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/UpdateViewModel.cs

@@ -114,7 +114,7 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
             }
             catch (IOException ex)
             {
-                NoticeDialog.Show("FAILED_DOWNLOADING_TITLE", "FAILED_DOWNLOADING");
+                NoticeDialog.Show("FAILED_DOWNLOADING", "FAILED_DOWNLOADING_TITLE");
                 return false;
             }
             catch(TaskCanceledException ex)