Explorar o código

Async rendering progress

Krzysztof Krysiński hai 3 semanas
pai
achega
518820e65d

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

@@ -4,7 +4,7 @@ using PixiEditor.Common;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 
-public interface IReadOnlyNodeGraph : ICacheable
+public interface IReadOnlyNodeGraph : ICacheable, IDisposable
 {
     public IReadOnlyCollection<IReadOnlyNode> AllNodes { get; }
     public IReadOnlyNode OutputNode { get; }
@@ -13,4 +13,5 @@ public interface IReadOnlyNodeGraph : ICacheable
     public bool TryTraverse(Action<IReadOnlyNode> action);
     public void Execute(RenderContext context);
     Queue<IReadOnlyNode> CalculateExecutionQueue(IReadOnlyNode endNode);
+    public IReadOnlyNodeGraph Clone();
 }

+ 50 - 2
src/PixiEditor.ChangeableDocument/Changeables/Graph/NodeGraph.cs

@@ -5,7 +5,7 @@ using PixiEditor.ChangeableDocument.Rendering;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph;
 
-public class NodeGraph : IReadOnlyNodeGraph, IDisposable
+public class NodeGraph : IReadOnlyNodeGraph
 {
     private ImmutableList<IReadOnlyNode>? cachedExecutionList;
     
@@ -61,7 +61,51 @@ public class NodeGraph : IReadOnlyNodeGraph, IDisposable
     {
         return new Queue<IReadOnlyNode>(CalculateExecutionQueueInternal(outputNode));
     }
-    
+
+    public IReadOnlyNodeGraph Clone()
+    {
+        var newGraph = new NodeGraph();
+        var nodeMapping = new Dictionary<Node, Node>();
+
+        // Clone nodes
+        foreach (var node in Nodes)
+        {
+            var clonedNode = node.Clone(true);
+            newGraph.AddNode(clonedNode);
+            nodeMapping[node] = clonedNode;
+        }
+
+        // Re-establish connections
+        foreach (var node in Nodes)
+        {
+            var clonedNode = nodeMapping[node];
+            foreach (var input in node.InputProperties)
+            {
+                if (input.Connection != null)
+                {
+                    var connectedNode = input.Connection.Node;
+                    if (nodeMapping.TryGetValue(connectedNode as Node, out var clonedConnectedNode))
+                    {
+                        var clonedOutput = clonedConnectedNode.OutputProperties.FirstOrDefault(o => o.InternalPropertyName == input.Connection.InternalPropertyName);
+                        var clonedInput = clonedNode.InputProperties.FirstOrDefault(i => i.InternalPropertyName == input.InternalPropertyName);
+                        if (clonedOutput != null && clonedInput != null)
+                        {
+                            clonedOutput.ConnectTo(clonedInput);
+                        }
+                    }
+                }
+            }
+        }
+
+        // Set custom output node if applicable
+        if (CustomOutputNode != null && nodeMapping.TryGetValue(CustomOutputNode, out var mappedOutputNode))
+        {
+            newGraph.CustomOutputNode = mappedOutputNode;
+        }
+
+        return newGraph;
+    }
+
     private ImmutableList<IReadOnlyNode> CalculateExecutionQueueInternal(IReadOnlyNode outputNode)
     {
         return cachedExecutionList ??= GraphUtils.CalculateExecutionQueue(outputNode).ToImmutableList();
@@ -93,8 +137,11 @@ public class NodeGraph : IReadOnlyNodeGraph, IDisposable
         return true;
     }
 
+    bool isexecuting = false;
     public void Execute(RenderContext context)
     {
+        if (isexecuting) return;
+        isexecuting = true;
         if (OutputNode == null) return;
         if(!CanExecute()) return;
 
@@ -113,6 +160,7 @@ public class NodeGraph : IReadOnlyNodeGraph, IDisposable
                 node.Execute(context);
             }
         }
+        isexecuting = false;
     }
     
     private bool CanExecute()

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

@@ -47,7 +47,7 @@ public abstract class Node : IReadOnlyNode, IDisposable
 
     private VecI lastRenderSize = new VecI(0, 0);
 
-    protected internal bool IsDisposed => _isDisposed;
+    public bool IsDisposed => _isDisposed;
     private bool _isDisposed;
 
     private int lastContentCacheHash = -1;

+ 4 - 63
src/PixiEditor.ChangeableDocument/Rendering/DocumentRenderer.cs

@@ -19,9 +19,7 @@ namespace PixiEditor.ChangeableDocument.Rendering;
 
 public class DocumentRenderer : IPreviewRenderable, IDisposable
 {
-    private Queue<RenderRequest> renderRequests = new();
     private Texture renderTexture;
-    private int lastExecutedGraphFrame = -1;
 
     public DocumentRenderer(IReadOnlyDocument document)
     {
@@ -31,7 +29,6 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
     private IReadOnlyDocument Document { get; }
     public bool IsBusy { get; private set; }
 
-    private bool isExecuting = false;
 
     public void UpdateChunk(VecI chunkPos, ChunkResolution resolution, KeyFrameTime frameTime)
     {
@@ -126,20 +123,6 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
         IsBusy = false;
     }
 
-    public async Task<bool> RenderNodePreview(IPreviewRenderable previewRenderable, DrawingSurface renderOn,
-        RenderContext context,
-        string elementToRenderName)
-    {
-        if (previewRenderable is Node { IsDisposed: true }) return false;
-        TaskCompletionSource<bool> tcs = new();
-        RenderRequest request = new(tcs, context, renderOn, previewRenderable, elementToRenderName);
-
-        renderRequests.Enqueue(request);
-        ExecuteRenderRequests(context.FrameTime);
-
-        return await tcs.Task;
-    }
-
     public static IReadOnlyNodeGraph ConstructMembersOnlyGraph(IReadOnlyNodeGraph fullGraph)
     {
         return ConstructMembersOnlyGraph(null, fullGraph);
@@ -201,13 +184,14 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
     {
         IsBusy = true;
 
+        /*
         renderOn.Canvas.Clear();
         int savedCount = renderOn.Canvas.Save();
         renderOn.Canvas.Scale((float)context.ChunkResolution.Multiplier());
         context.RenderSurface = renderOn;
         Document.NodeGraph.Execute(context);
-        lastExecutedGraphFrame = context.FrameTime.Frame;
         renderOn.Canvas.RestoreToCount(savedCount);
+        */
 
         IsBusy = false;
 
@@ -270,52 +254,9 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
         renderTexture.DrawingSurface.Canvas.Restore();
         toRenderOn.Canvas.Restore();
 
-        lastExecutedGraphFrame = frameTime.Frame;
-
         IsBusy = false;
     }
 
-    private void ExecuteRenderRequests(KeyFrameTime frameTime)
-    {
-        if (isExecuting) return;
-
-        isExecuting = true;
-        using var ctx = DrawingBackendApi.Current?.RenderingDispatcher.EnsureContext();
-
-        while (renderRequests.Count > 0)
-        {
-            RenderRequest request = renderRequests.Dequeue();
-
-            if (frameTime.Frame != lastExecutedGraphFrame && request.PreviewRenderable != this)
-            {
-                using Texture executeSurface = Texture.ForDisplay(new VecI(1));
-                RenderDocument(executeSurface.DrawingSurface, frameTime, VecI.One);
-            }
-
-            try
-            {
-                bool result = true;
-                if (request.PreviewRenderable != null)
-                {
-                    result = request.PreviewRenderable.RenderPreview(request.RenderOn, request.Context,
-                        request.ElementToRenderName);
-                }
-                else if (request.NodeGraph != null)
-                {
-                    request.NodeGraph.Execute(request.Context);
-                }
-
-                request.TaskCompletionSource.SetResult(result);
-            }
-            catch (Exception e)
-            {
-                request.TaskCompletionSource.SetException(e);
-            }
-        }
-
-        isExecuting = false;
-    }
-
     private static IInputProperty GetTargetInput(IInputProperty? input,
         IReadOnlyNodeGraph sourceGraph,
         NodeGraph membersOnlyGraph,
@@ -384,12 +325,12 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
         renderTexture?.Dispose();
         renderTexture = null;
 
-        foreach (var request in renderRequests)
+        /*foreach (var request in renderRequests)
         {
             if (request.TaskCompletionSource == null) continue;
 
             request.TaskCompletionSource.TrySetCanceled();
-        }
+        }*/
     }
 }
 

+ 2 - 2
src/PixiEditor/Models/DocumentModels/ActionAccumulator.cs

@@ -84,7 +84,7 @@ internal class ActionAccumulator
         TryExecuteAccumulatedActions();
     }
 
-    internal void TryExecuteAccumulatedActions()
+    internal async Task TryExecuteAccumulatedActions()
     {
         if (executing || queuedActions.Count == 0)
             return;
@@ -112,7 +112,7 @@ internal class ActionAccumulator
                 }
                 else
                 {
-                    changes = internals.Tracker.ProcessActionsSync(toExecute);
+                    changes = await internals.Tracker.ProcessActions(toExecute);
                 }
 
                 List<IChangeInfo> optimizedChanges = ChangeInfoListOptimizer.Optimize(changes);

+ 1 - 0
src/PixiEditor/Models/Handlers/IDocument.cs

@@ -51,6 +51,7 @@ internal interface IDocument : IHandler, Extensions.CommonApi.Documents.IDocumen
     public ISnappingHandler SnappingHandler { get; }
     public IReadOnlyCollection<Guid> SelectedMembers { get; }
     public PreviewPainter? MiniPreviewPainter { get; set; }
+    public PreviewRenderer PreviewRenderer { get; }
     public void RemoveSoftSelectedMember(IStructureMemberHandler member);
     public void ClearSoftSelectedMembers();
     public void AddSoftSelectedMember(IStructureMemberHandler member);

+ 8 - 7
src/PixiEditor/Models/Rendering/MemberPreviewUpdater.cs

@@ -17,6 +17,7 @@ internal class MemberPreviewUpdater
 {
     private readonly IDocument doc;
     private readonly DocumentInternalParts internals;
+    private PreviewRenderer renderer => doc.PreviewRenderer;
 
     private AnimationKeyFramePreviewRenderer AnimationKeyFramePreviewRenderer { get; }
 
@@ -72,7 +73,7 @@ internal class MemberPreviewUpdater
         var previewSize = StructureHelpers.CalculatePreviewSize(internals.Tracker.Document.Size);
         //float scaling = (float)previewSize.X / doc.SizeBindable.X;
 
-        doc.PreviewPainter ??= new PreviewPainter(doc.Renderer, doc.Renderer, doc.AnimationHandler.ActiveFrameTime,
+        doc.PreviewPainter ??= new PreviewPainter(renderer, doc.Renderer, doc.AnimationHandler.ActiveFrameTime,
             doc.SizeBindable, internals.Tracker.Document.ProcessingColorSpace);
 
         UpdateDocPreviewPainter(doc.PreviewPainter);
@@ -80,7 +81,7 @@ internal class MemberPreviewUpdater
         if (!renderMiniPreviews)
             return;
 
-        doc.MiniPreviewPainter ??= new PreviewPainter(doc.Renderer, doc.Renderer,
+        doc.MiniPreviewPainter ??= new PreviewPainter(renderer, doc.Renderer,
             doc.AnimationHandler.ActiveFrameTime,
             doc.SizeBindable, internals.Tracker.Document.ProcessingColorSpace);
 
@@ -111,7 +112,7 @@ internal class MemberPreviewUpdater
                         continue;
 
                     structureMemberHandler.PreviewPainter =
-                        new PreviewPainter(doc.Renderer, previewRenderable,
+                        new PreviewPainter(renderer, previewRenderable,
                             doc.AnimationHandler.ActiveFrameTime, doc.SizeBindable,
                             internals.Tracker.Document.ProcessingColorSpace);
                     structureMemberHandler.PreviewPainter.Repaint();
@@ -168,7 +169,7 @@ internal class MemberPreviewUpdater
             KeyFrameTime frameTime = doc.AnimationHandler.ActiveFrameTime;
             if (cel.PreviewPainter == null)
             {
-                cel.PreviewPainter = new PreviewPainter(doc.Renderer, AnimationKeyFramePreviewRenderer, frameTime,
+                cel.PreviewPainter = new PreviewPainter(renderer, AnimationKeyFramePreviewRenderer, frameTime,
                     doc.SizeBindable,
                     internals.Tracker.Document.ProcessingColorSpace, cel.Id.ToString());
             }
@@ -195,7 +196,7 @@ internal class MemberPreviewUpdater
             if (groupHandler.PreviewPainter == null)
             {
                 groupHandler.PreviewPainter =
-                    new PreviewPainter(doc.Renderer, AnimationKeyFramePreviewRenderer, frameTime, documentSize,
+                    new PreviewPainter(renderer, AnimationKeyFramePreviewRenderer, frameTime, documentSize,
                         processingColorSpace,
                         groupHandler.Id.ToString());
             }
@@ -226,7 +227,7 @@ internal class MemberPreviewUpdater
                 if (structureMemberHandler.MaskPreviewPainter == null)
                 {
                     structureMemberHandler.MaskPreviewPainter = new PreviewPainter(
-                        doc.Renderer,
+                        renderer,
                         previewRenderable,
                         doc.AnimationHandler.ActiveFrameTime,
                         doc.SizeBindable,
@@ -308,7 +309,7 @@ internal class MemberPreviewUpdater
         {
             if (nodeVm.ResultPainter == null)
             {
-                nodeVm.ResultPainter = new PreviewPainter(doc.Renderer, renderable,
+                nodeVm.ResultPainter = new PreviewPainter(renderer, renderable,
                     doc.AnimationHandler.ActiveFrameTime,
                     doc.SizeBindable, internals.Tracker.Document.ProcessingColorSpace);
                 nodeVm.ResultPainter.AllowPartialResolutions = false;

+ 3 - 3
src/PixiEditor/Models/Rendering/PreviewPainter.cs

@@ -21,7 +21,7 @@ public class PreviewPainter : IDisposable
     public ColorSpace ProcessingColorSpace { get; set; }
     public KeyFrameTime FrameTime { get; set; }
     public VecI DocumentSize { get; set; }
-    public DocumentRenderer Renderer { get; set; }
+    public PreviewRenderer Renderer { get; set; }
 
     public bool AllowPartialResolutions { get; set; } = true;
 
@@ -42,7 +42,7 @@ public class PreviewPainter : IDisposable
 
     private int lastRequestId = 0;
 
-    public PreviewPainter(DocumentRenderer renderer, IPreviewRenderable previewRenderable, KeyFrameTime frameTime,
+    public PreviewPainter(PreviewRenderer renderer, IPreviewRenderable previewRenderable, KeyFrameTime frameTime,
         VecI documentSize, ColorSpace processingColorSpace, string elementToRenderName = "")
     {
         PreviewRenderable = previewRenderable;
@@ -184,7 +184,7 @@ public class PreviewPainter : IDisposable
                 ProcessingColorSpace, samplingOptions);
 
             dirtyTextures.Remove(texture);
-            Renderer.RenderNodePreview(PreviewRenderable, renderTexture.DrawingSurface, context, ElementToRenderName)
+            Renderer.QueueRenderNodePreview(PreviewRenderable, renderTexture.DrawingSurface, context, ElementToRenderName)
                 .ContinueWith(_ =>
                 {
                     Dispatcher.UIThread.Invoke(() =>

+ 100 - 0
src/PixiEditor/Models/Rendering/PreviewRenderer.cs

@@ -0,0 +1,100 @@
+using ChunkyImageLib.DataHolders;
+using Drawie.Backend.Core;
+using Drawie.Backend.Core.Bridge;
+using Drawie.Backend.Core.Surfaces;
+using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Changeables.Animations;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.ChangeableDocument.Rendering;
+
+namespace PixiEditor.Models.Rendering;
+
+public class PreviewRenderer
+{
+    private Queue<RenderRequest> renderRequests = new();
+
+    private bool isExecuting = false;
+
+    public IReadOnlyDocument Document { get; }
+
+    public PreviewRenderer(IReadOnlyDocument document)
+    {
+        Document = document;
+    }
+
+    public async Task RenderPreviews(KeyFrameTime frameTime)
+    {
+        await ExecuteRenderRequests(frameTime);
+    }
+
+    public async Task<bool> QueueRenderNodePreview(IPreviewRenderable previewRenderable, DrawingSurface renderOn,
+        RenderContext context,
+        string elementToRenderName)
+    {
+        if (previewRenderable is Node { IsDisposed: true }) return false;
+        TaskCompletionSource<bool> tcs = new();
+        RenderRequest request = new(tcs, context, renderOn, previewRenderable, elementToRenderName);
+
+        renderRequests.Enqueue(request);
+
+        return await tcs.Task;
+    }
+
+    private async Task ExecuteRenderRequests(KeyFrameTime frameTime)
+    {
+        isExecuting = true;
+        using var ctx = DrawingBackendApi.Current?.RenderingDispatcher.EnsureContext();
+
+        ChunkResolution highestResolution = renderRequests.MaxBy(x => 8 - (int)x.Context.ChunkResolution).Context.ChunkResolution;
+        using Texture docSizeTex = Texture.ForDisplay(Document.Size);
+        RenderContext context = new RenderContext(docSizeTex.DrawingSurface, frameTime, highestResolution,
+            Document.Size,
+            Document.Size, Document.ProcessingColorSpace, SamplingOptions.Default);
+
+        Document.NodeGraph.Execute(context);
+
+        while (renderRequests.Count > 0)
+        {
+            RenderRequest request = renderRequests.Dequeue();
+
+            /*if (frameTime.Frame != lastExecutedGraphFrame && request.PreviewRenderable != this)
+            {
+                using Texture executeSurface = Texture.ForDisplay(new VecI(1));
+                RenderDocument(executeSurface.DrawingSurface, frameTime, VecI.One);
+            }*/
+
+            try
+            {
+                bool result = true;
+                if (request.PreviewRenderable != null)
+                {
+                    if (request.PreviewRenderable.GetType() == typeof(DocumentRenderer))
+                    {
+                        var renderOn = request.RenderOn;
+                        int saved = renderOn.Canvas.Save();
+                        renderOn.Canvas.Scale((float)request.Context.ChunkResolution.Multiplier());
+                        renderOn.Canvas.Clear();
+                        renderOn.Canvas.DrawSurface(docSizeTex.DrawingSurface, 0, 0);
+                        renderOn.Canvas.RestoreToCount(saved);
+                        result = true;
+                    }
+                    else
+                    {
+                        result = request.PreviewRenderable.RenderPreview(request.RenderOn, request.Context,
+                            request.ElementToRenderName);
+                    }
+                }
+
+                request.TaskCompletionSource.SetResult(result);
+            }
+            catch (Exception e)
+            {
+                request.TaskCompletionSource.SetException(e);
+            }
+        }
+
+        isExecuting = false;
+    }
+}

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

@@ -26,13 +26,15 @@ internal class SceneRenderer : IDisposable
     private int lastGraphCacheHash = -1;
     private KeyFrameTime lastFrameTime;
     private Dictionary<Guid, bool> lastFramesVisibility = new();
+    private PreviewRenderer previewRenderer;
 
     private ChunkResolution? lastResolution;
 
-    public SceneRenderer(IReadOnlyDocument trackerDocument, IDocument documentViewModel)
+    public SceneRenderer(IReadOnlyDocument trackerDocument, IDocument documentViewModel, PreviewRenderer previewRenderer)
     {
         Document = trackerDocument;
         DocumentViewModel = documentViewModel;
+        this.previewRenderer = previewRenderer;
     }
 
     public void RenderScene(DrawingSurface target, ChunkResolution resolution, SamplingOptions samplingOptions,
@@ -58,6 +60,7 @@ internal class SceneRenderer : IDisposable
             }
 
             var rendered = RenderGraph(target, resolution, samplingOptions, targetOutput, finalGraph);
+            //previewRenderer.RenderPreviews(DocumentViewModel.AnimationHandler.ActiveFrameTime);
             cachedTextures[adjustedTargetOutput] = rendered;
             return;
         }

+ 3 - 1
src/PixiEditor/ViewModels/Document/DocumentViewModel.cs

@@ -222,6 +222,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
     public AnimationDataViewModel AnimationDataViewModel { get; }
     public TextOverlayViewModel TextOverlayViewModel { get; }
 
+    public PreviewRenderer PreviewRenderer { get; }
 
     public IReadOnlyCollection<IStructureMemberHandler> SoftSelectedStructureMembers => softSelectedStructureMembers;
     private DocumentInternalParts Internals { get; }
@@ -299,7 +300,8 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         ReferenceLayerViewModel = new(this, Internals);
 
         Renderer = new DocumentRenderer(Internals.Tracker.Document);
-        SceneRenderer = new SceneRenderer(Internals.Tracker.Document, this);
+        PreviewRenderer = new PreviewRenderer(Internals.Tracker.Document);
+        SceneRenderer = new SceneRenderer(Internals.Tracker.Document, this, PreviewRenderer);
     }
 
     /// <summary>