فهرست منبع

render thread wip

Krzysztof Krysiński 2 روز پیش
والد
کامیت
f2ded841e0
21فایلهای تغییر یافته به همراه313 افزوده شده و 142 حذف شده
  1. 1 1
      src/Drawie
  2. 1 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FolderNode.cs
  3. 2 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/EllipseVectorData.cs
  4. 2 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/LineVectorData.cs
  5. 45 10
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/PathVectorData.cs
  6. 2 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/PointsVectorData.cs
  7. 2 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/RectangleVectorData.cs
  8. 2 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/ShapeVectorData.cs
  9. 2 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/TextVectorData.cs
  10. 2 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/RasterizeShapeNode.cs
  11. 27 6
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/VectorLayerNode.cs
  12. 2 1
      src/PixiEditor.ChangeableDocument/DocumentChangeTracker.cs
  13. 1 1
      src/PixiEditor/Models/DocumentModels/ActionAccumulator.cs
  14. 61 25
      src/PixiEditor/Models/Rendering/SceneRenderer.cs
  15. 6 6
      src/PixiEditor/ViewModels/SubViewModels/ViewportWindowViewModel.cs
  16. 1 0
      src/PixiEditor/Views/Main/ViewportControls/FixedViewport.axaml.cs
  17. 1 1
      src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml.cs
  18. 1 1
      src/PixiEditor/Views/Overlays/TransformOverlay/TransformOverlay.cs
  19. 138 72
      src/PixiEditor/Views/Rendering/Scene.cs
  20. 13 3
      src/PixiEditor/Views/Visuals/PreviewTextureControl.cs
  21. 1 1
      src/PixiEditor/Views/Windows/BetaExampleFile.cs

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit adfaa90105229e3183e3049276af982f3e5b1b5d
+Subproject commit 0823d664bac5bca06e9a2068c4cd05df7ad8a7d0

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

@@ -62,6 +62,7 @@ public class FolderNode : StructureNode, IReadOnlyFolderNode, IClipSource
                 paint.ImageFilter = Filters.Value?.ImageFilter;
             }
 
+            // TODO: Detect if any content layer needs blending or opacity, if not, skip SaveLayer
             int saved = sceneContext.RenderSurface.Canvas.SaveLayer(paint);
             Content.Value?.Paint(sceneContext, sceneContext.RenderSurface);
 

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

@@ -30,12 +30,12 @@ public class EllipseVectorData : ShapeVectorData, IReadOnlyEllipseData
         Radius = radius;
     }
 
-    public override void RasterizeGeometry(Canvas drawingSurface)
+    public override void RasterizeGeometry(Canvas drawingSurface, ChunkResolution resolution)
     {
         Rasterize(drawingSurface, false);
     }
 
-    public override void RasterizeTransformed(Canvas drawingSurface)
+    public override void RasterizeTransformed(Canvas drawingSurface, ChunkResolution resolution)
     {
         Rasterize(drawingSurface, true);
     }

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

@@ -62,12 +62,12 @@ public class LineVectorData : ShapeVectorData, IReadOnlyLineData
         Fill = false;
     }
 
-    public override void RasterizeGeometry(Canvas canvas)
+    public override void RasterizeGeometry(Canvas canvas, ChunkResolution resolution)
     {
         Rasterize(canvas, false);
     }
 
-    public override void RasterizeTransformed(Canvas canvas)
+    public override void RasterizeTransformed(Canvas canvas, ChunkResolution resolution)
     {
         Rasterize(canvas, true);
     }

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

@@ -10,7 +10,18 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 
 public class PathVectorData : ShapeVectorData, IReadOnlyPathData
 {
-    public VectorPath Path { get; set; }
+    private Dictionary<ChunkResolution, VectorPath> simplifiedPaths = new();
+    private VectorPath path;
+
+    public VectorPath Path
+    {
+        get => path;
+        set
+        {
+            path = value;
+            RecalculateSimplifiedPaths();
+        }
+    }
     public override RectD GeometryAABB => Path?.TightBounds ?? RectD.Empty;
     public override RectD VisualAABB => GeometryAABB.Inflate(StrokeWidth / 2);
 
@@ -44,17 +55,17 @@ public class PathVectorData : ShapeVectorData, IReadOnlyPathData
         }
     }
 
-    public override void RasterizeGeometry(Canvas canvas)
+    public override void RasterizeGeometry(Canvas canvas, ChunkResolution resolution)
     {
-        Rasterize(canvas, false);
+        Rasterize(canvas, false, resolution);
     }
 
-    public override void RasterizeTransformed(Canvas canvas)
+    public override void RasterizeTransformed(Canvas canvas, ChunkResolution resolution)
     {
-        Rasterize(canvas, true);
+        Rasterize(canvas, true, resolution);
     }
 
-    private void Rasterize(Canvas canvas, bool applyTransform)
+    private void Rasterize(Canvas canvas, bool applyTransform, ChunkResolution resolution)
     {
         if (Path == null)
         {
@@ -68,6 +79,8 @@ public class PathVectorData : ShapeVectorData, IReadOnlyPathData
             ApplyTransformTo(canvas);
         }
 
+        var finalPath = simplifiedPaths[resolution];
+
         using Paint paint = new Paint()
         {
             IsAntiAliased = true, StrokeJoin = StrokeLineJoin, StrokeCap = StrokeLineCap
@@ -78,7 +91,7 @@ public class PathVectorData : ShapeVectorData, IReadOnlyPathData
             paint.SetPaintable(FillPaintable);
             paint.Style = PaintStyle.Fill;
 
-            canvas.DrawPath(Path, paint);
+            canvas.DrawPath(finalPath, paint);
         }
 
         if (StrokeWidth > 0 && Stroke.AnythingVisible)
@@ -87,7 +100,7 @@ public class PathVectorData : ShapeVectorData, IReadOnlyPathData
             paint.Style = PaintStyle.Stroke;
             paint.StrokeWidth = StrokeWidth;
 
-            canvas.DrawPath(Path, paint);
+            canvas.DrawPath(finalPath, paint);
         }
 
         if (applyTransform)
@@ -131,10 +144,32 @@ public class PathVectorData : ShapeVectorData, IReadOnlyPathData
         return newPath;
     }
 
+    private void RecalculateSimplifiedPaths()
+    {
+        foreach (var sPath in simplifiedPaths.Values)
+        {
+            if (!Equals(sPath, Path))
+            {
+                sPath.Dispose();
+            }
+        }
+
+        simplifiedPaths.Clear();
+
+        simplifiedPaths[ChunkResolution.Full] = Path;
+        var half = Path.Simplify();
+        simplifiedPaths[ChunkResolution.Half] = half;
+        var quarter = half.Simplify();
+        simplifiedPaths[ChunkResolution.Quarter] = quarter;
+        var eighth = quarter.Simplify();
+        simplifiedPaths[ChunkResolution.Eighth] = eighth;
+    }
+
     protected bool Equals(PathVectorData other)
     {
-        return base.Equals(other) && Path.Equals(other.Path) && StrokeLineCap == other.StrokeLineCap && StrokeLineJoin == other.StrokeLineJoin
-                && FillType == other.FillType;
+        return base.Equals(other) && Path.Equals(other.Path) && StrokeLineCap == other.StrokeLineCap &&
+               StrokeLineJoin == other.StrokeLineJoin
+               && FillType == other.FillType;
     }
 
     public override bool Equals(object? obj)

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

@@ -24,12 +24,12 @@ public class PointsVectorData : ShapeVectorData
     public override ShapeCorners TransformationCorners => new ShapeCorners(
         GeometryAABB).WithMatrix(TransformationMatrix);
 
-    public override void RasterizeGeometry(Canvas drawingSurface)
+    public override void RasterizeGeometry(Canvas drawingSurface, ChunkResolution resolution)
     {
         Rasterize(drawingSurface, false);
     }
 
-    public override void RasterizeTransformed(Canvas drawingSurface)
+    public override void RasterizeTransformed(Canvas drawingSurface, ChunkResolution resolution)
     {
         Rasterize(drawingSurface, true);
     }

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

@@ -42,12 +42,12 @@ public class RectangleVectorData : ShapeVectorData, IReadOnlyRectangleData
         Size = new VecD(width, height);
     }
 
-    public override void RasterizeGeometry(Canvas canvas)
+    public override void RasterizeGeometry(Canvas canvas, ChunkResolution resolution)
     {
         Rasterize(canvas, false);
     }
 
-    public override void RasterizeTransformed(Canvas canvas)
+    public override void RasterizeTransformed(Canvas canvas, ChunkResolution resolution)
     {
         Rasterize(canvas, true);
     }

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

@@ -45,8 +45,8 @@ public abstract class ShapeVectorData : ICacheable, ICloneable, IReadOnlyShapeVe
         canvas.SetMatrix(final);
     }
 
-    public abstract void RasterizeGeometry(Canvas canvas);
-    public abstract void RasterizeTransformed(Canvas canvas);
+    public abstract void RasterizeGeometry(Canvas canvas, ChunkResolution resolution);
+    public abstract void RasterizeTransformed(Canvas canvas, ChunkResolution resolution);
     public abstract bool IsValid();
 
     public int GetCacheHash()

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

@@ -182,12 +182,12 @@ public class TextVectorData : ShapeVectorData, IReadOnlyTextData, IScalable
         return path;
     }
 
-    public override void RasterizeGeometry(Canvas canvas)
+    public override void RasterizeGeometry(Canvas canvas, ChunkResolution resolution)
     {
         Rasterize(canvas, false);
     }
 
-    public override void RasterizeTransformed(Canvas canvas)
+    public override void RasterizeTransformed(Canvas canvas, ChunkResolution resolution)
     {
         Rasterize(canvas, true);
     }

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/RasterizeShapeNode.cs

@@ -29,7 +29,7 @@ public class RasterizeShapeNode : RenderNode
 
         AllowHighDpiRendering = HighDpiRendering.Value;
 
-        shape.RasterizeTransformed(surface.Canvas);
+        shape.RasterizeTransformed(surface.Canvas, context.ChunkResolution);
     }
 
     public override Node CreateCopy() => new RasterizeShapeNode();
@@ -51,6 +51,6 @@ public class RasterizeShapeNode : RenderNode
         if (shape == null || !shape.IsValid())
             return;
 
-        shape.RasterizeTransformed(renderOn.Canvas);
+        shape.RasterizeTransformed(renderOn.Canvas, context.ChunkResolution);
     }
 }

+ 27 - 6
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/VectorLayerNode.cs

@@ -72,27 +72,43 @@ public class VectorLayerNode : LayerNode, ITransformableObject, IReadOnlyVectorN
     protected override void DrawWithoutFilters(SceneObjectRenderContext ctx, DrawingSurface workingSurface,
         Paint paint)
     {
-        if (RenderableShapeData == null)
+        if (RenderableShapeData == null || IsOutOfBounds(ctx.VisibleDocumentRegion))
         {
             return;
         }
 
-        Rasterize(workingSurface, paint);
+        Rasterize(workingSurface, paint, ctx.ChunkResolution);
     }
 
     protected override void DrawWithFilters(SceneObjectRenderContext ctx, DrawingSurface workingSurface, Paint paint)
     {
-        if (RenderableShapeData == null)
+        if (RenderableShapeData == null || IsOutOfBounds(ctx.VisibleDocumentRegion))
         {
             return;
         }
 
-        Rasterize(workingSurface, paint);
+        Rasterize(workingSurface, paint, ctx.ChunkResolution);
+    }
+
+    private bool IsOutOfBounds(RectI? visibleDocumentRegion)
+    {
+        if (visibleDocumentRegion == null)
+        {
+            return false;
+        }
+
+        if (RenderableShapeData == null)
+        {
+            return true;
+        }
+
+        RectD shapeBounds = RenderableShapeData.TransformedAABB;
+        return !shapeBounds.IntersectsWithInclusive((RectD)visibleDocumentRegion);
     }
 
     protected override bool ShouldRenderPreview(string elementToRenderName)
     {
-        if(RenderableShapeData == null)
+        if (RenderableShapeData == null)
         {
             return false;
         }
@@ -197,6 +213,11 @@ public class VectorLayerNode : LayerNode, ITransformableObject, IReadOnlyVectorN
     }
 
     public void Rasterize(DrawingSurface surface, Paint paint)
+    {
+        Rasterize(surface, paint, ChunkResolution.Full);
+    }
+
+    public void Rasterize(DrawingSurface surface, Paint paint, ChunkResolution resolution)
     {
         int layer;
         // TODO: This can be further optimized by passing opacity, blend mode and filters directly to the rasterization method
@@ -211,7 +232,7 @@ public class VectorLayerNode : LayerNode, ITransformableObject, IReadOnlyVectorN
             layer = surface.Canvas.Save();
         }
 
-        RenderableShapeData?.RasterizeTransformed(surface.Canvas);
+        RenderableShapeData?.RasterizeTransformed(surface.Canvas, resolution);
 
         surface.Canvas.RestoreToCount(layer);
     }

+ 2 - 1
src/PixiEditor.ChangeableDocument/DocumentChangeTracker.cs

@@ -431,7 +431,8 @@ public class DocumentChangeTracker : IDisposable
         if (running)
             throw new InvalidOperationException("Already currently processing");
         running = true;
-        var result = await DrawingBackendApi.Current.RenderingDispatcher.InvokeAsync(() => ProcessActionList(actions));
+        //TODO: Implement async processing
+        var result = ProcessActionList(actions);
         running = false;
         return result;
     }

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

@@ -165,7 +165,7 @@ internal class ActionAccumulator
                     .Select(x => x.Select(r => r.TextureUpdatedAction))
                     .SelectMany(x => x).ToList();
 
-                await document.SceneRenderer.RenderAsync(internals.State.Viewports, affectedAreas.MainImageArea,
+                document.SceneRenderer.RenderAsync(internals.State.Viewports, affectedAreas.MainImageArea,
                     !previewsDisabled && updateDelayed, previewTextures);
 
                 NotifyUpdatedPreviews(updatePreviewActions);

+ 61 - 25
src/PixiEditor/Models/Rendering/SceneRenderer.cs

@@ -20,7 +20,7 @@ namespace PixiEditor.Models.Rendering;
 
 internal class SceneRenderer
 {
-    public const double ZoomDiffToRerender = 20;
+    public const double ZoomDiffToRerender = 50;
     public const float OversizeFactor = 1.25f;
     public IReadOnlyDocument Document { get; }
     public IDocument DocumentViewModel { get; }
@@ -34,44 +34,62 @@ internal class SceneRenderer
 
     private TextureCache textureCache = new();
 
+    private SceneRenderRequest latestRequest;
+
+
     public SceneRenderer(IReadOnlyDocument trackerDocument, IDocument documentViewModel)
     {
         Document = trackerDocument;
         DocumentViewModel = documentViewModel;
     }
 
-    public async Task RenderAsync(Dictionary<Guid, ViewportInfo> stateViewports, AffectedArea affectedArea,
+    public void RenderAsync(Dictionary<Guid, ViewportInfo> stateViewports, AffectedArea affectedArea,
         bool updateDelayed, Dictionary<Guid, List<PreviewRenderRequest>>? previewTextures)
     {
-        await DrawingBackendApi.Current.RenderingDispatcher.InvokeInBackgroundAsync(() =>
+        latestRequest = new SceneRenderRequest
+        {
+            Viewports = stateViewports,
+            AffectedArea = affectedArea,
+            UpdateDelayed = updateDelayed,
+            PreviewTextures = previewTextures
+        };
+
+        DrawingBackendApi.Current.RenderingDispatcher.QueueRender(DispatchRender);
+    }
+
+    private void DispatchRender()
+    {
+        if (latestRequest.Equals(default(SceneRenderRequest))) return;
+
+        int renderedCount = 0;
+        foreach (var viewport in latestRequest.Viewports)
         {
-            using var ctx = DrawingBackendApi.Current.RenderingDispatcher.EnsureContext();
-            int renderedCount = 0;
-            foreach (var viewport in stateViewports)
+            if (viewport.Value.Delayed && !latestRequest.UpdateDelayed)
             {
-                if (viewport.Value.Delayed && !updateDelayed)
-                {
-                    continue;
-                }
+                continue;
+            }
 
-                if (viewport.Value.RealDimensions.ShortestAxis <= 0) continue;
+            if (viewport.Value.RealDimensions.ShortestAxis <= 0) continue;
 
-                var rendered = RenderScene(viewport.Value, affectedArea, previewTextures);
+            var rendered = RenderScene(viewport.Value, latestRequest.AffectedArea, latestRequest.PreviewTextures);
+            lock (DocumentViewModel.SceneTextures)
+            {
                 if (DocumentViewModel.SceneTextures.TryGetValue(viewport.Key, out var texture) && texture != rendered)
                 {
                     texture.Dispose();
                 }
 
                 DocumentViewModel.SceneTextures[viewport.Key] = rendered;
-                viewport.Value.InvalidateVisual();
-                renderedCount++;
             }
 
-            if (renderedCount == 0 && previewTextures is { Count: > 0 })
-            {
-                RenderOnlyPreviews(affectedArea, previewTextures);
-            }
-        });
+            Dispatcher.UIThread.Invoke(() => viewport.Value.InvalidateVisual());
+            renderedCount++;
+        }
+
+        if (renderedCount == 0 && latestRequest.PreviewTextures is { Count: > 0 })
+        {
+            RenderOnlyPreviews(latestRequest.AffectedArea, latestRequest.PreviewTextures);
+        }
     }
 
     private void RenderOnlyPreviews(AffectedArea affectedArea,
@@ -116,7 +134,8 @@ internal class SceneRenderer
         IReadOnlyNodeGraph finalGraph = RenderingUtils.SolveFinalNodeGraph(targetOutput, Document);
 
         float oversizeFactor = 1;
-        if (visibleDocumentRegion != null && viewport.IsScene && visibleDocumentRegion.Value != new RectI(0, 0, Document.Size.X, Document.Size.Y))
+        if (visibleDocumentRegion != null && viewport.IsScene &&
+            visibleDocumentRegion.Value != new RectI(0, 0, Document.Size.X, Document.Size.Y))
         {
             visibleDocumentRegion = (RectI)visibleDocumentRegion.Value.Scale(OversizeFactor,
                 visibleDocumentRegion.Value.Center);
@@ -129,9 +148,14 @@ internal class SceneRenderer
 
         if (shouldRerender)
         {
-            affectedArea = fullAffectedArea && viewport.VisibleDocumentRegion.HasValue ? new AffectedArea(OperationHelper.FindChunksTouchingRectangle(viewport.VisibleDocumentRegion.Value, ChunkyImage.FullChunkSize)) : affectedArea;
-            return RenderGraph(renderTargetSize, targetMatrix, viewportId, resolution, samplingOptions, affectedArea,
+            affectedArea = fullAffectedArea && viewport.VisibleDocumentRegion.HasValue
+                ? new AffectedArea(OperationHelper.FindChunksTouchingRectangle(viewport.VisibleDocumentRegion.Value,
+                    ChunkyImage.FullChunkSize))
+                : affectedArea;
+            var rendered = RenderGraph(renderTargetSize, targetMatrix, viewportId, resolution, samplingOptions,
+                affectedArea,
                 visibleDocumentRegion, targetOutput, viewport.IsScene, oversizeFactor, finalGraph, previewTextures);
+            return rendered;
         }
 
         var cachedTexture = DocumentViewModel.SceneTextures[viewportId];
@@ -295,6 +319,7 @@ internal class SceneRenderer
             OnionSkinning = DocumentViewModel.AnimationHandler.OnionSkinningEnabledBindable,
             GraphCacheHash = finalGraph.GetCacheHash(),
             ZoomLevel = matrix.ScaleX,
+            Matrix = matrix,
             VisibleDocumentRegion =
                 (RectD?)visibleDocumentRegion ?? new RectD(0, 0, Document.Size.X, Document.Size.Y)
         };
@@ -378,6 +403,7 @@ readonly struct RenderState
     public int OnionFrames { get; init; }
     public double OnionOpacity { get; init; }
     public bool OnionSkinning { get; init; }
+    public Matrix3X3 Matrix { get; init; }
 
     public bool ShouldRerender(RenderState other)
     {
@@ -385,7 +411,7 @@ readonly struct RenderState
                TargetOutput != other.TargetOutput || GraphCacheHash != other.GraphCacheHash ||
                OnionFrames != other.OnionFrames || Math.Abs(OnionOpacity - other.OnionOpacity) > 0.05 ||
                OnionSkinning != other.OnionSkinning ||
-               VisibleRegionChanged(other) || ZoomDiff(other) > 0;
+               VisibleRegionChanged(other) || ZoomDiff(other);
     }
 
     private bool VisibleRegionChanged(RenderState other)
@@ -393,8 +419,18 @@ readonly struct RenderState
         return !other.VisibleDocumentRegion.IsFullyInside(VisibleDocumentRegion);
     }
 
-    private double ZoomDiff(RenderState other)
+    private bool ZoomDiff(RenderState other)
     {
-        return Math.Abs(ZoomLevel - other.ZoomLevel);
+        var diff = VisibleDocumentRegion.Size.Length - other.VisibleDocumentRegion.Size.Length;
+
+        return Math.Abs(diff) > SceneRenderer.ZoomDiffToRerender;
     }
 }
+
+readonly record struct SceneRenderRequest
+{
+    public Dictionary<Guid, ViewportInfo> Viewports { get; init; }
+    public AffectedArea AffectedArea { get; init; }
+    public bool UpdateDelayed { get; init; }
+    public Dictionary<Guid, List<PreviewRenderRequest>>? PreviewTextures { get; init; }
+}

+ 6 - 6
src/PixiEditor/ViewModels/SubViewModels/ViewportWindowViewModel.cs

@@ -176,7 +176,7 @@ internal class ViewportWindowViewModel : SubViewModel<WindowViewModel>, IDockabl
         PixiEditorSettings.Scene.SecondaryBackgroundColor.ValueChanged += UpdateBackgroundBitmap;
 
         previewPainterControl = new TextureControl();
-        var nonZoomed = Document.SceneTextures.Where(x =>
+        /*var nonZoomed = Document.SceneTextures.Where(x =>
             x.Value is { DrawingSurface.Canvas.TotalMatrix: { TransX: 0, TransY: 0, SkewX: 0, SkewY: 0 } }).ToArray();
         if (nonZoomed.Length > 0)
         {
@@ -185,7 +185,7 @@ internal class ViewportWindowViewModel : SubViewModel<WindowViewModel>, IDockabl
             {
                 previewPainterControl.Texture = minSize.Value;
             }
-        }
+        }*/
 
         TabCustomizationSettings.Icon = previewPainterControl;
     }
@@ -199,15 +199,15 @@ internal class ViewportWindowViewModel : SubViewModel<WindowViewModel>, IDockabl
         }
         else if (e.PropertyName == nameof(DocumentViewModel.AllChangesSaved))
         {
-            var nonZoomed = Document.SceneTextures.Where(x =>
-                    x.Value is { DrawingSurface.Canvas.TotalMatrix: { TransX: 0, TransY: 0, SkewX: 0, SkewY: 0 } })
+            var nonZoomed = Document.SceneRenderer.LastRenderedStates.Where(x =>
+                    x.Value is { Matrix: { TransX: 0, TransY: 0, SkewX: 0, SkewY: 0 } })
                 .ToArray();
             if (nonZoomed.Length > 0)
             {
-                var minSize = nonZoomed.MinBy(x => x.Value.Size);
+                var minSize = Document.SceneTextures.Where(x => nonZoomed.Any(y => y.Key == x.Key)).MinBy(x => x.Value.Size);
                 if (minSize.Value != null)
                 {
-                    previewPainterControl.Texture = minSize.Value;
+                    //previewPainterControl.Texture = minSize.Value;
                 }
             }
             TabCustomizationSettings.SavedState = GetSaveState(Document);

+ 1 - 0
src/PixiEditor/Views/Main/ViewportControls/FixedViewport.axaml.cs

@@ -14,6 +14,7 @@ using PixiEditor.Models.Position;
 using Drawie.Numerics;
 using PixiEditor.Models.Rendering;
 using PixiEditor.ViewModels.Document;
+using Image = Drawie.Backend.Core.Surfaces.ImageData.Image;
 
 namespace PixiEditor.Views.Main.ViewportControls;
 

+ 1 - 1
src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml.cs

@@ -525,7 +525,7 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
     private ViewportInfo GetLocation()
     {
         return new(AngleRadians, Center, RealDimensions,
-            Scene.CalculateTransformMatrix().ToSKMatrix().ToMatrix3X3(),
+            Scene.CalculateTransformMatrix((float)Scene.AngleRadians, Scene.FlipX, Scene.FlipY, Scene.Scale, Scene.CanvasPos).ToSKMatrix().ToMatrix3X3(),
             CalculateVisibleRegion(),
             ViewportRenderOutput, Scene.CalculateSampling(), Dimensions, CalculateResolution(), GuidValue, Delayed,
             true, ForceRefreshFinalImage);

+ 1 - 1
src/PixiEditor/Views/Overlays/TransformOverlay/TransformOverlay.cs

@@ -485,7 +485,7 @@ internal class TransformOverlay : Overlay
 
             context.SetMatrix(context.TotalMatrix.Concat(matrix));
 
-            shearCursorGeometry.RasterizeTransformed(context);
+            shearCursorGeometry.RasterizeTransformed(context, ChunkResolution.Full);
         }
 
         context.RestoreToCount(saved);

+ 138 - 72
src/PixiEditor/Views/Rendering/Scene.cs

@@ -1,5 +1,6 @@
 using System.Collections.ObjectModel;
 using System.Collections.Specialized;
+using System.Diagnostics;
 using System.Globalization;
 using Avalonia;
 using Avalonia.Animation;
@@ -19,6 +20,7 @@ using Drawie.Backend.Core.Bridge;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Shaders;
 using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Text;
 using Drawie.Interop.Avalonia.Core;
@@ -187,12 +189,14 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
     private RenderApiResources resources;
 
     private DrawingSurface framebuffer;
-    private Texture renderTexture;
+    private Texture intermediateSurface;
 
-    private PixelSize lastSize = PixelSize.Empty;
+    private VecI lastSize = VecI.Zero;
     private Cursor lastCursor;
     private VecD lastMousePosition;
 
+    private IDisposable? toPresent;
+
     public static readonly StyledProperty<string> RenderOutputProperty =
         AvaloniaProperty.Register<Scene, string>("RenderOutput");
 
@@ -324,44 +328,83 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         QueueNextFrame();
     }
 
-    public void Draw(DrawingSurface texture)
+    public override void Render(DrawingContext context)
+    {
+        if (!string.IsNullOrEmpty(info))
+        {
+            Point center = new Point(Bounds.Width / 2, Bounds.Height / 2);
+            context.DrawText(
+                new FormattedText(info, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, Typeface.Default, 12,
+                    Brushes.White),
+                center);
+        }
+    }
+
+    private float angleRadians;
+    private bool flipX;
+    private bool flipY;
+    private float scale;
+    private VecD canvasPos;
+    private DocumentViewModel doc;
+    string renderOutput;
+    private void PrepareToDraw()
     {
-        if (Document == null || SceneRenderer == null) return;
+        angleRadians = (float)AngleRadians;
+        flipX = FlipX;
+        flipY = FlipY;
+        scale = (float)Scale;
+        canvasPos = CanvasPos;
+        doc = Document;
+        renderOutput = RenderOutput;
+    }
 
+    public void Draw(DrawingSurface texture)
+    {
         texture.Canvas.Save();
-        var matrix = CalculateTransformMatrix();
+        var matrix = CalculateTransformMatrix(angleRadians, flipX, flipY, scale, canvasPos);
 
         texture.Canvas.SetMatrix(matrix.ToSKMatrix().ToMatrix3X3());
 
-        VecI outputSize = FindOutputSize();
+        VecI outputSize = FindOutputSize(doc, renderOutput);
 
         RectD dirtyBounds = new RectD(0, 0, outputSize.X, outputSize.Y);
-        RenderScene(texture, dirtyBounds);
+        RenderScene(texture, dirtyBounds, doc, angleRadians, flipX, flipY, scale, canvasPos);
 
         texture.Canvas.Restore();
     }
 
-    private void RenderScene(DrawingSurface texture, RectD bounds)
+    private void RenderScene(DrawingSurface texture, RectD bounds, DocumentViewModel document,
+        float angleRadians, bool flipX, bool flipY, float scale, VecD canvasPos)
     {
-        var renderOutput = RenderOutput == "DEFAULT" ? null : RenderOutput;
-        DrawCheckerboard(texture, bounds);
-        DrawOverlays(texture, bounds, OverlayRenderSorting.Background);
+        //var renderOutput = RenderOutput == "DEFAULT" ? null : RenderOutput;
+        //DrawCheckerboard(texture, bounds);
+        //DrawOverlays(texture, bounds, OverlayRenderSorting.Background);
         try
         {
-            if(Document == null || Document.SceneTextures.TryGetValue(ViewportId, out var tex) == false)
+            Guid viewportId = default;
+            VecD realDimensions = VecD.Zero;
+            Dispatcher.UIThread.Invoke(() =>
+            {
+                viewportId = ViewportId;
+                realDimensions = RealDimensions;
+            });
+
+            if (document == null || document.SceneTextures.TryGetValue(viewportId, out var tex) == false)
                 return;
-            
+
             bool hasSaved = false;
             int saved = -1;
 
-            var matrix = CalculateTransformMatrix().ToSKMatrix().ToMatrix3X3();
-            if(!Document.SceneTextures.TryGetValue(ViewportId, out var cachedTexture))
+            var matrix = CalculateTransformMatrix(angleRadians, flipX, flipY, scale, canvasPos).ToSKMatrix()
+                .ToMatrix3X3();
+            if (!document.SceneTextures.TryGetValue(viewportId, out var cachedTexture))
                 return;
 
-            Matrix3X3 matrixDiff = SolveMatrixDiff(matrix, cachedTexture);
-            var target = cachedTexture.DrawingSurface;
+            Matrix3X3 matrixDiff =
+                SolveMatrixDiff(matrix, document.SceneRenderer.LastRenderedStates[viewportId].Matrix);
+            var target = cachedTexture;
 
-            if (tex.Size == (VecI)RealDimensions || tex.Size == (VecI)(RealDimensions * SceneRenderer.OversizeFactor))
+            if (tex.Size == (VecI)realDimensions || tex.Size == (VecI)(realDimensions * SceneRenderer.OversizeFactor))
             {
                 saved = texture.Canvas.Save();
                 texture.Canvas.ClipRect(bounds);
@@ -372,10 +415,11 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
             {
                 saved = texture.Canvas.Save();
                 ChunkResolution renderedResolution = ChunkResolution.Full;
-                if (SceneRenderer != null && SceneRenderer.LastRenderedStates.ContainsKey(ViewportId))
+                if (SceneRenderer != null && SceneRenderer.LastRenderedStates.ContainsKey(viewportId))
                 {
-                    renderedResolution = SceneRenderer.LastRenderedStates[ViewportId].ChunkResolution;
+                    renderedResolution = SceneRenderer.LastRenderedStates[viewportId].ChunkResolution;
                 }
+
                 texture.Canvas.SetMatrix(matrixDiff);
                 texture.Canvas.Scale((float)renderedResolution.InvertedMultiplier());
                 hasSaved = true;
@@ -384,7 +428,7 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
 
             texture.Canvas.Save();
 
-            texture.Canvas.DrawSurface(target, 0, 0);
+            texture.Canvas.DrawSurface(target.DrawingSurface, 0, 0);
             if (hasSaved)
             {
                 texture.Canvas.RestoreToCount(saved);
@@ -398,11 +442,11 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
             using Font defaultSizedFont = Font.CreateDefault();
             defaultSizedFont.Size = 24;
 
-            texture.Canvas.DrawText(new LocalizedString("ERROR_GRAPH"), renderTexture.Size / 2f,
+            texture.Canvas.DrawText(new LocalizedString("ERROR_GRAPH"), intermediateSurface.Size / 2f,
                 TextAlign.Center, defaultSizedFont, paint);
         }
 
-        DrawOverlays(texture, bounds, OverlayRenderSorting.Foreground);
+        //DrawOverlays(texture, bounds, OverlayRenderSorting.Foreground);
     }
 
     private void DrawCheckerboard(DrawingSurface surface, RectD dirtyBounds)
@@ -474,18 +518,18 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         }
     }
 
-    private VecI FindOutputSize()
+    private VecI FindOutputSize(DocumentViewModel doc, string renderOutput)
     {
-        VecI outputSize = Document.SizeBindable;
+        VecI outputSize = doc.SizeBindable;
 
-        if (!string.IsNullOrEmpty(RenderOutput))
+        if (!string.IsNullOrEmpty(renderOutput))
         {
-            if (Document.NodeGraph.CustomRenderOutputs.TryGetValue(RenderOutput, out var node))
+            if (doc.NodeGraph.CustomRenderOutputs.TryGetValue(renderOutput, out var node))
             {
                 var prop = node?.Inputs.FirstOrDefault(x => x.PropertyName == CustomOutputNode.SizePropertyName);
                 if (prop != null)
                 {
-                    VecI size = Document.NodeGraph.GetComputedPropertyValue<VecI>(prop);
+                    VecI size = doc.NodeGraph.GetComputedPropertyValue<VecI>(prop);
                     if (size.ShortestAxis > 0)
                     {
                         outputSize = size;
@@ -761,18 +805,18 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
 
     private VecD ToCanvasSpace(Point scenePosition)
     {
-        Matrix transform = CalculateTransformMatrix();
+        Matrix transform = CalculateTransformMatrix((float)AngleRadians, FlipX, FlipY, (float)Scale, CanvasPos);
         Point transformed = transform.Invert().Transform(scenePosition);
         return new VecD(transformed.X, transformed.Y);
     }
 
-    internal Matrix CalculateTransformMatrix()
+    internal Matrix CalculateTransformMatrix(float angleRadians, bool flipX, bool flipY, double scale, VecD canvasPos)
     {
         Matrix transform = Matrix.Identity;
-        transform = transform.Append(Matrix.CreateRotation((float)AngleRadians));
-        transform = transform.Append(Matrix.CreateScale(FlipX ? -1 : 1, FlipY ? -1 : 1));
-        transform = transform.Append(Matrix.CreateScale((float)Scale, (float)Scale));
-        transform = transform.Append(Matrix.CreateTranslation(CanvasPos.X, CanvasPos.Y));
+        transform = transform.Append(Matrix.CreateRotation(angleRadians));
+        transform = transform.Append(Matrix.CreateScale(flipX ? -1 : 1, flipY ? -1 : 1));
+        transform = transform.Append(Matrix.CreateScale((float)scale, (float)scale));
+        transform = transform.Append(Matrix.CreateTranslation(canvasPos.X, canvasPos.Y));
         return transform;
     }
 
@@ -843,10 +887,9 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
             return;
         }
 
-        var size = new PixelSize((int)Bounds.Width, (int)Bounds.Height);
         try
         {
-            RenderFrame(size);
+            RenderFrame();
             info = string.Empty;
         }
         catch (Exception e)
@@ -857,15 +900,41 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         }
     }
 
+    private void RenderFrame()
+    {
+        toPresent?.Dispose();
+        toPresent = null;
+    }
+
     public void QueueNextFrame()
     {
         if (initialized && !updateQueued && compositor != null && surface is { IsDisposed: false })
         {
-            updateQueued = true;
-            compositor.RequestCompositionUpdate(update);
+            QueueFrameRequested();
+            RequestBlit();
         }
     }
 
+
+    protected void QueueFrameRequested()
+    {
+        if (Bounds.Width <= 0 || Bounds.Height <= 0 || double.IsNaN(Bounds.Width) || double.IsNaN(Bounds.Height))
+            return;
+
+        PrepareToDraw();
+        DrawingBackendApi.Current.RenderingDispatcher.QueueRender(() =>
+        {
+            BeginDraw(new VecI((int)Bounds.Width, (int)Bounds.Height));
+            Dispatcher.UIThread.Post(RequestBlit);
+        });
+    }
+
+    private void RequestBlit()
+    {
+        updateQueued = true;
+        compositor.RequestCompositionUpdate(update);
+    }
+
     protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
     {
         if (change.Property == BoundsProperty)
@@ -877,9 +946,9 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
     }
 
 
-    private Matrix3X3 SolveMatrixDiff(Matrix3X3 matrix, Texture cachedTexture)
+    private Matrix3X3 SolveMatrixDiff(Matrix3X3 matrix, Matrix3X3 renderedMatrix)
     {
-        Matrix3X3 old = cachedTexture.DrawingSurface.Canvas.TotalMatrix;
+        Matrix3X3 old = renderedMatrix;
         Matrix3X3 current = matrix;
 
         Matrix3X3 solveMatrixDiff = current.Concat(old.Invert());
@@ -895,41 +964,36 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
             return (false, "Composition interop not available");
         }
 
-        return InitializeGraphicsResources(compositor, surface, interop);
-    }
-
-    protected (bool success, string info) InitializeGraphicsResources(Compositor targetCompositor,
-        CompositionDrawingSurface compositionDrawingSurface, ICompositionGpuInterop interop)
-    {
-        try
-        {
-            resources = IDrawieInteropContext.Current.CreateResources(compositionDrawingSurface, interop);
-        }
-        catch (Exception e)
+        var resources = InitializeGraphicsResources(compositor, surface, interop, out string info);
+        if (resources == null)
         {
-            return (false, new LocalizedString("ERROR_GPU_RESOURCES_CREATION", e.Message));
+            return (false, "Failed to create graphics resources: " + info);
         }
 
+        this.resources = resources;
         return (true, string.Empty);
     }
 
-    public override void Render(DrawingContext context)
+    protected RenderApiResources? InitializeGraphicsResources(Compositor targetCompositor,
+        CompositionDrawingSurface compositionDrawingSurface, ICompositionGpuInterop interop, out string? createInfo)
     {
-        if (!string.IsNullOrEmpty(info))
+        try
         {
-            Point center = new Point(Bounds.Width / 2, Bounds.Height / 2);
-            context.DrawText(
-                new FormattedText(info, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, Typeface.Default, 12,
-                    Brushes.White),
-                center);
+            createInfo = null;
+            return IDrawieInteropContext.Current.CreateResources(compositionDrawingSurface, interop);
+        }
+        catch (Exception e)
+        {
+            createInfo = e.Message;
+            return null;
         }
     }
 
     protected async Task FreeGraphicsResources()
     {
         using var ctx = DrawingBackendApi.Current.RenderingDispatcher.EnsureContext();
-        renderTexture?.Dispose();
-        renderTexture = null;
+        intermediateSurface?.Dispose();
+        intermediateSurface = null;
 
         framebuffer?.Dispose();
         framebuffer = null;
@@ -942,12 +1006,12 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         resources = null;
     }
 
-    protected void RenderFrame(PixelSize size)
+    public void BeginDraw(VecI size)
     {
-        if (resources != null && !resources.IsDisposed)
+        if (resources is { IsDisposed: false })
         {
             using var ctx = IDrawieInteropContext.Current.EnsureContext();
-            if (size.Width == 0 || size.Height == 0)
+            if (size.X == 0 || size.Y == 0)
             {
                 return;
             }
@@ -956,27 +1020,29 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
             {
                 resources.CreateTemporalObjects(size);
 
-                VecI sizeVec = new VecI(size.Width, size.Height);
+                VecI sizeVec = new VecI(size.X, size.Y);
 
                 framebuffer?.Dispose();
-                renderTexture?.Dispose();
 
                 framebuffer =
                     DrawingBackendApi.Current.CreateRenderSurface(sizeVec,
                         resources.Texture, SurfaceOrigin.BottomLeft);
 
-                renderTexture = Texture.ForDisplay(sizeVec);
+                intermediateSurface?.Dispose();
+                intermediateSurface = Texture.ForDisplay(sizeVec);
 
                 lastSize = size;
             }
 
-            resources.Render(size, () =>
+            toPresent = resources.Render(size, () =>
             {
                 framebuffer.Canvas.Clear();
-                renderTexture.DrawingSurface.Canvas.Clear();
-                Draw(renderTexture.DrawingSurface);
-                framebuffer.Canvas.DrawSurface(renderTexture.DrawingSurface, 0, 0);
-                framebuffer.Flush();
+                intermediateSurface?.DrawingSurface.Canvas.Clear();
+
+                Draw(intermediateSurface.DrawingSurface);
+                framebuffer.Canvas.DrawSurface(intermediateSurface.DrawingSurface, 0, 0);
+
+                //framebuffer.Flush();
             });
         }
     }

+ 13 - 3
src/PixiEditor/Views/Visuals/PreviewTextureControl.cs

@@ -1,4 +1,5 @@
 using Avalonia;
+using Avalonia.Threading;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Interop.Avalonia.Core.Controls;
 using Drawie.Numerics;
@@ -18,6 +19,9 @@ public class PreviewTextureControl : DrawieControl
         set => SetValue(TexturePreviewProperty, value);
     }
 
+    private TexturePreview? preview;
+    private Rect bounds;
+
     static PreviewTextureControl()
     {
         AffectsRender<PreviewTextureControl>(TexturePreviewProperty);
@@ -54,14 +58,20 @@ public class PreviewTextureControl : DrawieControl
             TexturePreview.TextureUpdated -= QueueNextFrame;
     }
 
+    protected override void PrepareToDraw()
+    {
+        preview = TexturePreview;
+        bounds = Bounds;
+    }
+
     public override void Draw(DrawingSurface surface)
     {
-        if (TexturePreview is { Preview: not null } && TexturePreview.Preview is { IsDisposed: false })
+        if (preview is { Preview: not null } && preview.Preview is { IsDisposed: false })
         {
-            VecD scaling = new(Bounds.Size.Width / TexturePreview.Preview.Size.X, Bounds.Size.Height / TexturePreview.Preview.Size.Y);
+            VecD scaling = new(bounds.Size.Width / preview.Preview.Size.X, bounds.Size.Height / preview.Preview.Size.Y);
             surface.Canvas.Save();
             surface.Canvas.Scale((float)scaling.X, (float)scaling.Y);
-            surface.Canvas.DrawSurface(TexturePreview.Preview.DrawingSurface, 0, 0);
+            surface.Canvas.DrawSurface(preview.Preview.DrawingSurface, 0, 0);
             surface.Canvas.Restore();
         }
     }

+ 1 - 1
src/PixiEditor/Views/Windows/BetaExampleFile.cs

@@ -22,7 +22,7 @@ public class BetaExampleFile : IDisposable
         var stream = GetStream();
         var bytes = PixiParser.ReadPreview(stream);
 
-        PreviewImage = Texture.Load(bytes);
+        //PreviewImage = Texture.Load(bytes);
     }
     
     public Stream GetStream() => AssetLoader.Open(new Uri(resourcePath));