From f2e93ecb87668f827f3ab03e91be44391fcc59fc Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 30 May 2021 00:59:05 +0200 Subject: [PATCH] Optimize shape rendering (pen creation mostly). --- src/Avalonia.Controls/Shapes/Shape.cs | 26 ++++- .../Media/Immutable/ImmutableDashStyle.cs | 12 +- .../Media/Immutable/ImmutablePen.cs | 2 +- .../Rendering/SceneGraph/SceneBuilder.cs | 2 + .../Rendering/SceneGraph/VisualNode.cs | 5 + .../NullDrawingContextImpl.cs | 103 ++++++++++++++++++ .../NullRenderingPlatform.cs | 4 +- .../Rendering/ShapeRendering.cs | 53 +++++++++ 8 files changed, 197 insertions(+), 10 deletions(-) create mode 100644 tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs create mode 100644 tests/Avalonia.Benchmarks/Rendering/ShapeRendering.cs diff --git a/src/Avalonia.Controls/Shapes/Shape.cs b/src/Avalonia.Controls/Shapes/Shape.cs index 0b7595ec9a..0d1d9e3ffe 100644 --- a/src/Avalonia.Controls/Shapes/Shape.cs +++ b/src/Avalonia.Controls/Shapes/Shape.cs @@ -1,6 +1,7 @@ using System; using Avalonia.Collections; using Avalonia.Media; +using Avalonia.Media.Immutable; #nullable enable @@ -199,8 +200,29 @@ namespace Avalonia.Controls.Shapes if (geometry != null) { - var pen = new Pen(Stroke, StrokeThickness, new DashStyle(StrokeDashArray, StrokeDashOffset), - StrokeLineCap, StrokeJoin); + var stroke = Stroke; + + ImmutablePen? pen = null; + + if (stroke != null) + { + var strokeDashArray = StrokeDashArray; + + ImmutableDashStyle? dashStyle = null; + + if (strokeDashArray != null && strokeDashArray.Count > 0) + { + dashStyle = new ImmutableDashStyle(strokeDashArray, StrokeDashOffset); + } + + pen = new ImmutablePen( + stroke.ToImmutable(), + StrokeThickness, + dashStyle, + StrokeLineCap, + StrokeJoin); + } + context.DrawGeometry(Fill, pen, geometry); } } diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableDashStyle.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableDashStyle.cs index e9a52fe6ed..2dd188e0a9 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableDashStyle.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableDashStyle.cs @@ -10,6 +10,8 @@ namespace Avalonia.Media.Immutable /// public class ImmutableDashStyle : IDashStyle, IEquatable { + private readonly double[] _dashes; + /// /// Initializes a new instance of the class. /// @@ -17,12 +19,12 @@ namespace Avalonia.Media.Immutable /// The dash sequence offset. public ImmutableDashStyle(IEnumerable dashes, double offset) { - Dashes = (IReadOnlyList)dashes?.ToList() ?? Array.Empty(); + _dashes = dashes?.ToArray() ?? Array.Empty(); Offset = offset; } /// - public IReadOnlyList Dashes { get; } + public IReadOnlyList Dashes => _dashes; /// public double Offset { get; } @@ -56,9 +58,9 @@ namespace Avalonia.Media.Immutable var hashCode = 717868523; hashCode = hashCode * -1521134295 + Offset.GetHashCode(); - if (Dashes != null) + if (_dashes != null) { - foreach (var i in Dashes) + foreach (var i in _dashes) { hashCode = hashCode * -1521134295 + i.GetHashCode(); } @@ -69,7 +71,7 @@ namespace Avalonia.Media.Immutable private static bool SequenceEqual(IReadOnlyList left, IReadOnlyList right) { - if (left == right) + if (ReferenceEquals(left, right)) { return true; } diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs index e586eaf3a9..32624fbf45 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs @@ -23,7 +23,7 @@ namespace Avalonia.Media.Immutable ImmutableDashStyle dashStyle = null, PenLineCap lineCap = PenLineCap.Flat, PenLineJoin lineJoin = PenLineJoin.Miter, - double miterLimit = 10.0) : this(new SolidColorBrush(color), thickness, dashStyle, lineCap, lineJoin, miterLimit) + double miterLimit = 10.0) : this(new ImmutableSolidColorBrush(color), thickness, dashStyle, lineCap, lineJoin, miterLimit) { } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs index 56f05db04e..f2a09b815e 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs @@ -249,6 +249,8 @@ namespace Avalonia.Rendering.SceneGraph { var visualChildren = (IList) visual.VisualChildren; + node.TryPreallocateChildren(visualChildren.Count); + if (visualChildren.Count == 1) { var childNode = GetOrCreateChildNode(scene, visualChildren[0], node); diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs index d0439feed2..db6b606b41 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs @@ -358,6 +358,11 @@ namespace Avalonia.Rendering.SceneGraph internal void TryPreallocateChildren(int count) { + if (count == 0) + { + return; + } + EnsureChildrenCreated(count); } diff --git a/tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs b/tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs new file mode 100644 index 0000000000..7626be7760 --- /dev/null +++ b/tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs @@ -0,0 +1,103 @@ +using Avalonia.Media; +using Avalonia.Platform; +using Avalonia.Rendering.SceneGraph; +using Avalonia.Utilities; +using Avalonia.Visuals.Media.Imaging; + +namespace Avalonia.Benchmarks +{ + internal class NullDrawingContextImpl : IDrawingContextImpl + { + public void Dispose() + { + } + + public Matrix Transform { get; set; } + + public void Clear(Color color) + { + } + + public void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, + BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default) + { + } + + public void DrawBitmap(IRef source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) + { + } + + public void DrawLine(IPen pen, Point p1, Point p2) + { + } + + public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry) + { + } + + public void DrawRectangle(IBrush brush, IPen pen, RoundedRect rect, BoxShadows boxShadows = default) + { + } + + public void DrawText(IBrush foreground, Point origin, IFormattedTextImpl text) + { + } + + public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) + { + } + + public IDrawingContextLayerImpl CreateLayer(Size size) + { + return null; + } + + public void PushClip(Rect clip) + { + } + + public void PushClip(RoundedRect clip) + { + } + + public void PopClip() + { + } + + public void PushOpacity(double opacity) + { + } + + public void PopOpacity() + { + } + + public void PushOpacityMask(IBrush mask, Rect bounds) + { + } + + public void PopOpacityMask() + { + } + + public void PushGeometryClip(IGeometryImpl clip) + { + } + + public void PopGeometryClip() + { + } + + public void PushBitmapBlendMode(BitmapBlendingMode blendingMode) + { + } + + public void PopBitmapBlendMode() + { + } + + public void Custom(ICustomDrawOperation custom) + { + } + } +} diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs index 8792cb5cb1..876a0de643 100644 --- a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs +++ b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs @@ -18,12 +18,12 @@ namespace Avalonia.Benchmarks public IGeometryImpl CreateEllipseGeometry(Rect rect) { - throw new NotImplementedException(); + return new MockStreamGeometryImpl(); } public IGeometryImpl CreateLineGeometry(Point p1, Point p2) { - throw new NotImplementedException(); + return new MockStreamGeometryImpl(); } public IGeometryImpl CreateRectangleGeometry(Rect rect) diff --git a/tests/Avalonia.Benchmarks/Rendering/ShapeRendering.cs b/tests/Avalonia.Benchmarks/Rendering/ShapeRendering.cs new file mode 100644 index 0000000000..b0db806afa --- /dev/null +++ b/tests/Avalonia.Benchmarks/Rendering/ShapeRendering.cs @@ -0,0 +1,53 @@ +using Avalonia.Controls.Shapes; +using Avalonia.Media; +using Avalonia.Platform; +using BenchmarkDotNet.Attributes; + +namespace Avalonia.Benchmarks.Rendering +{ + [MemoryDiagnoser] + public class ShapeRendering + { + private readonly DrawingContext _drawingContext; + private readonly Line _lineFill; + private readonly Line _lineFillAndStroke; + private readonly Line _lineNoBrushes; + private readonly Line _lineStroke; + + public ShapeRendering() + { + _lineNoBrushes = new Line(); + _lineStroke = new Line { Stroke = new SolidColorBrush() }; + _lineFill = new Line { Fill = new SolidColorBrush() }; + _lineFillAndStroke = new Line { Stroke = new SolidColorBrush(), Fill = new SolidColorBrush() }; + + _drawingContext = new DrawingContext(new NullDrawingContextImpl(), true); + + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new NullRenderingPlatform()); + } + + [Benchmark] + public void Render_Line_NoBrushes() + { + _lineNoBrushes.Render(_drawingContext); + } + + [Benchmark] + public void Render_Line_WithStroke() + { + _lineStroke.Render(_drawingContext); + } + + [Benchmark] + public void Render_Line_WithFill() + { + _lineFill.Render(_drawingContext); + } + + [Benchmark] + public void Render_Line_WithFillAndStroke() + { + _lineFillAndStroke.Render(_drawingContext); + } + } +}