Browse Source

UniversalScene -> Scene

flabbet 10 months ago
parent
commit
339a2f7970

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

@@ -1,5 +1,4 @@
-using PixiEditor.ChangeableDocument.Changeables.Animations;
-using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Helpers;
 using PixiEditor.ChangeableDocument.Helpers;
 using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core;

+ 4 - 0
src/PixiEditor.sln

@@ -138,6 +138,8 @@ Global
 		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Debug|x64.Build.0 = Debug|Any CPU
 		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Debug|x64.Build.0 = Debug|Any CPU
 		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Debug|ARM64.ActiveCfg = Debug|Any CPU
 		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Debug|ARM64.ActiveCfg = Debug|Any CPU
 		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Debug|ARM64.Build.0 = Debug|Any CPU
 		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Debug|ARM64.Build.0 = Debug|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Release|x64.ActiveCfg = Release|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Release|x64.Build.0 = Release|Any CPU
 		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Debug|x64.ActiveCfg = Debug|x64
 		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Debug|x64.ActiveCfg = Debug|x64
 		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Debug|x64.Build.0 = Debug|x64
 		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Debug|x64.Build.0 = Debug|x64
 		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Debug|x64.Deploy.0 = Debug|x64
 		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Debug|x64.Deploy.0 = Debug|x64
@@ -585,6 +587,8 @@ Global
 		{F2E992CA-12E3-49F3-B16F-2CEF5B191493}.Steam|x64.Build.0 = Steam|x64
 		{F2E992CA-12E3-49F3-B16F-2CEF5B191493}.Steam|x64.Build.0 = Steam|x64
 		{F2E992CA-12E3-49F3-B16F-2CEF5B191493}.Debug|ARM64.ActiveCfg = Debug|arm64
 		{F2E992CA-12E3-49F3-B16F-2CEF5B191493}.Debug|ARM64.ActiveCfg = Debug|arm64
 		{F2E992CA-12E3-49F3-B16F-2CEF5B191493}.Debug|ARM64.Build.0 = Debug|arm64
 		{F2E992CA-12E3-49F3-B16F-2CEF5B191493}.Debug|ARM64.Build.0 = Debug|arm64
+		{F2E992CA-12E3-49F3-B16F-2CEF5B191493}.Release|x64.ActiveCfg = Release|x64
+		{F2E992CA-12E3-49F3-B16F-2CEF5B191493}.Release|x64.Build.0 = Release|x64
 		{D72E70F3-BF37-432F-B78B-5B247C873852}.Debug|ARM64.ActiveCfg = Debug|Any CPU
 		{D72E70F3-BF37-432F-B78B-5B247C873852}.Debug|ARM64.ActiveCfg = Debug|Any CPU
 		{D72E70F3-BF37-432F-B78B-5B247C873852}.Debug|ARM64.Build.0 = Debug|Any CPU
 		{D72E70F3-BF37-432F-B78B-5B247C873852}.Debug|ARM64.Build.0 = Debug|Any CPU
 		{D72E70F3-BF37-432F-B78B-5B247C873852}.Debug|x64.ActiveCfg = Debug|Any CPU
 		{D72E70F3-BF37-432F-B78B-5B247C873852}.Debug|x64.ActiveCfg = Debug|Any CPU

+ 5 - 3
src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml

@@ -124,11 +124,13 @@
                            VerticalAlignment="Top"
                            VerticalAlignment="Top"
                            ToolSet="{Binding Source={viewModels:MainVM}, Path=ToolsSubViewModel.ActiveToolSet}" 
                            ToolSet="{Binding Source={viewModels:MainVM}, Path=ToolsSubViewModel.ActiveToolSet}" 
                            SwitchToolSetCommand="{xaml:Command Name=PixiEditor.Tools.SwitchToolSet, UseProvided=True}"/>
                            SwitchToolSetCommand="{xaml:Command Name=PixiEditor.Tools.SwitchToolSet, UseProvided=True}"/>
+        <!--
         <rendering:UniversalScene Name="scene" ZIndex="1" SceneRenderer="{Binding Source={viewModels:MainVM DocumentManagerSVM}, Path=ActiveDocument.SceneRenderer}"/>
         <rendering:UniversalScene Name="scene" ZIndex="1" SceneRenderer="{Binding Source={viewModels:MainVM DocumentManagerSVM}, Path=ActiveDocument.SceneRenderer}"/>
-        <!--<rendering:Scene
+        -->
+        <rendering:Scene
             Focusable="False" Name="scene"
             Focusable="False" Name="scene"
             ZIndex="1"
             ZIndex="1"
-            Surface="{Binding TargetBitmap, ElementName=vpUc}"
+            SceneRenderer="{Binding Source={viewModels:MainVM DocumentManagerSVM}, Path=ActiveDocument.SceneRenderer}"
             Document="{Binding Document, ElementName=vpUc, Mode=OneWay}"
             Document="{Binding Document, ElementName=vpUc, Mode=OneWay}"
             UseTouchGestures="{Binding UseTouchGestures, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=OneWay}"
             UseTouchGestures="{Binding UseTouchGestures, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=OneWay}"
             Center="{Binding Center, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=OneWayToSource}"
             Center="{Binding Center, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=OneWayToSource}"
@@ -143,7 +145,7 @@
             FadeOut="{Binding Source={viewModels:ToolVM ColorPickerToolViewModel}, Path=PickOnlyFromReferenceLayer, Mode=OneWay}"
             FadeOut="{Binding Source={viewModels:ToolVM ColorPickerToolViewModel}, Path=PickOnlyFromReferenceLayer, Mode=OneWay}"
             DefaultCursor="{Binding Source={viewModels:MainVM}, Path=ToolsSubViewModel.ToolCursor, Mode=OneWay}"
             DefaultCursor="{Binding Source={viewModels:MainVM}, Path=ToolsSubViewModel.ToolCursor, Mode=OneWay}"
             CheckerImagePath="/Images/CheckerTile.png"
             CheckerImagePath="/Images/CheckerTile.png"
-            ui:RenderOptionsBindable.BitmapInterpolationMode="{Binding Scale, Converter={converters:ScaleToBitmapScalingModeConverter}, RelativeSource={RelativeSource Self}}" />-->
+            ui:RenderOptionsBindable.BitmapInterpolationMode="{Binding Scale, Converter={converters:ScaleToBitmapScalingModeConverter}, RelativeSource={RelativeSource Self}}" />
 
 
         <!--Brush shape overlay is rendered separately, so it doesn't trigger rerender each mouse movement to scene-->
         <!--Brush shape overlay is rendered separately, so it doesn't trigger rerender each mouse movement to scene-->
         <!--I didn't measure it, but I thought that could impact performance-->
         <!--I didn't measure it, but I thought that could impact performance-->

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

@@ -325,7 +325,7 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
         TextBoxFocusBehavior.FallbackFocusElement.Focus();
         TextBoxFocusBehavior.FallbackFocusElement.Focus();
     }
     }
 
 
-    public UniversalScene Scene => scene;
+    public Scene Scene => scene;
 
 
     private void ForceRefreshFinalImage()
     private void ForceRefreshFinalImage()
     {
     {

+ 27 - 70
src/PixiEditor/Views/Rendering/Scene.cs

@@ -10,8 +10,10 @@ using Avalonia.Rendering.SceneGraph;
 using Avalonia.Skia;
 using Avalonia.Skia;
 using Avalonia.Threading;
 using Avalonia.Threading;
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.DataHolders;
+using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.Bridge;
 using PixiEditor.DrawingApi.Core.Bridge;
+using PixiEditor.DrawingApi.Core.Surfaces;
 using PixiEditor.DrawingApi.Skia;
 using PixiEditor.DrawingApi.Skia;
 using PixiEditor.DrawingApi.Skia.Extensions;
 using PixiEditor.DrawingApi.Skia.Extensions;
 using PixiEditor.Extensions.UI.Overlays;
 using PixiEditor.Extensions.UI.Overlays;
@@ -31,9 +33,6 @@ namespace PixiEditor.Views.Rendering;
 
 
 internal class Scene : Zoombox.Zoombox, ICustomHitTest
 internal class Scene : Zoombox.Zoombox, ICustomHitTest
 {
 {
-    public static readonly StyledProperty<Texture> SurfaceProperty = AvaloniaProperty.Register<SurfaceControl, Texture>(
-        nameof(Surface));
-
     public static readonly StyledProperty<DocumentViewModel> DocumentProperty =
     public static readonly StyledProperty<DocumentViewModel> DocumentProperty =
         AvaloniaProperty.Register<Scene, DocumentViewModel>(
         AvaloniaProperty.Register<Scene, DocumentViewModel>(
             nameof(Document));
             nameof(Document));
@@ -55,6 +54,14 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         AvaloniaProperty.Register<Scene, ViewportColorChannels>(
         AvaloniaProperty.Register<Scene, ViewportColorChannels>(
             nameof(Channels));
             nameof(Channels));
 
 
+    public static readonly StyledProperty<SceneRenderer> SceneRendererProperty = AvaloniaProperty.Register<Scene, SceneRenderer>(
+        nameof(SceneRenderer));
+
+    public SceneRenderer SceneRenderer
+    {
+        get => GetValue(SceneRendererProperty);
+        set => SetValue(SceneRendererProperty, value);
+    }
     public Cursor DefaultCursor
     public Cursor DefaultCursor
     {
     {
         get => GetValue(DefaultCursorProperty);
         get => GetValue(DefaultCursorProperty);
@@ -85,12 +92,6 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         set => SetValue(DocumentProperty, value);
         set => SetValue(DocumentProperty, value);
     }
     }
 
 
-    public Texture Surface
-    {
-        get => GetValue(SurfaceProperty);
-        set => SetValue(SurfaceProperty, value);
-    }
-
     public ViewportColorChannels Channels
     public ViewportColorChannels Channels
     {
     {
         get => GetValue(ChannelsProperty);
         get => GetValue(ChannelsProperty);
@@ -110,10 +111,9 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
     {
     {
         AffectsRender<Scene>(BoundsProperty, WidthProperty, HeightProperty, ScaleProperty, AngleRadiansProperty,
         AffectsRender<Scene>(BoundsProperty, WidthProperty, HeightProperty, ScaleProperty, AngleRadiansProperty,
             FlipXProperty,
             FlipXProperty,
-            FlipYProperty, DocumentProperty, SurfaceProperty, AllOverlaysProperty);
+            FlipYProperty, DocumentProperty, AllOverlaysProperty);
 
 
         FadeOutProperty.Changed.AddClassHandler<Scene>(FadeOutChanged);
         FadeOutProperty.Changed.AddClassHandler<Scene>(FadeOutChanged);
-        SurfaceProperty.Changed.AddClassHandler<Scene>(SurfaceChanged);
         CheckerImagePathProperty.Changed.AddClassHandler<Scene>(CheckerImagePathChanged);
         CheckerImagePathProperty.Changed.AddClassHandler<Scene>(CheckerImagePathChanged);
         AllOverlaysProperty.Changed.AddClassHandler<Scene>(ActiveOverlaysChanged);
         AllOverlaysProperty.Changed.AddClassHandler<Scene>(ActiveOverlaysChanged);
         DefaultCursorProperty.Changed.AddClassHandler<Scene>(DefaultCursorChanged);
         DefaultCursorProperty.Changed.AddClassHandler<Scene>(DefaultCursorChanged);
@@ -136,7 +136,7 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
 
 
     public override void Render(DrawingContext context)
     public override void Render(DrawingContext context)
     {
     {
-        if (Surface == null || Surface.IsDisposed || Document == null) return;
+        if (Document == null) return;
 
 
         float angle = (float)MathUtil.RadiansToDegrees(AngleRadians);
         float angle = (float)MathUtil.RadiansToDegrees(AngleRadians);
 
 
@@ -145,8 +145,7 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         RectD dirtyBounds = new RectD(0, 0, Document.Width / resolutionScale, Document.Height / resolutionScale);
         RectD dirtyBounds = new RectD(0, 0, Document.Width / resolutionScale, Document.Height / resolutionScale);
         Rect dirtyRect = new Rect(0, 0, Document.Width / resolutionScale, Document.Height / resolutionScale);
         Rect dirtyRect = new Rect(0, 0, Document.Width / resolutionScale, Document.Height / resolutionScale);
 
 
-        Surface.DrawingSurface.Flush();
-        using var operation = new DrawSceneOperation(Surface, Document, CanvasPos, Scale * resolutionScale,
+        using var operation = new DrawSceneOperation(SceneRenderer.RenderScene, Document, CanvasPos, Scale * resolutionScale,
             resolutionScale, angle,
             resolutionScale, angle,
             FlipX, FlipY,
             FlipX, FlipY,
             dirtyRect,
             dirtyRect,
@@ -159,7 +158,7 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
 
 
         var resolutionTransformation = context.PushTransform(Matrix.CreateScale(resolutionScale, resolutionScale));
         var resolutionTransformation = context.PushTransform(Matrix.CreateScale(resolutionScale, resolutionScale));
 
 
-        DrawCheckerboard(context, dirtyRect, operation.SurfaceRectToRender);
+        DrawCheckerboard(context, dirtyRect, new RectI(0, 0, operation.Document.SizeBindable.X, operation.Document.SizeBindable.Y));
 
 
         resolutionTransformation.Dispose();
         resolutionTransformation.Dispose();
 
 
@@ -374,10 +373,14 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
 
 
     private float CalculateResolutionScale()
     private float CalculateResolutionScale()
     {
     {
+        // TODO: Implement;
+        return 1;
+        /*
         float scaleX = (float)Document.Width / Surface.Size.X;
         float scaleX = (float)Document.Width / Surface.Size.X;
         float scaleY = (float)Document.Height / Surface.Size.Y;
         float scaleY = (float)Document.Height / Surface.Size.Y;
         var scaleUniform = Math.Min(scaleX, scaleY);
         var scaleUniform = Math.Min(scaleX, scaleY);
         return scaleUniform;
         return scaleUniform;
+    */
     }
     }
 
 
     private void CaptureOverlay(Overlay? overlay, IPointer pointer)
     private void CaptureOverlay(Overlay? overlay, IPointer pointer)
@@ -479,7 +482,6 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
 
 
 internal class DrawSceneOperation : SkiaDrawOperation
 internal class DrawSceneOperation : SkiaDrawOperation
 {
 {
-    public Texture Surface { get; set; }
     public DocumentViewModel Document { get; set; }
     public DocumentViewModel Document { get; set; }
     public VecD ContentPosition { get; set; }
     public VecD ContentPosition { get; set; }
     public double Scale { get; set; }
     public double Scale { get; set; }
@@ -489,17 +491,16 @@ internal class DrawSceneOperation : SkiaDrawOperation
     public bool FlipY { get; set; }
     public bool FlipY { get; set; }
     public Rect ViewportBounds { get; }
     public Rect ViewportBounds { get; }
 
 
-    public RectI SurfaceRectToRender { get; }
-
-    private bool hardwareAccelerationAvailable = DrawingBackendApi.Current.IsHardwareAccelerated;
+    
+    public Action<DrawingSurface> RenderScene;
 
 
     private double opacity;
     private double opacity;
 
 
-    public DrawSceneOperation(Texture surface, DocumentViewModel document, VecD contentPosition, double scale,
+    public DrawSceneOperation(Action<DrawingSurface> renderAction, DocumentViewModel document, VecD contentPosition, double scale,
         double resolutionScale,
         double resolutionScale,
         double angle, bool flipX, bool flipY, Rect dirtyBounds, Rect viewportBounds, double opacity) : base(dirtyBounds)
         double angle, bool flipX, bool flipY, Rect dirtyBounds, Rect viewportBounds, double opacity) : base(dirtyBounds)
     {
     {
-        Surface = surface;
+        RenderScene = renderAction; 
         Document = document;
         Document = document;
         ContentPosition = contentPosition;
         ContentPosition = contentPosition;
         Scale = scale;
         Scale = scale;
@@ -508,47 +509,27 @@ internal class DrawSceneOperation : SkiaDrawOperation
         FlipY = flipY;
         FlipY = flipY;
         ViewportBounds = viewportBounds;
         ViewportBounds = viewportBounds;
         ResolutionScale = resolutionScale;
         ResolutionScale = resolutionScale;
-        SurfaceRectToRender = FindRectToRender((float)scale);
         this.opacity = opacity;
         this.opacity = opacity;
     }
     }
 
 
     public override void Render(ISkiaSharpApiLease lease)
     public override void Render(ISkiaSharpApiLease lease)
     {
     {
-        if (Surface == null || Surface.IsDisposed || Document == null) return;
+        if (Document == null) return;
 
 
         SKCanvas canvas = lease.SkCanvas;
         SKCanvas canvas = lease.SkCanvas;
 
 
         int count = canvas.Save();
         int count = canvas.Save();
 
 
-        if (SurfaceRectToRender.IsZeroOrNegativeArea)
-        {
-            canvas.RestoreToCount(count);
-            return;
-        }
-
         using var ctx = DrawingBackendApi.Current.RenderOnDifferentGrContext(lease.GrContext);
         using var ctx = DrawingBackendApi.Current.RenderOnDifferentGrContext(lease.GrContext);
 
 
         using SKPaint paint = new SKPaint();
         using SKPaint paint = new SKPaint();
         paint.Color = paint.Color.WithAlpha((byte)(opacity * 255));
         paint.Color = paint.Color.WithAlpha((byte)(opacity * 255));
-        
-        RenderOnionSkin(canvas, paint);
 
 
-        /*var matrixValues = new float[ColorMatrix.Width * ColorMatrix.Height];
-        ColorMatrix.TryGetMembers(matrixValues);*/
-
-
-        if (!hardwareAccelerationAvailable)
-        {
-            // snapshotting wanted region on CPU is faster than rendering whole surface on CPU,
-            // but slower than rendering whole surface on GPU
-            using Image snapshot = Surface.DrawingSurface.Snapshot(SurfaceRectToRender);
-            canvas.DrawImage((SKImage)snapshot.Native, SurfaceRectToRender.X, SurfaceRectToRender.Y, paint);
-        }
-        else
-        {
-            canvas.DrawSurface(Surface.DrawingSurface.Native as SKSurface, 0, 0, paint);
-        }
+        RenderOnionSkin(canvas, paint);
 
 
+        DrawingSurface surface = DrawingSurface.FromNative(lease.SkSurface);
+        RenderScene?.Invoke(surface);
+        
         canvas.RestoreToCount(count);
         canvas.RestoreToCount(count);
     }
     }
 
 
@@ -573,30 +554,6 @@ internal class DrawSceneOperation : SkiaDrawOperation
         }
         }
     }
     }
 
 
-
-    private RectI FindRectToRender(float finalScale)
-    {
-        ShapeCorners surfaceInViewportSpace = SurfaceToViewport(new RectI(VecI.Zero, Surface.Size), finalScale);
-        RectI surfaceBoundsInViewportSpace = (RectI)surfaceInViewportSpace.AABBBounds.RoundOutwards();
-        RectI viewportBoundsInViewportSpace =
-            (RectI)(new RectD(ViewportBounds.X, ViewportBounds.Y, ViewportBounds.Width, ViewportBounds.Height))
-            .RoundOutwards();
-        RectI firstIntersectionInViewportSpace = surfaceBoundsInViewportSpace.Intersect(viewportBoundsInViewportSpace);
-        ShapeCorners firstIntersectionInSurfaceSpace = ViewportToSurface(firstIntersectionInViewportSpace, finalScale);
-        RectI firstIntersectionBoundsInSurfaceSpace = (RectI)firstIntersectionInSurfaceSpace.AABBBounds.RoundOutwards();
-
-        ShapeCorners viewportInSurfaceSpace = ViewportToSurface(viewportBoundsInViewportSpace, finalScale);
-        RectD viewportBoundsInSurfaceSpace = viewportInSurfaceSpace.AABBBounds;
-        RectD surfaceBoundsInSurfaceSpace = new(VecD.Zero, Surface.Size);
-        RectI secondIntersectionInSurfaceSpace =
-            (RectI)viewportBoundsInSurfaceSpace.Intersect(surfaceBoundsInSurfaceSpace).RoundOutwards();
-
-        //Inflate makes sure rounding doesn't cut any pixels.
-        RectI surfaceRectToRender =
-            firstIntersectionBoundsInSurfaceSpace.Intersect(secondIntersectionInSurfaceSpace).Inflate(1);
-        return surfaceRectToRender.Intersect(new RectI(VecI.Zero, Surface.Size)); // Clamp to surface size
-    }
-
     private ShapeCorners ViewportToSurface(RectI viewportRect, float scale)
     private ShapeCorners ViewportToSurface(RectI viewportRect, float scale)
     {
     {
         return new ShapeCorners()
         return new ShapeCorners()

+ 0 - 113
src/PixiEditor/Views/Rendering/UniversalScene.cs

@@ -1,113 +0,0 @@
-using Avalonia;
-using Avalonia.Media;
-using Avalonia.Rendering;
-using Avalonia.Rendering.SceneGraph;
-using Avalonia.Skia;
-using Avalonia.Threading;
-using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
-using PixiEditor.ChangeableDocument.Rendering;
-using PixiEditor.DrawingApi.Core;
-using PixiEditor.DrawingApi.Core.Surfaces;
-using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
-using PixiEditor.Numerics;
-using PixiEditor.Views.Visuals;
-using Colors = PixiEditor.DrawingApi.Core.ColorsImpl.Colors;
-using Point = Avalonia.Point;
-
-namespace PixiEditor.Views.Rendering;
-
-public class UniversalScene : Zoombox.Zoombox, ICustomHitTest
-{
-    public static readonly StyledProperty<SceneRenderer> SceneRendererProperty = AvaloniaProperty.Register<UniversalScene, SceneRenderer>(
-        nameof(SceneRenderer));
-
-    public SceneRenderer SceneRenderer
-    {
-        get => GetValue(SceneRendererProperty);
-        set => SetValue(SceneRendererProperty, value);
-    }
-    
-    public override void Render(DrawingContext context)
-    {
-        // TODO: Do bounds pass, that will be used to calculate dirty bounds
-        
-        if (SceneRenderer is null)
-        {
-            return;
-        }
-        
-        using var drawOperation = new DrawUniversalSceneOperation(SceneRenderer.RenderScene, Bounds, CalculateTransformMatrix());
-        context.Custom(drawOperation);
-
-        Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Render);
-    }
-
-    bool ICustomHitTest.HitTest(Point point)
-    {
-        return Bounds.Contains(point);
-    }
-
-    private Matrix CalculateTransformMatrix()
-    {
-        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));
-        return transform;
-    }
-}
-
-class DrawUniversalSceneOperation : SkiaDrawOperation
-{
-    public Action<DrawingSurface> RenderScene;
-    public Matrix TransformMatrix { get; set; }
-
-    public DrawUniversalSceneOperation(Action<DrawingSurface> renderAction, Rect dirtyBounds,
-        Matrix calculateTransformMatrix) : base(dirtyBounds)
-    {
-        RenderScene = renderAction;
-        TransformMatrix = calculateTransformMatrix;
-    }
-
-    public override bool Equals(ICustomDrawOperation? other)
-    {
-        return false;
-    }
-
-    public override void Render(ISkiaSharpApiLease lease)
-    {
-        var originalMatrix = lease.SkSurface.Canvas.TotalMatrix;
-
-        lease.SkSurface.Canvas.SetMatrix(TransformMatrix.ToSKMatrix());
-        DrawingSurface surface = DrawingSurface.FromNative(lease.SkSurface);
-        RenderScene?.Invoke(surface);
-
-        DrawDebugGrid(lease.SkSurface.Canvas);
-        lease.SkSurface.Canvas.SetMatrix(originalMatrix);
-    }
-
-    private void DrawDebugGrid(SKCanvas canvas)
-    {
-        canvas.DrawText("(0, 0)", 5, -5, new SKPaint() { Color = SKColors.White });
-        canvas.DrawCircle(0, 0, 5, new SKPaint() { Color = SKColors.White });
-        
-        canvas.DrawText("(100, 100)", 105, 95, new SKPaint() { Color = SKColors.White });
-        canvas.DrawCircle(100, 100, 5, new SKPaint() { Color = SKColors.White });
-        
-        canvas.DrawText("(-100, -100)", -105, -95, new SKPaint() { Color = SKColors.White });
-        canvas.DrawCircle(-100, -100, 5, new SKPaint() { Color = SKColors.White });
-        
-        canvas.DrawText("(100, -100)", 105, -95, new SKPaint() { Color = SKColors.White });
-        canvas.DrawCircle(100, -100, 5, new SKPaint() { Color = SKColors.White });
-        
-        canvas.DrawText("(-100, 100)", -105, 95, new SKPaint() { Color = SKColors.White });
-        canvas.DrawCircle(-100, 100, 5, new SKPaint() { Color = SKColors.White });
-        
-        for (int i = -1000; i < 1000; i += 100)
-        {
-            canvas.DrawLine(i, -1000, i, 1000, new SKPaint() { Color = SKColors.White });
-            canvas.DrawLine(-1000, i, 1000, i, new SKPaint() { Color = SKColors.White });
-        }
-    }
-}