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

2
src/Avalonia.Base/Media/DrawingGroup.cs

@ -228,6 +228,8 @@ namespace Avalonia.Media
throw new NotImplementedException(); 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) public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default)
{ {
throw new NotImplementedException(); throw new NotImplementedException();

14
src/Avalonia.Base/Platform/IDrawingContextImpl.cs

@ -172,6 +172,20 @@ namespace Avalonia.Platform
/// </summary> /// </summary>
/// <param name="custom">Custom draw operation</param> /// <param name="custom">Custom draw operation</param>
void Custom(ICustomDrawOperation custom); 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 public interface IDrawingContextLayerImpl : IRenderTargetBitmapImpl

2
src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs

@ -156,6 +156,8 @@ internal class CompositionDrawingContext : IDrawingContextImpl, IDrawingContextW
++_drawOperationIndex; ++_drawOperationIndex;
} }
public object? GetFeature(Type t) => null;
/// <inheritdoc/> /// <inheritdoc/>
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) 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 System.Numerics;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
@ -155,6 +156,8 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, IDrawingCont
_impl.Custom(custom); _impl.Custom(custom);
} }
public object? GetFeature(Type t) => _impl.GetFeature(t);
public class VisualBrushRenderer : IVisualBrushRenderer public class VisualBrushRenderer : IVisualBrushRenderer
{ {
public CompositionDrawList? VisualBrushDrawList { get; set; } public CompositionDrawList? VisualBrushDrawList { get; set; }

2
src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs

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

Loading…
Cancel
Save