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;