瀏覽代碼

Merge pull request #9962 from Mikolaytis/ShapedBufferPooling2

[Text] shaped buffer pooling
Benedikt Stebner 2 年之前
父節點
當前提交
7580b704df

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

+ 29 - 4
src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs

@@ -1,17 +1,24 @@
 using System;
+using System.Buffers;
 using System.Collections.Generic;
 using Avalonia.Utilities;
 
 namespace Avalonia.Media.TextFormatting
 {
-    public sealed class ShapedBuffer : IList<GlyphInfo>
+    public sealed class ShapedBuffer : IList<GlyphInfo>, IDisposable
     {
         private static readonly IComparer<GlyphInfo> s_clusterComparer = new CompareClusters();
+        private bool _bufferRented;
         
         public ShapedBuffer(CharacterBufferRange characterBufferRange, int bufferLength, IGlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel) : 
-            this(characterBufferRange, new GlyphInfo[bufferLength], glyphTypeface,  fontRenderingEmSize,  bidiLevel)
+            this(characterBufferRange, 
+                new ArraySlice<GlyphInfo>(ArrayPool<GlyphInfo>.Shared.Rent(bufferLength), 0, bufferLength), 
+                glyphTypeface,  
+                fontRenderingEmSize,  
+                bidiLevel)
         {
-
+            _bufferRented = true;
+            Length = bufferLength;
         }
 
         internal ShapedBuffer(CharacterBufferRange characterBufferRange, ArraySlice<GlyphInfo> glyphInfos, IGlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel)
@@ -21,11 +28,12 @@ namespace Avalonia.Media.TextFormatting
             GlyphTypeface = glyphTypeface;
             FontRenderingEmSize = fontRenderingEmSize;
             BidiLevel = bidiLevel;
+            Length = GlyphInfos.Length;
         }
 
         internal ArraySlice<GlyphInfo> GlyphInfos { get; }
         
-        public int Length => GlyphInfos.Length;
+        public int Length { get; }
 
         public IGlyphTypeface GlyphTypeface { get; }
 
@@ -260,6 +268,23 @@ namespace Avalonia.Media.TextFormatting
 
             System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
         }
+
+        public void Dispose()
+        {
+            GC.SuppressFinalize(this);
+            if (_bufferRented)
+            {
+                GlyphInfos.ReturnRent();
+            }
+        }
+
+        ~ShapedBuffer()
+        {
+            if (_bufferRented)
+            {
+                GlyphInfos.ReturnRent();
+            }
+        }
     }
 
     public readonly record struct GlyphInfo

+ 7 - 1
src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs

@@ -6,7 +6,7 @@ namespace Avalonia.Media.TextFormatting
     /// <summary>
     /// A text run that holds shaped characters.
     /// </summary>
-    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();
+        }
     }
 }

+ 9 - 1
src/Avalonia.Base/Media/TextFormatting/TextLayout.cs

@@ -8,7 +8,7 @@ namespace Avalonia.Media.TextFormatting
     /// <summary>
     /// Represents a multi line text layout.
     /// </summary>
-    public class TextLayout
+    public class TextLayout : IDisposable
     {
         private readonly ITextSource _textSource;
         private readonly TextParagraphProperties _paragraphProperties;
@@ -566,5 +566,13 @@ namespace Avalonia.Media.TextFormatting
 
             return _textTrimming.CreateCollapsingProperties(new TextCollapsingCreateInfo(width, _paragraphProperties.DefaultTextRunProperties));
         }
+
+        public void Dispose()
+        {
+            foreach (var line in TextLines)
+            {
+                line.Dispose();
+            }
+        }
     }
 }

+ 3 - 1
src/Avalonia.Base/Media/TextFormatting/TextLine.cs

@@ -6,7 +6,7 @@ namespace Avalonia.Media.TextFormatting
     /// <summary>
     /// Represents a line of text that is used for text rendering.
     /// </summary>
-    public abstract class TextLine
+    public abstract class TextLine : IDisposable
     {
         /// <summary>
         /// Gets the text runs that are contained within a line.
@@ -207,5 +207,7 @@ namespace Avalonia.Media.TextFormatting
         /// <param name="textLength">number of characters of the specified range</param>
         /// <returns>an array of bounding rectangles.</returns>
         public abstract IReadOnlyList<TextBounds> GetTextBounds(int firstTextSourceCharacterIndex, int textLength);
+
+        public abstract void Dispose();
     }
 }

+ 12 - 0
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
@@ -978,6 +979,17 @@ namespace Avalonia.Media.TextFormatting
             return GetTextBoundsRightToLeft(firstTextSourceIndex, textLength);
         }
 
+        public override void Dispose()
+        {
+            for (int i = 0; i < _textRuns.Count; i++)
+            {
+                if (_textRuns[i] is ShapedTextRun shapedTextRun)
+                {
+                    shapedTextRun.Dispose();
+                }
+            }
+        }
+
         public TextLineImpl FinalizeLine()
         {
             _textLineMetrics = CreateLineMetrics();

+ 9 - 0
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
 
         /// <inheritdoc/>
         int IReadOnlyCollection<T>.Count => Length;
+
+        public void ReturnRent()
+        {
+            if (_data != null)
+            {
+                ArrayPool<T>.Shared.Return(_data);
+            }
+        }
     }
 }

+ 3 - 0
src/Avalonia.Controls/Presenters/TextPresenter.cs

@@ -588,6 +588,7 @@ namespace Avalonia.Controls.Presenters
 
         protected virtual void InvalidateTextLayout()
         {
+            _textLayout?.Dispose();
             _textLayout = null;
 
             InvalidateMeasure();
@@ -597,6 +598,7 @@ namespace Avalonia.Controls.Presenters
         {
             _constraint = availableSize;
 
+            _textLayout?.Dispose();
             _textLayout = null;
 
             InvalidateArrange();
@@ -622,6 +624,7 @@ namespace Avalonia.Controls.Presenters
 
             _constraint = new Size(Math.Ceiling(finalSize.Width), double.PositiveInfinity);
 
+            _textLayout?.Dispose();
             _textLayout = null;
 
             return finalSize;

+ 4 - 7
src/Avalonia.Controls/TextBlock.cs

@@ -173,13 +173,7 @@ namespace Avalonia.Controls
         /// <summary>
         /// Gets the <see cref="TextLayout"/> used to render the text.
         /// </summary>
-        public TextLayout TextLayout
-        {
-            get
-            {
-                return _textLayout ??= CreateTextLayout(_text);
-            }
-        }
+        public TextLayout TextLayout => _textLayout ??= CreateTextLayout(_text);
 
         /// <summary>
         /// Gets or sets the padding to place around the <see cref="Text"/>.
@@ -648,6 +642,7 @@ namespace Avalonia.Controls
         /// </summary>
         protected void InvalidateTextLayout()
         {
+            _textLayout?.Dispose();
             _textLayout = null;
 
             InvalidateVisual();
@@ -663,6 +658,7 @@ namespace Avalonia.Controls
 
             _constraint = availableSize.Deflate(padding);
 
+            _textLayout?.Dispose();
             _textLayout = null;
 
             var inlines = Inlines;
@@ -726,6 +722,7 @@ namespace Avalonia.Controls
 
             _constraint = new Size(Math.Ceiling(finalSize.Deflate(padding).Width), double.PositiveInfinity);
 
+            _textLayout?.Dispose();
             _textLayout = null;
 
             if (HasComplexContent)

+ 2 - 1
src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs

@@ -14,7 +14,8 @@ namespace Avalonia.Direct2D1.Media
 
         public void Dispose()
         {
-            GlyphRun?.Dispose();
+            //SharpDX already handles this.
+            //GlyphRun?.Dispose();
         }
 
         public IReadOnlyList<float> GetIntersections(float lowerBound, float upperBound)

+ 27 - 2
tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Linq;
+using Avalonia.Controls;
 using Avalonia.Media;
 using Avalonia.Media.TextFormatting;
 using Avalonia.UnitTests;
@@ -70,8 +71,32 @@ 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);
+    [Benchmark]
+    public void VirtualizeTextBlocks()
+    {
+        var blocks = new TextBlock[10];
+        for (var i = 0; i < blocks.Length; i++)
+        {
+            blocks[i] = new TextBlock
+            {
+                Width = 120,
+                Height = 32,
+            };
+        }
+
+        for (int i = 0, j = 0; i < _manySmallStrings.Length; i++, j = j < blocks.Length - 1 ? j + 1 : 0)
+        {
+            blocks[j].Text = _manySmallStrings[i];
+            blocks[j].Measure(new Size(200, 200));
+        }
+    }
+
+    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()
     {