Browse Source

Merge pull request #9441 from DJGosnell/render-loop-skpaint-cache

Render Loop SKPaint Cache
pull/9462/head
Max Katz 4 years ago
committed by GitHub
parent
commit
061beb6927
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 82
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  2. 13
      src/Skia/Avalonia.Skia/GeometryImpl.cs
  3. 76
      src/Skia/Avalonia.Skia/SKPaintCache.cs

82
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -35,9 +35,9 @@ namespace Avalonia.Skia
private GRContext _grContext; private GRContext _grContext;
public GRContext GrContext => _grContext; public GRContext GrContext => _grContext;
private ISkiaGpu _gpu; private ISkiaGpu _gpu;
private readonly SKPaint _strokePaint = new SKPaint(); private readonly SKPaint _strokePaint = SKPaintCache.Get();
private readonly SKPaint _fillPaint = new SKPaint(); private readonly SKPaint _fillPaint = SKPaintCache.Get();
private readonly SKPaint _boxShadowPaint = new SKPaint(); private readonly SKPaint _boxShadowPaint = SKPaintCache.Get();
private static SKShader s_acrylicNoiseShader; private static SKShader s_acrylicNoiseShader;
private readonly ISkiaGpuRenderSession _session; private readonly ISkiaGpuRenderSession _session;
private bool _leased = false; private bool _leased = false;
@ -187,17 +187,13 @@ namespace Avalonia.Skia
var s = sourceRect.ToSKRect(); var s = sourceRect.ToSKRect();
var d = destRect.ToSKRect(); var d = destRect.ToSKRect();
using (var paint = var paint = SKPaintCache.Get();
new SKPaint paint.Color = new SKColor(255, 255, 255, (byte)(255 * opacity * _currentOpacity));
{ paint.FilterQuality = bitmapInterpolationMode.ToSKFilterQuality();
Color = new SKColor(255, 255, 255, (byte)(255 * opacity * _currentOpacity)) paint.BlendMode = _currentBlendingMode.ToSKBlendMode();
})
{
paint.FilterQuality = bitmapInterpolationMode.ToSKFilterQuality();
paint.BlendMode = _currentBlendingMode.ToSKBlendMode();
drawableImage.Draw(this, s, d, paint); drawableImage.Draw(this, s, d, paint);
} SKPaintCache.ReturnReset(paint);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -561,6 +557,11 @@ namespace Avalonia.Skia
CheckLease(); CheckLease();
try try
{ {
// Return leased paints.
SKPaintCache.ReturnReset(_strokePaint);
SKPaintCache.ReturnReset(_fillPaint);
SKPaintCache.ReturnReset(_boxShadowPaint);
if (_grContext != null) if (_grContext != null)
{ {
Monitor.Exit(_grContext); Monitor.Exit(_grContext);
@ -620,26 +621,33 @@ namespace Avalonia.Skia
public void PushOpacityMask(IBrush mask, Rect bounds) public void PushOpacityMask(IBrush mask, Rect bounds)
{ {
CheckLease(); CheckLease();
// TODO: This should be disposed
var paint = new SKPaint(); var paint = SKPaintCache.Get();
Canvas.SaveLayer(paint); Canvas.SaveLayer(paint);
_maskStack.Push(CreatePaint(paint, mask, bounds.Size, true)); _maskStack.Push(CreatePaint(paint, mask, bounds.Size));
} }
/// <inheritdoc /> /// <inheritdoc />
public void PopOpacityMask() public void PopOpacityMask()
{ {
CheckLease(); CheckLease();
using (var paint = new SKPaint { BlendMode = SKBlendMode.DstIn })
var paint = SKPaintCache.Get();
paint.BlendMode = SKBlendMode.DstIn;
Canvas.SaveLayer(paint);
SKPaintCache.ReturnReset(paint);
PaintWrapper paintWrapper;
using (paintWrapper = _maskStack.Pop())
{ {
Canvas.SaveLayer(paint); Canvas.DrawPaint(paintWrapper.Paint);
using (var paintWrapper = _maskStack.Pop())
{
Canvas.DrawPaint(paintWrapper.Paint);
}
Canvas.Restore();
} }
// 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(); Canvas.Restore();
} }
@ -974,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; paint.IsAntialias = true;
@ -1023,11 +1031,10 @@ namespace Avalonia.Skia
/// <param name="paint">The paint to wrap.</param> /// <param name="paint">The paint to wrap.</param>
/// <param name="brush">Source brush.</param> /// <param name="brush">Source brush.</param>
/// <param name="targetSize">Target size.</param> /// <param name="targetSize">Target size.</param>
/// <param name="disposePaint">Optional dispose of the supplied paint.</param>
/// <returns>Paint wrapper for given brush.</returns> /// <returns>Paint wrapper for given brush.</returns>
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; paint.IsAntialias = true;
@ -1080,9 +1087,8 @@ namespace Avalonia.Skia
/// <param name="paint">The paint to wrap.</param> /// <param name="paint">The paint to wrap.</param>
/// <param name="pen">Source pen.</param> /// <param name="pen">Source pen.</param>
/// <param name="targetSize">Target size.</param> /// <param name="targetSize">Target size.</param>
/// <param name="disposePaint">Optional dispose of the supplied paint.</param>
/// <returns></returns> /// <returns></returns>
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 // In Skia 0 thickness means - use hairline rendering
// and for us it means - there is nothing rendered. // and for us it means - there is nothing rendered.
@ -1091,7 +1097,7 @@ namespace Avalonia.Skia
return default; return default;
} }
var rv = CreatePaint(paint, pen.Brush, targetSize, disposePaint); var rv = CreatePaint(paint, pen.Brush, targetSize);
paint.IsStroke = true; paint.IsStroke = true;
paint.StrokeWidth = (float) pen.Thickness; paint.StrokeWidth = (float) pen.Thickness;
@ -1206,16 +1212,14 @@ namespace Avalonia.Skia
{ {
//We are saving memory allocations there //We are saving memory allocations there
public readonly SKPaint Paint; public readonly SKPaint Paint;
private readonly bool _disposePaint;
private IDisposable _disposable1; private IDisposable _disposable1;
private IDisposable _disposable2; private IDisposable _disposable2;
private IDisposable _disposable3; private IDisposable _disposable3;
public PaintWrapper(SKPaint paint, bool disposePaint) public PaintWrapper(SKPaint paint)
{ {
Paint = paint; Paint = paint;
_disposePaint = disposePaint;
_disposable1 = null; _disposable1 = null;
_disposable2 = null; _disposable2 = null;
@ -1263,15 +1267,7 @@ namespace Avalonia.Skia
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
{ {
if (_disposePaint) Paint?.Reset();
{
Paint?.Dispose();
}
else
{
Paint?.Reset();
}
_disposable1?.Dispose(); _disposable1?.Dispose();
_disposable2?.Dispose(); _disposable2?.Dispose();
_disposable3?.Dispose(); _disposable3?.Dispose();

13
src/Skia/Avalonia.Skia/GeometryImpl.cs

@ -81,15 +81,14 @@ namespace Avalonia.Skia
} }
else else
{ {
using (var paint = new SKPaint()) var paint = SKPaintCache.Get();
{ paint.IsStroke = true;
paint.IsStroke = true; paint.StrokeWidth = strokeWidth;
paint.StrokeWidth = strokeWidth; paint.GetFillPath(EffectivePath, strokePath);
paint.GetFillPath(EffectivePath, strokePath); SKPaintCache.ReturnReset(paint);
_pathCache.Cache(strokePath, strokeWidth, strokePath.TightBounds.ToAvaloniaRect()); _pathCache.Cache(strokePath, strokeWidth, strokePath.TightBounds.ToAvaloniaRect());
}
} }
} }

76
src/Skia/Avalonia.Skia/SKPaintCache.cs

@ -0,0 +1,76 @@
using System.Collections.Concurrent;
using SkiaSharp;
namespace Avalonia.Skia
{
/// <summary>
/// Cache for SKPaints.
/// </summary>
internal static class 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>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="paint"></param>
public static void ReturnReset(SKPaint paint)
{
paint.Reset();
s_cachedPaints.Add(paint);
}
/// <summary>
/// Clears and disposes all cached paints.
/// </summary>
public static void Clear()
{
while (s_cachedPaints.TryTake(out var paint))
{
paint.Dispose();
}
}
}
}
Loading…
Cancel
Save