flabbet 1 year ago
parent
commit
b2ffae52f1

+ 5 - 5
src/PixiEditor.AvaloniaUI/Models/Rendering/MemberPreviewUpdater.cs

@@ -190,7 +190,7 @@ internal class MemberPreviewUpdater
             if (member is null)
                 continue;
 
-            if (forMasks && member.Mask is null)
+            if (forMasks && member.Mask.Value is null)
             {
                 newPreviewBitmapSizes.Add(guid, null);
                 continue;
@@ -277,7 +277,7 @@ internal class MemberPreviewUpdater
     private RectI? GetOrFindMemberTightBounds(IReadOnlyStructureNode member, int atFrame,
         AffectedArea currentlyAffectedArea, bool forMask)
     {
-        if (forMask && member.Mask is null)
+        if (forMask && member.Mask.Value is null)
             throw new InvalidOperationException();
 
         RectI? prevTightBounds = null;
@@ -307,10 +307,10 @@ internal class MemberPreviewUpdater
     /// </summary>
     private RectI? FindLayerTightBounds(IReadOnlyLayerNode layer, int frame, bool forMask)
     {
-        if (layer.Mask is null && forMask)
+        if (layer.Mask.Value is null && forMask)
             throw new InvalidOperationException();
 
-        if (layer.Mask is not null && forMask)
+        if (layer.Mask.Value is not null && forMask)
             return FindImageTightBoundsFast(layer.Mask.Value);
 
         if (layer is IReadOnlyImageNode raster)
@@ -328,7 +328,7 @@ internal class MemberPreviewUpdater
     {
         if (forMask)
         {
-            if (folder.Mask is null)
+            if (folder.Mask.Value is null)
                 throw new InvalidOperationException();
             return FindImageTightBoundsFast(folder.Mask.Value);
         }

+ 5 - 0
src/PixiEditor.ChangeableDocument/Changeables/Animations/KeyFrameTime.cs

@@ -10,4 +10,9 @@ public record KeyFrameTime
     public int Frame { get; init; }
     
     public static implicit operator KeyFrameTime(int frame) => new KeyFrameTime(frame);
+
+    public virtual bool Equals(KeyFrameTime? other)
+    {
+        return other != null && Frame == other.Frame;
+    }
 }

+ 45 - 2
src/PixiEditor.ChangeableDocument/Changeables/Graph/InputProperty.cs

@@ -6,6 +6,7 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph;
 public class InputProperty : IInputProperty
 {
     private object _internalValue;
+    private int _lastExecuteHash = -1;
     public string InternalPropertyName { get; }
     public string DisplayName { get; }
 
@@ -17,11 +18,53 @@ public class InputProperty : IInputProperty
     public object NonOverridenValue
     {
         get => _internalValue;
-        set => _internalValue = value;
+        set
+        {
+            _internalValue = value;
+        }
     }
-    
+
     public Node Node { get; }
     public Type ValueType { get; } 
+    internal bool CacheChanged
+    {
+        get
+        {
+            if (Value is ICacheable cacheable)
+            {
+                return cacheable.GetCacheHash() != _lastExecuteHash;
+            }
+
+            if(Value is null)
+            {
+                return _lastExecuteHash != 0;
+            }
+            
+            if(Value.GetType().IsValueType || Value.GetType() == typeof(string))
+            {
+                return Value.GetHashCode() != _lastExecuteHash;
+            }
+
+            return true;
+        }
+    }
+
+    internal void UpdateCache()
+    {
+        if (Value is null)
+        {
+            _lastExecuteHash = 0;
+        }
+        else if (Value is ICacheable cacheable)
+        {
+            _lastExecuteHash = cacheable.GetCacheHash();
+        }
+        else
+        {
+            _lastExecuteHash = Value.GetHashCode();
+        }
+    }
+    
     IReadOnlyNode INodeProperty.Node => Node;
     
     public IOutputProperty? Connection { get; set; }

+ 6 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/ICacheable.cs

@@ -0,0 +1,6 @@
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+
+public interface ICacheable
+{
+    public int GetCacheHash();
+}

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

@@ -1,4 +1,5 @@
 using System.Collections;
+using System.Diagnostics;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.DrawingApi.Core.Surface.ImageData;
@@ -96,6 +97,7 @@ public class NodeGraph : IReadOnlyNodeGraph, IDisposable
 
     public Image? Execute(int frame)
     {
+        Stopwatch stopwatch = new();
         if (OutputNode == null) return null;
 
         var queue = CalculateExecutionQueue(OutputNode);
@@ -103,8 +105,10 @@ public class NodeGraph : IReadOnlyNodeGraph, IDisposable
         while (queue.Count > 0)
         {
             var node = queue.Dequeue();
-
+            
+            stopwatch.Restart();
             node.Execute(frame);
+            Console.WriteLine($"{node.GetType().Name} took {stopwatch.ElapsedMilliseconds}ms to execute");
         }
 
         return OutputNode.Input.Value;

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

@@ -42,8 +42,7 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
             return Output.Value;
         }
 
-        var imageFrame = frames.FirstOrDefault(x => x.IsInFrame(frame.Frame));
-        var frameImage = imageFrame?.Image ?? frames[0].Image;
+        var frameImage = GetFrameImage(frame).Image;
 
         Surface workingSurface;
         
@@ -57,7 +56,13 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
             workingSurface.DrawingSurface.Canvas.DrawImage(Background.Value, 0, 0, blendPaint);
             blendPaint.BlendMode = RenderingContext.GetDrawingBlendMode(BlendMode.Value);
             
-            frameImage.DrawMostUpToDateRegionOn(
+            using ChunkyImage img = new ChunkyImage(targetSize);
+            img.EnqueueDrawUpToDateChunkyImage(VecI.Zero, frameImage);
+
+            ApplyMaskIfPresent(img);
+            img.CommitChanges();
+
+            img.DrawMostUpToDateRegionOn(
                 new RectI(0, 0, frameImage.LatestSize.X, frameImage.LatestSize.Y), 
                 ChunkResolution.Full, workingSurface.DrawingSurface, VecI.Zero, blendPaint);
         }
@@ -65,7 +70,13 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
         {
             workingSurface = new Surface(frameImage.LatestSize);
             
-            frameImage.DrawMostUpToDateRegionOn(
+            using ChunkyImage img = new ChunkyImage(frameImage.LatestSize);
+            img.EnqueueDrawUpToDateChunkyImage(VecI.Zero, frameImage);
+            
+            ApplyMaskIfPresent(img);
+            img.CommitChanges();
+            
+            img.DrawMostUpToDateRegionOn(
                 new RectI(0, 0, frameImage.LatestSize.X, frameImage.LatestSize.Y), 
                 ChunkResolution.Full, workingSurface.DrawingSurface, VecI.Zero, blendPaint);
         }
@@ -77,6 +88,37 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
         return Output.Value;
     }
 
+    private ImageFrame GetFrameImage(KeyFrameTime frame)
+    {
+        var imageFrame = frames.FirstOrDefault(x => x.IsInFrame(frame.Frame));
+        var frameImage = imageFrame ?? frames[0];
+        return frameImage;
+    }
+
+    private void ApplyMaskIfPresent(ChunkyImage img)
+    {
+        if (Mask.Value != null)
+        {
+            img.EnqueueApplyMask(Mask.Value);
+        }
+    }
+
+    protected override bool CacheChanged(KeyFrameTime frameTime)
+    {
+        var frame = GetFrameImage(frameTime);
+        return base.CacheChanged(frameTime) || frame?.RequiresUpdate == true;
+    }
+
+    protected override void UpdateCache(KeyFrameTime time)
+    {
+        base.UpdateCache(time);
+        var imageFrame = GetFrameImage(time);
+        if (imageFrame is not null && imageFrame.RequiresUpdate)
+        {
+            imageFrame.RequiresUpdate = false; 
+        }
+    }
+
     public override void Dispose()
     {
         base.Dispose();
@@ -210,8 +252,21 @@ class ImageFrame
     public int StartFrame { get; set; }
     public int Duration { get; set; }
     public ChunkyImage Image { get; set; }
+    
+    public bool RequiresUpdate
+    {
+        get
+        {
+            return Image.QueueLength != lastQueueLength;
+        }
+        set
+        {
+            lastQueueLength = Image.QueueLength;
+        }
+    }
 
     public Guid KeyFrameGuid { get; set; }
+    private int lastQueueLength = 0;
 
     public ImageFrame(Guid keyFrameGuid, int startFrame, int duration, ChunkyImage image)
     {

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

@@ -25,14 +25,34 @@ public abstract class Node : IReadOnlyNode, IDisposable
     IReadOnlyCollection<IOutputProperty> IReadOnlyNode.OutputProperties => outputs;
     public VecD Position { get; set; }
 
+    private KeyFrameTime _lastFrameTime = new KeyFrameTime(-1);
+
     public Image? Execute(KeyFrameTime frameTime)
     {
+        if(!CacheChanged(frameTime)) return CachedResult;
+        
         CachedResult = OnExecute(frameTime);
+        UpdateCache(frameTime);
         return CachedResult;
     }
 
     protected abstract Image? OnExecute(KeyFrameTime frameTime);
     public abstract bool Validate();
+    
+    protected virtual bool CacheChanged(KeyFrameTime frameTime)
+    {
+        return !frameTime.Equals(_lastFrameTime) || inputs.Any(x => x.CacheChanged);
+    }
+    
+    protected virtual void UpdateCache(KeyFrameTime time)
+    {
+        foreach (var input in inputs)
+        {
+            input.UpdateCache();
+        }
+        
+        _lastFrameTime = time;
+    }
 
     public void RemoveKeyFrame(Guid keyFrameGuid)
     {

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changes/Drawing/DrawingChangeHelper.cs

@@ -65,9 +65,9 @@ internal static class DrawingChangeHelper
 
         if (drawOnMask)
         {
-            if (member.Mask is null)
+            if (member.Mask.NonOverridenValue is null)
                 throw new InvalidOperationException("Trying to draw on a mask that doesn't exist");
-            return member.Mask.Value;
+            return member.Mask.NonOverridenValue;
         }
 
         if (member is FolderNode)

+ 3 - 3
src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/CreateStructureMemberMask_Change.cs

@@ -14,13 +14,13 @@ internal class CreateStructureMemberMask_Change : Change
 
     public override bool InitializeAndValidate(Document target)
     {
-        return target.TryFindMember(targetMember, out var member) && member.Mask is null;
+        return target.TryFindMember(targetMember, out var member) && member.Mask.NonOverridenValue is null;
     }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
     {
         var member = target.FindMemberOrThrow(targetMember);
-        if (member.Mask is not null)
+        if (member.Mask.NonOverridenValue is not null)
             throw new InvalidOperationException("Cannot create a mask; the target member already has one");
         member.Mask.NonOverridenValue = new ChunkyImage(target.Size);
 
@@ -31,7 +31,7 @@ internal class CreateStructureMemberMask_Change : Change
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
     {
         var member = target.FindMemberOrThrow(targetMember);
-        if (member.Mask is null)
+        if (member.Mask.NonOverridenValue is null)
             throw new InvalidOperationException("Cannot delete the mask; the target member has no mask");
         member.Mask.NonOverridenValue.Dispose();
         member.Mask.NonOverridenValue = null;

+ 9 - 2
src/PixiEditor.ChangeableDocument/Rendering/DocumentEvaluator.cs

@@ -30,7 +30,14 @@ public static class DocumentEvaluator
                 chunk.Surface.DrawingSurface.Canvas.ClipRect((RectD)transformedClippingRect);
             }
             
-            chunk.Surface.DrawingSurface.Canvas.DrawImage(evaluated, 0, 0, context.ReplacingPaintWithOpacity);
+            VecD pos = chunkPos * resolution.Multiplier();
+            int x = (int)(pos.X * ChunkyImage.FullChunkSize);
+            int y = (int)(pos.Y * ChunkyImage.FullChunkSize);
+            
+            RectD sourceRect = new(x, y, ChunkyImage.FullChunkSize, ChunkyImage.FullChunkSize);
+            RectD destRect = new(0, 0, ChunkyImage.FullChunkSize, ChunkyImage.FullChunkSize);
+            
+            chunk.Surface.DrawingSurface.Canvas.DrawImage(evaluated, sourceRect, destRect, context.ReplacingPaintWithOpacity);
 
             chunk.Surface.DrawingSurface.Canvas.Restore();
 
@@ -66,7 +73,7 @@ public static class DocumentEvaluator
                 chunk.Surface.DrawingSurface.Canvas.ClipRect((RectD)transformedClippingRect);
             }
             
-            chunk.Surface.DrawingSurface.Canvas.DrawImage(evaluated, 0, 0, context.ReplacingPaintWithOpacity);
+            chunk.Surface.DrawingSurface.Canvas.DrawImage(evaluated, transformedClippingRect.Value.X, transformedClippingRect.Value.Y, context.ReplacingPaintWithOpacity);
 
             chunk.Surface.DrawingSurface.Canvas.Restore();
 

+ 2 - 1
src/PixiEditor.DrawingApi.Core/Bridge/Operations/ICanvasImplementation.cs

@@ -34,7 +34,8 @@ namespace PixiEditor.DrawingApi.Core.Bridge.Operations
         public void DrawColor(IntPtr objPtr, Color color, BlendMode paintBlendMode);
         public void RotateRadians(IntPtr objPtr, float radians, float centerX, float centerY);
         public void RotateDegrees(IntPtr objectPointer, float degrees, float centerX, float centerY);
-        public void DrawImage(IntPtr objPtr, Image image, RectD rect, Paint paint);
+        public void DrawImage(IntPtr objPtr, Image image, RectD destRect, Paint paint);
+        public void DrawImage(IntPtr objPtr, Image image, RectD sourceRect, RectD destRect, Paint paint);
         public void DrawBitmap(IntPtr objPtr, Bitmap bitmap, int x, int y);
         public void Dispose(IntPtr objectPointer);
         public object GetNativeCanvas(IntPtr objectPointer);

+ 5 - 2
src/PixiEditor.DrawingApi.Core/Surface/Canvas.cs

@@ -47,8 +47,11 @@ namespace PixiEditor.DrawingApi.Core.Surface
         public void DrawImage(Image image, int x, int y, Paint paint) =>
             DrawingBackendApi.Current.CanvasImplementation.DrawImage(ObjectPointer, image, x, y, paint);
 
-        public void DrawImage(Image image, RectD rect, Paint paint) =>
-            DrawingBackendApi.Current.CanvasImplementation.DrawImage(ObjectPointer, image, rect, paint);
+        public void DrawImage(Image image, RectD destRect, Paint paint) =>
+            DrawingBackendApi.Current.CanvasImplementation.DrawImage(ObjectPointer, image, destRect, paint);
+        
+        public void DrawImage(Image image, RectD sourceRect, RectD destRect, Paint paint) =>
+            DrawingBackendApi.Current.CanvasImplementation.DrawImage(ObjectPointer, image, sourceRect, destRect, paint);
 
         public int Save()
         {

+ 11 - 2
src/PixiEditor.DrawingApi.Skia/Implementations/SkiaCanvasImplementation.cs

@@ -183,11 +183,20 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
             ManagedInstances[objectPointer].RotateDegrees(degrees, centerX, centerY);
         }
 
-        public void DrawImage(IntPtr objPtr, Image image, RectD rect, Paint paint)
+        public void DrawImage(IntPtr objPtr, Image image, RectD destRect, Paint paint)
         {
             ManagedInstances[objPtr].DrawImage(
                 _imageImpl[image.ObjectPointer],
-                rect.ToSKRect(),
+                destRect.ToSKRect(),
+                _paintImpl[paint.ObjectPointer]);
+        }
+
+        public void DrawImage(IntPtr obj, Image image, RectD sourceRect, RectD destRect, Paint paint)
+        {
+            ManagedInstances[obj].DrawImage(
+                _imageImpl[image.ObjectPointer],
+                sourceRect.ToSKRect(),
+                destRect.ToSKRect(),
                 _paintImpl[paint.ObjectPointer]);
         }