From 18d59ff64988c3d725cb6f3407257975eaddd466 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 14 Mar 2019 14:36:07 +0300 Subject: [PATCH] Custom draw operations, exposed SkCanvas and reduced Skia api visibility --- samples/RenderDemo/MainWindow.xaml | 3 + samples/RenderDemo/Pages/CustomSkiaPage.cs | 119 ++++++++++++++++++ src/Avalonia.Visuals/Media/DrawingContext.cs | 7 ++ .../Platform/IDrawingContextImpl.cs | 7 ++ .../SceneGraph/CustomDrawOperation.cs | 39 ++++++ .../SceneGraph/DeferredDrawingContextImpl.cs | 9 ++ src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 7 +- src/Skia/Avalonia.Skia/FormattedTextImpl.cs | 2 +- .../Avalonia.Skia/FramebufferRenderTarget.cs | 2 +- src/Skia/Avalonia.Skia/GeometryImpl.cs | 2 +- src/Skia/Avalonia.Skia/GlRenderTarget.cs | 2 +- .../Avalonia.Skia/ISkiaDrawingContextImpl.cs | 10 ++ src/Skia/Avalonia.Skia/ImmutableBitmap.cs | 2 +- .../Avalonia.Skia/PlatformRenderInterface.cs | 2 +- src/Skia/Avalonia.Skia/StreamGeometryImpl.cs | 2 +- src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs | 2 +- .../Avalonia.Skia/TransformedGeometryImpl.cs | 2 +- src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs | 2 +- .../Media/DrawingContextImpl.cs | 3 + .../Media/FormattedTextImpl.cs | 2 +- 20 files changed, 214 insertions(+), 12 deletions(-) create mode 100644 samples/RenderDemo/Pages/CustomSkiaPage.cs create mode 100644 src/Avalonia.Visuals/Rendering/SceneGraph/CustomDrawOperation.cs create mode 100644 src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs diff --git a/samples/RenderDemo/MainWindow.xaml b/samples/RenderDemo/MainWindow.xaml index 41164c7780..c15abad188 100644 --- a/samples/RenderDemo/MainWindow.xaml +++ b/samples/RenderDemo/MainWindow.xaml @@ -33,6 +33,9 @@ + + + diff --git a/samples/RenderDemo/Pages/CustomSkiaPage.cs b/samples/RenderDemo/Pages/CustomSkiaPage.cs new file mode 100644 index 0000000000..2e59d934a1 --- /dev/null +++ b/samples/RenderDemo/Pages/CustomSkiaPage.cs @@ -0,0 +1,119 @@ +using System; +using System.Diagnostics; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Platform; +using Avalonia.Rendering.SceneGraph; +using Avalonia.Skia; +using Avalonia.Threading; +using SkiaSharp; + +namespace RenderDemo.Pages +{ + public class CustomSkiaPage : Control + { + public CustomSkiaPage() + { + ClipToBounds = true; + } + + class CustomDrawOp : ICustomDrawOperation + { + private readonly FormattedText _noSkia; + + public CustomDrawOp(Rect bounds, FormattedText noSkia) + { + _noSkia = noSkia; + Bounds = bounds; + } + + public void Dispose() + { + // No-op + } + + public Rect Bounds { get; } + public bool HitTest(Point p) => false; + public bool Equals(ICustomDrawOperation other) => false; + static Stopwatch St = Stopwatch.StartNew(); + public void Render(IDrawingContextImpl context) + { + var canvas = (context as ISkiaDrawingContextImpl)?.SkCanvas; + if (canvas == null) + context.DrawText(Brushes.Black, new Point(), _noSkia.PlatformImpl); + else + { + canvas.Save(); + // create the first shader + var colors = new SKColor[] { + new SKColor(0, 255, 255), + new SKColor(255, 0, 255), + new SKColor(255, 255, 0), + new SKColor(0, 255, 255) + }; + + var sx = Animate(100, 2, 10); + var sy = Animate(1000, 5, 15); + var lightPosition = new SKPoint( + (float)(Bounds.Width / 2 + Math.Cos(St.Elapsed.TotalSeconds) * Bounds.Width / 4), + (float)(Bounds.Height / 2 + Math.Sin(St.Elapsed.TotalSeconds) * Bounds.Height / 4)); + using (var sweep = + SKShader.CreateSweepGradient(new SKPoint((int)Bounds.Width / 2, (int)Bounds.Height / 2), colors, + null)) + using(var turbulence = SKShader.CreatePerlinNoiseFractalNoise(0.05f, 0.05f, 4, 0)) + using(var shader = SKShader.CreateCompose(sweep, turbulence, SKBlendMode.SrcATop)) + using(var blur = SKImageFilter.CreateBlur(Animate(100, 2, 10), Animate(100, 5, 15))) + using (var paint = new SKPaint + { + Shader = shader, + ImageFilter = blur + }) + canvas.DrawPaint(paint); + + using (var pseudoLight = SKShader.CreateRadialGradient( + lightPosition, + (float) (Bounds.Width/3), + new [] { + new SKColor(255, 200, 200, 100), + SKColors.Transparent, + new SKColor(40,40,40, 220), + new SKColor(20,20,20, (byte)Animate(100, 200,220)) }, + new float[] { 0.3f, 0.3f, 0.8f, 1 }, + SKShaderTileMode.Clamp)) + using (var paint = new SKPaint + { + Shader = pseudoLight + }) + canvas.DrawPaint(paint); + canvas.Restore(); + } + } + static int Animate(int d, int from, int to) + { + var ms = (int)(St.ElapsedMilliseconds / d); + var diff = to - from; + var range = diff * 2; + var v = ms % range; + if (v > diff) + v = range - v; + var rv = v + from; + if (rv < from || rv > to) + throw new Exception("WTF"); + return rv; + } + } + + + + public override void Render(DrawingContext context) + { + var noSkia = new FormattedText() + { + Text = "Current rendering API is not Skia" + }; + context.Custom(new CustomDrawOp(new Rect(0, 0, Bounds.Width, Bounds.Height), noSkia)); + Dispatcher.UIThread.InvokeAsync(InvalidateVisual, DispatcherPriority.Background); + } + } +} diff --git a/src/Avalonia.Visuals/Media/DrawingContext.cs b/src/Avalonia.Visuals/Media/DrawingContext.cs index fd593db991..d3af71ffcb 100644 --- a/src/Avalonia.Visuals/Media/DrawingContext.cs +++ b/src/Avalonia.Visuals/Media/DrawingContext.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using Avalonia.Media.Imaging; using Avalonia.Platform; +using Avalonia.Rendering.SceneGraph; using Avalonia.Threading; using Avalonia.Visuals.Media.Imaging; @@ -131,6 +132,12 @@ namespace Avalonia.Media } } + /// + /// Draws a custom drawing operation + /// + /// custom operation + public void Custom(ICustomDrawOperation custom) => PlatformImpl.Custom(custom); + /// /// Draws text. /// diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs index 57b974f900..e5be04ebf9 100644 --- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs @@ -3,6 +3,7 @@ using System; using Avalonia.Media; +using Avalonia.Rendering.SceneGraph; using Avalonia.Utilities; using Avalonia.Visuals.Media.Imaging; @@ -139,5 +140,11 @@ namespace Avalonia.Platform /// Pops the latest pushed geometry clip. /// void PopGeometryClip(); + + /// + /// Adds a custom draw operation + /// + /// Custom draw operation + void Custom(ICustomDrawOperation custom); } } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/CustomDrawOperation.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/CustomDrawOperation.cs new file mode 100644 index 0000000000..68e2237430 --- /dev/null +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/CustomDrawOperation.cs @@ -0,0 +1,39 @@ +using System; +using Avalonia.Media; +using Avalonia.Platform; + +namespace Avalonia.Rendering.SceneGraph +{ + internal sealed class CustomDrawOperation : DrawOperation + { + public Matrix Transform { get; } + public ICustomDrawOperation Custom { get; } + public CustomDrawOperation(ICustomDrawOperation custom, Matrix transform) + : base(custom.Bounds, transform, null) + { + Transform = transform; + Custom = custom; + } + + public override bool HitTest(Point p) + { + return Custom.HitTest(p * Transform); + } + + public override void Render(IDrawingContextImpl context) + { + context.Transform = Transform; + Custom.Render(context); + } + + public override void Dispose() => Custom.Dispose(); + + public bool Equals(Matrix transform, ICustomDrawOperation custom) => + Transform == transform && Custom?.Equals(custom) == true; + } + + public interface ICustomDrawOperation : IDrawOperation, IEquatable + { + + } +} diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs index dfed0d911c..0b33851911 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs @@ -165,6 +165,15 @@ namespace Avalonia.Rendering.SceneGraph ++_drawOperationindex; } } + + public void Custom(ICustomDrawOperation custom) + { + var next = NextDrawAs(); + if (next == null || !next.Item.Equals(Transform, custom)) + Add(new CustomDrawOperation(custom, Transform)); + else + ++_drawOperationindex; + } /// public void DrawText(IBrush foreground, Point origin, IFormattedTextImpl text) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 10e746cbd5..af6b0fe22f 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -9,6 +9,7 @@ using System.Threading; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Rendering; +using Avalonia.Rendering.SceneGraph; using Avalonia.Rendering.Utilities; using Avalonia.Utilities; using Avalonia.Visuals.Media.Imaging; @@ -19,7 +20,7 @@ namespace Avalonia.Skia /// /// Skia based drawing context. /// - public class DrawingContextImpl : IDrawingContextImpl + internal class DrawingContextImpl : IDrawingContextImpl, ISkiaDrawingContextImpl { private IDisposable[] _disposables; private readonly Vector _dpi; @@ -99,6 +100,8 @@ namespace Avalonia.Skia /// public SKCanvas Canvas { get; } + SKCanvas ISkiaDrawingContextImpl.SkCanvas => Canvas; + /// public void Clear(Color color) { @@ -296,6 +299,8 @@ namespace Avalonia.Skia Canvas.Restore(); } + public void Custom(ICustomDrawOperation custom) => custom.Render(this); + /// public void PushOpacityMask(IBrush mask, Rect bounds) { diff --git a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs index 7e3f4aa40a..47852a4d9e 100644 --- a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs +++ b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs @@ -13,7 +13,7 @@ namespace Avalonia.Skia /// /// Skia formatted text implementation. /// - public class FormattedTextImpl : IFormattedTextImpl + internal class FormattedTextImpl : IFormattedTextImpl { public FormattedTextImpl( string text, diff --git a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs index 0cb4c3db67..1af3d2968c 100644 --- a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs @@ -13,7 +13,7 @@ namespace Avalonia.Skia /// /// Skia render target that renders to a framebuffer surface. No gpu acceleration available. /// - public class FramebufferRenderTarget : IRenderTarget + internal class FramebufferRenderTarget : IRenderTarget { private readonly IFramebufferPlatformSurface _platformSurface; private SKImageInfo _currentImageInfo; diff --git a/src/Skia/Avalonia.Skia/GeometryImpl.cs b/src/Skia/Avalonia.Skia/GeometryImpl.cs index fbbd6eb58c..5940de418e 100644 --- a/src/Skia/Avalonia.Skia/GeometryImpl.cs +++ b/src/Skia/Avalonia.Skia/GeometryImpl.cs @@ -11,7 +11,7 @@ namespace Avalonia.Skia /// /// A Skia implementation of . /// - public abstract class GeometryImpl : IGeometryImpl + internal abstract class GeometryImpl : IGeometryImpl { private PathCache _pathCache; diff --git a/src/Skia/Avalonia.Skia/GlRenderTarget.cs b/src/Skia/Avalonia.Skia/GlRenderTarget.cs index cd8c334b53..7c0c42ca37 100644 --- a/src/Skia/Avalonia.Skia/GlRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/GlRenderTarget.cs @@ -8,7 +8,7 @@ using static Avalonia.OpenGL.GlConsts; namespace Avalonia.Skia { - public class GlRenderTarget : IRenderTarget + internal class GlRenderTarget : IRenderTarget { private readonly GRContext _grContext; private IGlPlatformSurfaceRenderTarget _surface; diff --git a/src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs b/src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs new file mode 100644 index 0000000000..ff82990c47 --- /dev/null +++ b/src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs @@ -0,0 +1,10 @@ +using Avalonia.Platform; +using SkiaSharp; + +namespace Avalonia.Skia +{ + public interface ISkiaDrawingContextImpl : IDrawingContextImpl + { + SKCanvas SkCanvas { get; } + } +} diff --git a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs index 49992df395..f283040eac 100644 --- a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs +++ b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs @@ -12,7 +12,7 @@ namespace Avalonia.Skia /// /// Immutable Skia bitmap. /// - public class ImmutableBitmap : IDrawableBitmapImpl + internal class ImmutableBitmap : IDrawableBitmapImpl { private readonly SKImage _image; diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index c6e68b1c8b..6e13b154b4 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -15,7 +15,7 @@ namespace Avalonia.Skia /// /// Skia platform render interface. /// - public class PlatformRenderInterface : IPlatformRenderInterface + internal class PlatformRenderInterface : IPlatformRenderInterface { private GRContext GrContext { get; } diff --git a/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs b/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs index c19ff79d87..2764c65c6f 100644 --- a/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs +++ b/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs @@ -10,7 +10,7 @@ namespace Avalonia.Skia /// /// A Skia implementation of a . /// - public class StreamGeometryImpl : GeometryImpl, IStreamGeometryImpl + internal class StreamGeometryImpl : GeometryImpl, IStreamGeometryImpl { private Rect _bounds; private readonly SKPath _effectivePath; diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs index 4b7eae1af4..9340c9add4 100644 --- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs @@ -14,7 +14,7 @@ namespace Avalonia.Skia /// /// Skia render target that writes to a surface. /// - public class SurfaceRenderTarget : IRenderTargetBitmapImpl, IDrawableBitmapImpl + internal class SurfaceRenderTarget : IRenderTargetBitmapImpl, IDrawableBitmapImpl { private readonly SKSurface _surface; private readonly SKCanvas _canvas; diff --git a/src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs b/src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs index e95069eef3..9826bc2ce3 100644 --- a/src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs +++ b/src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs @@ -9,7 +9,7 @@ namespace Avalonia.Skia /// /// A Skia implementation of a . /// - public class TransformedGeometryImpl : GeometryImpl, ITransformedGeometryImpl + internal class TransformedGeometryImpl : GeometryImpl, ITransformedGeometryImpl { /// /// Initializes a new instance of the class. diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs index c9d6fa6c11..fea21cde58 100644 --- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs @@ -13,7 +13,7 @@ namespace Avalonia.Skia /// /// Skia based writeable bitmap. /// - public class WriteableBitmapImpl : IWriteableBitmapImpl, IDrawableBitmapImpl + internal class WriteableBitmapImpl : IWriteableBitmapImpl, IDrawableBitmapImpl { private static readonly SKBitmapReleaseDelegate s_releaseDelegate = ReleaseProc; private readonly SKBitmap _bitmap; diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 5e150ff647..8ecada1ced 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Rendering; +using Avalonia.Rendering.SceneGraph; using Avalonia.Utilities; using SharpDX; using SharpDX.Direct2D1; @@ -508,5 +509,7 @@ namespace Avalonia.Direct2D1.Media { PopLayer(); } + + public void Custom(ICustomDrawOperation custom) => custom.Render(this); } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs index 7164ec7c0d..91adc29214 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs @@ -9,7 +9,7 @@ using DWrite = SharpDX.DirectWrite; namespace Avalonia.Direct2D1.Media { - public class FormattedTextImpl : IFormattedTextImpl + internal class FormattedTextImpl : IFormattedTextImpl { public FormattedTextImpl( string text,