Browse Source

Use GetFeature + API lease approach for accessing SKCanvas

Nikita Tsukanov 3 years ago
parent
commit
3717aec4f5

+ 4 - 2
samples/RenderDemo/Pages/CustomSkiaPage.cs

@@ -40,14 +40,16 @@ namespace RenderDemo.Pages
             static Stopwatch St = Stopwatch.StartNew();
             public void Render(IDrawingContextImpl context)
             {
-                var canvas = (context as ISkiaDrawingContextImpl)?.SkCanvas;
-                if (canvas == null)
+                var leaseFeature = context.GetFeature<ISkiaSharpApiLeaseFeature>();
+                if (leaseFeature == null)
                     using (var c = new DrawingContext(context, false))
                     {
                         c.DrawText(_noSkia, new Point());
                     }
                 else
                 {
+                    using var lease = leaseFeature.Lease();
+                    var canvas = lease.SkCanvas;
                     canvas.Save();
                     // create the first shader
                     var colors = new SKColor[] {

+ 2 - 0
src/Avalonia.Base/Media/DrawingGroup.cs

@@ -228,6 +228,8 @@ namespace Avalonia.Media
                 throw new NotImplementedException();
             }
 
+            public object? GetFeature(Type t) => null;
+
             public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default)
             {
                 throw new NotImplementedException();

+ 14 - 0
src/Avalonia.Base/Platform/IDrawingContextImpl.cs

@@ -172,6 +172,20 @@ namespace Avalonia.Platform
         /// </summary>
         /// <param name="custom">Custom draw operation</param>
         void Custom(ICustomDrawOperation custom);
+
+        /// <summary>
+        /// Attempts to get an optional feature from the drawing context implementation
+        /// </summary>
+        object? GetFeature(Type t);
+    }
+
+    public static class DrawingContextImplExtensions
+    {
+        /// <summary>
+        /// Attempts to get an optional feature from the drawing context implementation
+        /// </summary>
+        public static T? GetFeature<T>(this IDrawingContextImpl context) where T : class =>
+            (T?)context.GetFeature(typeof(T));
     }
 
     public interface IDrawingContextLayerImpl : IRenderTargetBitmapImpl

+ 2 - 0
src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs

@@ -156,6 +156,8 @@ internal class CompositionDrawingContext : IDrawingContextImpl, IDrawingContextW
             ++_drawOperationIndex;
     }
 
+    public object? GetFeature(Type t) => null;
+
     /// <inheritdoc/>
     public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
     {

+ 3 - 0
src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs

@@ -1,3 +1,4 @@
+using System;
 using System.Numerics;
 using Avalonia.Media;
 using Avalonia.Media.Imaging;
@@ -155,6 +156,8 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, IDrawingCont
         _impl.Custom(custom);
     }
 
+    public object? GetFeature(Type t) => _impl.GetFeature(t);
+
     public class VisualBrushRenderer : IVisualBrushRenderer
     {
         public CompositionDrawList? VisualBrushDrawList { get; set; }

+ 2 - 0
src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs

@@ -203,6 +203,8 @@ namespace Avalonia.Rendering.SceneGraph
                 ++_drawOperationindex;
         }
 
+        public object? GetFeature(Type t) => null;
+
         /// <inheritdoc/>
         public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
         {

+ 5 - 0
src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@@ -416,6 +416,11 @@ namespace Avalonia.Headless
 
             }
 
+            public object GetFeature(Type t)
+            {
+                return null;
+            }
+
             public void DrawLine(IPen pen, Point p1, Point p2)
             {
             }

+ 88 - 9
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@@ -10,6 +10,7 @@ using Avalonia.Rendering.SceneGraph;
 using Avalonia.Rendering.Utilities;
 using Avalonia.Utilities;
 using Avalonia.Media.Imaging;
+using JetBrains.Annotations;
 using SkiaSharp;
 
 namespace Avalonia.Skia
@@ -17,7 +18,7 @@ namespace Avalonia.Skia
     /// <summary>
     /// Skia based drawing context.
     /// </summary>
-    internal class DrawingContextImpl : IDrawingContextImpl, ISkiaDrawingContextImpl, IDrawingContextWithAcrylicLikeSupport
+    internal class DrawingContextImpl : IDrawingContextImpl, IDrawingContextWithAcrylicLikeSupport
     {
         private IDisposable[] _disposables;
         private readonly Vector _dpi;
@@ -38,7 +39,8 @@ namespace Avalonia.Skia
         private readonly SKPaint _fillPaint = new SKPaint();
         private readonly SKPaint _boxShadowPaint = new SKPaint();
         private static SKShader s_acrylicNoiseShader;
-        private readonly ISkiaGpuRenderSession _session; 
+        private readonly ISkiaGpuRenderSession _session;
+        private bool _leased = false;
 
         /// <summary>
         /// Context create info.
@@ -83,6 +85,47 @@ namespace Avalonia.Skia
             public ISkiaGpuRenderSession CurrentSession;
         }
 
+        class SkiaLeaseFeature : ISkiaSharpApiLeaseFeature
+        {
+            private readonly DrawingContextImpl _context;
+
+            public SkiaLeaseFeature(DrawingContextImpl context)
+            {
+                _context = context;
+            }
+
+            public ISkiaSharpApiLease Lease()
+            {
+                _context.CheckLease();
+                return new ApiLease(_context);
+            }
+
+            class ApiLease : ISkiaSharpApiLease
+            {
+                private DrawingContextImpl _context;
+                private readonly SKMatrix _revertTransform;
+
+                public ApiLease(DrawingContextImpl context)
+                {
+                    _revertTransform = context.Canvas.TotalMatrix;
+                    _context = context;
+                    _context._leased = true;
+                }
+
+                public SKCanvas SkCanvas => _context.Canvas;
+                public GRContext GrContext => _context.GrContext;
+                public SKSurface SkSurface => _context.Surface;
+                public double CurrentOpacity => _context._currentOpacity;
+                
+                public void Dispose()
+                {
+                    _context.Canvas.SetMatrix(_revertTransform);
+                    _context._leased = false;
+                    _context = null;
+                }
+            }
+        }
+        
         /// <summary>
         /// Create new drawing context.
         /// </summary>
@@ -123,20 +166,23 @@ namespace Avalonia.Skia
         public SKCanvas Canvas { get; }
         public SKSurface Surface { get; }
 
-        SKCanvas ISkiaDrawingContextImpl.SkCanvas => Canvas;
-        SKSurface ISkiaDrawingContextImpl.SkSurface => Surface;
-        GRContext ISkiaDrawingContextImpl.GrContext => _grContext;
-        double ISkiaDrawingContextImpl.CurrentOpacity => _currentOpacity;
-
+        private void CheckLease()
+        {
+            if (_leased)
+                throw new InvalidOperationException("The underlying graphics API is currently leased");
+        }
+        
         /// <inheritdoc />
         public void Clear(Color color)
         {
+            CheckLease();
             Canvas.Clear(color.ToSKColor());
         }
 
         /// <inheritdoc />
         public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
         {
+            CheckLease();
             var drawableImage = (IDrawableBitmapImpl)source.Item;
             var s = sourceRect.ToSKRect();
             var d = destRect.ToSKRect();
@@ -157,6 +203,7 @@ namespace Avalonia.Skia
         /// <inheritdoc />
         public void DrawBitmap(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
         {
+            CheckLease();
             PushOpacityMask(opacityMask, opacityMaskRect);
             DrawBitmap(source, 1, new Rect(0, 0, source.Item.PixelSize.Width, source.Item.PixelSize.Height), destRect, BitmapInterpolationMode.Default);
             PopOpacityMask();
@@ -165,6 +212,7 @@ namespace Avalonia.Skia
         /// <inheritdoc />
         public void DrawLine(IPen pen, Point p1, Point p2)
         {
+            CheckLease();
             using (var paint = CreatePaint(_strokePaint, pen, new Size(Math.Abs(p2.X - p1.X), Math.Abs(p2.Y - p1.Y))))
             {
                 if (paint.Paint is object)
@@ -177,6 +225,7 @@ namespace Avalonia.Skia
         /// <inheritdoc />
         public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry)
         {
+            CheckLease();
             var impl = (GeometryImpl) geometry;
             var size = geometry.Bounds.Size;
 
@@ -260,6 +309,7 @@ namespace Avalonia.Skia
         {
             if (rect.Rect.Height <= 0 || rect.Rect.Width <= 0)
                 return;
+            CheckLease();
             
             var rc = rect.Rect.ToSKRect();
             var isRounded = rect.IsRounded;
@@ -296,6 +346,7 @@ namespace Avalonia.Skia
         {
             if (rect.Rect.Height <= 0 || rect.Rect.Width <= 0)
                 return;
+            CheckLease();
             // Arbitrary chosen values
             // On OSX Skia breaks OpenGL context when asked to draw, e. g. (0, 0, 623, 6666600) rect
             if (rect.Rect.Height > 8192 || rect.Rect.Width > 8192)
@@ -421,7 +472,8 @@ namespace Avalonia.Skia
         {
             if (rect.Height <= 0 || rect.Width <= 0)
                 return;
-
+            CheckLease();
+            
             var rc = rect.ToSKRect();
 
             if (brush != null)
@@ -447,6 +499,7 @@ namespace Avalonia.Skia
         /// <inheritdoc />
         public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
         {
+            CheckLease();
             using (var paintWrapper = CreatePaint(_fillPaint, foreground, glyphRun.Size))
             {
                 var glyphRunImpl = (GlyphRunImpl)glyphRun.GlyphRunImpl;
@@ -459,18 +512,21 @@ namespace Avalonia.Skia
         /// <inheritdoc />
         public IDrawingContextLayerImpl CreateLayer(Size size)
         {
+            CheckLease();
             return CreateRenderTarget(size, true);
         }
 
         /// <inheritdoc />
         public void PushClip(Rect clip)
         {
+            CheckLease();
             Canvas.Save();
             Canvas.ClipRect(clip.ToSKRect());
         }
 
         public void PushClip(RoundedRect clip)
         {
+            CheckLease();
             Canvas.Save();
             Canvas.ClipRoundRect(clip.ToSKRoundRect(), antialias:true);
         }
@@ -478,12 +534,14 @@ namespace Avalonia.Skia
         /// <inheritdoc />
         public void PopClip()
         {
+            CheckLease();
             Canvas.Restore();
         }
 
         /// <inheritdoc />
         public void PushOpacity(double opacity)
         {
+            CheckLease();
             _opacityStack.Push(_currentOpacity);
             _currentOpacity *= opacity;
         }
@@ -491,6 +549,7 @@ namespace Avalonia.Skia
         /// <inheritdoc />
         public void PopOpacity()
         {
+            CheckLease();
             _currentOpacity = _opacityStack.Pop();
         }
 
@@ -499,6 +558,7 @@ namespace Avalonia.Skia
         {
             if(_disposed)
                 return;
+            CheckLease();
             try
             {
                 if (_grContext != null)
@@ -523,6 +583,7 @@ namespace Avalonia.Skia
         /// <inheritdoc />
         public void PushGeometryClip(IGeometryImpl clip)
         {
+            CheckLease();
             Canvas.Save();
             Canvas.ClipPath(((GeometryImpl)clip).EffectivePath, SKClipOperation.Intersect, true);
         }
@@ -530,12 +591,14 @@ namespace Avalonia.Skia
         /// <inheritdoc />
         public void PopGeometryClip()
         {
+            CheckLease();
             Canvas.Restore();
         }
 
         /// <inheritdoc />
         public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
         {
+            CheckLease();
             _blendingModeStack.Push(_currentBlendingMode);
             _currentBlendingMode = blendingMode;
         }
@@ -543,14 +606,20 @@ namespace Avalonia.Skia
         /// <inheritdoc />
         public void PopBitmapBlendMode()
         {
+            CheckLease();
             _currentBlendingMode = _blendingModeStack.Pop();
         }
 
-        public void Custom(ICustomDrawOperation custom) => custom.Render(this);
+        public void Custom(ICustomDrawOperation custom)
+        {
+            CheckLease();
+            custom.Render(this);
+        }
 
         /// <inheritdoc />
         public void PushOpacityMask(IBrush mask, Rect bounds)
         {
+            CheckLease();
             // TODO: This should be disposed
             var paint = new SKPaint();
 
@@ -561,6 +630,7 @@ namespace Avalonia.Skia
         /// <inheritdoc />
         public void PopOpacityMask()
         {
+            CheckLease();
             using (var paint = new SKPaint { BlendMode = SKBlendMode.DstIn })
             {
                 Canvas.SaveLayer(paint);
@@ -580,6 +650,7 @@ namespace Avalonia.Skia
             get { return _currentTransform; }
             set
             {
+                CheckLease();
                 if (_currentTransform == value)
                     return;
 
@@ -596,6 +667,14 @@ namespace Avalonia.Skia
             }
         }
 
+        [CanBeNull]
+        public object GetFeature(Type t)
+        {
+            if (t == typeof(ISkiaSharpApiLeaseFeature))
+                return new SkiaLeaseFeature(this);
+            return null;
+        }
+
         /// <summary>
         /// Configure paint wrapper for using gradient brush.
         /// </summary>

+ 7 - 5
src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs

@@ -33,7 +33,7 @@ namespace Avalonia.Skia.Helpers
         /// Unsupported - Wraps a GPU Backed SkiaSurface in an Avalonia DrawingContext.
         /// </summary>
         [Obsolete]
-        public static ISkiaDrawingContextImpl WrapSkiaSurface(this SKSurface surface, GRContext grContext, Vector dpi, params IDisposable[] disposables)
+        public static IDrawingContextImpl WrapSkiaSurface(this SKSurface surface, GRContext grContext, Vector dpi, params IDisposable[] disposables)
         {
             var createInfo = new DrawingContextImpl.CreateInfo
             {
@@ -50,7 +50,7 @@ namespace Avalonia.Skia.Helpers
         /// Unsupported - Wraps a non-GPU Backed SkiaSurface in an Avalonia DrawingContext.
         /// </summary>
         [Obsolete]
-        public static ISkiaDrawingContextImpl WrapSkiaSurface(this SKSurface surface, Vector dpi, params IDisposable[] disposables)
+        public static IDrawingContextImpl WrapSkiaSurface(this SKSurface surface, Vector dpi, params IDisposable[] disposables)
         {
             var createInfo = new DrawingContextImpl.CreateInfo
             {
@@ -63,7 +63,7 @@ namespace Avalonia.Skia.Helpers
         }
 
         [Obsolete]
-        public static ISkiaDrawingContextImpl CreateDrawingContext(Size size, Vector dpi, GRContext grContext = null)
+        public static IDrawingContextImpl CreateDrawingContext(Size size, Vector dpi, GRContext grContext = null)
         {
             if (grContext is null)
             {
@@ -90,9 +90,11 @@ namespace Avalonia.Skia.Helpers
         }
         
         [Obsolete]
-        public static void DrawTo(this ISkiaDrawingContextImpl source, ISkiaDrawingContextImpl destination, SKPaint paint = null)
+        public static void DrawTo(this IDrawingContextImpl source, IDrawingContextImpl destination, SKPaint paint = null)
         {
-            destination.SkCanvas.DrawSurface(source.SkSurface, new SKPoint(0, 0), paint);
+            var src = (DrawingContextImpl)source;
+            var dst = (DrawingContextImpl)destination;
+            dst.Canvas.DrawSurface(src.Surface, new SKPoint(0, 0), paint);
         }
     }
 }

+ 0 - 15
src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs

@@ -1,15 +0,0 @@
-using Avalonia.Metadata;
-using Avalonia.Platform;
-using SkiaSharp;
-
-namespace Avalonia.Skia
-{
-    [Unstable]
-    public interface ISkiaDrawingContextImpl : IDrawingContextImpl
-    {
-        SKCanvas SkCanvas { get; }
-        GRContext GrContext { get; }
-        SKSurface SkSurface { get; }
-        double CurrentOpacity { get; }
-    }
-}

+ 20 - 0
src/Skia/Avalonia.Skia/ISkiaSharpApiLeaseFeature.cs

@@ -0,0 +1,20 @@
+using System;
+using Avalonia.Metadata;
+using SkiaSharp;
+
+namespace Avalonia.Skia;
+
+[Unstable]
+public interface ISkiaSharpApiLeaseFeature
+{
+    public ISkiaSharpApiLease Lease();
+}
+
+[Unstable]
+public interface ISkiaSharpApiLease : IDisposable
+{
+    SKCanvas SkCanvas { get; }
+    GRContext GrContext { get; }
+    SKSurface SkSurface { get; }
+    double CurrentOpacity { get; }
+}

+ 1 - 0
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@@ -614,5 +614,6 @@ namespace Avalonia.Direct2D1.Media
         }
         
         public void Custom(ICustomDrawOperation custom) => custom.Render(this);
+        public object GetFeature(Type t) => null;
     }
 }

+ 4 - 1
tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs

@@ -1,4 +1,5 @@
-using Avalonia.Media;
+using System;
+using Avalonia.Media;
 using Avalonia.Platform;
 using Avalonia.Rendering.SceneGraph;
 using Avalonia.Utilities;
@@ -99,5 +100,7 @@ namespace Avalonia.Benchmarks
         public void Custom(ICustomDrawOperation custom)
         {
         }
+
+        public object GetFeature(Type t) => null;
     }
 }