Browse Source

Render api independend custom rendering

Krzysztof Krysiński 1 year ago
parent
commit
5622ede66c

+ 2 - 2
src/PixiEditor.AvaloniaUI.Desktop/Program.cs

@@ -18,10 +18,10 @@ public class Program
         => AppBuilder.Configure<App>()
         => AppBuilder.Configure<App>()
             .UsePlatformDetect()
             .UsePlatformDetect()
             .WithInterFont()
             .WithInterFont()
-            .With(new Win32PlatformOptions()
+            /*.With(new Win32PlatformOptions()
             {
             {
                 RenderingMode = new[] { Win32RenderingMode.Wgl },
                 RenderingMode = new[] { Win32RenderingMode.Wgl },
                 OverlayPopups = true
                 OverlayPopups = true
-            })
+            })*/
             .LogToTrace();
             .LogToTrace();
 }
 }

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

@@ -97,7 +97,7 @@ internal partial class FixedViewport : UserControl, INotifyPropertyChanged
 
 
     private void ForceRefreshFinalImage()
     private void ForceRefreshFinalImage()
     {
     {
-        mainImage.RequestNextFrameRendering();
+        mainImage.InvalidateVisual();
     }
     }
 
 
     private ViewportInfo GetLocation()
     private ViewportInfo GetLocation()

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

@@ -310,7 +310,7 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
 
 
     private void ForceRefreshFinalImage()
     private void ForceRefreshFinalImage()
     {
     {
-        Scene.RequestNextFrameRendering();
+        Scene.InvalidateVisual();
         MainImage?.InvalidateVisual();
         MainImage?.InvalidateVisual();
     }
     }
 
 

+ 47 - 34
src/PixiEditor.AvaloniaUI/Views/Visuals/Scene.cs

@@ -1,17 +1,21 @@
 using System.Diagnostics;
 using System.Diagnostics;
 using System.Linq;
 using System.Linq;
 using Avalonia;
 using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Media;
 using Avalonia.OpenGL;
 using Avalonia.OpenGL;
 using Avalonia.OpenGL.Controls;
 using Avalonia.OpenGL.Controls;
+using Avalonia.Rendering.SceneGraph;
+using Avalonia.Skia;
 using ChunkyImageLib;
 using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.DataHolders;
 using PixiEditor.AvaloniaUI.ViewModels.Document;
 using PixiEditor.AvaloniaUI.ViewModels.Document;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface.ImageData;
+using Image = PixiEditor.DrawingApi.Core.Surface.ImageData.Image;
 
 
 namespace PixiEditor.AvaloniaUI.Views.Visuals;
 namespace PixiEditor.AvaloniaUI.Views.Visuals;
 
 
-internal class Scene : OpenGlControlBase
+internal class Scene : Control
 {
 {
     public static readonly StyledProperty<Surface> SurfaceProperty = AvaloniaProperty.Register<SurfaceControl, Surface>(
     public static readonly StyledProperty<Surface> SurfaceProperty = AvaloniaProperty.Register<SurfaceControl, Surface>(
         nameof(Surface));
         nameof(Surface));
@@ -77,10 +81,6 @@ internal class Scene : OpenGlControlBase
         set { SetValue(FlipYProperty, value); }
         set { SetValue(FlipYProperty, value); }
     }
     }
 
 
-    private SKSurface _outputSurface;
-    private SKPaint _paint = new SKPaint();
-    private GRContext? gr;
-
     static Scene()
     static Scene()
     {
     {
         AffectsRender<Scene>(BoundsProperty, WidthProperty, HeightProperty, ScaleProperty, AngleProperty, FlipXProperty, FlipYProperty, ContentPositionProperty, DocumentProperty, SurfaceProperty);
         AffectsRender<Scene>(BoundsProperty, WidthProperty, HeightProperty, ScaleProperty, AngleProperty, FlipXProperty, FlipYProperty, ContentPositionProperty, DocumentProperty, SurfaceProperty);
@@ -94,31 +94,55 @@ internal class Scene : OpenGlControlBase
         ClipToBounds = true;
         ClipToBounds = true;
     }
     }
 
 
-    protected override void OnOpenGlInit(GlInterface gl)
+    public override void Render(DrawingContext context)
     {
     {
-        gr = GRContext.CreateGl(GRGlInterface.Create(gl.GetProcAddress));
-        CreateOutputSurface();
+        if (Surface == null || Document == null) return;
+
+        var operation = new DrawSceneOperation(Surface, Document, ContentPosition, Scale, Angle, FlipX, FlipY, Bounds);
+        context.Custom(operation);
     }
     }
 
 
-    private void CreateOutputSurface()
+    private static void BoundsChanged(Scene sender, AvaloniaPropertyChangedEventArgs e)
     {
     {
-        if (gr == null) return;
+        sender.InvalidateVisual();
+    }
 
 
-        _outputSurface?.Dispose();
-        GRGlFramebufferInfo frameBuffer = new GRGlFramebufferInfo(0, SKColorType.Rgba8888.ToGlSizedFormat());
-        GRBackendRenderTarget desc = new GRBackendRenderTarget((int)Bounds.Width, (int)Bounds.Height, 4, 0, frameBuffer);
-        _outputSurface = SKSurface.Create(gr, desc, GRSurfaceOrigin.BottomLeft, SKImageInfo.PlatformColorType);
+    private static void RequestRendering(Scene sender, AvaloniaPropertyChangedEventArgs e)
+    {
+        sender.InvalidateVisual();
     }
     }
+}
+
+internal class DrawSceneOperation : SkiaDrawOperation
+{
+    public Surface Surface { get; set; }
+    public DocumentViewModel Document { get; set; }
+    public VecI ContentPosition { get; set; }
+    public double Scale { get; set; }
+    public double Angle { get; set; }
+    public bool FlipX { get; set; }
+    public bool FlipY { get; set; }
+
+    private SKPaint _paint = new SKPaint();
 
 
-    protected override void OnOpenGlRender(GlInterface gl, int fb)
+    public DrawSceneOperation(Surface surface, DocumentViewModel document, VecI contentPosition, double scale, double angle, bool flipX, bool flipY, Rect bounds) : base(bounds)
+    {
+        Surface = surface;
+        Document = document;
+        ContentPosition = contentPosition;
+        Scale = scale;
+        Angle = angle;
+        FlipX = flipX;
+        FlipY = flipY;
+    }
+
+    public override void Render(ISkiaSharpApiLease lease)
     {
     {
         if (Surface == null || Document == null) return;
         if (Surface == null || Document == null) return;
 
 
-        SKCanvas canvas = _outputSurface.Canvas;
+        SKCanvas canvas = lease.SkCanvas;
 
 
         canvas.Save();
         canvas.Save();
-        canvas.ClipRect(new SKRect(0, 0, (float)Bounds.Width, (float)Bounds.Height));
-        canvas.Clear(SKColors.Transparent);
 
 
         float finalScale = CalculateFinalScale();
         float finalScale = CalculateFinalScale();
 
 
@@ -128,7 +152,6 @@ internal class Scene : OpenGlControlBase
         {
         {
             canvas.Restore();
             canvas.Restore();
             canvas.Flush();
             canvas.Flush();
-            RequestNextFrameRendering();
             return;
             return;
         }
         }
 
 
@@ -159,7 +182,7 @@ internal class Scene : OpenGlControlBase
     {
     {
         ShapeCorners surfaceInViewportSpace = SurfaceToViewport(new RectI(VecI.Zero, Surface.Size), finalScale);
         ShapeCorners surfaceInViewportSpace = SurfaceToViewport(new RectI(VecI.Zero, Surface.Size), finalScale);
         RectI surfaceBoundsInViewportSpace = (RectI)surfaceInViewportSpace.AABBBounds.RoundOutwards();
         RectI surfaceBoundsInViewportSpace = (RectI)surfaceInViewportSpace.AABBBounds.RoundOutwards();
-        RectI viewportBoundsInViewportSpace = (RectI)(new RectD(FinalBounds.X, FinalBounds.Y, FinalBounds.Width, FinalBounds.Height)).RoundOutwards();
+        RectI viewportBoundsInViewportSpace = (RectI)(new RectD(Bounds.X, Bounds.Y, Bounds.Width, Bounds.Height)).RoundOutwards();
         RectI firstIntersectionInViewportSpace = surfaceBoundsInViewportSpace.Intersect(viewportBoundsInViewportSpace);
         RectI firstIntersectionInViewportSpace = surfaceBoundsInViewportSpace.Intersect(viewportBoundsInViewportSpace);
         ShapeCorners firstIntersectionInSurfaceSpace = ViewportToSurface(firstIntersectionInViewportSpace, finalScale);
         ShapeCorners firstIntersectionInSurfaceSpace = ViewportToSurface(firstIntersectionInViewportSpace, finalScale);
         RectI firstIntersectionBoundsInSurfaceSpace = (RectI)firstIntersectionInSurfaceSpace.AABBBounds.RoundOutwards();
         RectI firstIntersectionBoundsInSurfaceSpace = (RectI)firstIntersectionInSurfaceSpace.AABBBounds.RoundOutwards();
@@ -193,7 +216,7 @@ internal class Scene : OpenGlControlBase
             BottomRight = ViewportToSurface(viewportRect.BottomRight, scale),
             BottomRight = ViewportToSurface(viewportRect.BottomRight, scale),
         };
         };
     }
     }
-    
+
     private ShapeCorners SurfaceToViewport(RectI viewportRect, float scale)
     private ShapeCorners SurfaceToViewport(RectI viewportRect, float scale)
     {
     {
         return new ShapeCorners()
         return new ShapeCorners()
@@ -273,18 +296,8 @@ internal class Scene : OpenGlControlBase
         return pos;
         return pos;
     }
     }
 
 
-    private static void BoundsChanged(Scene sender, AvaloniaPropertyChangedEventArgs e)
-    {
-        if (e.NewValue is Rect bounds)
-        {
-            sender.CreateOutputSurface();
-        }
-
-        sender.RequestNextFrameRendering();
-    }
-
-    private static void RequestRendering(Scene sender, AvaloniaPropertyChangedEventArgs e)
+    public override bool Equals(ICustomDrawOperation? other)
     {
     {
-        sender.RequestNextFrameRendering();
+        return false;
     }
     }
 }
 }

+ 37 - 0
src/PixiEditor.AvaloniaUI/Views/Visuals/SkiaDrawOperation.cs

@@ -0,0 +1,37 @@
+using Avalonia;
+using Avalonia.Media;
+using Avalonia.Platform;
+using Avalonia.Rendering.SceneGraph;
+using Avalonia.Skia;
+
+namespace PixiEditor.AvaloniaUI.Views.Visuals;
+
+internal abstract class SkiaDrawOperation : ICustomDrawOperation
+{
+    public Rect Bounds { get; }
+
+    public SkiaDrawOperation(Rect bounds)
+    {
+        Bounds = bounds;
+    }
+
+    public abstract bool Equals(ICustomDrawOperation? other);
+
+    public virtual void Dispose() { }
+
+    public bool HitTest(Point p) => false;
+
+    public void Render(ImmediateDrawingContext context)
+    {
+        if (!context.TryGetFeature(out ISkiaSharpApiLeaseFeature leaseFeature))
+        {
+            throw new InvalidOperationException("SkiaSharp API lease feature is not available.");
+        }
+
+        using var lease = leaseFeature.Lease();
+
+        Render(lease);
+    }
+
+    public abstract void Render(ISkiaSharpApiLease lease);
+}

+ 64 - 53
src/PixiEditor.AvaloniaUI/Views/Visuals/SurfaceControl.cs

@@ -1,14 +1,18 @@
 using Avalonia;
 using Avalonia;
+using Avalonia.Controls;
 using Avalonia.Media;
 using Avalonia.Media;
 using Avalonia.OpenGL;
 using Avalonia.OpenGL;
 using Avalonia.OpenGL.Controls;
 using Avalonia.OpenGL.Controls;
+using Avalonia.Platform;
+using Avalonia.Rendering.SceneGraph;
+using Avalonia.Skia;
 using Avalonia.Threading;
 using Avalonia.Threading;
 using ChunkyImageLib;
 using ChunkyImageLib;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Numerics;
 
 
 namespace PixiEditor.AvaloniaUI.Views.Visuals;
 namespace PixiEditor.AvaloniaUI.Views.Visuals;
 
 
-public class SurfaceControl : OpenGlControlBase
+internal class SurfaceControl : Control
 {
 {
     public static readonly StyledProperty<Surface> SurfaceProperty = AvaloniaProperty.Register<SurfaceControl, Surface>(
     public static readonly StyledProperty<Surface> SurfaceProperty = AvaloniaProperty.Register<SurfaceControl, Surface>(
         nameof(Surface));
         nameof(Surface));
@@ -82,34 +86,77 @@ public class SurfaceControl : OpenGlControlBase
         }
         }
     }
     }
 
 
-    protected override void OnOpenGlInit(GlInterface gl)
+    public override void Render(DrawingContext context)
     {
     {
-        gr = GRContext.CreateGl(GRGlInterface.Create(gl.GetProcAddress));
-        CreateWorkingSurface();
+        if (Surface == null)
+        {
+            return;
+        }
+
+        var bounds = new Rect(Bounds.Size);
+        var operation = new DrawSurfaceOperation(bounds, Surface, Stretch);
+        context.Custom(operation);
     }
     }
 
 
-    private void CreateWorkingSurface()
+    private void SurfaceChanged(RectD? changedRect)
     {
     {
-        if (gr == null) return;
+        if (changedRect.HasValue)
+        {
+            var rect = changedRect.Value;
+            var rectI = new RectI((int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height);
+            nextDirtyRect = nextDirtyRect?.Union(rectI) ?? rectI;
+        }
+        else
+        {
+            nextDirtyRect = null;
+        }
+
+        Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Render);
+    }
+
+    private static void BoundsChanged(SurfaceControl sender, AvaloniaPropertyChangedEventArgs e)
+    {
+        Dispatcher.UIThread.Post(sender.InvalidateVisual, DispatcherPriority.Render);
+    }
+
+    private static void Rerender(SurfaceControl sender, AvaloniaPropertyChangedEventArgs e)
+    {
+        if (e.OldValue is Surface oldSurface)
+        {
+            oldSurface.Changed -= sender.SurfaceChanged;
+        }
+        if (e.NewValue is Surface newSurface)
+        {
+            newSurface.Changed += sender.SurfaceChanged;
+        }
+
+        Dispatcher.UIThread.Post(sender.InvalidateVisual, DispatcherPriority.Render);
+    }
+}
+
+internal class DrawSurfaceOperation : SkiaDrawOperation
+{
+    public Surface Surface { get; }
+    public Stretch Stretch { get; }
+
+    private SKPaint _paint = new SKPaint();
 
 
-        _workingSurface?.Dispose();
-        GRGlFramebufferInfo frameBuffer = new GRGlFramebufferInfo(0, SKColorType.Rgba8888.ToGlSizedFormat());
-        GRBackendRenderTarget desc = new GRBackendRenderTarget((int)Bounds.Width, (int)Bounds.Height, 4, 0, frameBuffer);
-        _workingSurface = SKSurface.Create(gr, desc, GRSurfaceOrigin.BottomLeft, SKImageInfo.PlatformColorType);
+    public DrawSurfaceOperation(Rect bounds, Surface surface, Stretch stretch) : base(bounds)
+    {
+        Surface = surface;
+        Stretch = stretch;
     }
     }
 
 
-    protected override void OnOpenGlRender(GlInterface gl, int fb)
+    public override void Render(ISkiaSharpApiLease lease)
     {
     {
+        SKCanvas canvas = lease.SkCanvas;
         if (Surface == null)
         if (Surface == null)
         {
         {
             return;
             return;
         }
         }
 
 
-        SKCanvas canvas = _workingSurface.Canvas;
         canvas.Save();
         canvas.Save();
-        canvas.ClipRect(new SKRect(0, 0, (float)Bounds.Width, (float)Bounds.Height));
         ScaleCanvas(canvas);
         ScaleCanvas(canvas);
-        canvas.Clear(SKColors.Transparent);
 
 
         //TODO: Implement dirty rect rendering
         //TODO: Implement dirty rect rendering
         /*if (nextDirtyRect.HasValue)
         /*if (nextDirtyRect.HasValue)
@@ -123,11 +170,10 @@ public class SurfaceControl : OpenGlControlBase
         }*/
         }*/
 
 
         canvas.DrawSurface((SKSurface)Surface.DrawingSurface.Native, new SKPoint(0, 0), _paint);
         canvas.DrawSurface((SKSurface)Surface.DrawingSurface.Native, new SKPoint(0, 0), _paint);
-
         canvas.Restore();
         canvas.Restore();
-        canvas.Flush();
     }
     }
 
 
+
     private void ScaleCanvas(SKCanvas canvas)
     private void ScaleCanvas(SKCanvas canvas)
     {
     {
         if (Stretch == Stretch.Fill)
         if (Stretch == Stretch.Fill)
@@ -156,43 +202,8 @@ public class SurfaceControl : OpenGlControlBase
         }
         }
     }
     }
 
 
-    private void SurfaceChanged(RectD? changedRect)
+    public override bool Equals(ICustomDrawOperation? other)
     {
     {
-        if (changedRect.HasValue)
-        {
-            var rect = changedRect.Value;
-            var rectI = new RectI((int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height);
-            nextDirtyRect = nextDirtyRect?.Union(rectI) ?? rectI;
-        }
-        else
-        {
-            nextDirtyRect = null;
-        }
-
-        Dispatcher.UIThread.Post(RequestNextFrameRendering, DispatcherPriority.Render);
-    }
-
-    private static void BoundsChanged(SurfaceControl sender, AvaloniaPropertyChangedEventArgs e)
-    {
-        if (e.NewValue is Rect bounds)
-        {
-            sender.CreateWorkingSurface();
-        }
-
-        Dispatcher.UIThread.Post(sender.RequestNextFrameRendering, DispatcherPriority.Render);
-    }
-
-    private static void Rerender(SurfaceControl sender, AvaloniaPropertyChangedEventArgs e)
-    {
-        if (e.OldValue is Surface oldSurface)
-        {
-            oldSurface.Changed -= sender.SurfaceChanged;
-        }
-        if (e.NewValue is Surface newSurface)
-        {
-            newSurface.Changed += sender.SurfaceChanged;
-        }
-
-        Dispatcher.UIThread.Post(sender.RequestNextFrameRendering, DispatcherPriority.Render);
+        return other is DrawSurfaceOperation otherOp && otherOp.Surface == Surface && otherOp.Stretch == Stretch;
     }
     }
 }
 }

+ 1 - 0
src/PixiEditor.UI.Common/Controls/Button.axaml

@@ -12,6 +12,7 @@
         <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}" />
         <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}" />
         <Setter Property="HorizontalContentAlignment" Value="Center" />
         <Setter Property="HorizontalContentAlignment" Value="Center" />
         <Setter Property="VerticalContentAlignment" Value="Center" />
         <Setter Property="VerticalContentAlignment" Value="Center" />
+        <Setter Property="FontSize" Value="15" />
         <Setter Property="Padding" Value="5"/>
         <Setter Property="Padding" Value="5"/>
         <Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}"/>
         <Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}"/>
         <Setter Property="Template">
         <Setter Property="Template">