Преглед изворни кода

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

Render Loop SKPaint Cache
Max Katz пре 3 година
родитељ
комит
061beb6927

+ 39 - 43
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.ReturnReset(paint);
         }
 
         /// <inheritdoc />
@@ -561,6 +557,11 @@ namespace Avalonia.Skia
             CheckLease();
             try
             {
+                // Return leased paints.
+                SKPaintCache.ReturnReset(_strokePaint);
+                SKPaintCache.ReturnReset(_fillPaint);
+                SKPaintCache.ReturnReset(_boxShadowPaint);
+
                 if (_grContext != null)
                 {
                     Monitor.Exit(_grContext);
@@ -620,26 +621,33 @@ 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));
         }
 
         /// <inheritdoc />
         public void PopOpacityMask()
         {
             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);
-                using (var paintWrapper = _maskStack.Pop())
-                {
-                    Canvas.DrawPaint(paintWrapper.Paint);
-                }
-                Canvas.Restore();
+                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();
         }
@@ -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;
 
@@ -1023,11 +1031,10 @@ namespace Avalonia.Skia
         /// <param name="paint">The paint to wrap.</param>
         /// <param name="brush">Source brush.</param>
         /// <param name="targetSize">Target size.</param>
-        /// <param name="disposePaint">Optional dispose of the supplied paint.</param>
         /// <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;
 
@@ -1080,9 +1087,8 @@ namespace Avalonia.Skia
         /// <param name="paint">The paint to wrap.</param>
         /// <param name="pen">Source pen.</param>
         /// <param name="targetSize">Target size.</param>
-        /// <param name="disposePaint">Optional dispose of the supplied paint.</param>
         /// <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
             // and for us it means - there is nothing rendered.
@@ -1091,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;
@@ -1206,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;
@@ -1263,15 +1267,7 @@ namespace Avalonia.Skia
             /// <inheritdoc />
             public void Dispose()
             {
-                if (_disposePaint)
-                {
-                    Paint?.Dispose();
-                }
-                else
-                {
-                    Paint?.Reset();
-                }
-
+                Paint?.Reset();
                 _disposable1?.Dispose();
                 _disposable2?.Dispose();
                 _disposable3?.Dispose();

+ 6 - 7
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.ReturnReset(paint);
 
-                    _pathCache.Cache(strokePath, strokeWidth, strokePath.TightBounds.ToAvaloniaRect());
-                }
+                _pathCache.Cache(strokePath, strokeWidth, strokePath.TightBounds.ToAvaloniaRect());
             }
         }
 

+ 76 - 0
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();
+            }
+        }
+
+    }
+}