Browse Source

Initial work for adding caches for SKTextBlobBuilder, SKRoundRect, SKFont usages.

Updates SKPaintCache for new caching base class.
pull/10303/head
DJGosnell 3 years ago
parent
commit
4b0ff3be63
  1. 43
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  2. 4
      src/Skia/Avalonia.Skia/GeometryImpl.cs
  3. 4
      src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs
  4. 47
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  5. 72
      src/Skia/Avalonia.Skia/SKCacheBase.cs
  6. 13
      src/Skia/Avalonia.Skia/SKFontCache.cs
  7. 58
      src/Skia/Avalonia.Skia/SKPaintCache.cs
  8. 26
      src/Skia/Avalonia.Skia/SKRoundRectCache.cs
  9. 13
      src/Skia/Avalonia.Skia/SKTextBlobBuilderCache.cs
  10. 6
      src/Skia/Avalonia.Skia/TextShaperImpl.cs

43
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);
}
/// <inheritdoc />
@ -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);
}
/// <inheritdoc />
@ -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();

4
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());
}

4
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;

47
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);
}
}
}

72
src/Skia/Avalonia.Skia/SKCacheBase.cs

@ -0,0 +1,72 @@
using System;
using System.Collections.Concurrent;
using SkiaSharp;
namespace Avalonia.Skia
{
/// <summary>
/// Cache base for Skia objects.
/// </summary>
internal abstract class SKCacheBase<TCachedItem, TCache>
where TCachedItem : IDisposable, new()
where TCache : new()
{
/// <summary>
/// Bag to hold the cached items.
/// </summary>
protected readonly ConcurrentBag<TCachedItem> Cache;
/// <summary>
/// Shared cache.
/// </summary>
public static readonly TCache Shared = new TCache();
protected SKCacheBase()
{
Cache = new ConcurrentBag<TCachedItem>();
}
/// <summary>
/// Gets a cached item for usage.
/// </summary>
/// <remarks>
/// If there is a available item in the cache, the cached item will be returned..
/// Otherwise a new cached item will be created.
/// </remarks>
/// <returns></returns>
public TCachedItem Get()
{
if (!Cache.TryTake(out var item))
{
item = new TCachedItem();
}
return item;
}
/// <summary>
/// Returns the item for reuse later.
/// </summary>
/// <remarks>
/// Do not use the item further.
/// Do not return the same item multiple times as that will break the cache.
/// </remarks>
/// <param name="item"></param>
public void Return(TCachedItem item)
{
Cache.Add(item);
}
/// <summary>
/// Clears and disposes all cached items.
/// </summary>
public void Clear()
{
while (Cache.TryTake(out var item))
{
item.Dispose();
}
}
}
}

13
src/Skia/Avalonia.Skia/SKFontCache.cs

@ -0,0 +1,13 @@
using System.Collections.Concurrent;
using SkiaSharp;
namespace Avalonia.Skia
{
/// <summary>
/// Cache for SKFonts.
/// </summary>
internal class SKFontCache : SKCacheBase<SKFont, SKFontCache>
{
}
}

58
src/Skia/Avalonia.Skia/SKPaintCache.cs

@ -6,46 +6,8 @@ namespace Avalonia.Skia
/// <summary>
/// Cache for SKPaints.
/// </summary>
internal static class SKPaintCache
internal class SKPaintCache : SKCacheBase<SKPaint, SKPaintCache>
{
private static ConcurrentBag<SKPaint> s_cachedPaints;
static SKPaintCache()
{
s_cachedPaints = new ConcurrentBag<SKPaint>();
}
/// <summary>
/// Gets a SKPaint for usage.
/// </summary>
/// <remarks>
/// If a SKPaint is in the cache, that existing SKPaint will be returned.
/// Otherwise a new SKPaint will be created.
/// </remarks>
/// <returns></returns>
public static SKPaint Get()
{
if (!s_cachedPaints.TryTake(out var paint))
{
paint = new SKPaint();
}
return paint;
}
/// <summary>
/// Returns a SKPaint for reuse later.
/// </summary>
/// <remarks>
/// Do not use the paint further.
/// Do not return the same paint multiple times as that will break the cache.
/// </remarks>
/// <param name="paint"></param>
public static void Return(SKPaint paint)
{
s_cachedPaints.Add(paint);
}
/// <summary>
/// Returns a SKPaint and resets it for reuse later.
/// </summary>
@ -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.
/// </remarks>
/// <param name="paint"></param>
public static void ReturnReset(SKPaint paint)
/// <param name="paint">Paint to reset.</param>
public void ReturnReset(SKPaint paint)
{
paint.Reset();
s_cachedPaints.Add(paint);
Cache.Add(paint);
}
/// <summary>
/// Clears and disposes all cached paints.
/// </summary>
public static void Clear()
{
while (s_cachedPaints.TryTake(out var paint))
{
paint.Dispose();
}
}
}
}

26
src/Skia/Avalonia.Skia/SKRoundRectCache.cs

@ -0,0 +1,26 @@
using System.Collections.Concurrent;
using SkiaSharp;
namespace Avalonia.Skia
{
/// <summary>
/// Cache for SKPaints.
/// </summary>
internal class SKRoundRectCache : SKCacheBase<SKRoundRect, SKRoundRectCache>
{
/// <summary>
/// Returns a SKPaint and resets it for reuse later.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="rect">Rectangle to reset</param>
public void ReturnReset(SKRoundRect rect)
{
rect.SetEmpty();
Cache.Add(rect);
}
}
}

13
src/Skia/Avalonia.Skia/SKTextBlobBuilderCache.cs

@ -0,0 +1,13 @@
using System.Collections.Concurrent;
using SkiaSharp;
namespace Avalonia.Skia
{
/// <summary>
/// Cache for SKTextBlobBuilder.
/// </summary>
internal class SKTextBlobBuilderCache : SKCacheBase<SKTextBlobBuilder, SKTextBlobBuilderCache>
{
}
}

6
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<int, Language> s_cachedLanguage = new();
public ShapedBuffer ShapeText(ReadOnlyMemory<char> 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;

Loading…
Cancel
Save