diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionRenderData.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionRenderData.cs index 0ad70d7102..89d25b2e88 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionRenderData.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionRenderData.cs @@ -31,11 +31,9 @@ internal class CompositionRenderData : ICompositorSerializable, IDisposable { if (!_itemsSent) { - foreach(var i in _items) - if (i is IDisposable disp) - disp.Dispose(); + RenderDataItemPoolHelper.DisposeAndReturnToPool(_items); } - + _items.Dispose(); _itemsSent = false; foreach(var r in _resources) diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataBitmapNode.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataBitmapNode.cs index 11a1b87fc9..e08501fee1 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataBitmapNode.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataBitmapNode.cs @@ -4,8 +4,12 @@ using Avalonia.Utilities; namespace Avalonia.Rendering.Composition.Drawing.Nodes; -class RenderDataBitmapNode : IRenderDataItem, IDisposable +class RenderDataBitmapNode : IRenderDataItem, IDisposable, IPoolableRenderDataItem { + private static readonly RenderDataNodePool s_pool = new(); + + public static RenderDataBitmapNode Get() => s_pool.Get(); + public IRef? Bitmap { get; set; } public double Opacity { get; set; } public Rect SourceRect { get; set; } @@ -25,4 +29,13 @@ class RenderDataBitmapNode : IRenderDataItem, IDisposable Bitmap?.Dispose(); Bitmap = null; } + + public void ReturnToPool() + { + Dispose(); + Opacity = default; + SourceRect = default; + DestRect = default; + s_pool.Return(this); + } } \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataEllipseNode.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataEllipseNode.cs index 4e77281cc5..e6061fd4cd 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataEllipseNode.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataEllipseNode.cs @@ -4,10 +4,14 @@ using Avalonia.Platform; namespace Avalonia.Rendering.Composition.Drawing.Nodes; -class RenderDataEllipseNode :RenderDataBrushAndPenNode +class RenderDataEllipseNode : RenderDataBrushAndPenNode, IPoolableRenderDataItem { + private static readonly RenderDataNodePool s_pool = new(); + + public static RenderDataEllipseNode Get() => s_pool.Get(); + public Rect Rect { get; set; } - + bool Contains(double dx, double dy, double radiusX, double radiusY) { var rx2 = radiusX * radiusX; @@ -17,7 +21,7 @@ class RenderDataEllipseNode :RenderDataBrushAndPenNode return distance <= rx2 * ry2; } - + public override bool HitTest(Point p) { var center = Rect.Center; @@ -50,7 +54,7 @@ class RenderDataEllipseNode :RenderDataBrushAndPenNode return inStroke && !inInner; } - + return false; } @@ -58,4 +62,13 @@ class RenderDataEllipseNode :RenderDataBrushAndPenNode context.Context.DrawEllipse(ServerBrush, ServerPen, Rect); public override Rect? Bounds => Rect.Inflate(ServerPen?.Thickness ?? 0); -} \ No newline at end of file + + public void ReturnToPool() + { + ServerBrush = null; + ServerPen = null; + ClientPen = null; + Rect = default; + s_pool.Return(this); + } +} diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataGeometryNode.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataGeometryNode.cs index 5a40a6b279..bccfb5a2ac 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataGeometryNode.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataGeometryNode.cs @@ -5,20 +5,24 @@ using Avalonia.Rendering.SceneGraph; namespace Avalonia.Rendering.Composition.Drawing.Nodes; -class RenderDataGeometryNode : RenderDataBrushAndPenNode +class RenderDataGeometryNode : RenderDataBrushAndPenNode, IPoolableRenderDataItem { + private static readonly RenderDataNodePool s_pool = new(); + + public static RenderDataGeometryNode Get() => s_pool.Get(); + public IGeometryImpl? Geometry { get; set; } - + public override bool HitTest(Point p) { if (Geometry == null) return false; - + return (ServerBrush != null // null check is safe && Geometry.FillContains(p)) || (ClientPen != null && Geometry.StrokeContains(ClientPen, p)); } - + public override void Invoke(ref RenderDataNodeRenderContext context) { Debug.Assert(Geometry != null); @@ -26,4 +30,13 @@ class RenderDataGeometryNode : RenderDataBrushAndPenNode } public override Rect? Bounds => Geometry?.GetRenderBounds(ServerPen) ?? default; -} \ No newline at end of file + + public void ReturnToPool() + { + ServerBrush = null; + ServerPen = null; + ClientPen = null; + Geometry = null; + s_pool.Return(this); + } +} diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataGlyphRunNode.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataGlyphRunNode.cs index ca2748cd55..523a1f3a1f 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataGlyphRunNode.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataGlyphRunNode.cs @@ -6,8 +6,12 @@ using Avalonia.Utilities; namespace Avalonia.Rendering.Composition.Drawing.Nodes; -class RenderDataGlyphRunNode : IRenderDataItemWithServerResources, IDisposable +class RenderDataGlyphRunNode : IRenderDataItemWithServerResources, IDisposable, IPoolableRenderDataItem { + private static readonly RenderDataNodePool s_pool = new(); + + public static RenderDataGlyphRunNode Get() => s_pool.Get(); + public IBrush? ServerBrush { get; set; } // Dispose only happens once, so it's safe to have one reference public IRef? GlyphRun { get; set; } @@ -32,4 +36,11 @@ class RenderDataGlyphRunNode : IRenderDataItemWithServerResources, IDisposable GlyphRun?.Dispose(); GlyphRun = null; } + + public void ReturnToPool() + { + Dispose(); + ServerBrush = null; + s_pool.Return(this); + } } \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataLineNode.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataLineNode.cs index 6b6b9bdb01..ad7bc96fee 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataLineNode.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataLineNode.cs @@ -5,13 +5,17 @@ using Avalonia.Rendering.SceneGraph; namespace Avalonia.Rendering.Composition.Drawing.Nodes; -class RenderDataLineNode : IRenderDataItemWithServerResources +class RenderDataLineNode : IRenderDataItemWithServerResources, IPoolableRenderDataItem { + private static readonly RenderDataNodePool s_pool = new(); + + public static RenderDataLineNode Get() => s_pool.Get(); + public IPen? ServerPen { get; set; } public IPen? ClientPen { get; set; } public Point P1 { get; set; } public Point P2 { get; set; } - + public bool HitTest(Point p) { if (ClientPen == null) @@ -52,9 +56,9 @@ class RenderDataLineNode : IRenderDataItemWithServerResources return Math.Abs(distance) <= halfThickness; } - - public void Invoke(ref RenderDataNodeRenderContext context) + + public void Invoke(ref RenderDataNodeRenderContext context) => context.Context.DrawLine(ServerPen, P1, P2); public Rect? Bounds => LineBoundsHelper.CalculateBounds(P1, P2, ServerPen!); @@ -62,4 +66,13 @@ class RenderDataLineNode : IRenderDataItemWithServerResources { collector.AddRenderDataServerResource(ServerPen); } -} \ No newline at end of file + + public void ReturnToPool() + { + ServerPen = null; + ClientPen = null; + P1 = default; + P2 = default; + s_pool.Return(this); + } +} diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataNodePool.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataNodePool.cs new file mode 100644 index 0000000000..342ee96858 --- /dev/null +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataNodePool.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Avalonia.Rendering.Composition.Drawing.Nodes; + +internal interface IRenderDataNodePool +{ + void Reduce(); +} + +/// +/// Manages a single cleanup timer shared by all instances. +/// Uses weak references so pools that go out of scope can be garbage collected. +/// +internal static class RenderDataNodePoolCleanup +{ + private static readonly List> s_pools = new(); + private static Timer? s_timer; + + public static void Register(IRenderDataNodePool pool) + { + lock (s_pools) + { + s_pools.Add(new WeakReference(pool)); + s_timer ??= new Timer(_ => RunCleanup(), null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1)); + } + } + + private static void RunCleanup() + { + lock (s_pools) + { + for (var i = s_pools.Count - 1; i >= 0; i--) + { + if (s_pools[i].TryGetTarget(out var pool)) + pool.Reduce(); + else + s_pools.RemoveAt(i); + } + } + } +} + +/// +/// Object pool for render data nodes with gradual reclamation during idle periods. +/// While the pool is actively used, items are retained for reuse. Once activity stops, +/// a shared timer gradually releases a third of pooled items per cycle until empty. +/// +internal sealed class RenderDataNodePool : IRenderDataNodePool where T : class, new() +{ + private T[] _items = Array.Empty(); + private int _count; + private bool _active; + + public RenderDataNodePool() + { + RenderDataNodePoolCleanup.Register(this); + } + + public T Get() + { + lock (_items) + { + _active = true; + + if (_count > 0) + return _items[--_count]; + } + + return new T(); + } + + public void Return(T item) + { + lock (_items) + { + _active = true; + + if (_count == _items.Length) + Array.Resize(ref _items, Math.Max(4, _items.Length * 2)); + + _items[_count++] = item; + } + } + + public void Reduce() + { + lock (_items) + { + if (_active) + { + _active = false; + return; + } + + if (_count == 0) + return; + + var release = Math.Max(1, _count / 3); + var newCount = _count - release; + Array.Clear(_items, newCount, release); + _count = newCount; + } + } +} diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataNodes.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataNodes.cs index c7d10f69a1..1b9c803404 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataNodes.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataNodes.cs @@ -53,6 +53,14 @@ struct RenderDataNodeRenderContext : IDisposable } } +/// +/// Implemented by render data nodes that support object pooling to reduce GC pressure. +/// +interface IPoolableRenderDataItem +{ + void ReturnToPool(); +} + interface IRenderDataItem { /// @@ -78,8 +86,12 @@ interface IRenderDataItem bool HitTest(Point p); } -class RenderDataCustomNode : IRenderDataItem, IDisposable +class RenderDataCustomNode : IRenderDataItem, IDisposable, IPoolableRenderDataItem { + private static readonly RenderDataNodePool s_pool = new(); + + public static RenderDataCustomNode Get() => s_pool.Get(); + public ICustomDrawOperation? Operation { get; set; } public bool HitTest(Point p) => Operation?.HitTest(p) ?? false; public void Invoke(ref RenderDataNodeRenderContext context) => Operation?.Render(new(context.Context, false)); @@ -91,6 +103,12 @@ class RenderDataCustomNode : IRenderDataItem, IDisposable Operation?.Dispose(); Operation = null; } + + public void ReturnToPool() + { + Dispose(); + s_pool.Return(this); + } } abstract class RenderDataPushNode : IRenderDataItem, IDisposable @@ -143,8 +161,12 @@ abstract class RenderDataPushNode : IRenderDataItem, IDisposable } } -class RenderDataClipNode : RenderDataPushNode +class RenderDataClipNode : RenderDataPushNode, IPoolableRenderDataItem { + private static readonly RenderDataNodePool s_pool = new(); + + public static RenderDataClipNode Get() => s_pool.Get(); + public RoundedRect Rect { get; set; } public override void Push(ref RenderDataNodeRenderContext context) => context.Context.PushClip(Rect); @@ -158,13 +180,23 @@ class RenderDataClipNode : RenderDataPushNode return false; return base.HitTest(p); } + + public void ReturnToPool() + { + Rect = default; + s_pool.Return(this); + } } -class RenderDataGeometryClipNode : RenderDataPushNode +class RenderDataGeometryClipNode : RenderDataPushNode, IPoolableRenderDataItem { + private static readonly RenderDataNodePool s_pool = new(); + + public static RenderDataGeometryClipNode Get() => s_pool.Get(); + public IGeometryImpl? Geometry { get; set; } public bool Contains(Point p) => Geometry?.FillContains(p) ?? false; - + public override void Push(ref RenderDataNodeRenderContext context) { if (Geometry != null) @@ -183,10 +215,20 @@ class RenderDataGeometryClipNode : RenderDataPushNode return false; return base.HitTest(p); } + + public void ReturnToPool() + { + Geometry = null; + s_pool.Return(this); + } } -class RenderDataOpacityNode : RenderDataPushNode +class RenderDataOpacityNode : RenderDataPushNode, IPoolableRenderDataItem { + private static readonly RenderDataNodePool s_pool = new(); + + public static RenderDataOpacityNode Get() => s_pool.Get(); + public double Opacity { get; set; } public override void Push(ref RenderDataNodeRenderContext context) { @@ -199,6 +241,12 @@ class RenderDataOpacityNode : RenderDataPushNode if (Opacity != 1) context.Context.PopOpacity(); } + + public void ReturnToPool() + { + Opacity = default; + s_pool.Return(this); + } } abstract class RenderDataBrushAndPenNode : IRenderDataItemWithServerResources @@ -218,8 +266,12 @@ abstract class RenderDataBrushAndPenNode : IRenderDataItemWithServerResources public abstract bool HitTest(Point p); } -class RenderDataRenderOptionsNode : RenderDataPushNode +class RenderDataRenderOptionsNode : RenderDataPushNode, IPoolableRenderDataItem { + private static readonly RenderDataNodePool s_pool = new(); + + public static RenderDataRenderOptionsNode Get() => s_pool.Get(); + public RenderOptions RenderOptions { get; set; } public override void Push(ref RenderDataNodeRenderContext context) @@ -231,10 +283,20 @@ class RenderDataRenderOptionsNode : RenderDataPushNode { context.Context.PopRenderOptions(); } + + public void ReturnToPool() + { + RenderOptions = default; + s_pool.Return(this); + } } -class RenderDataTextOptionsNode : RenderDataPushNode +class RenderDataTextOptionsNode : RenderDataPushNode, IPoolableRenderDataItem { + private static readonly RenderDataNodePool s_pool = new(); + + public static RenderDataTextOptionsNode Get() => s_pool.Get(); + public TextOptions TextOptions { get; set; } public override void Push(ref RenderDataNodeRenderContext context) @@ -246,4 +308,34 @@ class RenderDataTextOptionsNode : RenderDataPushNode { context.Context.PopTextOptions(); } + + public void ReturnToPool() + { + TextOptions = default; + s_pool.Return(this); + } +} + +static class RenderDataItemPoolHelper +{ + /// + /// Disposes disposable items and returns poolable items to their object pools. + /// Recurses into push node children. + /// + public static void DisposeAndReturnToPool(PooledInlineList items) + { + foreach (var item in items) + { + if (item is RenderDataPushNode pushNode) + { + DisposeAndReturnToPool(pushNode.Children); + pushNode.Children.Dispose(); + } + + if (item is IPoolableRenderDataItem poolable) + poolable.ReturnToPool(); + else if (item is IDisposable disp) + disp.Dispose(); + } + } } diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataPushMatrixNode.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataPushMatrixNode.cs index 2ccfa0e7a5..548c499704 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataPushMatrixNode.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataPushMatrixNode.cs @@ -1,7 +1,11 @@ namespace Avalonia.Rendering.Composition.Drawing.Nodes; -class RenderDataPushMatrixNode : RenderDataPushNode +class RenderDataPushMatrixNode : RenderDataPushNode, IPoolableRenderDataItem { + private static readonly RenderDataNodePool s_pool = new(); + + public static RenderDataPushMatrixNode Get() => s_pool.Get(); + public Matrix Matrix { get; set; } public override void Push(ref RenderDataNodeRenderContext context) @@ -24,4 +28,10 @@ class RenderDataPushMatrixNode : RenderDataPushNode } public override Rect? Bounds => base.Bounds?.TransformToAABB(Matrix); + + public void ReturnToPool() + { + Matrix = default; + s_pool.Return(this); + } } \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataPushOpacityMaskNode.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataPushOpacityMaskNode.cs index 14607282da..e8f69237b3 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataPushOpacityMaskNode.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataPushOpacityMaskNode.cs @@ -3,8 +3,12 @@ using Avalonia.Platform; namespace Avalonia.Rendering.Composition.Drawing.Nodes; -class RenderDataOpacityMaskNode : RenderDataPushNode, IRenderDataItemWithServerResources +class RenderDataOpacityMaskNode : RenderDataPushNode, IRenderDataItemWithServerResources, IPoolableRenderDataItem { + private static readonly RenderDataNodePool s_pool = new(); + + public static RenderDataOpacityMaskNode Get() => s_pool.Get(); + public IBrush? ServerBrush { get; set; } public Rect BoundsRect { get; set; } @@ -22,4 +26,11 @@ class RenderDataOpacityMaskNode : RenderDataPushNode, IRenderDataItemWithServerR public override void Pop(ref RenderDataNodeRenderContext context) => context.Context.PopOpacityMask(); + + public void ReturnToPool() + { + ServerBrush = null; + BoundsRect = default; + s_pool.Return(this); + } } \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataRectangleNode.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataRectangleNode.cs index c6a3859e3d..e4dafbe980 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataRectangleNode.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataRectangleNode.cs @@ -2,8 +2,12 @@ using Avalonia.Media; namespace Avalonia.Rendering.Composition.Drawing.Nodes; -class RenderDataRectangleNode : RenderDataBrushAndPenNode +class RenderDataRectangleNode : RenderDataBrushAndPenNode, IPoolableRenderDataItem { + private static readonly RenderDataNodePool s_pool = new(); + + public static RenderDataRectangleNode Get() => s_pool.Get(); + public RoundedRect Rect { get; set; } public BoxShadows BoxShadows { get; set; } @@ -21,7 +25,7 @@ class RenderDataRectangleNode : RenderDataBrushAndPenNode var innerRoundedRect = Rect.Deflate(strokeThicknessAdjustment, strokeThicknessAdjustment); return !innerRoundedRect.ContainsExclusive(p); - } + } } else { @@ -43,4 +47,14 @@ class RenderDataRectangleNode : RenderDataBrushAndPenNode context.Context.DrawRectangle(ServerBrush, ServerPen, Rect, BoxShadows); public override Rect? Bounds => BoxShadows.TransformBounds(Rect.Rect).Inflate((ServerPen?.Thickness ?? 0) / 2); + + public void ReturnToPool() + { + ServerBrush = null; + ServerPen = null; + ClientPen = null; + Rect = default; + BoxShadows = default; + s_pool.Return(this); + } } diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/RenderDataDrawingContext.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/RenderDataDrawingContext.cs index ead6a3f730..757a133948 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/RenderDataDrawingContext.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/RenderDataDrawingContext.cs @@ -124,13 +124,12 @@ internal class RenderDataDrawingContext : DrawingContext if(pen == null) return; AddResource(pen); - Add(new RenderDataLineNode - { - ClientPen = pen, - ServerPen = pen.GetServer(_compositor), - P1 = p1, - P2 = p2 - }); + var lineNode = RenderDataLineNode.Get(); + lineNode.ClientPen = pen; + lineNode.ServerPen = pen.GetServer(_compositor); + lineNode.P1 = p1; + lineNode.P2 = p2; + Add(lineNode); } protected override void DrawGeometryCore(IBrush? brush, IPen? pen, IGeometryImpl geometry) @@ -139,13 +138,12 @@ internal class RenderDataDrawingContext : DrawingContext return; AddResource(brush); AddResource(pen); - Add(new RenderDataGeometryNode - { - ServerBrush = brush.GetServer(_compositor), - ServerPen = pen.GetServer(_compositor), - ClientPen = pen, - Geometry = geometry - }); + var geoNode = RenderDataGeometryNode.Get(); + geoNode.ServerBrush = brush.GetServer(_compositor); + geoNode.ServerPen = pen.GetServer(_compositor); + geoNode.ClientPen = pen; + geoNode.Geometry = geometry; + Add(geoNode); } protected override void DrawRectangleCore(IBrush? brush, IPen? pen, RoundedRect rrect, BoxShadows boxShadows = default) @@ -156,14 +154,13 @@ internal class RenderDataDrawingContext : DrawingContext return; AddResource(brush); AddResource(pen); - Add(new RenderDataRectangleNode - { - ServerBrush = brush.GetServer(_compositor), - ServerPen = pen.GetServer(_compositor), - ClientPen = pen, - Rect = rrect, - BoxShadows = boxShadows - }); + var rectNode = RenderDataRectangleNode.Get(); + rectNode.ServerBrush = brush.GetServer(_compositor); + rectNode.ServerPen = pen.GetServer(_compositor); + rectNode.ClientPen = pen; + rectNode.Rect = rrect; + rectNode.BoxShadows = boxShadows; + Add(rectNode); } protected override void DrawEllipseCore(IBrush? brush, IPen? pen, Rect rect) @@ -175,51 +172,56 @@ internal class RenderDataDrawingContext : DrawingContext return; AddResource(brush); AddResource(pen); - Add(new RenderDataEllipseNode - { - ServerBrush = brush.GetServer(_compositor), - ServerPen = pen.GetServer(_compositor), - ClientPen = pen, - Rect = rect, - }); + var ellipseNode = RenderDataEllipseNode.Get(); + ellipseNode.ServerBrush = brush.GetServer(_compositor); + ellipseNode.ServerPen = pen.GetServer(_compositor); + ellipseNode.ClientPen = pen; + ellipseNode.Rect = rect; + Add(ellipseNode); } - public override void Custom(ICustomDrawOperation custom) => Add(new RenderDataCustomNode + public override void Custom(ICustomDrawOperation custom) { - Operation = custom - }); + var node = RenderDataCustomNode.Get(); + node.Operation = custom; + Add(node); + } public override void DrawGlyphRun(IBrush? foreground, GlyphRun? glyphRun) { if (foreground == null || glyphRun == null) return; AddResource(foreground); - Add(new RenderDataGlyphRunNode - { - ServerBrush = foreground.GetServer(_compositor), - GlyphRun = glyphRun.PlatformImpl.Clone() - }); + var glyphNode = RenderDataGlyphRunNode.Get(); + glyphNode.ServerBrush = foreground.GetServer(_compositor); + glyphNode.GlyphRun = glyphRun.PlatformImpl.Clone(); + Add(glyphNode); } - protected override void PushClipCore(RoundedRect rect) => Push(new RenderDataClipNode + protected override void PushClipCore(RoundedRect rect) { - Rect = rect - }); + var node = RenderDataClipNode.Get(); + node.Rect = rect; + Push(node); + } - protected override void PushClipCore(Rect rect) => Push(new RenderDataClipNode + protected override void PushClipCore(Rect rect) { - Rect = rect - }); + var node = RenderDataClipNode.Get(); + node.Rect = rect; + Push(node); + } protected override void PushGeometryClipCore(Geometry? clip) { if (clip == null) Push(); else - Push(new RenderDataGeometryClipNode - { - Geometry = clip?.PlatformImpl - }); + { + var node = RenderDataGeometryClipNode.Get(); + node.Geometry = clip?.PlatformImpl; + Push(node); + } } protected override void PushOpacityCore(double opacity) @@ -227,10 +229,11 @@ internal class RenderDataDrawingContext : DrawingContext if (opacity == 1) Push(); else - Push(new RenderDataOpacityNode - { - Opacity = opacity - }); + { + var node = RenderDataOpacityNode.Get(); + node.Opacity = opacity; + Push(node); + } } protected override void PushOpacityMaskCore(IBrush? mask, Rect bounds) @@ -240,11 +243,10 @@ internal class RenderDataDrawingContext : DrawingContext else { AddResource(mask); - Push(new RenderDataOpacityMaskNode - { - ServerBrush = mask.GetServer(_compositor), - BoundsRect = bounds - }); + var node = RenderDataOpacityMaskNode.Get(); + node.ServerBrush = mask.GetServer(_compositor); + node.BoundsRect = bounds; + Push(node); } } @@ -253,21 +255,26 @@ internal class RenderDataDrawingContext : DrawingContext if (matrix.IsIdentity) Push(); else - Push(new RenderDataPushMatrixNode() - { - Matrix = matrix - }); + { + var node = RenderDataPushMatrixNode.Get(); + node.Matrix = matrix; + Push(node); + } } - protected override void PushRenderOptionsCore(RenderOptions renderOptions) => Push(new RenderDataRenderOptionsNode() + protected override void PushRenderOptionsCore(RenderOptions renderOptions) { - RenderOptions = renderOptions - }); + var node = RenderDataRenderOptionsNode.Get(); + node.RenderOptions = renderOptions; + Push(node); + } - protected override void PushTextOptionsCore(TextOptions textOptions) => Push(new RenderDataTextOptionsNode() + protected override void PushTextOptionsCore(TextOptions textOptions) { - TextOptions = textOptions - }); + var node = RenderDataTextOptionsNode.Get(); + node.TextOptions = textOptions; + Push(node); + } protected override void PopClipCore() => Pop(); @@ -287,13 +294,12 @@ internal class RenderDataDrawingContext : DrawingContext { if (source == null || sourceRect.IsEmpty() || destRect.IsEmpty()) return; - Add(new RenderDataBitmapNode - { - Bitmap = source.Clone(), - Opacity = opacity, - SourceRect = sourceRect, - DestRect = destRect - }); + var bitmapNode = RenderDataBitmapNode.Get(); + bitmapNode.Bitmap = source.Clone(); + bitmapNode.Opacity = opacity; + bitmapNode.SourceRect = sourceRect; + bitmapNode.DestRect = destRect; + Add(bitmapNode); } diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/ServerCompositionRenderData.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/ServerCompositionRenderData.cs index 3745fb54d8..8ef8977d77 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/ServerCompositionRenderData.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/ServerCompositionRenderData.cs @@ -137,9 +137,7 @@ class ServerCompositionRenderData : SimpleServerRenderResource foreach (var r in _referencedResources) r.RemoveObserver(this); _referencedResources.Dispose(); - foreach(var i in _items) - if (i is IDisposable disp) - disp.Dispose(); + RenderDataItemPoolHelper.DisposeAndReturnToPool(_items); _items.Dispose(); } diff --git a/src/Avalonia.Base/Threading/ThreadSafeObjectPool.cs b/src/Avalonia.Base/Threading/ThreadSafeObjectPool.cs index 322ec8f5a0..248357b44d 100644 --- a/src/Avalonia.Base/Threading/ThreadSafeObjectPool.cs +++ b/src/Avalonia.Base/Threading/ThreadSafeObjectPool.cs @@ -17,6 +17,12 @@ namespace Avalonia.Threading } } + public void Return(T obj) + { + lock (_stack) + _stack.Push(obj); + } + public void ReturnAndSetNull(ref T? obj) { if (obj == null)