Przeglądaj źródła

Merge pull request #5997 from MarchingCube/shape-render-optimization

Optimize shape rendering (pen creation mostly).
Steven Kirk 4 lat temu
rodzic
commit
8f8393978e

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

+ 7 - 5
src/Avalonia.Visuals/Media/Immutable/ImmutableDashStyle.cs

@@ -10,6 +10,8 @@ namespace Avalonia.Media.Immutable
     /// </summary>
     public class ImmutableDashStyle : IDashStyle, IEquatable<IDashStyle>
     {
+        private readonly double[] _dashes;
+
         /// <summary>
         /// Initializes a new instance of the <see cref="ImmutableDashStyle"/> class.
         /// </summary>
@@ -17,12 +19,12 @@ namespace Avalonia.Media.Immutable
         /// <param name="offset">The dash sequence offset.</param>
         public ImmutableDashStyle(IEnumerable<double> dashes, double offset)
         {
-            Dashes = (IReadOnlyList<double>)dashes?.ToList() ?? Array.Empty<double>();
+            _dashes = dashes?.ToArray() ?? Array.Empty<double>();
             Offset = offset;
         }
 
         /// <inheritdoc/>
-        public IReadOnlyList<double> Dashes { get; }
+        public IReadOnlyList<double> Dashes => _dashes;
 
         /// <inheritdoc/>
         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<double> left, IReadOnlyList<double> right)
         {
-            if (left == right)
+            if (ReferenceEquals(left, right))
             {
                 return true;
             }

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

+ 2 - 0
src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs

@@ -249,6 +249,8 @@ namespace Avalonia.Rendering.SceneGraph
                     {
                         var visualChildren = (IList<IVisual>) visual.VisualChildren;
 
+                        node.TryPreallocateChildren(visualChildren.Count);
+
                         if (visualChildren.Count == 1)
                         {
                             var childNode = GetOrCreateChildNode(scene, visualChildren[0], node);

+ 5 - 0
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);
         }
 

+ 103 - 0
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<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect,
+            BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default)
+        {
+        }
+
+        public void DrawBitmap(IRef<IBitmapImpl> 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)
+        {
+        }
+    }
+}

+ 2 - 2
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)

+ 53 - 0
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<IPlatformRenderInterface>().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);
+        }
+    }
+}