From b51ba178c9d2c4b306018b11bec0cbcdc793768f Mon Sep 17 00:00:00 2001 From: DJGosnell Date: Mon, 14 Nov 2022 15:49:41 -0500 Subject: [PATCH 1/3] Added SKPaintCache. Cached all SKPaint usages in the DrawingContextImpl to reduce disposals. --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 43 ++++++++++--------- src/Skia/Avalonia.Skia/GeometryImpl.cs | 13 +++--- src/Skia/Avalonia.Skia/SKPaintCache.cs | 45 ++++++++++++++++++++ 3 files changed, 74 insertions(+), 27 deletions(-) create mode 100644 src/Skia/Avalonia.Skia/SKPaintCache.cs diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 99ab60d1ac..e3957747bd 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -35,9 +35,9 @@ namespace Avalonia.Skia private GRContext _grContext; public GRContext GrContext => _grContext; private ISkiaGpu _gpu; - private readonly SKPaint _strokePaint = new SKPaint(); - private readonly SKPaint _fillPaint = new SKPaint(); - private readonly SKPaint _boxShadowPaint = new SKPaint(); + private readonly SKPaint _strokePaint = SKPaintCache.Get(); + private readonly SKPaint _fillPaint = SKPaintCache.Get(); + private readonly SKPaint _boxShadowPaint = SKPaintCache.Get(); private static SKShader s_acrylicNoiseShader; private readonly ISkiaGpuRenderSession _session; private bool _leased = false; @@ -187,17 +187,13 @@ namespace Avalonia.Skia var s = sourceRect.ToSKRect(); var d = destRect.ToSKRect(); - using (var paint = - new SKPaint - { - Color = new SKColor(255, 255, 255, (byte)(255 * opacity * _currentOpacity)) - }) - { - paint.FilterQuality = bitmapInterpolationMode.ToSKFilterQuality(); - paint.BlendMode = _currentBlendingMode.ToSKBlendMode(); + var paint = SKPaintCache.Get(); + paint.Color = new SKColor(255, 255, 255, (byte)(255 * opacity * _currentOpacity)); + paint.FilterQuality = bitmapInterpolationMode.ToSKFilterQuality(); + paint.BlendMode = _currentBlendingMode.ToSKBlendMode(); - drawableImage.Draw(this, s, d, paint); - } + drawableImage.Draw(this, s, d, paint); + SKPaintCache.Return(paint); } /// @@ -561,6 +557,11 @@ namespace Avalonia.Skia CheckLease(); try { + // Return leased paints. + SKPaintCache.Return(_strokePaint); + SKPaintCache.Return(_fillPaint); + SKPaintCache.Return(_boxShadowPaint); + if (_grContext != null) { Monitor.Exit(_grContext); @@ -631,15 +632,17 @@ namespace Avalonia.Skia public void PopOpacityMask() { CheckLease(); - using (var paint = new SKPaint { BlendMode = SKBlendMode.DstIn }) + + var paint = SKPaintCache.Get(); + paint.BlendMode = SKBlendMode.DstIn; + + Canvas.SaveLayer(paint); + SKPaintCache.Return(paint); + using (var paintWrapper = _maskStack.Pop()) { - Canvas.SaveLayer(paint); - using (var paintWrapper = _maskStack.Pop()) - { - Canvas.DrawPaint(paintWrapper.Paint); - } - Canvas.Restore(); + Canvas.DrawPaint(paintWrapper.Paint); } + Canvas.Restore(); Canvas.Restore(); } diff --git a/src/Skia/Avalonia.Skia/GeometryImpl.cs b/src/Skia/Avalonia.Skia/GeometryImpl.cs index 19dbcf9713..0e95b5d139 100644 --- a/src/Skia/Avalonia.Skia/GeometryImpl.cs +++ b/src/Skia/Avalonia.Skia/GeometryImpl.cs @@ -81,15 +81,14 @@ namespace Avalonia.Skia } else { - using (var paint = new SKPaint()) - { - paint.IsStroke = true; - paint.StrokeWidth = strokeWidth; + var paint = SKPaintCache.Get(); + paint.IsStroke = true; + paint.StrokeWidth = strokeWidth; + paint.GetFillPath(EffectivePath, strokePath); - paint.GetFillPath(EffectivePath, strokePath); + SKPaintCache.Return(paint); - _pathCache.Cache(strokePath, strokeWidth, strokePath.TightBounds.ToAvaloniaRect()); - } + _pathCache.Cache(strokePath, strokeWidth, strokePath.TightBounds.ToAvaloniaRect()); } } diff --git a/src/Skia/Avalonia.Skia/SKPaintCache.cs b/src/Skia/Avalonia.Skia/SKPaintCache.cs new file mode 100644 index 0000000000..79f9575591 --- /dev/null +++ b/src/Skia/Avalonia.Skia/SKPaintCache.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using SkiaSharp; + +namespace Avalonia.Skia +{ + internal static class SKPaintCache + { + private static ConcurrentBag s_cachedPaints; + + static SKPaintCache() + { + s_cachedPaints = new ConcurrentBag(); + } + + public static SKPaint Get() + { + if (!s_cachedPaints.TryTake(out var paint)) + { + paint = new SKPaint(); + } + + return paint; + } + + public static void Return(SKPaint paint) + { + paint.Reset(); + s_cachedPaints.Add(paint); + } + + public static void Clear() + { + while (s_cachedPaints.TryTake(out var paint)) + { + paint.Dispose(); + } + } + + } +} From 713d2cab359ee68092e1916b6ba7f3466676a7dc Mon Sep 17 00:00:00 2001 From: DJGosnell Date: Mon, 14 Nov 2022 15:55:47 -0500 Subject: [PATCH 2/3] Added documentation. --- src/Skia/Avalonia.Skia/SKPaintCache.cs | 30 ++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/Skia/Avalonia.Skia/SKPaintCache.cs b/src/Skia/Avalonia.Skia/SKPaintCache.cs index 79f9575591..9d80e3a729 100644 --- a/src/Skia/Avalonia.Skia/SKPaintCache.cs +++ b/src/Skia/Avalonia.Skia/SKPaintCache.cs @@ -1,13 +1,11 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Collections.Concurrent; using SkiaSharp; namespace Avalonia.Skia { + /// + /// Cache for SKPaints. + /// internal static class SKPaintCache { private static ConcurrentBag s_cachedPaints; @@ -17,6 +15,14 @@ namespace Avalonia.Skia s_cachedPaints = new ConcurrentBag(); } + /// + /// Gets a SKPaint for usage. + /// + /// + /// If a SKPaint is in the cache, that existing SKPaint will be returned. + /// Otherwise a new SKPaint will be created. + /// + /// public static SKPaint Get() { if (!s_cachedPaints.TryTake(out var paint)) @@ -27,12 +33,24 @@ namespace Avalonia.Skia return paint; } + /// + /// Returns a SKPaint for reuse later. + /// + /// + /// Do not use the paint further. + /// Do not return the same paint multiple times as that will break the cache. + /// Uses SKPaint.Reset() for reuse later. + /// + /// public static void Return(SKPaint paint) { paint.Reset(); s_cachedPaints.Add(paint); } + /// + /// Clears and disposes all cached paints. + /// public static void Clear() { while (s_cachedPaints.TryTake(out var paint)) From 9d999a6827eaa74a416678d6310256843f6000c9 Mon Sep 17 00:00:00 2001 From: DJGosnell Date: Mon, 14 Nov 2022 17:01:25 -0500 Subject: [PATCH 3/3] Changed SKPaintCache.Return to ReturnReset. Added SKPaintCache.Return which only returns the paint and does not reset. Removed PaintWrapper._disposePaint. Removed all method call's references to setting PaintWrapper._disposePaint. --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 51 +++++++++----------- src/Skia/Avalonia.Skia/GeometryImpl.cs | 2 +- src/Skia/Avalonia.Skia/SKPaintCache.cs | 15 +++++- 3 files changed, 37 insertions(+), 31 deletions(-) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index e3957747bd..6dcafdcfd7 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -193,7 +193,7 @@ namespace Avalonia.Skia paint.BlendMode = _currentBlendingMode.ToSKBlendMode(); drawableImage.Draw(this, s, d, paint); - SKPaintCache.Return(paint); + SKPaintCache.ReturnReset(paint); } /// @@ -558,9 +558,9 @@ namespace Avalonia.Skia try { // Return leased paints. - SKPaintCache.Return(_strokePaint); - SKPaintCache.Return(_fillPaint); - SKPaintCache.Return(_boxShadowPaint); + SKPaintCache.ReturnReset(_strokePaint); + SKPaintCache.ReturnReset(_fillPaint); + SKPaintCache.ReturnReset(_boxShadowPaint); if (_grContext != null) { @@ -621,11 +621,11 @@ namespace Avalonia.Skia public void PushOpacityMask(IBrush mask, Rect bounds) { CheckLease(); - // TODO: This should be disposed - var paint = new SKPaint(); + + var paint = SKPaintCache.Get(); Canvas.SaveLayer(paint); - _maskStack.Push(CreatePaint(paint, mask, bounds.Size, true)); + _maskStack.Push(CreatePaint(paint, mask, bounds.Size)); } /// @@ -637,11 +637,16 @@ namespace Avalonia.Skia paint.BlendMode = SKBlendMode.DstIn; Canvas.SaveLayer(paint); - SKPaintCache.Return(paint); - using (var paintWrapper = _maskStack.Pop()) + SKPaintCache.ReturnReset(paint); + + PaintWrapper paintWrapper; + using (paintWrapper = _maskStack.Pop()) { Canvas.DrawPaint(paintWrapper.Paint); } + // Return the paint wrapper's paint less the reset since the paint is already reset in the Dispose method above. + SKPaintCache.Return(paintWrapper.Paint); + Canvas.Restore(); Canvas.Restore(); @@ -977,9 +982,9 @@ namespace Avalonia.Skia ); } - internal PaintWrapper CreateAcrylicPaint (SKPaint paint, IExperimentalAcrylicMaterial material, bool disposePaint = false) + internal PaintWrapper CreateAcrylicPaint (SKPaint paint, IExperimentalAcrylicMaterial material) { - var paintWrapper = new PaintWrapper(paint, disposePaint); + var paintWrapper = new PaintWrapper(paint); paint.IsAntialias = true; @@ -1026,11 +1031,10 @@ namespace Avalonia.Skia /// The paint to wrap. /// Source brush. /// Target size. - /// Optional dispose of the supplied paint. /// Paint wrapper for given brush. - internal PaintWrapper CreatePaint(SKPaint paint, IBrush brush, Size targetSize, bool disposePaint = false) + internal PaintWrapper CreatePaint(SKPaint paint, IBrush brush, Size targetSize) { - var paintWrapper = new PaintWrapper(paint, disposePaint); + var paintWrapper = new PaintWrapper(paint); paint.IsAntialias = true; @@ -1083,9 +1087,8 @@ namespace Avalonia.Skia /// The paint to wrap. /// Source pen. /// Target size. - /// Optional dispose of the supplied paint. /// - private PaintWrapper CreatePaint(SKPaint paint, IPen pen, Size targetSize, bool disposePaint = false) + private PaintWrapper CreatePaint(SKPaint paint, IPen pen, Size targetSize) { // In Skia 0 thickness means - use hairline rendering // and for us it means - there is nothing rendered. @@ -1094,7 +1097,7 @@ namespace Avalonia.Skia return default; } - var rv = CreatePaint(paint, pen.Brush, targetSize, disposePaint); + var rv = CreatePaint(paint, pen.Brush, targetSize); paint.IsStroke = true; paint.StrokeWidth = (float) pen.Thickness; @@ -1209,16 +1212,14 @@ namespace Avalonia.Skia { //We are saving memory allocations there public readonly SKPaint Paint; - private readonly bool _disposePaint; private IDisposable _disposable1; private IDisposable _disposable2; private IDisposable _disposable3; - public PaintWrapper(SKPaint paint, bool disposePaint) + public PaintWrapper(SKPaint paint) { Paint = paint; - _disposePaint = disposePaint; _disposable1 = null; _disposable2 = null; @@ -1266,15 +1267,7 @@ namespace Avalonia.Skia /// public void Dispose() { - if (_disposePaint) - { - Paint?.Dispose(); - } - else - { - Paint?.Reset(); - } - + Paint?.Reset(); _disposable1?.Dispose(); _disposable2?.Dispose(); _disposable3?.Dispose(); diff --git a/src/Skia/Avalonia.Skia/GeometryImpl.cs b/src/Skia/Avalonia.Skia/GeometryImpl.cs index 0e95b5d139..4037cc4a35 100644 --- a/src/Skia/Avalonia.Skia/GeometryImpl.cs +++ b/src/Skia/Avalonia.Skia/GeometryImpl.cs @@ -86,7 +86,7 @@ namespace Avalonia.Skia paint.StrokeWidth = strokeWidth; paint.GetFillPath(EffectivePath, strokePath); - SKPaintCache.Return(paint); + SKPaintCache.ReturnReset(paint); _pathCache.Cache(strokePath, strokeWidth, strokePath.TightBounds.ToAvaloniaRect()); } diff --git a/src/Skia/Avalonia.Skia/SKPaintCache.cs b/src/Skia/Avalonia.Skia/SKPaintCache.cs index 9d80e3a729..6588ab8da8 100644 --- a/src/Skia/Avalonia.Skia/SKPaintCache.cs +++ b/src/Skia/Avalonia.Skia/SKPaintCache.cs @@ -39,10 +39,23 @@ namespace Avalonia.Skia /// /// Do not use the paint further. /// Do not return the same paint multiple times as that will break the cache. - /// Uses SKPaint.Reset() for reuse later. /// /// public static void Return(SKPaint paint) + { + s_cachedPaints.Add(paint); + } + + /// + /// Returns a SKPaint and resets it for reuse later. + /// + /// + /// Do not use the paint further. + /// Do not return the same paint multiple times as that will break the cache. + /// Uses SKPaint.Reset() for reuse later. + /// + /// + public static void ReturnReset(SKPaint paint) { paint.Reset(); s_cachedPaints.Add(paint);