From 74fafe3b09dbe75b810eb0a590ed588791422a39 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Tue, 11 Oct 2022 20:16:19 +0200 Subject: [PATCH] Optimize GlyphRun allocations --- src/Avalonia.Base/Media/GlyphRun.cs | 81 +++++++- src/Avalonia.Base/Media/GlyphTypeface.cs | 5 + src/Avalonia.Base/Platform/IGlyphRunBuffer.cs | 22 +++ .../Platform/IGlyphTypefaceImpl.cs | 5 + .../Platform/IPlatformRenderInterface.cs | 37 +++- .../HeadlessPlatformRenderInterface.cs | 38 +++- .../HeadlessPlatformStubs.cs | 2 + src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs | 4 + .../Avalonia.Skia/PlatformRenderInterface.cs | 182 ++++++++---------- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 89 +++++---- .../Media/GlyphTypefaceImpl.cs | 2 + .../VisualTree/MockRenderInterface.cs | 15 ++ .../NullRenderingPlatform.cs | 16 +- .../HarfBuzzGlyphTypefaceImpl.cs | 4 + tests/Avalonia.UnitTests/MockGlyphTypeface.cs | 1 + .../MockPlatformRenderInterface.cs | 15 ++ 16 files changed, 354 insertions(+), 164 deletions(-) create mode 100644 src/Avalonia.Base/Platform/IGlyphRunBuffer.cs diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs index 2289f98228..ce436ee017 100644 --- a/src/Avalonia.Base/Media/GlyphRun.cs +++ b/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(); + 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() diff --git a/src/Avalonia.Base/Media/GlyphTypeface.cs b/src/Avalonia.Base/Media/GlyphTypeface.cs index 45ef04e77f..36b6f3a5f8 100644 --- a/src/Avalonia.Base/Media/GlyphTypeface.cs +++ b/src/Avalonia.Base/Media/GlyphTypeface.cs @@ -67,6 +67,11 @@ namespace Avalonia.Media /// public bool IsFixedPitch => PlatformImpl.IsFixedPitch; + /// + /// Gets the number of glyphs held by this glyph typeface. + /// + public int GlyphCount => PlatformImpl.GlyphCount; + /// /// Returns an glyph index for the specified codepoint. /// diff --git a/src/Avalonia.Base/Platform/IGlyphRunBuffer.cs b/src/Avalonia.Base/Platform/IGlyphRunBuffer.cs new file mode 100644 index 0000000000..c1fc7a5967 --- /dev/null +++ b/src/Avalonia.Base/Platform/IGlyphRunBuffer.cs @@ -0,0 +1,22 @@ +using System; +using System.Drawing; + +namespace Avalonia.Platform +{ + public interface IGlyphRunBuffer + { + Span GlyphIndices { get; } + + IGlyphRunImpl Build(); + } + + public interface IHorizontalGlyphRunBuffer : IGlyphRunBuffer + { + Span GlyphPositions { get; } + } + + public interface IPositionedGlyphRunBuffer : IGlyphRunBuffer + { + Span GlyphPositions { get; } + } +} diff --git a/src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs b/src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs index 415f34fb29..5a38e133ff 100644 --- a/src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs +++ b/src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs @@ -51,6 +51,11 @@ namespace Avalonia.Platform /// bool IsFixedPitch { get; } + /// + /// Gets the number of glyphs held by this glyph typeface. + /// + int GlyphCount { get; } + /// /// Returns an glyph index for the specified codepoint. /// diff --git a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs index e39a4e23df..93105b21a4 100644 --- a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs +++ b/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); /// - /// Creates a platform implementation of a glyph run. + /// Allocates a platform glyph run buffer. /// - /// The glyph run. - /// - IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun); + /// The glyph typeface. + /// The font rendering em size. + /// The length. + /// An . + /// + /// This buffer only holds glyph indices. + /// + IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length); + + /// + /// Allocates a horizontal platform glyph run buffer. + /// + /// The glyph typeface. + /// The font rendering em size. + /// The length. + /// An . + /// + /// This buffer holds glyph indices and glyph advances. + /// + IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length); + + /// + /// Allocates a positioned platform glyph run buffer. + /// + /// The glyph typeface. + /// The font rendering em size. + /// The length. + /// An . + /// + /// This buffer holds glyph indices, glyph advances and glyph positions. + /// + IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length); /// /// Gets a value indicating whether the platform directly supports rectangles with rounded corners. diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index cb23c6c336..12a9a016f3 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/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 GlyphIndices => Span.Empty; + + public IGlyphRunImpl Build() + { + return new HeadlessGlyphRunStub(); + } + } + + class HeadlessHorizontalGlyphRunBufferStub : HeadlessGlyphRunBufferStub, IHorizontalGlyphRunBuffer + { + public Span GlyphPositions => Span.Empty; + } + + class HeadlessPositionedGlyphRunBufferStub : HeadlessGlyphRunBufferStub, IPositionedGlyphRunBuffer + { + public Span GlyphPositions => Span.Empty; + } + class HeadlessGlyphRunStub : IGlyphRunImpl { public void Dispose() diff --git a/src/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Avalonia.Headless/HeadlessPlatformStubs.cs index ed51529a96..b3bf92f06b 100644 --- a/src/Avalonia.Headless/HeadlessPlatformStubs.cs +++ b/src/Avalonia.Headless/HeadlessPlatformStubs.cs @@ -95,6 +95,8 @@ namespace Avalonia.Headless public bool IsFixedPitch => true; + public int GlyphCount => 1337; + public void Dispose() { } diff --git a/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs b/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs index dcb4eac7ca..a3174010a1 100644 --- a/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs +++ b/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 /// public bool IsFixedPitch { get; } + + public int GlyphCount { get; } public bool IsFakeBold { get; } diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index 91fe4fc085..aa9225ba64 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/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(); - 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 s_textBlobBuilderThreadLocal = new ThreadLocal(() => new SKTextBlobBuilder()); - - /// - 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 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 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 GlyphIndices => _buffer.GetGlyphSpan(); + + public Span 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 GlyphIndices => _buffer.GetGlyphSpan(); - public PixelFormat DefaultPixelFormat { get; } + public Span GlyphPositions => MemoryMarshal.Cast(_buffer.GetPositionSpan()); + } } } diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 81fa8c4bce..45744e6efa 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/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 GlyphIndices => MemoryMarshal.Cast(_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 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 GlyphPositions => MemoryMarshal.Cast(_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; diff --git a/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs index 4154b44702..443bb8a207 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs @@ -117,6 +117,8 @@ namespace Avalonia.Direct2D1.Media /// public bool IsFixedPitch { get; } + public int GlyphCount { get; set; } + /// public ushort GetGlyph(uint codepoint) { diff --git a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs index 1f0b82b465..d21879f349 100644 --- a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs +++ b/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(); diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs index 0193f5d772..a2c2936a4d 100644 --- a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs +++ b/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(); } diff --git a/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs b/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs index 32e0434cd4..04f316e5b4 100644 --- a/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs +++ b/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 /// public bool IsFixedPitch { get; } + + public int GlyphCount { get; set; } public bool IsFakeBold { get; } diff --git a/tests/Avalonia.UnitTests/MockGlyphTypeface.cs b/tests/Avalonia.UnitTests/MockGlyphTypeface.cs index c9c59a6e32..0b7beeb4fb 100644 --- a/tests/Avalonia.UnitTests/MockGlyphTypeface.cs +++ b/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) { diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs index f6a127b573..314cc95041 100644 --- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs +++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs @@ -152,6 +152,21 @@ namespace Avalonia.UnitTests return Mock.Of(); } + public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + return Mock.Of(); + } + + public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + return Mock.Of(); + } + + public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + return Mock.Of(); + } + public bool SupportsIndividualRoundRects { get; set; } public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;