Browse Source

Merge pull request #9962 from Mikolaytis/ShapedBufferPooling2

[Text] shaped buffer pooling
pull/9982/head
Benedikt Stebner 4 years ago
committed by GitHub
parent
commit
7580b704df
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/Avalonia.Base/Media/GlyphRun.cs
  2. 33
      src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs
  3. 8
      src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs
  4. 10
      src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
  5. 4
      src/Avalonia.Base/Media/TextFormatting/TextLine.cs
  6. 12
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  7. 9
      src/Avalonia.Base/Utilities/ArraySlice.cs
  8. 3
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  9. 11
      src/Avalonia.Controls/TextBlock.cs
  10. 3
      src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs
  11. 29
      tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs

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

33
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

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

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

4
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
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
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
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;

11
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)

3
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)

29
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()
{

Loading…
Cancel
Save