Browse Source

Use GetFeature + API lease approach for accessing SKCanvas

pull/8656/head
Nikita Tsukanov 4 years ago
parent
commit
3717aec4f5
  1. 6
      samples/RenderDemo/Pages/CustomSkiaPage.cs
  2. 2
      src/Avalonia.Base/Media/DrawingGroup.cs
  3. 14
      src/Avalonia.Base/Platform/IDrawingContextImpl.cs
  4. 2
      src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs
  5. 3
      src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
  6. 2
      src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  7. 5
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  8. 97
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  9. 12
      src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs
  10. 15
      src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs
  11. 20
      src/Skia/Avalonia.Skia/ISkiaSharpApiLeaseFeature.cs
  12. 1
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  13. 5
      tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs

6
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
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
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
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
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
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
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)
{
}

97
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>

12
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);
}
}
}

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
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
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;
}
}

5
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;
}
}

Loading…
Cancel
Save