WPF 动画/UI 功能性能和基准测试

Posted

技术标签:

【中文标题】WPF 动画/UI 功能性能和基准测试【英文标题】:WPF animation/UI features performance and benchmarking 【发布时间】:2009-06-07 13:20:00 【问题描述】:

我正在为一些业务线的一些花哨的 WPF UI 工作做一个相对较小的概念验证。甚至不用太疯狂,当我使用许多我认为是首先考虑将 WPF 用于 UI 构建的主要原因时,我已经看到了一些非常糟糕的性能。我在这里问了一个问题,为什么我的动画在第一次运行时就停止了,最后我发现一个非常简单的 UserControl 只需要半秒钟来构建它的可视化树。我能够解决该症状,但是初始化一个简单控件需要很长时间这一事实确实让我感到困扰。现在,我正在测试有无 DropShadowEffect 的动画,结果是白天黑夜。一个微妙的阴影让我的控件看起来好多了,但它完全破坏了动画的平滑度。让我也不要从字体渲染开始。当控件有一堆渐变画笔和一个阴影时,我的动画计算使文本模糊了大约整整一秒钟,然后慢慢变成焦点。

所以,我想我的问题是,是否有已知的研究、博客文章或文章详细说明当前 WPF 版本中哪些功能对业务关键型应用程序存在危害。效果(即 DropShadowEffect)、渐变画笔、关键帧动画等是否会对渲染质量(或者这些东西的组合)产生过多的负面影响? WPF 4.0 的最终版本会纠正其中的一些问题吗?我已经读到 VS2010 beta 有一些同样的问题,并且它们应该在最终版本中得到解决。这是因为 WPF 本身的改进,还是因为一半的应用程序将使用以前的技术重建?

【问题讨论】:

【参考方案1】:

我还在研究与 WPF 中的 DropShadow 效果相关的性能问题。

基本上,经验法则总是“不要使用它”。 WPF 中的 PixelShader 和位图 DropShadow 效果都不适用于真实世界的应用程序...除非您的 UI 保持 100% 静态且没有移动部件,否则您的程序将像垃圾一样在 95% 的客户群中运行。

我要做的是假装它。

这是我刚刚想出的一个快速的“ndirty DropShadow Decorator”,它可以作为 PixelShader 投影效果的合理复制品。您将希望通过缓存画笔等使其更清洁,但这应该会给您一个想法:

using System.Windows.Shapes;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Media;
using System;
using System.ComponentModel; 

namespace SichboPVR

    /// <summary>
    /// Emulates the System.Windows.Media.Effects.DropShadowEffect using
    /// rectangles and gradients, which performs a million times better
    /// and won't randomly crash a good percentage of your end-user's 
    /// video drivers.
    /// </summary>
    class FastShadow : Decorator 
    

        #region Dynamic Properties

        public static readonly DependencyProperty ColorProperty =
                DependencyProperty.Register(
                        "Color",
                        typeof(Color),
                        typeof(FastShadow),
                        new FrameworkPropertyMetadata(
                                Color.FromArgb(0x71, 0x00, 0x00, 0x00),
                                FrameworkPropertyMetadataOptions.AffectsRender));

        /// <summary>
        /// The Color property defines the Color used to fill the shadow region. 
        /// </summary> 
        [Category("Common Properties")]
        public Color Color
        
            get  return (Color)GetValue(ColorProperty); 
            set  SetValue(ColorProperty, value); 
        

        /// <summary>
        /// Distance from centre, why MS don't call this "distance" beats
        /// me.. Kept same as other Effects for consistency.
        /// </summary>
        [Category("Common Properties"), Description("Distance from centre")]
        public double ShadowDepth
        
            get  return (double)GetValue(ShadowDepthProperty); 
            set  SetValue(ShadowDepthProperty, value); 
        

        // Using a DependencyProperty as the backing store for ShadowDepth.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ShadowDepthProperty =
            DependencyProperty.Register("ShadowDepth", typeof(double), typeof(FastShadow), 
            new FrameworkPropertyMetadata(
                5.0, FrameworkPropertyMetadataOptions.AffectsRender,
                    new PropertyChangedCallback((o, e) => 
                        FastShadow f = o as FastShadow;
                        if ((double)e.NewValue < 0)
                            f.ShadowDepth = 0;
                    )));


        /// <summary>
        /// Size of the shadow
        /// </summary>
        [Category("Common Properties"), Description("Size of the drop shadow")]
        public double BlurRadius
        
            get  return (double)GetValue(BlurRadiusProperty); 
            set  SetValue(BlurRadiusProperty, value); 
        

        // Using a DependencyProperty as the backing store for BlurRadius.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty BlurRadiusProperty =
            DependencyProperty.Register("BlurRadius", typeof(double), typeof(FastShadow), 
            new FrameworkPropertyMetadata(10.0, 
                FrameworkPropertyMetadataOptions.AffectsRender,
                    new PropertyChangedCallback((o, e) => 
                        FastShadow f = o as FastShadow;
                        if ((double)e.NewValue < 0)
                            f.BlurRadius = 0;
                    )));


        /// <summary>
        /// Angle of the shadow
        /// </summary>
        [Category("Common Properties"), Description("Angle of the shadow")]
        public int Direction
        
            get  return (int)GetValue(DirectionProperty); 
            set  SetValue(DirectionProperty, value); 
        

        // Using a DependencyProperty as the backing store for Direction.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DirectionProperty =
            DependencyProperty.Register("Direction", typeof(int), typeof(FastShadow), 
            new FrameworkPropertyMetadata(315, FrameworkPropertyMetadataOptions.AffectsRender));


        #endregion Dynamic Properties

        #region Protected Methods

        protected override void OnRender(DrawingContext drawingContext)
        

            double distance = Math.Max(0, ShadowDepth);
            double blurRadius = Math.Max(BlurRadius, 0);
            double angle = Direction + 45; // Make it behave the same as DropShadowEffect

            Rect shadowBounds = new Rect(new Point(0, 0),
                             new Size(RenderSize.Width, RenderSize.Height));

            shadowBounds.Inflate(blurRadius, blurRadius);

            Color color = Color;

            // Transform angle for "Direction"
            double angleRad = angle * Math.PI / 180.0;
            double xDispl = distance;
            double yDispl = distance;
            double newX = xDispl * Math.Cos(angleRad) - yDispl * Math.Sin(angleRad);
            double newY = yDispl * Math.Cos(angleRad) + xDispl * Math.Sin(angleRad);

            TranslateTransform translate = new TranslateTransform(newX, newY);
            Rect transformed = translate.TransformBounds(shadowBounds);

            // Hint: you can make the blur radius consume more "centre"
            //       region of the bounding box by doubling this here
            // blurRadius = blurRadius * 2;

            // Build a set of rectangles for the shadow box
            Rect[] edges = new Rect[]  
                new Rect(new Point(transformed.X,transformed.Y), new Size(blurRadius,blurRadius)), // TL
                new Rect(new Point(transformed.X+blurRadius,transformed.Y), new Size(Math.Max(transformed.Width-(blurRadius*2),0),blurRadius)), // T
                new Rect(new Point(transformed.Right-blurRadius,transformed.Y), new Size(blurRadius,blurRadius)), // TR
                new Rect(new Point(transformed.Right-blurRadius,transformed.Y+blurRadius), new Size(blurRadius,Math.Max(transformed.Height-(blurRadius*2),0))), // R
                new Rect(new Point(transformed.Right-blurRadius,transformed.Bottom-blurRadius), new Size(blurRadius,blurRadius)), // BR
                new Rect(new Point(transformed.X+blurRadius,transformed.Bottom-blurRadius), new Size(Math.Max(transformed.Width-(blurRadius*2),0),blurRadius)), // B
                new Rect(new Point(transformed.X,transformed.Bottom-blurRadius), new Size(blurRadius,blurRadius)), // BL
                new Rect(new Point(transformed.X,transformed.Y+blurRadius), new Size(blurRadius,Math.Max(transformed.Height-(blurRadius*2),0))), // L
                new Rect(new Point(transformed.X+blurRadius,transformed.Y+blurRadius), new Size(Math.Max(transformed.Width-(blurRadius*2),0),Math.Max(transformed.Height-(blurRadius*2),0))), // C
            ;

            // Gradient stops look a lot prettier than
            // a perfectly linear gradient..
            GradientStopCollection gsc = new GradientStopCollection();
            Color stopColor = color;
            stopColor.A = (byte)(color.A);
            gsc.Add(new GradientStop(color, 0.0));
            stopColor.A = (byte)(.74336 * color.A);
            gsc.Add(new GradientStop(stopColor, 0.1));
            stopColor.A = (byte)(.38053 * color.A);
            gsc.Add(new GradientStop(stopColor, 0.3));
            stopColor.A = (byte)(.12389 * color.A);
            gsc.Add(new GradientStop(stopColor, 0.5));
            stopColor.A = (byte)(.02654 * color.A);
            gsc.Add(new GradientStop(stopColor, 0.7));
            stopColor.A = (byte)(0);
            gsc.Add(new GradientStop(stopColor, 0.9));

            gsc.Freeze(); 

            Brush[] colors = new Brush[]
                // TL
                new RadialGradientBrush(gsc) Center = new Point(1, 1), GradientOrigin = new Point(1, 1), RadiusX=1, RadiusY=1,
                // T
                new LinearGradientBrush(gsc, 0) StartPoint = new Point(0,1), EndPoint=new Point(0,0),
                // TR
                new RadialGradientBrush(gsc) Center = new Point(0, 1), GradientOrigin = new Point(0, 1), RadiusX=1, RadiusY=1,
                // R
                new LinearGradientBrush(gsc, 0) StartPoint = new Point(0,0), EndPoint=new Point(1,0),
                // BR
                new RadialGradientBrush(gsc) Center = new Point(0, 0), GradientOrigin = new Point(0, 0), RadiusX=1, RadiusY=1,
                // B
                new LinearGradientBrush(gsc, 0) StartPoint = new Point(0,0), EndPoint=new Point(0,1),
                // BL
                new RadialGradientBrush(gsc) Center = new Point(1, 0), GradientOrigin = new Point(1, 0), RadiusX=1, RadiusY=1,
                // L
                new LinearGradientBrush(gsc, 0) StartPoint = new Point(1,0), EndPoint=new Point(0,0),
                // C
                new SolidColorBrush(color), 
            ;

            // This is a test pattern, uncomment to see how I'm drawing this
            //Brush[] colors = new Brush[]
            //    Brushes.Red,
            //    Brushes.Green,
            //    Brushes.Blue,
            //    Brushes.Fuchsia,
            //    Brushes.Gainsboro,
            //    Brushes.LimeGreen,
            //    Brushes.Navy,
            //    Brushes.Orange,
            //    Brushes.White,
            //;
            double[] guidelineSetX = new double[]  transformed.X,
                                                    transformed.X+blurRadius,
                                                    transformed.Right-blurRadius,
                                                    transformed.Right;

            double[] guidelineSetY = new double[]  transformed.Y,
                                                    transformed.Y+blurRadius,
                                                    transformed.Bottom-blurRadius,
                                                    transformed.Bottom;

            drawingContext.PushGuidelineSet(new GuidelineSet(guidelineSetX, guidelineSetY)); 
            for (int i = 0; i < edges.Length; i++)
            
                drawingContext.DrawRoundedRectangle(colors[i], null, edges[i], 0.0, 0.0);
            
            drawingContext.Pop();
        

        #endregion

    

Soo.. 用法会是这样的;

<Sichbo:FastShadow Color="Black" ShadowDepth="0" BlurRadius="30">
<Grid>.. content here ..</Grid>
</Sichbo:FastShadow>

.. 输出应该非常接近 PS 阴影的效果.. 显然只适用于“四四方方”的元素,但运行时的速度差异应该是白天和黑夜,动画在高分辨率下应该如丝般流畅,并且你可以让你的 UI 看起来很漂亮。

【讨论】:

天哪,这太神奇了!我一直在努力尝试从标准投影效果中获得更好的性能。很高兴我找到了这个答案!【参考方案2】:

你看过微软的文章吗?

Optimizing WPF Application Performance Performance Profiling Tools for WPF

【讨论】:

目前 WPF Performance Suite 用于分析 .NET 4.5 应用程序的替代方案是什么?如果您看一下我的问题here,我将不胜感激。【参考方案3】:

Sichbo 的答案仍然很好,但仅适用于方形矩形。 我已经对其进行了调整以添加 CornerRadius 属性,以便能够在圆角矩形上绘制阴影,以便它与边框控件很好地配合。

我还将渐变计算更改为类似于我计算机上实际的 DropShadowEffect 的东西,但由于它具有非常特殊的渲染,因此似乎无法完美再现。在任何情况下,您都可以尝试使用expFactorattenuationFactor 来适应您的需求。 expFactor 控制阴影消失的速度,attenuationFactor 通过偏移范围使其更轻(而不是从 100% 到 0% 阴影,我们从例如 95% 到 -5%,上限为 0 %)。

渐变是在设置相关属性时计算的,而不是在每次渲染调用时计算的。

namespace CustomControls

    using System;
    using System.ComponentModel;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;

    /// <summary>
    /// Emulates the System.Windows.Media.Effects.DropShadowEffect using rectangles and 
    /// gradients, which performs a lot better.
    /// </summary>
    public class FastShadow : Decorator
    
        public static readonly DependencyProperty ColorProperty =
            DependencyProperty.Register("Color", typeof(Color), typeof(FastShadow),
            new FrameworkPropertyMetadata(
                Color.FromArgb(0x71, 0x00, 0x00, 0x00),
                FrameworkPropertyMetadataOptions.AffectsRender,
                new PropertyChangedCallback((o, e) =>
                
                    FastShadow f = o as FastShadow;
                    f.CalculateGradientStops();
                )));

        public static readonly DependencyProperty BlurRadiusProperty =
            DependencyProperty.Register("BlurRadius", typeof(double), typeof(FastShadow),
            new FrameworkPropertyMetadata(
                10.0,
                FrameworkPropertyMetadataOptions.AffectsRender,
                new PropertyChangedCallback((o, e) =>
                
                    FastShadow f = o as FastShadow;
                    if ((double)e.NewValue < 0)
                        f.BlurRadius = 0;
                    f.CalculateGradientStops();
                )));

        public static readonly DependencyProperty CornerRadiusProperty =
            DependencyProperty.Register("CornerRadius", typeof(double), typeof(FastShadow),
            new FrameworkPropertyMetadata(
                0.0,
                FrameworkPropertyMetadataOptions.AffectsRender,
                new PropertyChangedCallback((o, e) =>
                
                    FastShadow f = o as FastShadow;
                    if ((double)e.NewValue < 0)
                        f.BlurRadius = 0;
                    f.CalculateGradientStops();
                )));

        public static readonly DependencyProperty ShadowDepthProperty =
            DependencyProperty.Register("ShadowDepth", typeof(double), typeof(FastShadow),
            new FrameworkPropertyMetadata(
                0.0,
                FrameworkPropertyMetadataOptions.AffectsRender,
                new PropertyChangedCallback((o, e) => 
                    FastShadow f = o as FastShadow;
                    if ((double)e.NewValue < 0)
                        f.ShadowDepth = 0;
                )));

        public static readonly DependencyProperty DirectionProperty =
            DependencyProperty.Register("Direction", typeof(int), typeof(FastShadow),
            new FrameworkPropertyMetadata(315, FrameworkPropertyMetadataOptions.AffectsRender));

        private GradientStopCollection gradientStops;
        private Brush[] edgeBrushes;
        private Brush[] cornerBrushes;

        public FastShadow()
            : base()
        
            this.CalculateGradientStops();
        

        /// <summary>
        /// Color used to fill the shadow region.
        /// </summary>
        [Category("Common Properties")]
        public Color Color
        
            get => (Color)this.GetValue(ColorProperty);
            set
            
                this.SetValue(ColorProperty, value);
            
        

        /// <summary>
        /// Size of the shadow.
        /// </summary>
        [Category("Common Properties")]
        [Description("Size of the drop shadow")]
        public double BlurRadius
        
            get => (double)this.GetValue(BlurRadiusProperty);
            set
            
                this.SetValue(BlurRadiusProperty, value);
            
        

        /// <summary>
        /// Radius of the corners.
        /// </summary>
        [Category("Common Properties")]
        [Description("Radius of the corners of the shadow")]
        public double CornerRadius
        
            get => (double)this.GetValue(CornerRadiusProperty);
            set
            
                this.SetValue(CornerRadiusProperty, value);
            
        

        /// <summary>
        /// Distance from centre.
        /// </summary>
        [Category("Common Properties")]
        [Description("Distance from centre")]
        public double ShadowDepth
        
            get  return (double)this.GetValue(ShadowDepthProperty); 
            set  this.SetValue(ShadowDepthProperty, value); 
        

        /// <summary>
        /// Angle of the shadow
        /// </summary>
        [Category("Common Properties")]
        [Description("Angle of the shadow")]
        public int Direction
        
            get  return (int)this.GetValue(DirectionProperty); 
            set  this.SetValue(DirectionProperty, value); 
        

        /// <summary>
        /// Calculate gradient stops for an exponential gradient.
        /// It is designed to look similar to the WPF DropShadowEffect, but since that one renders
        /// differently depending on how zoomed in you are, a perfect fit seems impossible.
        /// </summary>
        protected void CalculateGradientStops()
        
            double blurRadius = Math.Max(this.BlurRadius, 0);
            double cornerRadius = Math.Max(this.CornerRadius, 0);
            
            // Portion of the gradient which is drawn "inside" the border
            double innerPart = (cornerRadius - blurRadius / 20) / (cornerRadius + blurRadius); 
            double remaining = 1.0 - innerPart;
            double expFactor = 1.5 + blurRadius / 50;
            double attenuationFactor = Math.Max(0.3 - blurRadius / 50, 0.05);

            GradientStopCollection gsc = new GradientStopCollection();
            float[] stops = new float[]  0.0f, 0.025f, 0.05f, 0.075f, 0.1f, 0.125f,
            0.015f, 0.2f, 0.25f, 0.3f, 0.35f, 0.4f, 0.45f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f ;
            Color stopColor = this.Color;

            gsc.Add(new GradientStop(stopColor, 0.0));
            gsc.Add(new GradientStop(stopColor, innerPart));
            foreach (float stop in stops)
            
                // Exponential gradient from 1 to 0
                double num = (Math.Exp(1 - expFactor * (stop + attenuationFactor)) - Math.Exp(1 - expFactor))
                double det = (Math.E - Math.Exp(1 - expFactor)));
                double factor = Math.Max(0, num / det);
                stopColor.A = (byte)(factor * this.Color.A);
                gsc.Add(new GradientStop(stopColor, innerPart + stop * remaining));
            

            stopColor.A = 0;
            gsc.Add(new GradientStop(stopColor, innerPart + 1.0 * remaining));
            gsc.Freeze();

            this.gradientStops = gsc;

            this.edgeBrushes = new Brush[]
            
                new LinearGradientBrush(this.gradientStops, 0)
                     StartPoint = new Point(0, 1), EndPoint = new Point(0, 0) , // T
                new LinearGradientBrush(this.gradientStops, 0)
                     StartPoint = new Point(0, 0), EndPoint = new Point(1, 0) , // R
                new LinearGradientBrush(this.gradientStops, 0)
                     StartPoint = new Point(0, 0), EndPoint = new Point(0, 1) , // B
                new LinearGradientBrush(this.gradientStops, 0)
                     StartPoint = new Point(1, 0), EndPoint = new Point(0, 0) , // L
                new SolidColorBrush(this.Color),
            ;

            for (int i = 0; i < this.edgeBrushes.Length; i++)
                this.edgeBrushes[i].Freeze();

            this.cornerBrushes = new Brush[]
            
                new RadialGradientBrush(this.gradientStops)  Center = new Point(1, 1),
                    GradientOrigin = new Point(1, 1), RadiusX = 1, RadiusY = 1 , // TL
                new RadialGradientBrush(this.gradientStops)  Center = new Point(0, 1),
                    GradientOrigin = new Point(0, 1), RadiusX = 1, RadiusY = 1 , // TR
                new RadialGradientBrush(this.gradientStops)  Center = new Point(0, 0),
                    GradientOrigin = new Point(0, 0), RadiusX = 1, RadiusY = 1 , // BR
                new RadialGradientBrush(this.gradientStops)  Center = new Point(1, 0),
                    GradientOrigin = new Point(1, 0), RadiusX = 1, RadiusY = 1 , // BL
            ;

            for (int i = 0; i < this.cornerBrushes.Length; i++)
                this.cornerBrushes[i].Freeze();
        

        protected override void OnRender(DrawingContext drawingContext)
        
            double distance = Math.Max(this.ShadowDepth, 0);
            double blurRadius = Math.Max(this.BlurRadius, 0);
            double cornerRadius = Math.Max(this.CornerRadius, 0);
            double totalRadius = blurRadius + cornerRadius;
            double angle = this.Direction + 45; // Make it behave the same as DropShadowEffect

            Rect shadowBounds = new Rect(new Point(0, 0),
                new Size(this.RenderSize.Width, this.RenderSize.Height));
            shadowBounds.Inflate(blurRadius, blurRadius);

            double angleRad = angle * Math.PI / 180.0;
            double xDispl = distance;
            double yDispl = distance;
            double newX = xDispl * Math.Cos(angleRad) - yDispl * Math.Sin(angleRad);
            double newY = yDispl * Math.Cos(angleRad) + xDispl * Math.Sin(angleRad);

            TranslateTransform translate = new TranslateTransform(newX, newY);
            Rect shadowRenderRect = translate.TransformBounds(shadowBounds);

            Color color = this.Color;

            // Build a set of rectangles for the shadow box
            Rect[] edges = new Rect[]
            
                new Rect(new Point(shadowRenderRect.X + totalRadius, shadowRenderRect.Y),
                    new Size(Math.Max(shadowRenderRect.Width - (totalRadius * 2), 0), totalRadius)), // T
                new Rect(new Point(shadowRenderRect.Right - totalRadius, shadowRenderRect.Y + totalRadius),
                    new Size(totalRadius, Math.Max(shadowRenderRect.Height - (totalRadius * 2), 0))), // R
                new Rect(new Point(shadowRenderRect.X + totalRadius, shadowRenderRect.Bottom - totalRadius),
                    new Size(Math.Max(shadowRenderRect.Width - (totalRadius * 2), 0), totalRadius)), // B
                new Rect(new Point(shadowRenderRect.X, shadowRenderRect.Y + totalRadius),
                    new Size(totalRadius, Math.Max(shadowRenderRect.Height - (totalRadius * 2), 0))), // L
                new Rect(new Point(shadowRenderRect.X + totalRadius, shadowRenderRect.Y + totalRadius),
                    new Size(
                        Math.Max(shadowRenderRect.Width - (totalRadius * 2), 0),
                        Math.Max(shadowRenderRect.Height - (totalRadius * 2), 0))), // C
            ;

            Rect[] corners = new Rect[]
            
                new Rect(shadowRenderRect.X, shadowRenderRect.Y, totalRadius * 2, totalRadius * 2), // TL
                new Rect(shadowRenderRect.Right - totalRadius * 2, shadowRenderRect.Y,
                    totalRadius * 2, totalRadius * 2), // TR
                new Rect(shadowRenderRect.Right - totalRadius * 2, shadowRenderRect.Bottom - totalRadius * 2,
                    totalRadius * 2, totalRadius * 2), // BR
                new Rect(shadowRenderRect.X, shadowRenderRect.Bottom - totalRadius * 2,
                    totalRadius * 2, totalRadius * 2), // BL
            ;

            double[] guidelineSetX = new double[]  
                shadowRenderRect.X,
                shadowRenderRect.X + totalRadius,
                shadowRenderRect.Right - totalRadius,
                shadowRenderRect.Right, ;
            double[] guidelineSetY = new double[] 
                shadowRenderRect.Y,
                shadowRenderRect.Y + totalRadius,
                shadowRenderRect.Bottom - totalRadius,
                shadowRenderRect.Bottom, ;

            drawingContext.PushGuidelineSet(new GuidelineSet(guidelineSetX, guidelineSetY));
            for (int i = 0; i < edges.Length; i++)
                drawingContext.DrawRectangle(this.edgeBrushes[i], null, edges[i]);

            drawingContext.DrawGeometry(this.cornerBrushes[0], null, CreateArcDrawing(corners[0], 180, 90));
            drawingContext.DrawGeometry(this.cornerBrushes[1], null, CreateArcDrawing(corners[1], 270, 90));
            drawingContext.DrawGeometry(this.cornerBrushes[2], null, CreateArcDrawing(corners[2], 0, 90));
            drawingContext.DrawGeometry(this.cornerBrushes[3], null, CreateArcDrawing(corners[3], 90, 90));

            drawingContext.Pop();
        

        /// <summary>
        /// Create an Arc geometry drawing of an ellipse or circle.
        /// </summary>
        /// <param name="rect">Box to hold the whole ellipse described by the arc</param>
        /// <param name="startDegrees">Start angle of the arc degrees within the ellipse. 0 degrees is a line to the right.</param>
        /// <param name="sweepDegrees">Sweep angle, -ve = Counterclockwise, +ve = Clockwise</param>
        /// <returns>GeometryDrawing object</returns>
        private static PathGeometry CreateArcDrawing(Rect rect, double startDegrees, double sweepDegrees)
        
            // degrees to radians conversion
            double startRadians = startDegrees * Math.PI / 180.0;
            double sweepRadians = sweepDegrees * Math.PI / 180.0;

            // x and y radius
            double dx = rect.Width / 2;
            double dy = rect.Height / 2;

            // determine the center point
            double xc = rect.X + dx;
            double yc = rect.Y + dy;

            // determine the start point
            double xs = rect.X + dx + (Math.Cos(startRadians) * dx);
            double ys = rect.Y + dy + (Math.Sin(startRadians) * dy);

            // determine the end point
            double xe = rect.X + dx + (Math.Cos(startRadians + sweepRadians) * dx);
            double ye = rect.Y + dy + (Math.Sin(startRadians + sweepRadians) * dy);

            bool isLargeArc = Math.Abs(sweepDegrees) > 180;
            SweepDirection sweepDirection = sweepDegrees < 0 ? SweepDirection.Counterclockwise : SweepDirection.Clockwise;

            PathGeometry pathGeometry = new PathGeometry();
            PathFigure pathFigure = new PathFigure();

            pathFigure.StartPoint = new Point(xc, yc);

            LineSegment line = new LineSegment(new Point(xs, ys), true);
            pathFigure.Segments.Add(line);

            pathFigure.StartPoint = new Point(xs, ys);
            ArcSegment arc = new ArcSegment(new Point(xe, ye), new Size(dx, dy), 0, isLargeArc, sweepDirection, true);
            pathFigure.Segments.Add(arc);

            line = new LineSegment(new Point(xc, yc), true);
            pathFigure.Segments.Add(line);

            pathFigure.IsFilled = true;
            pathGeometry.Figures.Add(pathFigure);

            return pathGeometry;
        
    

【讨论】:

以上是关于WPF 动画/UI 功能性能和基准测试的主要内容,如果未能解决你的问题,请参考以下文章

golang 基准测试和性能测试总结

Go语言之基准测试

什么是mysql基准测试

基准程序的基准程序测试

关于MySQL的基准测试

什么是mysql基准测试