浏览代码

Experimental previews rendering

flabbet 8 月之前
父节点
当前提交
46dd2e2226
共有 30 个文件被更改,包括 291 次插入120 次删除
  1. 13 13
      src/PixiEditor.ChangeableDocument/Changeables/Document.cs
  2. 3 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IPreviewRenderable.cs
  3. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/SeparateChannelsNode.cs
  4. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CreateImageNode.cs
  5. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CustomOutputNode.cs
  6. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ModifyImageLeftNode.cs
  7. 4 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/OutputNode.cs
  8. 54 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/RenderNode.cs
  9. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/RenderNodeGraph.cs
  10. 1 1
      src/PixiEditor.ChangeableDocument/Changes/Drawing/CombineStructureMembersOnto_Change.cs
  11. 3 3
      src/PixiEditor.ChangeableDocument/Changes/NodeGraph/CreateNodePair_Change.cs
  12. 3 3
      src/PixiEditor.ChangeableDocument/Changes/NodeGraph/CreateNode_Change.cs
  13. 5 5
      src/PixiEditor.ChangeableDocument/Changes/NodeGraph/DeleteNode_Change.cs
  14. 2 2
      src/PixiEditor.ChangeableDocument/Changes/NodeGraph/DuplicateNode_Change.cs
  15. 1 1
      src/PixiEditor.ChangeableDocument/Changes/NodeGraph/NodeOperations.cs
  16. 2 2
      src/PixiEditor.ChangeableDocument/Changes/NodeGraph/UpdateProperty_Change.cs
  17. 1 1
      src/PixiEditor.ChangeableDocument/Changes/Properties/ChangeProcessingColorSpace_Change.cs
  18. 3 3
      src/PixiEditor.ChangeableDocument/Changes/Structure/CreateStructureMember_Change.cs
  19. 3 3
      src/PixiEditor.ChangeableDocument/Changes/Structure/DeleteStructureMember_Change.cs
  20. 3 3
      src/PixiEditor.ChangeableDocument/Changes/Structure/DuplicateLayer_Change.cs
  21. 1 1
      src/PixiEditor.ChangeableDocument/Changes/Structure/MoveStructureMember_Change.cs
  22. 7 7
      src/PixiEditor.ChangeableDocument/Changes/Structure/RasterizeMember_Change.cs
  23. 62 32
      src/PixiEditor.ChangeableDocument/Rendering/DocumentRenderer.cs
  24. 24 0
      src/PixiEditor.ChangeableDocument/Rendering/PreviewRequest.cs
  25. 2 1
      src/PixiEditor.ChangeableDocument/Rendering/RenderContext.cs
  26. 1 1
      src/PixiEditor/Models/Rendering/AnimationPreviewRenderer.cs
  27. 16 17
      src/PixiEditor/Models/Rendering/MemberPreviewUpdater.cs
  28. 51 10
      src/PixiEditor/Models/Rendering/PreviewPainter.cs
  29. 10 1
      src/PixiEditor/Models/Rendering/SceneRenderer.cs
  30. 11 0
      src/PixiEditor/Views/Visuals/PreviewPainterControl.cs

+ 13 - 13
src/PixiEditor.ChangeableDocument/Changeables/Document.cs

@@ -16,7 +16,7 @@ namespace PixiEditor.ChangeableDocument.Changeables;
 internal class Document : IChangeable, IReadOnlyDocument
 {
     public Guid DocumentId { get; } = Guid.NewGuid();
-    IReadOnlyNodeGraph IReadOnlyDocument.NodeGraph => NodeGraph;
+    IReadOnlyNodeGraph IReadOnlyDocument.NodeGraph => RenderNodeGraph;
     IReadOnlySelection IReadOnlyDocument.Selection => Selection;
     IReadOnlyAnimationData IReadOnlyDocument.AnimationData => AnimationData;
     IReadOnlyStructureNode? IReadOnlyDocument.FindMember(Guid guid) => FindMember(guid);
@@ -39,7 +39,7 @@ internal class Document : IChangeable, IReadOnlyDocument
     /// </summary>
     public static VecI DefaultSize { get; } = new VecI(64, 64);
 
-    internal NodeGraph NodeGraph { get; } = new();
+    internal RenderNodeGraph RenderNodeGraph { get; } = new();
     internal Selection Selection { get; } = new();
     internal ReferenceLayer? ReferenceLayer { get; set; }
     internal AnimationData AnimationData { get; }
@@ -57,7 +57,7 @@ internal class Document : IChangeable, IReadOnlyDocument
 
     public void Dispose()
     {
-        NodeGraph.Dispose();
+        RenderNodeGraph.Dispose();
         Selection.Dispose();
     }
 
@@ -132,12 +132,12 @@ internal class Document : IChangeable, IReadOnlyDocument
     }
 
     public void ForEveryReadonlyMember(Action<IReadOnlyStructureNode> action) =>
-        ForEveryReadonlyMember(NodeGraph, action);
+        ForEveryReadonlyMember(RenderNodeGraph, action);
 
     /// <summary>
     /// Performs the specified action on each member of the document
     /// </summary>
-    public void ForEveryMember(Action<StructureNode> action) => ForEveryMember(NodeGraph, action);
+    public void ForEveryMember(Action<StructureNode> action) => ForEveryMember(RenderNodeGraph, action);
 
     public void InitProcessingColorSpace(ColorSpace processingColorSpace)
     {
@@ -157,7 +157,7 @@ internal class Document : IChangeable, IReadOnlyDocument
         });
     }
 
-    private void ForEveryMember(NodeGraph graph, Action<StructureNode> action)
+    private void ForEveryMember(RenderNodeGraph graph, Action<StructureNode> action)
     {
         graph.TryTraverse((node) =>
         {
@@ -177,7 +177,7 @@ internal class Document : IChangeable, IReadOnlyDocument
     /// <returns>True if the node exists, otherwise false.</returns>
     public bool HasNode(Guid id)
     {
-        return NodeGraph.Nodes.Any(x => x.Id == id);
+        return RenderNodeGraph.Nodes.Any(x => x.Id == id);
     }
 
     /// <summary>
@@ -188,7 +188,7 @@ internal class Document : IChangeable, IReadOnlyDocument
     /// <returns>True if the node exists and is of type <typeparamref name="T"/>, otherwise false.</returns>
     public bool HasNode<T>(Guid id) where T : Node
     {
-        return NodeGraph.Nodes.Any(x => x.Id == id && x is T);
+        return RenderNodeGraph.Nodes.Any(x => x.Id == id && x is T);
     }
 
     /// <summary>
@@ -251,14 +251,14 @@ internal class Document : IChangeable, IReadOnlyDocument
     /// <returns>The node with the given <paramref name="guid"/> or null if it doesn't exist.</returns>
     public Node? FindNode(Guid guid)
     {
-        return NodeGraph.Nodes.FirstOrDefault(x => x.Id == guid);
+        return RenderNodeGraph.Nodes.FirstOrDefault(x => x.Id == guid);
     }
 
     IReadOnlyNode IReadOnlyDocument.FindNode(Guid guid) => FindNodeOrThrow<Node>(guid);
 
     public T? FindNode<T>(Guid guid) where T : Node
     {
-        return NodeGraph.Nodes.FirstOrDefault(x => x.Id == guid && x is T) as T;
+        return RenderNodeGraph.Nodes.FirstOrDefault(x => x.Id == guid && x is T) as T;
     }
 
     /// <summary>
@@ -270,7 +270,7 @@ internal class Document : IChangeable, IReadOnlyDocument
     /// <returns>True if the node could be found, otherwise false.</returns>
     public bool TryFindNode<T>(Guid id, out T node) where T : Node
     {
-        node = (T?)NodeGraph.Nodes.FirstOrDefault(x => x.Id == id && x is T) ?? default;
+        node = (T?)RenderNodeGraph.Nodes.FirstOrDefault(x => x.Id == id && x is T) ?? default;
         return node != null;
     }
 
@@ -344,7 +344,7 @@ internal class Document : IChangeable, IReadOnlyDocument
     /// <returns>The path to the node.</returns>
     public List<Node> FindNodePath(Guid guid)
     {
-        if (NodeGraph.OutputNode == null) return [];
+        if (RenderNodeGraph.OutputNode == null) return [];
 
         var list = new List<Node>();
 
@@ -364,7 +364,7 @@ internal class Document : IChangeable, IReadOnlyDocument
     /// <param name="guid">The <see cref="StructureNode.Id"/> of the member</param>
     public List<StructureNode> FindMemberPath(Guid guid)
     {
-        if (NodeGraph.OutputNode == null) return [];
+        if (RenderNodeGraph.OutputNode == null) return [];
 
         var list = new List<StructureNode>();
         var targetNode = FindNode(guid);

+ 3 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IPreviewRenderable.cs

@@ -1,5 +1,6 @@
 using Drawie.Backend.Core;
 using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Rendering;
 
@@ -7,6 +8,8 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 
 public interface IPreviewRenderable
 {
+    public Dictionary<int, Image> LastRenderedPreviews { get; }
+    public Guid RenderableId { get; }
     public RectD? GetPreviewBounds(int frame, string elementToRenderName = ""); 
     public bool RenderPreview(DrawingSurface renderOn, RenderContext context,
         string elementToRenderName);

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

@@ -10,7 +10,7 @@ using Drawie.Numerics;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
 
 [NodeInfo("SeparateChannels")]
-public class SeparateChannelsNode : Node, IRenderInput, IPreviewRenderable
+public class SeparateChannelsNode : Node, IRenderInput/*, IPreviewRenderable*/
 {
     private readonly Paint _paint = new();
     

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

@@ -10,7 +10,7 @@ using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 [NodeInfo("CreateImage")]
-public class CreateImageNode : Node, IPreviewRenderable
+public class CreateImageNode : Node/*, IPreviewRenderable*/
 {
     public OutputProperty<Texture> Output { get; }
 

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

@@ -8,7 +8,7 @@ using Drawie.Numerics;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 [NodeInfo("CustomOutput")]
-public class CustomOutputNode : Node, IRenderInput, IPreviewRenderable
+public class CustomOutputNode : Node, IRenderInput/*, IPreviewRenderable*/
 {
     public const string OutputNamePropertyName = "OutputName";
     public RenderInputProperty Input { get; } 

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

@@ -12,7 +12,7 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 [NodeInfo("ModifyImageLeft")]
 [PairNode(typeof(ModifyImageRightNode), "ModifyImageZone", true)]
-public class ModifyImageLeftNode : Node, IPairNode, IPreviewRenderable
+public class ModifyImageLeftNode : Node, IPairNode/*, IPreviewRenderable*/
 {
     public InputProperty<Texture?> Image { get; }
 

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

@@ -3,18 +3,19 @@ using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Rendering;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 [NodeInfo("Output")]
-public class OutputNode : Node, IRenderInput, IPreviewRenderable
+public class OutputNode : Node, IRenderInput/*, IPreviewRenderable*/
 {
     public const string UniqueName = "PixiEditor.Output";
     public const string InputPropertyName = "Background";
 
     public RenderInputProperty Input { get; }
-
+   
     private VecI? lastDocumentSize;
 
     public OutputNode()
@@ -43,6 +44,7 @@ public class OutputNode : Node, IRenderInput, IPreviewRenderable
 
     RenderInputProperty IRenderInput.Background => Input;
 
+
     public RectD? GetPreviewBounds(int frame, string elementToRenderName = "")
     {
         if (lastDocumentSize == null)

+ 54 - 3
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/RenderNode.cs

@@ -1,5 +1,6 @@
 using Drawie.Backend.Core;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Rendering;
 using Drawie.Backend.Core.Surfaces;
@@ -12,6 +13,8 @@ public abstract class RenderNode : Node, IPreviewRenderable, IHighDpiRenderNode
 {
     public RenderOutputProperty Output { get; }
 
+    public Dictionary<int, Image> LastRenderedPreviews { get; private set; }
+    Guid IPreviewRenderable.RenderableId => Id;
     public bool AllowHighDpiRendering { get; set; } = false;
 
     private TextureCache textureCache = new();
@@ -53,6 +56,56 @@ public abstract class RenderNode : Node, IPreviewRenderable, IHighDpiRenderNode
         {
             surface.Canvas.DrawSurface(target, 0, 0);
         }
+
+        if (context.PendingPreviewRequests != null && context.PendingPreviewRequests.TryGetValue(Id, out var requests))
+        {
+            for (var i = 0; i < requests.Count; i++)
+            {
+                var request = requests[i];
+                RectD? bounds = GetPreviewBounds(request.Frame.Frame, request.ElementName);
+                if (bounds != null)
+                {
+                    using Texture previewSurface = Texture.ForProcessing(request.Size);
+                    previewSurface.DrawingSurface.Canvas.Save();
+                    
+                    UniformScale(bounds.Value, request.Size, previewSurface.DrawingSurface.Canvas);
+                    
+                    if (RenderPreview(previewSurface.DrawingSurface, context, request.ElementName))
+                    {
+                        if(LastRenderedPreviews == null)
+                        {
+                            LastRenderedPreviews = new Dictionary<int, Image>();
+                        }
+                        
+                        if (LastRenderedPreviews.ContainsKey(request.Id))
+                        {
+                            LastRenderedPreviews[request.Id].Dispose();
+                            LastRenderedPreviews[request.Id] = previewSurface.DrawingSurface.Snapshot();
+                        }
+                        else
+                        {
+                            LastRenderedPreviews.Add(request.Id, previewSurface.DrawingSurface.Snapshot());
+                        }
+                    }
+                    
+                    previewSurface.DrawingSurface.Canvas.Restore();
+                }
+            }
+        }
+    }
+    
+    private void UniformScale(RectD bounds, VecI size, Canvas canvas)
+    {
+        float scaleX = (float)size.X / (float)bounds.Width;
+        float scaleY = (float)size.Y / (float)bounds.Height;
+        float scale = Math.Min(scaleX, scaleY);
+        float dX = (float)size.X / 2 / scale - (float)bounds.Width / 2;
+        dX -= (float)bounds.X;
+        float dY = (float)size.Y / 2 / scale - (float)bounds.Height / 2;
+        dY -= (float)bounds.Y;
+        Matrix3X3 matrix = Matrix3X3.CreateScale(scale, scale);
+        matrix = matrix.Concat(Matrix3X3.CreateTranslation(dX, dY));
+        canvas.SetMatrix(matrix);
     }
 
     protected abstract void OnPaint(RenderContext context, DrawingSurface surface);
@@ -70,8 +123,6 @@ public abstract class RenderNode : Node, IPreviewRenderable, IHighDpiRenderNode
     public override void Dispose()
     {
         base.Dispose();
-        textureCache.Dispose(); 
+        textureCache.Dispose();
     }
-
-   
 }

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

@@ -5,7 +5,7 @@ using PixiEditor.ChangeableDocument.Rendering;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph;
 
-public class NodeGraph : IReadOnlyNodeGraph, IDisposable
+public class RenderNodeGraph : IReadOnlyNodeGraph, IDisposable
 {
     private ImmutableList<IReadOnlyNode>? cachedExecutionList;
     

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Drawing/CombineStructureMembersOnto_Change.cs

@@ -275,7 +275,7 @@ internal class CombineStructureMembersOnto_Change : Change
     private HashSet<Guid> OrderLayers(HashSet<Guid> layersToCombine, Document document)
     {
         HashSet<Guid> ordered = new();
-        document.NodeGraph.TryTraverse(node =>
+        document.RenderNodeGraph.TryTraverse(node =>
         {
             if (node is LayerNode layer && layersToCombine.Contains(layer.Id))
             {

+ 3 - 3
src/PixiEditor.ChangeableDocument/Changes/NodeGraph/CreateNodePair_Change.cs

@@ -53,8 +53,8 @@ internal class CreateNodePair_Change : Change
 
         end.Position = new VecD(100, 0);
 
-        target.NodeGraph.AddNode(start);
-        target.NodeGraph.AddNode(end);
+        target.RenderNodeGraph.AddNode(start);
+        target.RenderNodeGraph.AddNode(end);
 
         ignoreInUndo = false;
 
@@ -76,7 +76,7 @@ internal class CreateNodePair_Change : Change
     private static DeleteNode_ChangeInfo RemoveNode(Document target, Guid id)
     {
         Node node = target.FindNodeOrThrow<Node>(id);
-        target.NodeGraph.RemoveNode(node);
+        target.RenderNodeGraph.RemoveNode(node);
 
         return new DeleteNode_ChangeInfo(id);
     }

+ 3 - 3
src/PixiEditor.ChangeableDocument/Changes/NodeGraph/CreateNode_Change.cs

@@ -26,7 +26,7 @@ internal class CreateNode_Change : Change
     public override bool InitializeAndValidate(Document target)
     {
         bool canCreate = nodeType.IsSubclassOf(typeof(Node)) && nodeType is { IsAbstract: false, IsInterface: false };
-        return canCreate && (!nodeType.IsAssignableTo(typeof(OutputNode)) || target.NodeGraph.OutputNode is null);
+        return canCreate && (!nodeType.IsAssignableTo(typeof(OutputNode)) || target.RenderNodeGraph.OutputNode is null);
     }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply,
@@ -40,7 +40,7 @@ internal class CreateNode_Change : Change
         node.Position = new VecD(0, 0);
         node.Id = id;
 
-        target.NodeGraph.AddNode(node);
+        target.RenderNodeGraph.AddNode(node);
         ignoreInUndo = false;
 
         return CreateNode_ChangeInfo.CreateFromNode(node);
@@ -49,7 +49,7 @@ internal class CreateNode_Change : Change
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
     {
         Node node = target.FindNodeOrThrow<Node>(id);
-        target.NodeGraph.RemoveNode(node);
+        target.RenderNodeGraph.RemoveNode(node);
 
         return new DeleteNode_ChangeInfo(id);
     }

+ 5 - 5
src/PixiEditor.ChangeableDocument/Changes/NodeGraph/DeleteNode_Change.cs

@@ -27,7 +27,7 @@ internal class DeleteNode_Change : Change
     {
         Node node = target.FindNode<Node>(NodeId);
 
-        if (node is null || target.NodeGraph.OutputNode == node)
+        if (node is null || target.RenderNodeGraph.OutputNode == node)
             return false;
 
         originalConnections = NodeOperations.CreateConnectionsData(node);
@@ -57,9 +57,9 @@ internal class DeleteNode_Change : Change
         ignoreInUndo = false;
         var node = target.FindNode<Node>(NodeId);
 
-        List<IChangeInfo> changes = NodeOperations.DetachNode(target.NodeGraph, node);
+        List<IChangeInfo> changes = NodeOperations.DetachNode(target.RenderNodeGraph, node);
 
-        target.NodeGraph.RemoveNode(node);
+        target.RenderNodeGraph.RemoveNode(node);
 
         changes.Add(new DeleteNode_ChangeInfo(NodeId));
 
@@ -84,7 +84,7 @@ internal class DeleteNode_Change : Change
             pairNode.OtherNode = (savedCopy as IPairNode).OtherNode;
         }
 
-        doc.NodeGraph.AddNode(copy);
+        doc.RenderNodeGraph.AddNode(copy);
 
         List<IChangeInfo> changes = new();
 
@@ -93,7 +93,7 @@ internal class DeleteNode_Change : Change
         changes.Add(createChange);
 
         changes.AddRange(NodeOperations.CreateUpdateInputs(copy));
-        changes.AddRange(NodeOperations.ConnectStructureNodeProperties(originalConnections, copy, doc.NodeGraph));
+        changes.AddRange(NodeOperations.ConnectStructureNodeProperties(originalConnections, copy, doc.RenderNodeGraph));
         changes.Add(new NodePosition_ChangeInfo(copy.Id, copy.Position));
 
         RevertKeyFrames(doc, savedKeyFrameGroup, changes);

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changes/NodeGraph/DuplicateNode_Change.cs

@@ -28,7 +28,7 @@ internal class DuplicateNode_Change : Change
         Node clone = existingNode.Clone();
         clone.Id = createdNodeGuid;
 
-        target.NodeGraph.AddNode(clone);
+        target.RenderNodeGraph.AddNode(clone);
 
         ignoreInUndo = false;
 
@@ -38,7 +38,7 @@ internal class DuplicateNode_Change : Change
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
     {
         var node = target.FindNode(createdNodeGuid);
-        target.NodeGraph.RemoveNode(node);
+        target.RenderNodeGraph.RemoveNode(node);
         
         node.Dispose();
 

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/NodeGraph/NodeOperations.cs

@@ -201,7 +201,7 @@ public static class NodeOperations
         return changes;
     }
 
-    public static List<IChangeInfo> DetachNode(Changeables.Graph.NodeGraph target, Node? node)
+    public static List<IChangeInfo> DetachNode(Changeables.Graph.RenderNodeGraph target, Node? node)
     {
         List<IChangeInfo> changes = new();
         if (node == null)

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changes/NodeGraph/UpdateProperty_Change.cs

@@ -32,7 +32,7 @@ internal class UpdatePropertyValue_Change : Change
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply,
         out bool ignoreInUndo)
     {
-        var node = target.NodeGraph.Nodes.First(x => x.Id == _nodeId);
+        var node = target.RenderNodeGraph.Nodes.First(x => x.Id == _nodeId);
         var property = node.GetInputProperty(_propertyName);
 
         previousValue = GetValue(property);
@@ -57,7 +57,7 @@ internal class UpdatePropertyValue_Change : Change
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
     {
-        var node = target.NodeGraph.Nodes.First(x => x.Id == _nodeId);
+        var node = target.RenderNodeGraph.Nodes.First(x => x.Id == _nodeId);
         var property = node.GetInputProperty(_propertyName);
         SetValue(property, previousValue);
 

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Properties/ChangeProcessingColorSpace_Change.cs

@@ -44,7 +44,7 @@ internal class ChangeProcessingColorSpace_Change : Change
 
     private void ConvertImageNodes(Document target, ColorSpace newColorSpace)
     {
-        foreach (var node in target.NodeGraph.Nodes)
+        foreach (var node in target.RenderNodeGraph.Nodes)
         {
             if (node is ImageLayerNode imageLayerNode)
             {

+ 3 - 3
src/PixiEditor.ChangeableDocument/Changes/Structure/CreateStructureMember_Change.cs

@@ -48,12 +48,12 @@ internal class CreateStructureMember_Change : Change
         
         if (member is FolderNode folder)
         {
-            document.NodeGraph.AddNode(member);
+            document.RenderNodeGraph.AddNode(member);
             AppendFolder(targetInput, folder, changes);
         }
         else
         {
-            document.NodeGraph.AddNode(member);
+            document.RenderNodeGraph.AddNode(member);
             List<ConnectProperty_ChangeInfo> connectPropertyChangeInfo =
                 NodeOperations.AppendMember(targetInput, member.Output, member.Background, member.Id);
             changes.AddRange(connectPropertyChangeInfo);
@@ -86,7 +86,7 @@ internal class CreateStructureMember_Change : Change
         var childBackgroundConnection = child.Background.Connection;
         child.Dispose();
 
-        document.NodeGraph.RemoveNode(child);
+        document.RenderNodeGraph.RemoveNode(child);
 
         List<IChangeInfo> changes = new() { new DeleteStructureMember_ChangeInfo(newMemberGuid), };
 

+ 3 - 3
src/PixiEditor.ChangeableDocument/Changes/Structure/DeleteStructureMember_Change.cs

@@ -47,7 +47,7 @@ internal class DeleteStructureMember_Change : Change
         var bgConnection = node.Background.Connection;
         var outputConnections = node.Output.Connections.ToArray();
 
-        document.NodeGraph.RemoveNode(node);
+        document.RenderNodeGraph.RemoveNode(node);
 
         List<IChangeInfo> changes = new();
 
@@ -81,7 +81,7 @@ internal class DeleteStructureMember_Change : Change
         var copy = (StructureNode)savedCopy!.Clone();
         copy.Id = memberGuid;
 
-        doc.NodeGraph.AddNode(copy);
+        doc.RenderNodeGraph.AddNode(copy);
 
         List<IChangeInfo> changes = new();
 
@@ -94,7 +94,7 @@ internal class DeleteStructureMember_Change : Change
 
         changes.Add(createChange);
 
-        changes.AddRange(NodeOperations.ConnectStructureNodeProperties(originalConnections, copy, doc.NodeGraph));
+        changes.AddRange(NodeOperations.ConnectStructureNodeProperties(originalConnections, copy, doc.RenderNodeGraph));
         
         DeleteNode_Change.RevertKeyFrames(doc, savedKeyFrameGroup, changes);
 

+ 3 - 3
src/PixiEditor.ChangeableDocument/Changes/Structure/DuplicateLayer_Change.cs

@@ -45,7 +45,7 @@ internal class DuplicateLayer_Change : Change
 
         List<IChangeInfo> operations = new();
 
-        target.NodeGraph.AddNode(clone);
+        target.RenderNodeGraph.AddNode(clone);
 
         operations.Add(CreateLayer_ChangeInfo.FromLayer(clone));
         
@@ -60,7 +60,7 @@ internal class DuplicateLayer_Change : Change
     {
         var (member, parent) = target.FindChildAndParentOrThrow(duplicateGuid);
 
-        target.NodeGraph.RemoveNode(member);
+        target.RenderNodeGraph.RemoveNode(member);
         member.Dispose();
 
         List<IChangeInfo> changes = new();
@@ -71,7 +71,7 @@ internal class DuplicateLayer_Change : Change
         if (connectionsData is not null)
         {
             Node originalNode = target.FindNodeOrThrow<Node>(layerGuid);
-            changes.AddRange(NodeOperations.ConnectStructureNodeProperties(connectionsData, originalNode, target.NodeGraph));
+            changes.AddRange(NodeOperations.ConnectStructureNodeProperties(connectionsData, originalNode, target.RenderNodeGraph));
         }
         
         return changes;

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Structure/MoveStructureMember_Change.cs

@@ -86,7 +86,7 @@ internal class MoveStructureMember_Change : Change
         MoveStructureMember_ChangeInfo changeInfo = new(memberGuid, targetNodeGuid, originalFolderGuid);
         
         changes.AddRange(NodeOperations.DetachStructureNode(member));
-        changes.AddRange(NodeOperations.ConnectStructureNodeProperties(originalConnections, member, target.NodeGraph));
+        changes.AddRange(NodeOperations.ConnectStructureNodeProperties(originalConnections, member, target.RenderNodeGraph));
         
         changes.Add(changeInfo);
         

+ 7 - 7
src/PixiEditor.ChangeableDocument/Changes/Structure/RasterizeMember_Change.cs

@@ -47,7 +47,7 @@ internal class RasterizeMember_Change : Change
         ImageLayerNode imageLayer = new ImageLayerNode(target.Size, target.ProcessingColorSpace);
         imageLayer.MemberName = node.DisplayName;
 
-        target.NodeGraph.AddNode(imageLayer);
+        target.RenderNodeGraph.AddNode(imageLayer);
         
         using Surface surface = new Surface(target.Size);
         rasterizable.Rasterize(surface.DrawingSurface, null);
@@ -85,10 +85,10 @@ internal class RasterizeMember_Change : Change
             changeInfos.Add(new ConnectProperty_ChangeInfo(conn.connection.Node.Id, imageLayer.Id, conn.connection.InternalPropertyName, conn.inputPropName));
         }
         
-        changeInfos.AddRange(NodeOperations.DetachNode(target.NodeGraph, node));
+        changeInfos.AddRange(NodeOperations.DetachNode(target.RenderNodeGraph, node));
         
         node.Dispose();
-        target.NodeGraph.RemoveNode(node);
+        target.RenderNodeGraph.RemoveNode(node);
         
         changeInfos.Add(new DeleteNode_ChangeInfo(node.Id));
         
@@ -103,21 +103,21 @@ internal class RasterizeMember_Change : Change
         Node node = target.FindMember(createdNodeId);
         
         List<IChangeInfo> changeInfos = new();
-        changeInfos.AddRange(NodeOperations.DetachNode(target.NodeGraph, node));
+        changeInfos.AddRange(NodeOperations.DetachNode(target.RenderNodeGraph, node));
         
         node.Dispose();
-        target.NodeGraph.RemoveNode(node);
+        target.RenderNodeGraph.RemoveNode(node);
         
         changeInfos.Add(new DeleteNode_ChangeInfo(node.Id));
         
         var restoredNode = originalNode.Clone();
         restoredNode.Id = memberId;
         
-        target.NodeGraph.AddNode(restoredNode);
+        target.RenderNodeGraph.AddNode(restoredNode);
         
         changeInfos.Add(CreateNode_ChangeInfo.CreateFromNode(restoredNode));
         
-        changeInfos.AddRange(NodeOperations.ConnectStructureNodeProperties(originalConnections, restoredNode, target.NodeGraph));
+        changeInfos.AddRange(NodeOperations.ConnectStructureNodeProperties(originalConnections, restoredNode, target.RenderNodeGraph));
         
         return changeInfos;   
     }

+ 62 - 32
src/PixiEditor.ChangeableDocument/Rendering/DocumentRenderer.cs

@@ -12,15 +12,13 @@ using Drawie.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Rendering;
 
-public class DocumentRenderer : IPreviewRenderable
+public class DocumentRenderer/* : IPreviewRenderable*/
 {
-    private Paint ClearPaint { get; } = new Paint()
-    {
-        BlendMode = BlendMode.Src, Color = Drawie.Backend.Core.ColorsImpl.Colors.Transparent
-    };
-
+    Dictionary<Guid, List<PreviewRequest>> queuedPreviewSizes = new();
     private Texture renderTexture;
     
+    public Dictionary<Guid, List<PreviewRequest>> QueuedPreviewSizes => queuedPreviewSizes;
+    
     public DocumentRenderer(IReadOnlyDocument document)
     {
         Document = document;
@@ -84,6 +82,21 @@ public class DocumentRenderer : IPreviewRenderable
         node.RenderForOutput(context, renderOn, null);
         IsBusy = false;
     }
+
+    public int QueueRenderPreview(VecI sizeToRequest, Guid nodeId, string elementName, KeyFrameTime frame,
+        Action onRendered)
+    {
+        if (!queuedPreviewSizes.ContainsKey(nodeId))
+        {
+            queuedPreviewSizes[nodeId] = new List<PreviewRequest>();
+        }
+        
+        PreviewRequest request = new(queuedPreviewSizes.Count, sizeToRequest, frame, nodeId, elementName, onRendered);
+        
+        TryMergeRequests(request);
+        
+        return request.Id;
+    }
     
     public void RenderNodePreview(IPreviewRenderable previewRenderable, DrawingSurface renderOn, RenderContext context, string elementToRenderName)
     {
@@ -94,9 +107,11 @@ public class DocumentRenderer : IPreviewRenderable
         
         IsBusy = true;
         
-        if(previewRenderable is Node { IsDisposed: true }) return;
+        /*if(previewRenderable is Node { IsDisposed: true }) return;
+        
+        previewRenderable.RenderPreview(renderOn, context, elementToRenderName);*/
+        
         
-        previewRenderable.RenderPreview(renderOn, context, elementToRenderName);
         
         IsBusy = false;
     }
@@ -110,7 +125,7 @@ public class DocumentRenderer : IPreviewRenderable
         HashSet<Guid>? membersToCombine,
         IReadOnlyNodeGraph fullGraph)
     {
-        NodeGraph membersOnlyGraph = new();
+        RenderNodeGraph membersOnlyGraph = new();
 
         OutputNode outputNode = new();
 
@@ -157,28 +172,6 @@ public class DocumentRenderer : IPreviewRenderable
     public RectD? GetPreviewBounds(int frame, string elementNameToRender = "") =>
         new(0, 0, Document.Size.X, Document.Size.Y);
 
-    public bool RenderPreview(DrawingSurface renderOn, RenderContext context,
-        string elementToRenderName)
-    {
-        IsBusy = true;
-
-        if (renderTexture == null || renderTexture.Size != Document.Size)
-        {
-            renderTexture?.Dispose();
-            renderTexture = Texture.ForProcessing(Document.Size, Document.ProcessingColorSpace);
-        }
-
-        renderTexture.DrawingSurface.Canvas.Clear();
-        context.RenderSurface = renderTexture.DrawingSurface;
-        Document.NodeGraph.Execute(context);
-
-        renderOn.Canvas.DrawSurface(renderTexture.DrawingSurface, 0, 0);
-
-        IsBusy = false;
-
-        return true;
-    }
-
     public void RenderDocument(DrawingSurface toRenderOn, KeyFrameTime frameTime)
     {
         IsBusy = true;
@@ -201,7 +194,7 @@ public class DocumentRenderer : IPreviewRenderable
     
     private static IInputProperty GetTargetInput(IInputProperty? input, 
         IReadOnlyNodeGraph sourceGraph,
-        NodeGraph membersOnlyGraph,
+        RenderNodeGraph membersOnlyGraph,
         Dictionary<Guid, Guid> nodeMapping)
     {
         if (input == null)
@@ -237,4 +230,41 @@ public class DocumentRenderer : IPreviewRenderable
         
         return found ?? (membersOnlyGraph.OutputNode as IRenderInput)?.Background;
     }
+    
+    private void TryMergeRequests(PreviewRequest request)
+    {
+        if (queuedPreviewSizes[request.NodeId].Count == 0)
+        {
+            queuedPreviewSizes[request.NodeId].Add(request);
+            return;
+        }
+
+        for (var i = 0; i < queuedPreviewSizes[request.NodeId].Count; i++)
+        {
+            var queuedRequest = queuedPreviewSizes[request.NodeId][i];
+            bool targetMatches = queuedRequest.ElementName == request.ElementName &&
+                                 queuedRequest.Frame.Frame == request.Frame.Frame;
+
+            VecI targetSize = new(Math.Max(queuedRequest.Size.X, request.Size.X),
+                Math.Max(queuedRequest.Size.Y, request.Size.Y));
+            if (targetMatches)
+            {
+                queuedPreviewSizes[request.NodeId][i] = new PreviewRequest(request.Id, targetSize, request.Frame, request.NodeId,
+                    request.ElementName, () =>
+                    {
+                        queuedRequest.OnRendered?.Invoke();
+                        request.OnRendered?.Invoke();
+                    });
+                return;
+            }
+        }
+    }
+
+    public void NotifyPreviewRendered()
+    {
+        foreach (var request in queuedPreviewSizes.Values.SelectMany(x => x))
+        {
+            request.OnRendered?.Invoke();
+        }
+    }
 }

+ 24 - 0
src/PixiEditor.ChangeableDocument/Rendering/PreviewRequest.cs

@@ -0,0 +1,24 @@
+using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Changeables.Animations;
+
+namespace PixiEditor.ChangeableDocument.Rendering;
+
+public struct PreviewRequest
+{
+    public VecI Size { get; }
+    public KeyFrameTime Frame { get; }
+    public Guid NodeId { get; }
+    public string ElementName { get; }
+    public int Id { get; set; }
+    public Action OnRendered { get; set; }
+
+    public PreviewRequest(int id, VecI size, KeyFrameTime frame, Guid nodeId, string elementName, Action onRendered)
+    {
+        Id = id;
+        Size = size;
+        Frame = frame;
+        NodeId = nodeId;
+        ElementName = elementName;
+        OnRendered = onRendered;
+    }
+}

+ 2 - 1
src/PixiEditor.ChangeableDocument/Rendering/RenderContext.cs

@@ -19,7 +19,8 @@ public class RenderContext
     public bool FullRerender { get; set; } = false;
     
     public ColorSpace ProcessingColorSpace { get; set; }
-    public string? TargetOutput { get; set; }   
+    public string? TargetOutput { get; set; }
+    public Dictionary<Guid, List<PreviewRequest>>? PendingPreviewRequests { get; set; }
 
 
     public RenderContext(DrawingSurface renderSurface, KeyFrameTime frameTime, ChunkResolution chunkResolution,

+ 1 - 1
src/PixiEditor/Models/Rendering/AnimationPreviewRenderer.cs

@@ -8,7 +8,7 @@ using PixiEditor.ChangeableDocument.Rendering;
 
 namespace PixiEditor.Models.Rendering;
 
-internal class AnimationKeyFramePreviewRenderer(DocumentInternalParts internals) : IPreviewRenderable
+internal class AnimationKeyFramePreviewRenderer(DocumentInternalParts internals)/* : IPreviewRenderable*/
 {
     public RectD? GetPreviewBounds(int frame, string elementToRenderName = "")
     {

+ 16 - 17
src/PixiEditor/Models/Rendering/MemberPreviewUpdater.cs

@@ -52,17 +52,16 @@ internal class MemberPreviewUpdater
         Guid[] nodesGuids = nodesToUpdate as Guid[] ?? nodesToUpdate.ToArray();
         Guid[] keyFramesGuids = keyFramesToUpdate as Guid[] ?? keyFramesToUpdate.ToArray();
 
-        //TODO: It probably is a good idea to add this check for insignificant previews, like document tab icon
-        // But navigation uses the same preview painter so it must be separated before this check is added
-        //if (undoBoundaryPassed)
+        if (!undoBoundaryPassed)
         {
-            RenderWholeCanvasPreview();
+            return;
         }
-        
+
+        //RenderWholeCanvasPreview();
         RenderLayersPreview(memberGuids);
-        RenderMaskPreviews(maskGuids);
-        
-        RenderAnimationPreviews(memberGuids, keyFramesGuids);
+        //RenderMaskPreviews(maskGuids);
+
+        //RenderAnimationPreviews(memberGuids, keyFramesGuids);
 
         RenderNodePreviews(nodesGuids);
     }
@@ -75,7 +74,7 @@ internal class MemberPreviewUpdater
         var previewSize = StructureHelpers.CalculatePreviewSize(internals.Tracker.Document.Size);
         //float scaling = (float)previewSize.X / doc.SizeBindable.X;
 
-        if (doc.PreviewPainter == null)
+        /*if (doc.PreviewPainter == null)
         {
             doc.PreviewPainter = new PreviewPainter(doc.Renderer, doc.Renderer, doc.AnimationHandler.ActiveFrameTime,
                 doc.SizeBindable, internals.Tracker.Document.ProcessingColorSpace);
@@ -84,7 +83,7 @@ internal class MemberPreviewUpdater
         doc.PreviewPainter.DocumentSize = doc.SizeBindable;
         doc.PreviewPainter.ProcessingColorSpace = internals.Tracker.Document.ProcessingColorSpace;
         doc.PreviewPainter.FrameTime = doc.AnimationHandler.ActiveFrameTime;
-        doc.PreviewPainter.Repaint();
+        doc.PreviewPainter.Repaint();*/
     }
 
     private void RenderLayersPreview(Guid[] memberGuids)
@@ -156,7 +155,7 @@ internal class MemberPreviewUpdater
     {
         if (internals.Tracker.Document.AnimationData.TryFindKeyFrame(cel.Id, out KeyFrame _))
         {
-            KeyFrameTime frameTime = doc.AnimationHandler.ActiveFrameTime;
+            /*KeyFrameTime frameTime = doc.AnimationHandler.ActiveFrameTime;
             if (cel.PreviewPainter == null)
             {
                 cel.PreviewPainter = new PreviewPainter(doc.Renderer, AnimationKeyFramePreviewRenderer, frameTime,
@@ -170,14 +169,14 @@ internal class MemberPreviewUpdater
                 cel.PreviewPainter.ProcessingColorSpace = internals.Tracker.Document.ProcessingColorSpace;
             }
 
-            cel.PreviewPainter.Repaint();
+            cel.PreviewPainter.Repaint();*/
         }
     }
 
     private void RenderGroupPreview(ICelGroupHandler groupHandler)
     {
         var group = internals.Tracker.Document.AnimationData.KeyFrames.FirstOrDefault(x => x.Id == groupHandler.Id);
-        if (group != null)
+        /*if (group != null)
         {
             KeyFrameTime frameTime = doc.AnimationHandler.ActiveFrameTime;
             ColorSpace processingColorSpace = internals.Tracker.Document.ProcessingColorSpace;
@@ -198,7 +197,7 @@ internal class MemberPreviewUpdater
             }
 
             groupHandler.PreviewPainter.Repaint();
-        }
+        }*/
     }
 
     private void RenderMaskPreviews(Guid[] members)
@@ -247,10 +246,10 @@ internal class MemberPreviewUpdater
         var executionQueue =
             internals.Tracker.Document.NodeGraph
                 .AllNodes; //internals.Tracker.Document.NodeGraph.CalculateExecutionQueue(outputNode);
-        
-        if(nodesGuids.Length == 0)
+
+        if (nodesGuids.Length == 0)
             return;
-        
+
         foreach (var node in executionQueue)
         {
             if (node is null)

+ 51 - 10
src/PixiEditor/Models/Rendering/PreviewPainter.cs

@@ -20,10 +20,13 @@ public class PreviewPainter
     public KeyFrameTime FrameTime { get; set; }
     public VecI DocumentSize { get; set; }
     public DocumentRenderer Renderer { get; set; }
-    
-    private Texture renderTexture;
-    
-    public PreviewPainter(DocumentRenderer renderer, IPreviewRenderable previewRenderable, KeyFrameTime frameTime, VecI documentSize, ColorSpace processingColorSpace, string elementToRenderName = "")
+    public VecI SizeToRequest { get; set; }
+
+    private int renderRequestId;
+    //private Texture renderTexture;
+
+    public PreviewPainter(DocumentRenderer renderer, IPreviewRenderable previewRenderable, KeyFrameTime frameTime,
+        VecI documentSize, ColorSpace processingColorSpace, string elementToRenderName = "")
     {
         PreviewRenderable = previewRenderable;
         ElementToRenderName = elementToRenderName;
@@ -35,32 +38,70 @@ public class PreviewPainter
 
     public void Paint(DrawingSurface renderOn, VecI boundsSize, Matrix3X3 matrix)
     {
-        if (PreviewRenderable == null)
+        if (PreviewRenderable?.LastRenderedPreviews == null)
         {
             return;
         }
 
-        if (renderTexture == null || renderTexture.Size != boundsSize)
+        if (PreviewRenderable.LastRenderedPreviews.TryGetValue(renderRequestId, out Image preview))
+        {
+            renderOn.Canvas.Clear();
+            int saved = renderOn.Canvas.Save();
+            
+            Matrix3X3 targetMatrix = ScaleToFitUniform(new RectD(0, 0, preview.Size.X, preview.Size.Y), SizeToRequest);
+
+            renderOn.Canvas.SetMatrix(targetMatrix);
+
+            renderOn.Canvas.DrawImage(preview, 0, 0);
+
+            renderOn.Canvas.RestoreToCount(saved);
+        }
+
+        /*if (renderTexture == null || renderTexture.Size != boundsSize)
         {
             renderTexture?.Dispose();
             renderTexture = Texture.ForProcessing(boundsSize, ProcessingColorSpace);
         }
-        
+
         renderTexture.DrawingSurface.Canvas.Clear();
         renderTexture.DrawingSurface.Canvas.Save();
 
         renderTexture.DrawingSurface.Canvas.SetMatrix(matrix);
-        
+
         RenderContext context = new(renderTexture.DrawingSurface, FrameTime, ChunkResolution.Full, DocumentSize, ProcessingColorSpace);
 
         Renderer.RenderNodePreview(PreviewRenderable, renderTexture.DrawingSurface, context, ElementToRenderName);
         renderTexture.DrawingSurface.Canvas.Restore();
-        
-        renderOn.Canvas.DrawSurface(renderTexture.DrawingSurface, 0, 0);
+
+        renderOn.Canvas.DrawSurface(renderTexture.DrawingSurface, 0, 0);*/
     }
 
     public void Repaint()
+    {
+        if (SizeToRequest.X == 0 || SizeToRequest.Y == 0)
+        {
+            return;
+        }
+
+        renderRequestId = Renderer.QueueRenderPreview(SizeToRequest, PreviewRenderable.RenderableId,
+            ElementToRenderName, FrameTime, OnRendered);
+    }
+
+    private void OnRendered()
     {
         RequestRepaint?.Invoke();
     }
+
+    private Matrix3X3 ScaleToFitUniform(RectD bounds, VecI size)
+    {
+        float scaleX = (float)size.X / (float)bounds.Width;
+        float scaleY = (float)size.Y / (float)bounds.Height;
+        float scale = Math.Min(scaleX, scaleY);
+        float dX = (float)size.X / 2 / scale - (float)bounds.Width / 2;
+        dX -= (float)bounds.X;
+        float dY = (float)size.Y / 2 / scale - (float)bounds.Height / 2;
+        dY -= (float)bounds.Y;
+        Matrix3X3 matrix = Matrix3X3.CreateScale(scale, scale);
+        return matrix.Concat(Matrix3X3.CreateTranslation(dX, dY));
+    }
 }

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

@@ -50,12 +50,21 @@ internal class SceneRenderer : IDisposable
         RenderContext context = new(renderTarget, DocumentViewModel.AnimationHandler.ActiveFrameTime,
             resolution, Document.Size, Document.ProcessingColorSpace);
         context.TargetOutput = targetOutput;
+
+        if (targetOutput == null)
+        {
+            context.PendingPreviewRequests = DocumentViewModel.Renderer.QueuedPreviewSizes;
+        }
+        
         SolveFinalNodeGraph(context.TargetOutput).Execute(context);
 
         if (renderTexture != null)
         {
             target.Canvas.DrawSurface(renderTexture.DrawingSurface, 0, 0);
         }
+        
+        DocumentViewModel.Renderer.NotifyPreviewRendered();
+        DocumentViewModel.Renderer.QueuedPreviewSizes.Clear();
     }
 
     private IReadOnlyNodeGraph SolveFinalNodeGraph(string? targetOutput)
@@ -80,7 +89,7 @@ internal class SceneRenderer : IDisposable
 
     private IReadOnlyNodeGraph GraphFromOutputNode(CustomOutputNode outputNode)
     {
-        NodeGraph graph = new();
+        RenderNodeGraph graph = new();
         outputNode.TraverseBackwards(n =>
         {
             if (n is Node node)

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

@@ -32,6 +32,7 @@ public class PreviewPainterControl : DrawieControl
     public PreviewPainterControl()
     {
         PreviewPainterProperty.Changed.Subscribe(PainterChanged);
+        BoundsProperty.Changed.Subscribe(UpdatePainterBounds);
     }
 
     public PreviewPainterControl(PreviewPainter previewPainter, int frameToRender)
@@ -98,4 +99,14 @@ public class PreviewPainterControl : DrawieControl
         Matrix3X3 matrix = Matrix3X3.CreateScale(scale, scale);
         return matrix.Concat(Matrix3X3.CreateTranslation(dX, dY));
     }
+    
+    private void UpdatePainterBounds(AvaloniaPropertyChangedEventArgs<Rect> args)
+    {
+        if (PreviewPainter == null)
+        {
+            return;
+        }
+
+        PreviewPainter.SizeToRequest = new VecI((int)Bounds.Size.Width, (int)Bounds.Size.Height);
+    }
 }