From 4b0ff3be639755567691c0964f39ee37796ac9f7 Mon Sep 17 00:00:00 2001 From: DJGosnell Date: Thu, 9 Feb 2023 15:05:46 -0500 Subject: [PATCH 1/4] Initial work for adding caches for SKTextBlobBuilder, SKRoundRect, SKFont usages. Updates SKPaintCache for new caching base class. --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 43 +++++++---- src/Skia/Avalonia.Skia/GeometryImpl.cs | 4 +- .../Gpu/OpenGl/GlRenderTarget.cs | 4 +- .../Avalonia.Skia/PlatformRenderInterface.cs | 47 ++++++------ src/Skia/Avalonia.Skia/SKCacheBase.cs | 72 +++++++++++++++++++ src/Skia/Avalonia.Skia/SKFontCache.cs | 13 ++++ src/Skia/Avalonia.Skia/SKPaintCache.cs | 58 ++------------- src/Skia/Avalonia.Skia/SKRoundRectCache.cs | 26 +++++++ .../Avalonia.Skia/SKTextBlobBuilderCache.cs | 13 ++++ src/Skia/Avalonia.Skia/TextShaperImpl.cs | 6 +- 10 files changed, 194 insertions(+), 92 deletions(-) create mode 100644 src/Skia/Avalonia.Skia/SKCacheBase.cs create mode 100644 src/Skia/Avalonia.Skia/SKFontCache.cs create mode 100644 src/Skia/Avalonia.Skia/SKRoundRectCache.cs create mode 100644 src/Skia/Avalonia.Skia/SKTextBlobBuilderCache.cs diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index ba646c64ee..a29cbb1cc3 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -34,9 +34,9 @@ namespace Avalonia.Skia private GRContext _grContext; public GRContext GrContext => _grContext; private ISkiaGpu _gpu; - private readonly SKPaint _strokePaint = SKPaintCache.Get(); - private readonly SKPaint _fillPaint = SKPaintCache.Get(); - private readonly SKPaint _boxShadowPaint = SKPaintCache.Get(); + private readonly SKPaint _strokePaint = SKPaintCache.Shared.Get(); + private readonly SKPaint _fillPaint = SKPaintCache.Shared.Get(); + private readonly SKPaint _boxShadowPaint = SKPaintCache.Shared.Get(); private static SKShader s_acrylicNoiseShader; private readonly ISkiaGpuRenderSession _session; private bool _leased = false; @@ -186,13 +186,13 @@ namespace Avalonia.Skia var s = sourceRect.ToSKRect(); var d = destRect.ToSKRect(); - var paint = SKPaintCache.Get(); + var paint = SKPaintCache.Shared.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); - SKPaintCache.ReturnReset(paint); + SKPaintCache.Shared.ReturnReset(paint); } /// @@ -535,7 +535,24 @@ namespace Avalonia.Skia { CheckLease(); Canvas.Save(); - Canvas.ClipRoundRect(clip.ToSKRoundRect(), antialias:true); + + // Get the rounded rectangle + var rc = clip.Rect.ToSKRect(); + + // Get a round rect from the cache. + var roundRect = SKRoundRectCache.Shared.Get(); + + roundRect.SetRectRadii(rc, + new[] + { + clip.RadiiTopLeft.ToSKPoint(), clip.RadiiTopRight.ToSKPoint(), + clip.RadiiBottomRight.ToSKPoint(), clip.RadiiBottomLeft.ToSKPoint(), + }); + + Canvas.ClipRoundRect(roundRect, antialias:true); + + // Should not need to reset as SetRectRadii overrides the values. + SKRoundRectCache.Shared.Return(roundRect); } /// @@ -569,9 +586,9 @@ namespace Avalonia.Skia try { // Return leased paints. - SKPaintCache.ReturnReset(_strokePaint); - SKPaintCache.ReturnReset(_fillPaint); - SKPaintCache.ReturnReset(_boxShadowPaint); + SKPaintCache.Shared.ReturnReset(_strokePaint); + SKPaintCache.Shared.ReturnReset(_fillPaint); + SKPaintCache.Shared.ReturnReset(_boxShadowPaint); if (_grContext != null) { @@ -633,7 +650,7 @@ namespace Avalonia.Skia { CheckLease(); - var paint = SKPaintCache.Get(); + var paint = SKPaintCache.Shared.Get(); Canvas.SaveLayer(paint); _maskStack.Push(CreatePaint(paint, mask, bounds.Size)); @@ -644,11 +661,11 @@ namespace Avalonia.Skia { CheckLease(); - var paint = SKPaintCache.Get(); + var paint = SKPaintCache.Shared.Get(); paint.BlendMode = SKBlendMode.DstIn; Canvas.SaveLayer(paint); - SKPaintCache.ReturnReset(paint); + SKPaintCache.Shared.ReturnReset(paint); PaintWrapper paintWrapper; using (paintWrapper = _maskStack.Pop()) @@ -656,7 +673,7 @@ namespace Avalonia.Skia 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); + SKPaintCache.Shared.Return(paintWrapper.Paint); Canvas.Restore(); diff --git a/src/Skia/Avalonia.Skia/GeometryImpl.cs b/src/Skia/Avalonia.Skia/GeometryImpl.cs index 15a3ebff40..51386d2a45 100644 --- a/src/Skia/Avalonia.Skia/GeometryImpl.cs +++ b/src/Skia/Avalonia.Skia/GeometryImpl.cs @@ -81,12 +81,12 @@ namespace Avalonia.Skia } else { - var paint = SKPaintCache.Get(); + var paint = SKPaintCache.Shared.Get(); paint.IsStroke = true; paint.StrokeWidth = strokeWidth; paint.GetFillPath(EffectivePath, strokePath); - SKPaintCache.ReturnReset(paint); + SKPaintCache.Shared.ReturnReset(paint); _pathCache.Cache(strokePath, strokeWidth, strokePath.TightBounds.ToAvaloniaRect()); } diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs index 4b3c7a016d..25e004f4ef 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs @@ -13,7 +13,7 @@ namespace Avalonia.Skia { private readonly GRContext _grContext; private IGlPlatformSurfaceRenderTarget _surface; - + private static readonly SKSurfaceProperties _surfaceProperties = new SKSurfaceProperties(SKPixelGeometry.RgbHorizontal); public GlRenderTarget(GRContext grContext, IGlContext glContext, IGlPlatformSurface glSurface) { _grContext = grContext; @@ -92,7 +92,7 @@ namespace Avalonia.Skia var renderTarget = new GRBackendRenderTarget(size.Width, size.Height, samples, disp.StencilSize, glInfo); var surface = SKSurface.Create(_grContext, renderTarget, glSession.IsYFlipped ? GRSurfaceOrigin.TopLeft : GRSurfaceOrigin.BottomLeft, - colorType, new SKSurfaceProperties(SKPixelGeometry.RgbHorizontal)); + colorType, _surfaceProperties); success = true; diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index e795f3d304..8e9a19239b 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -76,13 +76,14 @@ namespace Avalonia.Skia } var fontRenderingEmSize = (float)glyphRun.FontRenderingEmSize; - var skFont = new SKFont(glyphTypeface.Typeface, fontRenderingEmSize) - { - Size = fontRenderingEmSize, - Edging = SKFontEdging.Alias, - Hinting = SKFontHinting.None, - LinearMetrics = true - }; + + var skFont = SKFontCache.Shared.Get(); + + skFont.Typeface = glyphTypeface.Typeface; + skFont.Size = fontRenderingEmSize; + skFont.Edging = SKFontEdging.Alias; + skFont.Hinting = SKFontHinting.None; + skFont.LinearMetrics = true; SKPath path = new SKPath(); @@ -101,6 +102,8 @@ namespace Avalonia.Skia currentX += glyphRun.GlyphInfos[i].GlyphAdvance; } + SKFontCache.Shared.Return(skFont); + return new StreamGeometryImpl(path); } @@ -224,20 +227,19 @@ namespace Avalonia.Skia var glyphTypefaceImpl = glyphTypeface as GlyphTypefaceImpl; - var font = new SKFont - { - LinearMetrics = true, - Subpixel = true, - Edging = SKFontEdging.SubpixelAntialias, - Hinting = SKFontHinting.Full, - Size = (float)fontRenderingEmSize, - Typeface = glyphTypefaceImpl.Typeface, - Embolden = (glyphTypefaceImpl.FontSimulations & FontSimulations.Bold) != 0, - SkewX = (glyphTypefaceImpl.FontSimulations & FontSimulations.Oblique) != 0 ? -0.2f : 0 - }; + var font = SKFontCache.Shared.Get(); + + font.LinearMetrics = true; + font.Subpixel = true; + font.Edging = SKFontEdging.SubpixelAntialias; + font.Hinting = SKFontHinting.Full; + font.Size = (float)fontRenderingEmSize; + font.Typeface = glyphTypefaceImpl.Typeface; + font.Embolden = (glyphTypefaceImpl.FontSimulations & FontSimulations.Bold) != 0; + font.SkewX = (glyphTypefaceImpl.FontSimulations & FontSimulations.Oblique) != 0 ? -0.2f : 0; - var builder = new SKTextBlobBuilder(); + var builder = SKTextBlobBuilderCache.Shared.Get(); var count = glyphInfos.Count; var runBuffer = builder.AllocatePositionedRun(font, count); @@ -245,6 +247,8 @@ namespace Avalonia.Skia var glyphSpan = runBuffer.GetGlyphSpan(); var positionSpan = runBuffer.GetPositionSpan(); + SKFontCache.Shared.Return(font); + var width = 0.0; for (int i = 0; i < count; i++) @@ -261,8 +265,11 @@ namespace Avalonia.Skia var scale = fontRenderingEmSize / glyphTypeface.Metrics.DesignEmHeight; var height = glyphTypeface.Metrics.LineSpacing * scale; + var skTextBlob = builder.Build(); + + SKTextBlobBuilderCache.Shared.Return(builder); - return new GlyphRunImpl(builder.Build(), new Size(width, height), baselineOrigin); + return new GlyphRunImpl(skTextBlob, new Size(width, height), baselineOrigin); } } } diff --git a/src/Skia/Avalonia.Skia/SKCacheBase.cs b/src/Skia/Avalonia.Skia/SKCacheBase.cs new file mode 100644 index 0000000000..e1e78cd081 --- /dev/null +++ b/src/Skia/Avalonia.Skia/SKCacheBase.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Concurrent; +using SkiaSharp; + +namespace Avalonia.Skia +{ + /// + /// Cache base for Skia objects. + /// + internal abstract class SKCacheBase + where TCachedItem : IDisposable, new() + where TCache : new() + { + /// + /// Bag to hold the cached items. + /// + protected readonly ConcurrentBag Cache; + + /// + /// Shared cache. + /// + public static readonly TCache Shared = new TCache(); + + protected SKCacheBase() + { + Cache = new ConcurrentBag(); + } + + /// + /// Gets a cached item for usage. + /// + /// + /// If there is a available item in the cache, the cached item will be returned.. + /// Otherwise a new cached item will be created. + /// + /// + public TCachedItem Get() + { + if (!Cache.TryTake(out var item)) + { + item = new TCachedItem(); + } + + return item; + } + + /// + /// Returns the item for reuse later. + /// + /// + /// Do not use the item further. + /// Do not return the same item multiple times as that will break the cache. + /// + /// + public void Return(TCachedItem item) + { + Cache.Add(item); + } + + /// + /// Clears and disposes all cached items. + /// + public void Clear() + { + while (Cache.TryTake(out var item)) + { + item.Dispose(); + } + } + + } +} diff --git a/src/Skia/Avalonia.Skia/SKFontCache.cs b/src/Skia/Avalonia.Skia/SKFontCache.cs new file mode 100644 index 0000000000..348e085253 --- /dev/null +++ b/src/Skia/Avalonia.Skia/SKFontCache.cs @@ -0,0 +1,13 @@ +using System.Collections.Concurrent; +using SkiaSharp; + +namespace Avalonia.Skia +{ + /// + /// Cache for SKFonts. + /// + internal class SKFontCache : SKCacheBase + { + + } +} diff --git a/src/Skia/Avalonia.Skia/SKPaintCache.cs b/src/Skia/Avalonia.Skia/SKPaintCache.cs index 6588ab8da8..82c4dd23c7 100644 --- a/src/Skia/Avalonia.Skia/SKPaintCache.cs +++ b/src/Skia/Avalonia.Skia/SKPaintCache.cs @@ -6,46 +6,8 @@ namespace Avalonia.Skia /// /// Cache for SKPaints. /// - internal static class SKPaintCache + internal class SKPaintCache : SKCacheBase { - private static ConcurrentBag s_cachedPaints; - - static SKPaintCache() - { - 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)) - { - paint = new SKPaint(); - } - - 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. - /// - /// - public static void Return(SKPaint paint) - { - s_cachedPaints.Add(paint); - } - /// /// Returns a SKPaint and resets it for reuse later. /// @@ -54,23 +16,11 @@ namespace Avalonia.Skia /// 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 to reset. + public void ReturnReset(SKPaint paint) { paint.Reset(); - s_cachedPaints.Add(paint); + Cache.Add(paint); } - - /// - /// Clears and disposes all cached paints. - /// - public static void Clear() - { - while (s_cachedPaints.TryTake(out var paint)) - { - paint.Dispose(); - } - } - } } diff --git a/src/Skia/Avalonia.Skia/SKRoundRectCache.cs b/src/Skia/Avalonia.Skia/SKRoundRectCache.cs new file mode 100644 index 0000000000..e164f97d6a --- /dev/null +++ b/src/Skia/Avalonia.Skia/SKRoundRectCache.cs @@ -0,0 +1,26 @@ +using System.Collections.Concurrent; +using SkiaSharp; + +namespace Avalonia.Skia +{ + /// + /// Cache for SKPaints. + /// + internal class SKRoundRectCache : SKCacheBase + { + /// + /// Returns a SKPaint and resets it for reuse later. + /// + /// + /// Do not use the rect further. + /// Do not return the same rect multiple times as that will break the cache. + /// Uses SKRoundRect.SetEmpty(); for reuse later. + /// + /// Rectangle to reset + public void ReturnReset(SKRoundRect rect) + { + rect.SetEmpty(); + Cache.Add(rect); + } + } +} diff --git a/src/Skia/Avalonia.Skia/SKTextBlobBuilderCache.cs b/src/Skia/Avalonia.Skia/SKTextBlobBuilderCache.cs new file mode 100644 index 0000000000..8c010ecb05 --- /dev/null +++ b/src/Skia/Avalonia.Skia/SKTextBlobBuilderCache.cs @@ -0,0 +1,13 @@ +using System.Collections.Concurrent; +using SkiaSharp; + +namespace Avalonia.Skia +{ + /// + /// Cache for SKTextBlobBuilder. + /// + internal class SKTextBlobBuilderCache : SKCacheBase + { + + } +} diff --git a/src/Skia/Avalonia.Skia/TextShaperImpl.cs b/src/Skia/Avalonia.Skia/TextShaperImpl.cs index e1a6b93692..a21038839c 100644 --- a/src/Skia/Avalonia.Skia/TextShaperImpl.cs +++ b/src/Skia/Avalonia.Skia/TextShaperImpl.cs @@ -1,5 +1,6 @@ using System; using System.Buffers; +using System.Collections.Concurrent; using System.Globalization; using System.Runtime.InteropServices; using Avalonia.Media.TextFormatting; @@ -13,6 +14,7 @@ namespace Avalonia.Skia { internal class TextShaperImpl : ITextShaperImpl { + private static readonly ConcurrentDictionary s_cachedLanguage = new(); public ShapedBuffer ShapeText(ReadOnlyMemory text, TextShaperOptions options) { var textSpan = text.Span; @@ -33,7 +35,9 @@ namespace Avalonia.Skia buffer.Direction = (bidiLevel & 1) == 0 ? Direction.LeftToRight : Direction.RightToLeft; - buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture); + var usedCulture = culture ?? CultureInfo.CurrentCulture; + + buffer.Language = s_cachedLanguage.GetOrAdd(usedCulture.LCID, i => new Language(usedCulture)); var font = ((GlyphTypefaceImpl)typeface).Font; From b11786424daa1eafb4bb53edf83b57e4daca4117 Mon Sep 17 00:00:00 2001 From: DJGosnell Date: Thu, 9 Feb 2023 16:53:40 -0500 Subject: [PATCH 2/4] Cached round SKRoundRects created with DrawingContextImpl.DrawRectangle --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 34 +++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index a29cbb1cc3..2bb6f1dc7e 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -315,15 +315,20 @@ namespace Avalonia.Skia var rc = rect.Rect.ToSKRect(); var isRounded = rect.IsRounded; var needRoundRect = rect.IsRounded; - using var skRoundRect = needRoundRect ? new SKRoundRect() : null; + SKRoundRect skRoundRect = null; if (needRoundRect) + { + skRoundRect = SKRoundRectCache.Shared.Get(); skRoundRect.SetRectRadii(rc, new[] { - rect.RadiiTopLeft.ToSKPoint(), rect.RadiiTopRight.ToSKPoint(), - rect.RadiiBottomRight.ToSKPoint(), rect.RadiiBottomLeft.ToSKPoint(), + rect.RadiiTopLeft.ToSKPoint(), + rect.RadiiTopRight.ToSKPoint(), + rect.RadiiBottomRight.ToSKPoint(), + rect.RadiiBottomLeft.ToSKPoint(), }); + } if (material != null) { @@ -332,6 +337,7 @@ namespace Avalonia.Skia if (isRounded) { Canvas.DrawRoundRect(skRoundRect, paint.Paint); + SKRoundRectCache.Shared.Return(skRoundRect); } else { @@ -356,14 +362,19 @@ namespace Avalonia.Skia var rc = rect.Rect.ToSKRect(); var isRounded = rect.IsRounded; var needRoundRect = rect.IsRounded || (boxShadows.HasInsetShadows); - using var skRoundRect = needRoundRect ? new SKRoundRect() : null; + SKRoundRect skRoundRect = null; if (needRoundRect) + { + skRoundRect = SKRoundRectCache.Shared.Get(); skRoundRect.SetRectRadii(rc, new[] { - rect.RadiiTopLeft.ToSKPoint(), rect.RadiiTopRight.ToSKPoint(), - rect.RadiiBottomRight.ToSKPoint(), rect.RadiiBottomLeft.ToSKPoint(), + rect.RadiiTopLeft.ToSKPoint(), + rect.RadiiTopRight.ToSKPoint(), + rect.RadiiBottomRight.ToSKPoint(), + rect.RadiiBottomLeft.ToSKPoint(), }); + } foreach (var boxShadow in boxShadows) { @@ -378,7 +389,8 @@ namespace Avalonia.Skia Canvas.Save(); if (isRounded) { - using var shadowRect = new SKRoundRect(skRoundRect); + var shadowRect = SKRoundRectCache.Shared.Get(); + shadowRect.SetRectRadii(skRoundRect!.Rect, skRoundRect.Radii); if (spread != 0) shadowRect.Inflate(spread, spread); Canvas.ClipRoundRect(skRoundRect, @@ -388,6 +400,7 @@ namespace Avalonia.Skia Transform = oldTransform * Matrix.CreateTranslation(boxShadow.OffsetX, boxShadow.OffsetY); Canvas.DrawRoundRect(shadowRect, shadow.Paint); Transform = oldTransform; + SKRoundRectCache.Shared.Return(shadowRect); } else { @@ -433,7 +446,8 @@ namespace Avalonia.Skia var outerRect = AreaCastingShadowInHole(rc, (float)boxShadow.Blur, spread, offsetX, offsetY); Canvas.Save(); - using var shadowRect = new SKRoundRect(skRoundRect); + var shadowRect = SKRoundRectCache.Shared.Get(); + shadowRect.SetRectRadii(skRoundRect!.Rect, skRoundRect.Radii); if (spread != 0) shadowRect.Deflate(spread, spread); Canvas.ClipRoundRect(skRoundRect, @@ -445,6 +459,7 @@ namespace Avalonia.Skia Canvas.DrawRoundRectDifference(outerRRect, shadowRect, shadow.Paint); Transform = oldTransform; Canvas.Restore(); + SKRoundRectCache.Shared.Return(shadowRect); } } } @@ -466,6 +481,9 @@ namespace Avalonia.Skia } } } + + if(isRounded) + SKRoundRectCache.Shared.Return(skRoundRect); } /// From 416ef6b601462f2eca229cfec1dd3481a8098aaa Mon Sep 17 00:00:00 2001 From: DJGosnell Date: Fri, 10 Feb 2023 18:45:20 -0500 Subject: [PATCH 3/4] Added two GetAndSetRadii methods. Added documentaiton. --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 16 +---- src/Skia/Avalonia.Skia/SKRoundRectCache.cs | 76 +++++++++++++++++++- 2 files changed, 77 insertions(+), 15 deletions(-) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 2bb6f1dc7e..eededb2836 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -365,15 +365,7 @@ namespace Avalonia.Skia SKRoundRect skRoundRect = null; if (needRoundRect) { - skRoundRect = SKRoundRectCache.Shared.Get(); - skRoundRect.SetRectRadii(rc, - new[] - { - rect.RadiiTopLeft.ToSKPoint(), - rect.RadiiTopRight.ToSKPoint(), - rect.RadiiBottomRight.ToSKPoint(), - rect.RadiiBottomLeft.ToSKPoint(), - }); + skRoundRect = SKRoundRectCache.Shared.GetAndSetRadii(rc, rect); } foreach (var boxShadow in boxShadows) @@ -389,8 +381,7 @@ namespace Avalonia.Skia Canvas.Save(); if (isRounded) { - var shadowRect = SKRoundRectCache.Shared.Get(); - shadowRect.SetRectRadii(skRoundRect!.Rect, skRoundRect.Radii); + var shadowRect = SKRoundRectCache.Shared.GetAndSetRadii(skRoundRect!.Rect, skRoundRect.Radii); if (spread != 0) shadowRect.Inflate(spread, spread); Canvas.ClipRoundRect(skRoundRect, @@ -446,8 +437,7 @@ namespace Avalonia.Skia var outerRect = AreaCastingShadowInHole(rc, (float)boxShadow.Blur, spread, offsetX, offsetY); Canvas.Save(); - var shadowRect = SKRoundRectCache.Shared.Get(); - shadowRect.SetRectRadii(skRoundRect!.Rect, skRoundRect.Radii); + var shadowRect = SKRoundRectCache.Shared.GetAndSetRadii(skRoundRect!.Rect, skRoundRect.Radii); if (spread != 0) shadowRect.Deflate(spread, spread); Canvas.ClipRoundRect(skRoundRect, diff --git a/src/Skia/Avalonia.Skia/SKRoundRectCache.cs b/src/Skia/Avalonia.Skia/SKRoundRectCache.cs index e164f97d6a..8de9e65553 100644 --- a/src/Skia/Avalonia.Skia/SKRoundRectCache.cs +++ b/src/Skia/Avalonia.Skia/SKRoundRectCache.cs @@ -1,13 +1,73 @@ -using System.Collections.Concurrent; +using System.Buffers; +using System.Collections.Concurrent; +using System.Collections.Generic; using SkiaSharp; namespace Avalonia.Skia { /// - /// Cache for SKPaints. + /// Cache for SKRoundRectCache. /// internal class SKRoundRectCache : SKCacheBase { + /// + /// Cache for points to use for setting the radii. + /// + private readonly ConcurrentBag _radiiCache = new(); + + /// + /// Gets a cached SKRoundRect and sets it with the passed rectangle and Radii. + /// + /// Rectangle size to set the cached rectangle to. + /// Rounded rectangle to copy the radii from. + /// Configured rounded rectangle + public SKRoundRect GetAndSetRadii(in SKRect rectangle, in RoundedRect roundedRect) + { + if (!Cache.TryTake(out var item)) + { + item = new SKRoundRect(); + } + + // Try and acquire a cached point array. + if (!_radiiCache.TryTake(out var skArray)) + { + skArray = new SKPoint[4]; + } + + skArray[0].X = (float)roundedRect.RadiiTopLeft.X; + skArray[0].Y = (float)roundedRect.RadiiTopLeft.Y; + skArray[1].X = (float)roundedRect.RadiiTopRight.X; + skArray[1].Y = (float)roundedRect.RadiiTopRight.Y; + skArray[2].X = (float)roundedRect.RadiiBottomRight.X; + skArray[2].Y = (float)roundedRect.RadiiBottomRight.Y; + skArray[3].X = (float)roundedRect.RadiiBottomLeft.X; + skArray[3].Y = (float)roundedRect.RadiiBottomLeft.Y; + + item.SetRectRadii(rectangle, skArray); + + // Add the array back to the cache. + _radiiCache.Add(skArray); + + return item; + } + + /// + /// Gets a cached SKRoundRect and sets it with the passed rectangle and Radii. + /// + /// Rectangle size to set the cached rectangle to. + /// point array of radii. + /// Configured rounded rectangle + public SKRoundRect GetAndSetRadii(in SKRect rectangle, in SKPoint[] radii) + { + if (!Cache.TryTake(out var item)) + { + item = new SKRoundRect(); + } + + item.SetRectRadii(rectangle, radii); + + return item; + } /// /// Returns a SKPaint and resets it for reuse later. /// @@ -22,5 +82,17 @@ namespace Avalonia.Skia rect.SetEmpty(); Cache.Add(rect); } + + /// + /// Clears and disposes all cached items. + /// + public new void Clear() + { + base.Clear(); + + // Clear out the cache of SKPoint arrays. + _radiiCache.Clear(); + } + } } From 4a0a5e68d680a0c4ecf1de0ac69ee26c87b1b41b Mon Sep 17 00:00:00 2001 From: DJGosnell Date: Mon, 13 Feb 2023 13:26:16 -0500 Subject: [PATCH 4/4] Fix for netstandard2.0. --- src/Skia/Avalonia.Skia/SKRoundRectCache.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Skia/Avalonia.Skia/SKRoundRectCache.cs b/src/Skia/Avalonia.Skia/SKRoundRectCache.cs index 8de9e65553..4be6171a93 100644 --- a/src/Skia/Avalonia.Skia/SKRoundRectCache.cs +++ b/src/Skia/Avalonia.Skia/SKRoundRectCache.cs @@ -91,8 +91,9 @@ namespace Avalonia.Skia base.Clear(); // Clear out the cache of SKPoint arrays. - _radiiCache.Clear(); + while (_radiiCache.TryTake(out var item)) + { + } } - } }