Browse Source

Merge 20a56bc08c into 658afb8717

pull/20885/merge
Matt 21 hours ago
committed by GitHub
parent
commit
a277b4dadb
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      src/Avalonia.Base/Rendering/Composition/Drawing/CompositionRenderData.cs
  2. 15
      src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataBitmapNode.cs
  3. 23
      src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataEllipseNode.cs
  4. 23
      src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataGeometryNode.cs
  5. 13
      src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataGlyphRunNode.cs
  6. 23
      src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataLineNode.cs
  7. 106
      src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataNodePool.cs
  8. 106
      src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataNodes.cs
  9. 12
      src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataPushMatrixNode.cs
  10. 13
      src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataPushOpacityMaskNode.cs
  11. 18
      src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataRectangleNode.cs
  12. 152
      src/Avalonia.Base/Rendering/Composition/Drawing/RenderDataDrawingContext.cs
  13. 4
      src/Avalonia.Base/Rendering/Composition/Drawing/ServerCompositionRenderData.cs
  14. 6
      src/Avalonia.Base/Threading/ThreadSafeObjectPool.cs

6
src/Avalonia.Base/Rendering/Composition/Drawing/CompositionRenderData.cs

@ -31,11 +31,9 @@ internal class CompositionRenderData : ICompositorSerializable, IDisposable
{ {
if (!_itemsSent) if (!_itemsSent)
{ {
foreach(var i in _items) RenderDataItemPoolHelper.DisposeAndReturnToPool(_items);
if (i is IDisposable disp)
disp.Dispose();
} }
_items.Dispose(); _items.Dispose();
_itemsSent = false; _itemsSent = false;
foreach(var r in _resources) foreach(var r in _resources)

15
src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataBitmapNode.cs

@ -4,8 +4,12 @@ using Avalonia.Utilities;
namespace Avalonia.Rendering.Composition.Drawing.Nodes; namespace Avalonia.Rendering.Composition.Drawing.Nodes;
class RenderDataBitmapNode : IRenderDataItem, IDisposable class RenderDataBitmapNode : IRenderDataItem, IDisposable, IPoolableRenderDataItem
{ {
private static readonly RenderDataNodePool<RenderDataBitmapNode> s_pool = new();
public static RenderDataBitmapNode Get() => s_pool.Get();
public IRef<IBitmapImpl>? Bitmap { get; set; } public IRef<IBitmapImpl>? Bitmap { get; set; }
public double Opacity { get; set; } public double Opacity { get; set; }
public Rect SourceRect { get; set; } public Rect SourceRect { get; set; }
@ -25,4 +29,13 @@ class RenderDataBitmapNode : IRenderDataItem, IDisposable
Bitmap?.Dispose(); Bitmap?.Dispose();
Bitmap = null; Bitmap = null;
} }
public void ReturnToPool()
{
Dispose();
Opacity = default;
SourceRect = default;
DestRect = default;
s_pool.Return(this);
}
} }

23
src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataEllipseNode.cs

@ -4,10 +4,14 @@ using Avalonia.Platform;
namespace Avalonia.Rendering.Composition.Drawing.Nodes; namespace Avalonia.Rendering.Composition.Drawing.Nodes;
class RenderDataEllipseNode :RenderDataBrushAndPenNode class RenderDataEllipseNode : RenderDataBrushAndPenNode, IPoolableRenderDataItem
{ {
private static readonly RenderDataNodePool<RenderDataEllipseNode> s_pool = new();
public static RenderDataEllipseNode Get() => s_pool.Get();
public Rect Rect { get; set; } public Rect Rect { get; set; }
bool Contains(double dx, double dy, double radiusX, double radiusY) bool Contains(double dx, double dy, double radiusX, double radiusY)
{ {
var rx2 = radiusX * radiusX; var rx2 = radiusX * radiusX;
@ -17,7 +21,7 @@ class RenderDataEllipseNode :RenderDataBrushAndPenNode
return distance <= rx2 * ry2; return distance <= rx2 * ry2;
} }
public override bool HitTest(Point p) public override bool HitTest(Point p)
{ {
var center = Rect.Center; var center = Rect.Center;
@ -50,7 +54,7 @@ class RenderDataEllipseNode :RenderDataBrushAndPenNode
return inStroke && !inInner; return inStroke && !inInner;
} }
return false; return false;
} }
@ -58,4 +62,13 @@ class RenderDataEllipseNode :RenderDataBrushAndPenNode
context.Context.DrawEllipse(ServerBrush, ServerPen, Rect); context.Context.DrawEllipse(ServerBrush, ServerPen, Rect);
public override Rect? Bounds => Rect.Inflate(ServerPen?.Thickness ?? 0); public override Rect? Bounds => Rect.Inflate(ServerPen?.Thickness ?? 0);
}
public void ReturnToPool()
{
ServerBrush = null;
ServerPen = null;
ClientPen = null;
Rect = default;
s_pool.Return(this);
}
}

23
src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataGeometryNode.cs

@ -5,20 +5,24 @@ using Avalonia.Rendering.SceneGraph;
namespace Avalonia.Rendering.Composition.Drawing.Nodes; namespace Avalonia.Rendering.Composition.Drawing.Nodes;
class RenderDataGeometryNode : RenderDataBrushAndPenNode class RenderDataGeometryNode : RenderDataBrushAndPenNode, IPoolableRenderDataItem
{ {
private static readonly RenderDataNodePool<RenderDataGeometryNode> s_pool = new();
public static RenderDataGeometryNode Get() => s_pool.Get();
public IGeometryImpl? Geometry { get; set; } public IGeometryImpl? Geometry { get; set; }
public override bool HitTest(Point p) public override bool HitTest(Point p)
{ {
if (Geometry == null) if (Geometry == null)
return false; return false;
return (ServerBrush != null // null check is safe return (ServerBrush != null // null check is safe
&& Geometry.FillContains(p)) || && Geometry.FillContains(p)) ||
(ClientPen != null && Geometry.StrokeContains(ClientPen, p)); (ClientPen != null && Geometry.StrokeContains(ClientPen, p));
} }
public override void Invoke(ref RenderDataNodeRenderContext context) public override void Invoke(ref RenderDataNodeRenderContext context)
{ {
Debug.Assert(Geometry != null); Debug.Assert(Geometry != null);
@ -26,4 +30,13 @@ class RenderDataGeometryNode : RenderDataBrushAndPenNode
} }
public override Rect? Bounds => Geometry?.GetRenderBounds(ServerPen) ?? default; public override Rect? Bounds => Geometry?.GetRenderBounds(ServerPen) ?? default;
}
public void ReturnToPool()
{
ServerBrush = null;
ServerPen = null;
ClientPen = null;
Geometry = null;
s_pool.Return(this);
}
}

13
src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataGlyphRunNode.cs

@ -6,8 +6,12 @@ using Avalonia.Utilities;
namespace Avalonia.Rendering.Composition.Drawing.Nodes; namespace Avalonia.Rendering.Composition.Drawing.Nodes;
class RenderDataGlyphRunNode : IRenderDataItemWithServerResources, IDisposable class RenderDataGlyphRunNode : IRenderDataItemWithServerResources, IDisposable, IPoolableRenderDataItem
{ {
private static readonly RenderDataNodePool<RenderDataGlyphRunNode> s_pool = new();
public static RenderDataGlyphRunNode Get() => s_pool.Get();
public IBrush? ServerBrush { get; set; } public IBrush? ServerBrush { get; set; }
// Dispose only happens once, so it's safe to have one reference // Dispose only happens once, so it's safe to have one reference
public IRef<IGlyphRunImpl>? GlyphRun { get; set; } public IRef<IGlyphRunImpl>? GlyphRun { get; set; }
@ -32,4 +36,11 @@ class RenderDataGlyphRunNode : IRenderDataItemWithServerResources, IDisposable
GlyphRun?.Dispose(); GlyphRun?.Dispose();
GlyphRun = null; GlyphRun = null;
} }
public void ReturnToPool()
{
Dispose();
ServerBrush = null;
s_pool.Return(this);
}
} }

23
src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataLineNode.cs

@ -5,13 +5,17 @@ using Avalonia.Rendering.SceneGraph;
namespace Avalonia.Rendering.Composition.Drawing.Nodes; namespace Avalonia.Rendering.Composition.Drawing.Nodes;
class RenderDataLineNode : IRenderDataItemWithServerResources class RenderDataLineNode : IRenderDataItemWithServerResources, IPoolableRenderDataItem
{ {
private static readonly RenderDataNodePool<RenderDataLineNode> s_pool = new();
public static RenderDataLineNode Get() => s_pool.Get();
public IPen? ServerPen { get; set; } public IPen? ServerPen { get; set; }
public IPen? ClientPen { get; set; } public IPen? ClientPen { get; set; }
public Point P1 { get; set; } public Point P1 { get; set; }
public Point P2 { get; set; } public Point P2 { get; set; }
public bool HitTest(Point p) public bool HitTest(Point p)
{ {
if (ClientPen == null) if (ClientPen == null)
@ -52,9 +56,9 @@ class RenderDataLineNode : IRenderDataItemWithServerResources
return Math.Abs(distance) <= halfThickness; return Math.Abs(distance) <= halfThickness;
} }
public void Invoke(ref RenderDataNodeRenderContext context)
public void Invoke(ref RenderDataNodeRenderContext context)
=> context.Context.DrawLine(ServerPen, P1, P2); => context.Context.DrawLine(ServerPen, P1, P2);
public Rect? Bounds => LineBoundsHelper.CalculateBounds(P1, P2, ServerPen!); public Rect? Bounds => LineBoundsHelper.CalculateBounds(P1, P2, ServerPen!);
@ -62,4 +66,13 @@ class RenderDataLineNode : IRenderDataItemWithServerResources
{ {
collector.AddRenderDataServerResource(ServerPen); collector.AddRenderDataServerResource(ServerPen);
} }
}
public void ReturnToPool()
{
ServerPen = null;
ClientPen = null;
P1 = default;
P2 = default;
s_pool.Return(this);
}
}

106
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();
}
/// <summary>
/// Manages a single cleanup timer shared by all <see cref="RenderDataNodePool{T}"/> instances.
/// Uses weak references so pools that go out of scope can be garbage collected.
/// </summary>
internal static class RenderDataNodePoolCleanup
{
private static readonly List<WeakReference<IRenderDataNodePool>> s_pools = new();
private static Timer? s_timer;
public static void Register(IRenderDataNodePool pool)
{
lock (s_pools)
{
s_pools.Add(new WeakReference<IRenderDataNodePool>(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);
}
}
}
}
/// <summary>
/// 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.
/// </summary>
internal sealed class RenderDataNodePool<T> : IRenderDataNodePool where T : class, new()
{
private T[] _items = Array.Empty<T>();
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;
}
}
}

106
src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataNodes.cs

@ -53,6 +53,14 @@ struct RenderDataNodeRenderContext : IDisposable
} }
} }
/// <summary>
/// Implemented by render data nodes that support object pooling to reduce GC pressure.
/// </summary>
interface IPoolableRenderDataItem
{
void ReturnToPool();
}
interface IRenderDataItem interface IRenderDataItem
{ {
/// <summary> /// <summary>
@ -78,8 +86,12 @@ interface IRenderDataItem
bool HitTest(Point p); bool HitTest(Point p);
} }
class RenderDataCustomNode : IRenderDataItem, IDisposable class RenderDataCustomNode : IRenderDataItem, IDisposable, IPoolableRenderDataItem
{ {
private static readonly RenderDataNodePool<RenderDataCustomNode> s_pool = new();
public static RenderDataCustomNode Get() => s_pool.Get();
public ICustomDrawOperation? Operation { get; set; } public ICustomDrawOperation? Operation { get; set; }
public bool HitTest(Point p) => Operation?.HitTest(p) ?? false; public bool HitTest(Point p) => Operation?.HitTest(p) ?? false;
public void Invoke(ref RenderDataNodeRenderContext context) => Operation?.Render(new(context.Context, false)); public void Invoke(ref RenderDataNodeRenderContext context) => Operation?.Render(new(context.Context, false));
@ -91,6 +103,12 @@ class RenderDataCustomNode : IRenderDataItem, IDisposable
Operation?.Dispose(); Operation?.Dispose();
Operation = null; Operation = null;
} }
public void ReturnToPool()
{
Dispose();
s_pool.Return(this);
}
} }
abstract class RenderDataPushNode : IRenderDataItem, IDisposable 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<RenderDataClipNode> s_pool = new();
public static RenderDataClipNode Get() => s_pool.Get();
public RoundedRect Rect { get; set; } public RoundedRect Rect { get; set; }
public override void Push(ref RenderDataNodeRenderContext context) => public override void Push(ref RenderDataNodeRenderContext context) =>
context.Context.PushClip(Rect); context.Context.PushClip(Rect);
@ -158,13 +180,23 @@ class RenderDataClipNode : RenderDataPushNode
return false; return false;
return base.HitTest(p); return base.HitTest(p);
} }
public void ReturnToPool()
{
Rect = default;
s_pool.Return(this);
}
} }
class RenderDataGeometryClipNode : RenderDataPushNode class RenderDataGeometryClipNode : RenderDataPushNode, IPoolableRenderDataItem
{ {
private static readonly RenderDataNodePool<RenderDataGeometryClipNode> s_pool = new();
public static RenderDataGeometryClipNode Get() => s_pool.Get();
public IGeometryImpl? Geometry { get; set; } public IGeometryImpl? Geometry { get; set; }
public bool Contains(Point p) => Geometry?.FillContains(p) ?? false; public bool Contains(Point p) => Geometry?.FillContains(p) ?? false;
public override void Push(ref RenderDataNodeRenderContext context) public override void Push(ref RenderDataNodeRenderContext context)
{ {
if (Geometry != null) if (Geometry != null)
@ -183,10 +215,20 @@ class RenderDataGeometryClipNode : RenderDataPushNode
return false; return false;
return base.HitTest(p); return base.HitTest(p);
} }
public void ReturnToPool()
{
Geometry = null;
s_pool.Return(this);
}
} }
class RenderDataOpacityNode : RenderDataPushNode class RenderDataOpacityNode : RenderDataPushNode, IPoolableRenderDataItem
{ {
private static readonly RenderDataNodePool<RenderDataOpacityNode> s_pool = new();
public static RenderDataOpacityNode Get() => s_pool.Get();
public double Opacity { get; set; } public double Opacity { get; set; }
public override void Push(ref RenderDataNodeRenderContext context) public override void Push(ref RenderDataNodeRenderContext context)
{ {
@ -199,6 +241,12 @@ class RenderDataOpacityNode : RenderDataPushNode
if (Opacity != 1) if (Opacity != 1)
context.Context.PopOpacity(); context.Context.PopOpacity();
} }
public void ReturnToPool()
{
Opacity = default;
s_pool.Return(this);
}
} }
abstract class RenderDataBrushAndPenNode : IRenderDataItemWithServerResources abstract class RenderDataBrushAndPenNode : IRenderDataItemWithServerResources
@ -218,8 +266,12 @@ abstract class RenderDataBrushAndPenNode : IRenderDataItemWithServerResources
public abstract bool HitTest(Point p); public abstract bool HitTest(Point p);
} }
class RenderDataRenderOptionsNode : RenderDataPushNode class RenderDataRenderOptionsNode : RenderDataPushNode, IPoolableRenderDataItem
{ {
private static readonly RenderDataNodePool<RenderDataRenderOptionsNode> s_pool = new();
public static RenderDataRenderOptionsNode Get() => s_pool.Get();
public RenderOptions RenderOptions { get; set; } public RenderOptions RenderOptions { get; set; }
public override void Push(ref RenderDataNodeRenderContext context) public override void Push(ref RenderDataNodeRenderContext context)
@ -231,10 +283,20 @@ class RenderDataRenderOptionsNode : RenderDataPushNode
{ {
context.Context.PopRenderOptions(); context.Context.PopRenderOptions();
} }
public void ReturnToPool()
{
RenderOptions = default;
s_pool.Return(this);
}
} }
class RenderDataTextOptionsNode : RenderDataPushNode class RenderDataTextOptionsNode : RenderDataPushNode, IPoolableRenderDataItem
{ {
private static readonly RenderDataNodePool<RenderDataTextOptionsNode> s_pool = new();
public static RenderDataTextOptionsNode Get() => s_pool.Get();
public TextOptions TextOptions { get; set; } public TextOptions TextOptions { get; set; }
public override void Push(ref RenderDataNodeRenderContext context) public override void Push(ref RenderDataNodeRenderContext context)
@ -246,4 +308,34 @@ class RenderDataTextOptionsNode : RenderDataPushNode
{ {
context.Context.PopTextOptions(); context.Context.PopTextOptions();
} }
public void ReturnToPool()
{
TextOptions = default;
s_pool.Return(this);
}
}
static class RenderDataItemPoolHelper
{
/// <summary>
/// Disposes disposable items and returns poolable items to their object pools.
/// Recurses into push node children.
/// </summary>
public static void DisposeAndReturnToPool(PooledInlineList<IRenderDataItem> 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();
}
}
} }

12
src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataPushMatrixNode.cs

@ -1,7 +1,11 @@
namespace Avalonia.Rendering.Composition.Drawing.Nodes; namespace Avalonia.Rendering.Composition.Drawing.Nodes;
class RenderDataPushMatrixNode : RenderDataPushNode class RenderDataPushMatrixNode : RenderDataPushNode, IPoolableRenderDataItem
{ {
private static readonly RenderDataNodePool<RenderDataPushMatrixNode> s_pool = new();
public static RenderDataPushMatrixNode Get() => s_pool.Get();
public Matrix Matrix { get; set; } public Matrix Matrix { get; set; }
public override void Push(ref RenderDataNodeRenderContext context) public override void Push(ref RenderDataNodeRenderContext context)
@ -24,4 +28,10 @@ class RenderDataPushMatrixNode : RenderDataPushNode
} }
public override Rect? Bounds => base.Bounds?.TransformToAABB(Matrix); public override Rect? Bounds => base.Bounds?.TransformToAABB(Matrix);
public void ReturnToPool()
{
Matrix = default;
s_pool.Return(this);
}
} }

13
src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataPushOpacityMaskNode.cs

@ -3,8 +3,12 @@ using Avalonia.Platform;
namespace Avalonia.Rendering.Composition.Drawing.Nodes; namespace Avalonia.Rendering.Composition.Drawing.Nodes;
class RenderDataOpacityMaskNode : RenderDataPushNode, IRenderDataItemWithServerResources class RenderDataOpacityMaskNode : RenderDataPushNode, IRenderDataItemWithServerResources, IPoolableRenderDataItem
{ {
private static readonly RenderDataNodePool<RenderDataOpacityMaskNode> s_pool = new();
public static RenderDataOpacityMaskNode Get() => s_pool.Get();
public IBrush? ServerBrush { get; set; } public IBrush? ServerBrush { get; set; }
public Rect BoundsRect { get; set; } public Rect BoundsRect { get; set; }
@ -22,4 +26,11 @@ class RenderDataOpacityMaskNode : RenderDataPushNode, IRenderDataItemWithServerR
public override void Pop(ref RenderDataNodeRenderContext context) => public override void Pop(ref RenderDataNodeRenderContext context) =>
context.Context.PopOpacityMask(); context.Context.PopOpacityMask();
public void ReturnToPool()
{
ServerBrush = null;
BoundsRect = default;
s_pool.Return(this);
}
} }

18
src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataRectangleNode.cs

@ -2,8 +2,12 @@ using Avalonia.Media;
namespace Avalonia.Rendering.Composition.Drawing.Nodes; namespace Avalonia.Rendering.Composition.Drawing.Nodes;
class RenderDataRectangleNode : RenderDataBrushAndPenNode class RenderDataRectangleNode : RenderDataBrushAndPenNode, IPoolableRenderDataItem
{ {
private static readonly RenderDataNodePool<RenderDataRectangleNode> s_pool = new();
public static RenderDataRectangleNode Get() => s_pool.Get();
public RoundedRect Rect { get; set; } public RoundedRect Rect { get; set; }
public BoxShadows BoxShadows { get; set; } public BoxShadows BoxShadows { get; set; }
@ -21,7 +25,7 @@ class RenderDataRectangleNode : RenderDataBrushAndPenNode
var innerRoundedRect = Rect.Deflate(strokeThicknessAdjustment, strokeThicknessAdjustment); var innerRoundedRect = Rect.Deflate(strokeThicknessAdjustment, strokeThicknessAdjustment);
return !innerRoundedRect.ContainsExclusive(p); return !innerRoundedRect.ContainsExclusive(p);
} }
} }
else else
{ {
@ -43,4 +47,14 @@ class RenderDataRectangleNode : RenderDataBrushAndPenNode
context.Context.DrawRectangle(ServerBrush, ServerPen, Rect, BoxShadows); context.Context.DrawRectangle(ServerBrush, ServerPen, Rect, BoxShadows);
public override Rect? Bounds => BoxShadows.TransformBounds(Rect.Rect).Inflate((ServerPen?.Thickness ?? 0) / 2); 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);
}
} }

152
src/Avalonia.Base/Rendering/Composition/Drawing/RenderDataDrawingContext.cs

@ -124,13 +124,12 @@ internal class RenderDataDrawingContext : DrawingContext
if(pen == null) if(pen == null)
return; return;
AddResource(pen); AddResource(pen);
Add(new RenderDataLineNode var lineNode = RenderDataLineNode.Get();
{ lineNode.ClientPen = pen;
ClientPen = pen, lineNode.ServerPen = pen.GetServer(_compositor);
ServerPen = pen.GetServer(_compositor), lineNode.P1 = p1;
P1 = p1, lineNode.P2 = p2;
P2 = p2 Add(lineNode);
});
} }
protected override void DrawGeometryCore(IBrush? brush, IPen? pen, IGeometryImpl geometry) protected override void DrawGeometryCore(IBrush? brush, IPen? pen, IGeometryImpl geometry)
@ -139,13 +138,12 @@ internal class RenderDataDrawingContext : DrawingContext
return; return;
AddResource(brush); AddResource(brush);
AddResource(pen); AddResource(pen);
Add(new RenderDataGeometryNode var geoNode = RenderDataGeometryNode.Get();
{ geoNode.ServerBrush = brush.GetServer(_compositor);
ServerBrush = brush.GetServer(_compositor), geoNode.ServerPen = pen.GetServer(_compositor);
ServerPen = pen.GetServer(_compositor), geoNode.ClientPen = pen;
ClientPen = pen, geoNode.Geometry = geometry;
Geometry = geometry Add(geoNode);
});
} }
protected override void DrawRectangleCore(IBrush? brush, IPen? pen, RoundedRect rrect, BoxShadows boxShadows = default) protected override void DrawRectangleCore(IBrush? brush, IPen? pen, RoundedRect rrect, BoxShadows boxShadows = default)
@ -156,14 +154,13 @@ internal class RenderDataDrawingContext : DrawingContext
return; return;
AddResource(brush); AddResource(brush);
AddResource(pen); AddResource(pen);
Add(new RenderDataRectangleNode var rectNode = RenderDataRectangleNode.Get();
{ rectNode.ServerBrush = brush.GetServer(_compositor);
ServerBrush = brush.GetServer(_compositor), rectNode.ServerPen = pen.GetServer(_compositor);
ServerPen = pen.GetServer(_compositor), rectNode.ClientPen = pen;
ClientPen = pen, rectNode.Rect = rrect;
Rect = rrect, rectNode.BoxShadows = boxShadows;
BoxShadows = boxShadows Add(rectNode);
});
} }
protected override void DrawEllipseCore(IBrush? brush, IPen? pen, Rect rect) protected override void DrawEllipseCore(IBrush? brush, IPen? pen, Rect rect)
@ -175,51 +172,56 @@ internal class RenderDataDrawingContext : DrawingContext
return; return;
AddResource(brush); AddResource(brush);
AddResource(pen); AddResource(pen);
Add(new RenderDataEllipseNode var ellipseNode = RenderDataEllipseNode.Get();
{ ellipseNode.ServerBrush = brush.GetServer(_compositor);
ServerBrush = brush.GetServer(_compositor), ellipseNode.ServerPen = pen.GetServer(_compositor);
ServerPen = pen.GetServer(_compositor), ellipseNode.ClientPen = pen;
ClientPen = pen, ellipseNode.Rect = rect;
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) public override void DrawGlyphRun(IBrush? foreground, GlyphRun? glyphRun)
{ {
if (foreground == null || glyphRun == null) if (foreground == null || glyphRun == null)
return; return;
AddResource(foreground); AddResource(foreground);
Add(new RenderDataGlyphRunNode var glyphNode = RenderDataGlyphRunNode.Get();
{ glyphNode.ServerBrush = foreground.GetServer(_compositor);
ServerBrush = foreground.GetServer(_compositor), glyphNode.GlyphRun = glyphRun.PlatformImpl.Clone();
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) protected override void PushGeometryClipCore(Geometry? clip)
{ {
if (clip == null) if (clip == null)
Push(); Push();
else else
Push(new RenderDataGeometryClipNode {
{ var node = RenderDataGeometryClipNode.Get();
Geometry = clip?.PlatformImpl node.Geometry = clip?.PlatformImpl;
}); Push(node);
}
} }
protected override void PushOpacityCore(double opacity) protected override void PushOpacityCore(double opacity)
@ -227,10 +229,11 @@ internal class RenderDataDrawingContext : DrawingContext
if (opacity == 1) if (opacity == 1)
Push(); Push();
else else
Push(new RenderDataOpacityNode {
{ var node = RenderDataOpacityNode.Get();
Opacity = opacity node.Opacity = opacity;
}); Push(node);
}
} }
protected override void PushOpacityMaskCore(IBrush? mask, Rect bounds) protected override void PushOpacityMaskCore(IBrush? mask, Rect bounds)
@ -240,11 +243,10 @@ internal class RenderDataDrawingContext : DrawingContext
else else
{ {
AddResource(mask); AddResource(mask);
Push(new RenderDataOpacityMaskNode var node = RenderDataOpacityMaskNode.Get();
{ node.ServerBrush = mask.GetServer(_compositor);
ServerBrush = mask.GetServer(_compositor), node.BoundsRect = bounds;
BoundsRect = bounds Push(node);
});
} }
} }
@ -253,21 +255,26 @@ internal class RenderDataDrawingContext : DrawingContext
if (matrix.IsIdentity) if (matrix.IsIdentity)
Push(); Push();
else else
Push(new RenderDataPushMatrixNode() {
{ var node = RenderDataPushMatrixNode.Get();
Matrix = matrix 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<RenderDataClipNode>(); protected override void PopClipCore() => Pop<RenderDataClipNode>();
@ -287,13 +294,12 @@ internal class RenderDataDrawingContext : DrawingContext
{ {
if (source == null || sourceRect.IsEmpty() || destRect.IsEmpty()) if (source == null || sourceRect.IsEmpty() || destRect.IsEmpty())
return; return;
Add(new RenderDataBitmapNode var bitmapNode = RenderDataBitmapNode.Get();
{ bitmapNode.Bitmap = source.Clone();
Bitmap = source.Clone(), bitmapNode.Opacity = opacity;
Opacity = opacity, bitmapNode.SourceRect = sourceRect;
SourceRect = sourceRect, bitmapNode.DestRect = destRect;
DestRect = destRect Add(bitmapNode);
});
} }

4
src/Avalonia.Base/Rendering/Composition/Drawing/ServerCompositionRenderData.cs

@ -137,9 +137,7 @@ class ServerCompositionRenderData : SimpleServerRenderResource
foreach (var r in _referencedResources) foreach (var r in _referencedResources)
r.RemoveObserver(this); r.RemoveObserver(this);
_referencedResources.Dispose(); _referencedResources.Dispose();
foreach(var i in _items) RenderDataItemPoolHelper.DisposeAndReturnToPool(_items);
if (i is IDisposable disp)
disp.Dispose();
_items.Dispose(); _items.Dispose();
} }

6
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) public void ReturnAndSetNull(ref T? obj)
{ {
if (obj == null) if (obj == null)

Loading…
Cancel
Save