From 39374451ddbc733d818255c8a06b2650796b91d3 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Wed, 11 Jan 2023 02:54:04 +0300 Subject: [PATCH] [Text] Add pooling to shapedBuffer. works only with dispose. --- src/Avalonia.Base/Media/GlyphRun.cs | 2 +- .../Media/TextFormatting/ShapedBuffer.cs | 29 ++++++++++++++++--- .../Media/TextFormatting/ShapedTextRun.cs | 8 ++++- .../Media/TextFormatting/TextLayout.cs | 10 ++++++- .../Media/TextFormatting/TextLine.cs | 4 ++- .../Media/TextFormatting/TextLineImpl.cs | 9 ++++++ src/Avalonia.Base/Utilities/ArraySlice.cs | 9 ++++++ .../Text/HugeTextLayout.cs | 8 +++-- 8 files changed, 69 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs index af9e458a28..811479fde8 100644 --- a/src/Avalonia.Base/Media/GlyphRun.cs +++ b/src/Avalonia.Base/Media/GlyphRun.cs @@ -918,7 +918,7 @@ namespace Avalonia.Media _glyphRunImpl = platformRenderInterface.CreateGlyphRun(GlyphTypeface, FontRenderingEmSize, GlyphIndices, GlyphAdvances, GlyphOffsets); } - void IDisposable.Dispose() + public void Dispose() { _glyphRunImpl?.Dispose(); } diff --git a/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs b/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs index 902b897240..5cf153d199 100644 --- a/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs +++ b/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs @@ -1,17 +1,20 @@ using System; +using System.Buffers; using System.Collections.Generic; using Avalonia.Utilities; namespace Avalonia.Media.TextFormatting { - public sealed class ShapedBuffer : IList + public sealed class ShapedBuffer : IList, IDisposable { private static readonly IComparer s_clusterComparer = new CompareClusters(); + private bool _rented; public ShapedBuffer(CharacterBufferRange characterBufferRange, int bufferLength, IGlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel) : - this(characterBufferRange, new GlyphInfo[bufferLength], glyphTypeface, fontRenderingEmSize, bidiLevel) + this(characterBufferRange, ArrayPool.Shared.Rent(bufferLength), glyphTypeface, fontRenderingEmSize, bidiLevel) { - + _rented = true; + Length = bufferLength; } internal ShapedBuffer(CharacterBufferRange characterBufferRange, ArraySlice glyphInfos, IGlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel) @@ -21,11 +24,12 @@ namespace Avalonia.Media.TextFormatting GlyphTypeface = glyphTypeface; FontRenderingEmSize = fontRenderingEmSize; BidiLevel = bidiLevel; + Length = GlyphInfos.Length; } internal ArraySlice GlyphInfos { get; } - public int Length => GlyphInfos.Length; + public int Length { get; } public IGlyphTypeface GlyphTypeface { get; } @@ -260,6 +264,23 @@ namespace Avalonia.Media.TextFormatting System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); } + + public void Dispose() + { + GC.SuppressFinalize(this); + if (_rented) + { + GlyphInfos.ReturnRent(); + } + } + + ~ShapedBuffer() + { + if (_rented) + { + GlyphInfos.ReturnRent(); + } + } } public readonly record struct GlyphInfo diff --git a/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs b/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs index 3149bc2cda..665723b284 100644 --- a/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs +++ b/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs @@ -6,7 +6,7 @@ namespace Avalonia.Media.TextFormatting /// /// A text run that holds shaped characters. /// - public sealed class ShapedTextRun : DrawableTextRun + public sealed class ShapedTextRun : DrawableTextRun, IDisposable { private GlyphRun? _glyphRun; @@ -199,5 +199,11 @@ namespace Avalonia.Media.TextFormatting ShapedBuffer.GlyphClusters, BidiLevel); } + + public void Dispose() + { + _glyphRun?.Dispose(); + ShapedBuffer.Dispose(); + } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs index f803001481..969b960c6b 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs @@ -8,7 +8,7 @@ namespace Avalonia.Media.TextFormatting /// /// Represents a multi line text layout. /// - public class TextLayout + public class TextLayout : IDisposable { private readonly ITextSource _textSource; private readonly TextParagraphProperties _paragraphProperties; @@ -561,5 +561,13 @@ namespace Avalonia.Media.TextFormatting return _textTrimming.CreateCollapsingProperties(new TextCollapsingCreateInfo(width, _paragraphProperties.DefaultTextRunProperties)); } + + public void Dispose() + { + foreach (var line in TextLines) + { + line.Dispose(); + } + } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLine.cs b/src/Avalonia.Base/Media/TextFormatting/TextLine.cs index 61b24dc8c5..3cb26882dc 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLine.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLine.cs @@ -6,7 +6,7 @@ namespace Avalonia.Media.TextFormatting /// /// Represents a line of text that is used for text rendering. /// - public abstract class TextLine + public abstract class TextLine : IDisposable { /// /// Gets the text runs that are contained within a line. @@ -207,5 +207,7 @@ namespace Avalonia.Media.TextFormatting /// number of characters of the specified range /// an array of bounding rectangles. public abstract IReadOnlyList GetTextBounds(int firstTextSourceCharacterIndex, int textLength); + + public abstract void Dispose(); } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index 5fb1171221..93ee139763 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Avalonia.Utilities; namespace Avalonia.Media.TextFormatting @@ -934,6 +935,14 @@ namespace Avalonia.Media.TextFormatting return GetTextBoundsRightToLeft(firstTextSourceIndex, textLength); } + public override void Dispose() + { + foreach (var run in _textRuns.OfType()) + { + run.Dispose(); + } + } + public TextLineImpl FinalizeLine() { _textLineMetrics = CreateLineMetrics(); diff --git a/src/Avalonia.Base/Utilities/ArraySlice.cs b/src/Avalonia.Base/Utilities/ArraySlice.cs index 39c0cd5556..b70088a907 100644 --- a/src/Avalonia.Base/Utilities/ArraySlice.cs +++ b/src/Avalonia.Base/Utilities/ArraySlice.cs @@ -3,6 +3,7 @@ // Ported from: https://github.com/SixLabors/Fonts/ using System; +using System.Buffers; using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; @@ -185,5 +186,13 @@ namespace Avalonia.Utilities /// int IReadOnlyCollection.Count => Length; + + public void ReturnRent() + { + if (_data != null) + { + ArrayPool.Shared.Return(_data); + } + } } } diff --git a/tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs b/tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs index e696b976ad..20fa268a85 100644 --- a/tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs +++ b/tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs @@ -70,8 +70,12 @@ In respect that the structure of the sufficient amount poses problems and challe [Benchmark] public TextLayout[] BuildManySmallTexts() => _manySmallStrings.Select(MakeLayout).ToArray(); - private static TextLayout MakeLayout(string str) - => new TextLayout(str, Typeface.Default, 12d, Brushes.Black, maxWidth:120); + private static TextLayout MakeLayout(string str) + { + var layout = new TextLayout(str, Typeface.Default, 12d, Brushes.Black, maxWidth: 120); + layout.Dispose(); + return layout; + } public void Dispose() {