Browse Source

Optimize GlyphRun allocations

Benedikt Stebner 3 years ago
parent
commit
74fafe3b09

+ 80 - 1
src/Avalonia.Base/Media/GlyphRun.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Drawing;
 using Avalonia.Media.TextFormatting.Unicode;
 using Avalonia.Platform;
 using Avalonia.Utilities;
@@ -854,9 +855,87 @@ namespace Avalonia.Media
                 throw new InvalidOperationException();
             }
 
+            _glyphRunImpl = CreateGlyphRunImpl();
+        }
+
+        private IGlyphRunImpl CreateGlyphRunImpl()
+        {
+            IGlyphRunImpl glyphRunImpl;
+
             var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
+            var count = GlyphIndices.Count;
+            var scale = (float)(FontRenderingEmSize / GlyphTypeface.DesignEmHeight);
+
+            if (GlyphOffsets == null)
+            {
+                if (GlyphTypeface.IsFixedPitch)
+                {
+                    var buffer = platformRenderInterface.AllocateGlyphRun(GlyphTypeface, (float)FontRenderingEmSize, count);
+
+                    var glyphs = buffer.GlyphIndices;
+
+                    for (int i = 0; i < glyphs.Length; i++)
+                    {
+                        glyphs[i] = GlyphIndices[i];
+                    }
+
+                    glyphRunImpl = buffer.Build();
+                }
+                else
+                {
+                    var buffer = platformRenderInterface.AllocateHorizontalGlyphRun(GlyphTypeface, (float)FontRenderingEmSize, count);
+                    var glyphs = buffer.GlyphIndices;
+                    var positions = buffer.GlyphPositions;
+                    var width = 0d;
+
+                    for (var i = 0; i < count; i++)
+                    {
+                        positions[i] = (float)width;
+
+                        if (GlyphAdvances == null)
+                        {
+                            width += GlyphTypeface.GetGlyphAdvance(GlyphIndices[i]) * scale;
+                        }
+                        else
+                        {
+                            width += GlyphAdvances[i];
+                        }
+
+                        glyphs[i] = GlyphIndices[i];
+                    }
+
+                    glyphRunImpl = buffer.Build();
+                }
+            }
+            else
+            {
+                var buffer = platformRenderInterface.AllocatePositionedGlyphRun(GlyphTypeface, (float)FontRenderingEmSize, count);
+                var glyphs = buffer.GlyphIndices;
+                var glyphPositions = buffer.GlyphPositions;
+                var currentX = 0.0;
+
+                for (var i = 0; i < count; i++)
+                {
+                    var glyphOffset = GlyphOffsets[i];
+
+                    glyphPositions[i] = new PointF((float)(currentX + glyphOffset.X), (float)glyphOffset.Y);
+
+                    if (GlyphAdvances == null)
+                    {
+                        currentX += GlyphTypeface.GetGlyphAdvance(GlyphIndices[i]) * scale;
+                    }
+                    else
+                    {
+                        currentX += GlyphAdvances[i];
+                    }
+
+                    glyphs[i] = GlyphIndices[i];
+                }
+
+                glyphRunImpl = buffer.Build();
+            }
 
-            _glyphRunImpl = platformRenderInterface.CreateGlyphRun(this);
+            return glyphRunImpl;
         }
 
         void IDisposable.Dispose()

+ 5 - 0
src/Avalonia.Base/Media/GlyphTypeface.cs

@@ -67,6 +67,11 @@ namespace Avalonia.Media
         /// </summary>
         public bool IsFixedPitch => PlatformImpl.IsFixedPitch;
 
+        /// <summary>
+        ///     Gets the number of glyphs held by this glyph typeface. 
+        /// </summary>
+        public int GlyphCount => PlatformImpl.GlyphCount;
+
         /// <summary>
         ///     Returns an glyph index for the specified codepoint.
         /// </summary>

+ 22 - 0
src/Avalonia.Base/Platform/IGlyphRunBuffer.cs

@@ -0,0 +1,22 @@
+using System;
+using System.Drawing;
+
+namespace Avalonia.Platform
+{
+    public interface IGlyphRunBuffer
+    {
+        Span<ushort> GlyphIndices { get; }
+
+        IGlyphRunImpl Build();
+    }
+
+    public interface IHorizontalGlyphRunBuffer : IGlyphRunBuffer
+    {
+        Span<float> GlyphPositions { get; }
+    }
+
+    public interface IPositionedGlyphRunBuffer : IGlyphRunBuffer
+    {
+        Span<PointF> GlyphPositions { get; }
+    }
+}

+ 5 - 0
src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs

@@ -51,6 +51,11 @@ namespace Avalonia.Platform
         /// </summary>
         bool IsFixedPitch { get; }
 
+        /// <summary>
+        ///     Gets the number of glyphs held by this glyph typeface. 
+        /// </summary>
+        int GlyphCount { get; }
+
         /// <summary>
         ///     Returns an glyph index for the specified codepoint.
         /// </summary>

+ 33 - 4
src/Avalonia.Base/Platform/IPlatformRenderInterface.cs

@@ -171,11 +171,40 @@ namespace Avalonia.Platform
         IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride);
 
         /// <summary>
-        /// Creates a platform implementation of a glyph run.
+        /// Allocates a platform glyph run buffer.
         /// </summary>
-        /// <param name="glyphRun">The glyph run.</param>
-        /// <returns></returns>
-        IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun);
+        /// <param name="glyphTypeface">The glyph typeface.</param>
+        /// <param name="fontRenderingEmSize">The font rendering em size.</param>
+        /// <param name="length">The length.</param>
+        /// <returns>An <see cref="IGlyphRunBuffer"/>.</returns>
+        /// <remarks>
+        /// This buffer only holds glyph indices.
+        /// </remarks>
+        IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length);
+
+        /// <summary>
+        /// Allocates a horizontal platform glyph run buffer.
+        /// </summary>
+        /// <param name="glyphTypeface">The glyph typeface.</param>
+        /// <param name="fontRenderingEmSize">The font rendering em size.</param>
+        /// <param name="length">The length.</param>
+        /// <returns>An <see cref="IGlyphRunBuffer"/>.</returns>
+        /// <remarks>
+        /// This buffer holds glyph indices and glyph advances.
+        /// </remarks>
+        IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length);
+
+        /// <summary>
+        /// Allocates a positioned platform glyph run buffer.
+        /// </summary>
+        /// <param name="glyphTypeface">The glyph typeface.</param>
+        /// <param name="fontRenderingEmSize">The font rendering em size.</param>
+        /// <param name="length">The length.</param>
+        /// <returns>An <see cref="IGlyphRunBuffer"/>.</returns>
+        /// <remarks>
+        /// This buffer holds glyph indices, glyph advances and glyph positions.
+        /// </remarks>
+        IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length);
 
         /// <summary>
         /// Gets a value indicating whether the platform directly supports rectangles with rounded corners.

+ 34 - 4
src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@@ -110,14 +110,24 @@ namespace Avalonia.Headless
             return new HeadlessBitmapStub(destinationSize, new Vector(96, 96));
         }
 
-        public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun)
+        public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
         {
-            return new HeadlessGlyphRunStub();
+            return new HeadlessGeometryStub(new Rect(glyphRun.Size));
         }
 
-        public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
+        public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
         {
-            return new HeadlessGeometryStub(new Rect(glyphRun.Size));
+            return new HeadlessGlyphRunBufferStub();
+        }
+
+        public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
+        {
+            return new HeadlessHorizontalGlyphRunBufferStub();
+        }
+
+        public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
+        {
+            return new HeadlessPositionedGlyphRunBufferStub();
         }
 
         class HeadlessGeometryStub : IGeometryImpl
@@ -203,6 +213,26 @@ namespace Avalonia.Headless
             public Matrix Transform { get; }
         }
 
+        class HeadlessGlyphRunBufferStub : IGlyphRunBuffer
+        {
+            public Span<ushort> GlyphIndices => Span<ushort>.Empty;
+
+            public IGlyphRunImpl Build()
+            {
+                return new HeadlessGlyphRunStub();
+            }
+        }
+
+        class HeadlessHorizontalGlyphRunBufferStub : HeadlessGlyphRunBufferStub, IHorizontalGlyphRunBuffer
+        {
+            public Span<float> GlyphPositions => Span<float>.Empty;
+        }
+
+        class HeadlessPositionedGlyphRunBufferStub : HeadlessGlyphRunBufferStub, IPositionedGlyphRunBuffer
+        {
+            public Span<System.Drawing.PointF> GlyphPositions => Span<System.Drawing.PointF>.Empty;
+        }
+
         class HeadlessGlyphRunStub : IGlyphRunImpl
         {
             public void Dispose()

+ 2 - 0
src/Avalonia.Headless/HeadlessPlatformStubs.cs

@@ -95,6 +95,8 @@ namespace Avalonia.Headless
 
         public bool IsFixedPitch => true;
 
+        public int GlyphCount => 1337;
+
         public void Dispose()
         {
         }

+ 4 - 0
src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs

@@ -55,6 +55,8 @@ namespace Avalonia.Skia
 
             IsFixedPitch = Typeface.IsFixedPitch;
 
+            GlyphCount = Typeface.GlyphCount;
+
             IsFakeBold = isFakeBold;
 
             IsFakeItalic = isFakeItalic;
@@ -94,6 +96,8 @@ namespace Avalonia.Skia
 
         /// <inheritdoc cref="IGlyphTypefaceImpl"/>
         public bool IsFixedPitch { get; }
+
+        public int GlyphCount { get; }
         
         public bool IsFakeBold { get; }
         

+ 75 - 107
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@@ -1,7 +1,7 @@
 using System;
 using System.Collections;
 using System.Collections.Generic;
-using System.IO; 
+using System.IO;
 using System.Linq;
 using System.Threading;
 
@@ -12,6 +12,8 @@ using Avalonia.OpenGL.Imaging;
 using Avalonia.Platform;
 using Avalonia.Media.Imaging;
 using SkiaSharp;
+using System.Runtime.InteropServices;
+using System.Drawing;
 
 namespace Avalonia.Skia
 {
@@ -33,13 +35,17 @@ namespace Avalonia.Skia
             }
 
             var gl = AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>();
-            if (gl != null) 
+            if (gl != null)
                 _skiaGpu = new GlSkiaGpu(gl, maxResourceBytes);
-
-            //TODO: SKFont crashes when disposed in finalizer so we keep it alive
-            GC.SuppressFinalize(s_font);
         }
 
+
+        public bool SupportsIndividualRoundRects => true;
+
+        public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
+
+        public PixelFormat DefaultPixelFormat { get; }
+
         public IGeometryImpl CreateEllipseGeometry(Rect rect) => new EllipseGeometryImpl(rect);
 
         public IGeometryImpl CreateLineGeometry(Point p1, Point p2) => new LineGeometryImpl(p1, p2);
@@ -228,133 +234,95 @@ namespace Avalonia.Skia
             return new WriteableBitmapImpl(size, dpi, format, alphaFormat);
         }
 
-        private static readonly SKFont s_font = new SKFont
-        {
-            Subpixel = true,
-            Edging = SKFontEdging.SubpixelAntialias,
-            Hinting = SKFontHinting.Full,
-            LinearMetrics = true
-        };
-
-        private static readonly ThreadLocal<SKTextBlobBuilder> s_textBlobBuilderThreadLocal = new ThreadLocal<SKTextBlobBuilder>(() => new SKTextBlobBuilder());
-
-        /// <inheritdoc />
-        public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun)
+        public IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi)
         {
-            var count = glyphRun.GlyphIndices.Count;
-            var textBlobBuilder = s_textBlobBuilderThreadLocal.Value;
-
-            var glyphTypeface = (GlyphTypefaceImpl)glyphRun.GlyphTypeface.PlatformImpl;
+            if (_skiaGpu is IOpenGlAwareSkiaGpu glAware)
+                return glAware.CreateOpenGlBitmap(size, dpi);
+            if (_skiaGpu == null)
+                throw new PlatformNotSupportedException("GPU acceleration is not available");
+            throw new PlatformNotSupportedException(
+                "Current GPU acceleration backend does not support OpenGL integration");
+        }
 
-            var typeface = glyphTypeface.Typeface;
+        public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) 
+            => new SKGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
 
-            s_font.Size = (float)glyphRun.FontRenderingEmSize;
-            s_font.Typeface = typeface;
-            s_font.Embolden = glyphTypeface.IsFakeBold;
-            s_font.SkewX = glyphTypeface.IsFakeItalic ? -0.2f : 0;
+        public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) 
+            => new SKHorizontalGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
 
-            SKTextBlob textBlob;
+        public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) 
+            => new SKPositionedGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
 
-            var scale = (float)(glyphRun.FontRenderingEmSize / glyphTypeface.DesignEmHeight);
+        private abstract class SKGlyphRunBufferBase : IGlyphRunBuffer
+        {
+            protected readonly SKTextBlobBuilder _builder;
+            protected readonly SKFont _font;
 
-            if (glyphRun.GlyphOffsets == null)
+            public SKGlyphRunBufferBase(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
             {
-                if (glyphTypeface.IsFixedPitch)
-                {
-                    var buffer = textBlobBuilder.AllocateRun(s_font, glyphRun.GlyphIndices.Count, 0, 0);
-
-                    var glyphs = buffer.GetGlyphSpan();
+                _builder = new SKTextBlobBuilder();
 
-                    for (int i = 0; i < glyphs.Length; i++)
-                    {
-                        glyphs[i] = glyphRun.GlyphIndices[i];
-                    }
+                var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface.PlatformImpl;
 
-                    textBlob = textBlobBuilder.Build();
-                }
-                else
+                _font = new SKFont
                 {
-                    var buffer = textBlobBuilder.AllocateHorizontalRun(s_font, count, 0);
-
-                    var positions = buffer.GetPositionSpan();
-
-                    var width = 0d;
-
-                    for (var i = 0; i < count; i++)
-                    {
-                        positions[i] = (float)width;
-
-                        if (glyphRun.GlyphAdvances == null)
-                        {
-                            width += glyphTypeface.GetGlyphAdvance(glyphRun.GlyphIndices[i]) * scale;
-                        }
-                        else
-                        {
-                            width += glyphRun.GlyphAdvances[i];
-                        }
-                    }
-
-                    var glyphs = buffer.GetGlyphSpan();
+                    Subpixel = true,
+                    Edging = SKFontEdging.SubpixelAntialias,
+                    Hinting = SKFontHinting.Full,
+                    LinearMetrics = true,                   
+                    Size = fontRenderingEmSize,
+                    Typeface = glyphTypefaceImpl.Typeface,
+                    Embolden = glyphTypefaceImpl.IsFakeBold,
+                    SkewX = glyphTypefaceImpl.IsFakeItalic ? -0.2f : 0
+                };
+            }
 
-                    for (int i = 0; i < glyphs.Length; i++)
-                    {
-                        glyphs[i] = glyphRun.GlyphIndices[i];
-                    }
+            public abstract Span<ushort> GlyphIndices { get; }
 
-                    textBlob = textBlobBuilder.Build();
-                }
-            }
-            else
+            public IGlyphRunImpl Build()
             {
-                var buffer = textBlobBuilder.AllocatePositionedRun(s_font, count);
-
-                var glyphPositions = buffer.GetPositionSpan();
+                return new GlyphRunImpl(_builder.Build());
+            }
+        }
 
-                var currentX = 0.0;
+        private sealed class SKGlyphRunBuffer : SKGlyphRunBufferBase
+        {
+            private readonly SKRunBuffer _buffer;
 
-                for (var i = 0; i < count; i++)
-                {
-                    var glyphOffset = glyphRun.GlyphOffsets[i];
-
-                    glyphPositions[i] = new SKPoint((float)(currentX + glyphOffset.X), (float)glyphOffset.Y);
-
-                    if (glyphRun.GlyphAdvances == null)
-                    {
-                        currentX += glyphTypeface.GetGlyphAdvance(glyphRun.GlyphIndices[i]) * scale;
-                    }
-                    else
-                    {
-                        currentX += glyphRun.GlyphAdvances[i];
-                    }
-                }
+            public SKGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length)
+            {
+                _buffer = _builder.AllocateRun(_font, length, 0, 0);
+            }
 
-                var glyphs = buffer.GetGlyphSpan();
+            public override Span<ushort> GlyphIndices => _buffer.GetGlyphSpan();
+        }
 
-                for (int i = 0; i < glyphs.Length; i++)
-                {
-                    glyphs[i] = glyphRun.GlyphIndices[i];
-                }
+        private sealed class SKHorizontalGlyphRunBuffer : SKGlyphRunBufferBase, IHorizontalGlyphRunBuffer
+        {
+            private readonly SKHorizontalRunBuffer _buffer;
 
-                textBlob = textBlobBuilder.Build();
+            public SKHorizontalGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length)
+            {
+                _buffer = _builder.AllocateHorizontalRun(_font, length, 0);
             }
 
-            return new GlyphRunImpl(textBlob);
+            public override Span<ushort> GlyphIndices => _buffer.GetGlyphSpan();
+
+            public Span<float> GlyphPositions => _buffer.GetPositionSpan();
         }
 
-        public IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi)
+        private sealed class SKPositionedGlyphRunBuffer : SKGlyphRunBufferBase, IPositionedGlyphRunBuffer
         {
-            if (_skiaGpu is IOpenGlAwareSkiaGpu glAware)
-                return glAware.CreateOpenGlBitmap(size, dpi);
-            if (_skiaGpu == null)
-                throw new PlatformNotSupportedException("GPU acceleration is not available");
-            throw new PlatformNotSupportedException(
-                "Current GPU acceleration backend does not support OpenGL integration");
-        }
+            private readonly SKPositionedRunBuffer _buffer;
 
-        public bool SupportsIndividualRoundRects => true;
+            public SKPositionedGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length)
+            {
+                _buffer = _builder.AllocatePositionedRun(_font, length);
+            }
 
-        public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
+            public override Span<ushort> GlyphIndices => _buffer.GetGlyphSpan();
 
-        public PixelFormat DefaultPixelFormat { get; }
+            public Span<PointF> GlyphPositions => MemoryMarshal.Cast<SKPoint, PointF>(_buffer.GetPositionSpan());
+        }
     }
 }

+ 44 - 45
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@@ -12,6 +12,8 @@ using SharpDX.DirectWrite;
 using GlyphRun = Avalonia.Media.GlyphRun;
 using TextAlignment = Avalonia.Media.TextAlignment;
 using SharpDX.Mathematics.Interop;
+using System.Runtime.InteropServices;
+using System.Drawing;
 
 namespace Avalonia
 {
@@ -258,69 +260,66 @@ namespace Avalonia.Direct2D1
             return new WicBitmapImpl(format, alphaFormat, data, size, dpi, stride);
         }
 
-        public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun)
+        private class DWGlyphRunBuffer : IGlyphRunBuffer
         {
-            var glyphTypeface = (GlyphTypefaceImpl)glyphRun.GlyphTypeface.PlatformImpl;
+            protected readonly SharpDX.DirectWrite.GlyphRun _dwRun;
 
-            var glyphCount = glyphRun.GlyphIndices.Count;
-
-            var run = new SharpDX.DirectWrite.GlyphRun
+            public DWGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
             {
-                FontFace = glyphTypeface.FontFace,
-                FontSize = (float)glyphRun.FontRenderingEmSize
-            };
-
-            var indices = new short[glyphCount];
+                var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface.PlatformImpl;
 
-            for (var i = 0; i < glyphCount; i++)
-            {
-                indices[i] = (short)glyphRun.GlyphIndices[i];
+                _dwRun = new SharpDX.DirectWrite.GlyphRun
+                {
+                    FontFace = glyphTypefaceImpl.FontFace,
+                    FontSize = fontRenderingEmSize,
+                    Indices = new short[length]
+                };
             }
 
-            run.Indices = indices;
-
-            run.Advances = new float[glyphCount];
+            public Span<ushort> GlyphIndices => MemoryMarshal.Cast<short, ushort>(_dwRun.Indices.AsSpan());
 
-            var scale = (float)(glyphRun.FontRenderingEmSize / glyphTypeface.DesignEmHeight);
-
-            if (glyphRun.GlyphAdvances == null)
+            public IGlyphRunImpl Build()
             {
-                for (var i = 0; i < glyphCount; i++)
-                {
-                    var advance = glyphTypeface.GetGlyphAdvance(glyphRun.GlyphIndices[i]) * scale;
-
-                    run.Advances[i] = advance;
-                }
+                return new GlyphRunImpl(_dwRun);
             }
-            else
-            {
-                for (var i = 0; i < glyphCount; i++)
-                {
-                    var advance = (float)glyphRun.GlyphAdvances[i];
+        }
 
-                    run.Advances[i] = advance;
-                }
+        private class DWHorizontalGlyphRunBuffer : DWGlyphRunBuffer, IHorizontalGlyphRunBuffer
+        {
+            public DWHorizontalGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) 
+                : base(glyphTypeface, fontRenderingEmSize, length)
+            {
+                _dwRun.Advances = new float[length];
             }
 
-            if (glyphRun.GlyphOffsets == null)
+            public Span<float> GlyphPositions => _dwRun.Advances.AsSpan();
+        }
+
+        private class DWPositionedGlyphRunBuffer : DWGlyphRunBuffer, IPositionedGlyphRunBuffer
+        {
+            public DWPositionedGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
+                : base(glyphTypeface, fontRenderingEmSize, length)
             {
-                return new GlyphRunImpl(run);
+                _dwRun.Advances = new float[length];
+                _dwRun.Offsets = new GlyphOffset[length];
             }
 
-            run.Offsets = new GlyphOffset[glyphCount];
+            public Span<PointF> GlyphPositions => MemoryMarshal.Cast<GlyphOffset, PointF>(_dwRun.Offsets.AsSpan());
+        }
 
-            for (var i = 0; i < glyphCount; i++)
-            {
-                var (x, y) = glyphRun.GlyphOffsets[i];
+        public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
+        {
+            return new DWGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
+        }
 
-                run.Offsets[i] = new GlyphOffset
-                {
-                    AdvanceOffset = (float)x,
-                    AscenderOffset = (float)y
-                };
-            }
+        public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
+        {
+            return new DWHorizontalGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
+        }
 
-            return new GlyphRunImpl(run);
+        public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
+        {
+            return new DWPositionedGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
         }
 
         public bool SupportsIndividualRoundRects => false;

+ 2 - 0
src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs

@@ -117,6 +117,8 @@ namespace Avalonia.Direct2D1.Media
         /// <inheritdoc cref="IGlyphTypefaceImpl"/>
         public bool IsFixedPitch { get; }
 
+        public int GlyphCount { get; set; }
+
         /// <inheritdoc cref="IGlyphTypefaceImpl"/>
         public ushort GetGlyph(uint codepoint)
         {

+ 15 - 0
tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs

@@ -126,6 +126,21 @@ namespace Avalonia.Base.UnitTests.VisualTree
             throw new NotImplementedException();
         }
 
+        public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
+        {
+            throw new NotImplementedException();
+        }
+
+        public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
+        {
+            throw new NotImplementedException();
+        }
+
+        public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
+        {
+            throw new NotImplementedException();
+        }
+
         class MockStreamGeometry : IStreamGeometryImpl
         {
             private MockStreamGeometryContext _impl = new MockStreamGeometryContext();

+ 13 - 3
tests/Avalonia.Benchmarks/NullRenderingPlatform.cs

@@ -112,12 +112,22 @@ namespace Avalonia.Benchmarks
             return new MockFontManagerImpl();
         }
 
-        public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun)
+        public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
         {
-            return new NullGlyphRun();
+            return new MockStreamGeometryImpl();
         }
 
-        public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
+        public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
+        {
+            throw new NotImplementedException();
+        }
+
+        public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
+        {
+            throw new NotImplementedException();
+        }
+
+        public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
         {
             throw new NotImplementedException();
         }

+ 4 - 0
tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs

@@ -44,6 +44,8 @@ namespace Avalonia.UnitTests
 
             IsFixedPitch = GetGlyphAdvance(GetGlyph('a')) == GetGlyphAdvance(GetGlyph('b'));
 
+            GlyphCount = Face.GlyphCount;
+
             IsFakeBold = isFakeBold;
 
             IsFakeItalic = isFakeItalic;
@@ -79,6 +81,8 @@ namespace Avalonia.UnitTests
 
         /// <inheritdoc cref="IGlyphTypefaceImpl"/>
         public bool IsFixedPitch { get; }
+
+        public int GlyphCount { get; set; }
         
         public bool IsFakeBold { get; }
         

+ 1 - 0
tests/Avalonia.UnitTests/MockGlyphTypeface.cs

@@ -14,6 +14,7 @@ namespace Avalonia.UnitTests
         public int StrikethroughPosition { get; }
         public int StrikethroughThickness { get; }
         public bool IsFixedPitch { get; }
+        public int GlyphCount => 1337;
 
         public ushort GetGlyph(uint codepoint)
         {

+ 15 - 0
tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs

@@ -152,6 +152,21 @@ namespace Avalonia.UnitTests
             return Mock.Of<IGeometryImpl>();
         }
 
+        public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
+        {
+            return Mock.Of<IGlyphRunBuffer>();
+        }
+
+        public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
+        {
+            return Mock.Of<IHorizontalGlyphRunBuffer>();
+        }
+
+        public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
+        {
+            return Mock.Of<IPositionedGlyphRunBuffer>();
+        }
+
         public bool SupportsIndividualRoundRects { get; set; }
 
         public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;