WPF performance optimization - high refresh drawing

Background introduction

The author received a request to display the patient's real-time physiological signals (ECG, etc.) on WPF in real time. The team developed the request and finished it soon (the effect picture of Unit test is as follows)

However, it was later released to the product and found that the resource consumption is larger than that of the local machine. After local monitoring, it was found that the memory and Page Faults are increasing over time. If the product is run for a long time (several months or even longer), it may lead to performance problems. Then performance optimization is needed.

Page Faults

MSDN:The number of times that data had to be retrieved from disk for this process because it was not found in memory. This value is accumulated from the time the process is started.
Page faults/sec is the rate of page faults over time.

Page faults: it can't explain any problem, but it proves that the memory reads and writes frequently

A memory page fault occurs when requested data is not found in memory. The system generates a fault, which normally indicates that the system looks for data in the paging file. In this circumstance, however, the missing data is identified as being located within an area of memory that cannot be paged out to disk. The system faults, but cannot find, the data and is unable to recover. Faulty hardware, a buggy system service, antivirus software, and a corrupted NTFS volume can all generate this type of error.

A memory page error occurs when the requested data cannot be found in memory. The system generates a failure, usually indicating that the system is looking for data in the paging file. However, in this case, the lost data is identified as located in the memory area that cannot be paged to disk. The system fails, but the data cannot be found and cannot be recovered. Faulty hardware, defective System services, anti-virus software, and corrupted NTFS volumes can all produce such errors.

performance optimization

Initial scheme: we started drawing with WPF's built-in Polyline.

After viewing MSDN, it is found that Polyline is redundant, and image rendering in WPF is provided by Visual class.

WPF main types of responsibilities

Visual: Provides rendering support in WPF, which includes hit testing, coordinate transformation, and bounding box calculations.

UIElement: Based on Visual, it mainly adds routing events that can respond to user input (including controlling the sending location of input through processing event routing or command routing) and trigger routing through element tree.

You can check MSDN for more details, so I won't list them one by one.

In the case of drawing physiological signals, only the graphics can be rendered to the container in real time, and only the functions provided by Visual type are required.

Improved scheme

It is found that the DrawingVisual type is more lightweight and has no redundant attributes inherited by the Polyline type.
Optimize Demo:

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;

namespace MVVMDemoTest.DrawVisual
{
    // Create a host visual derived from the FrameworkElement class.
    // This class provides layout, event handling, and container support for
    // the child visual objects.
    public class MyVisualHost : FrameworkElement
    {
        // Create a collection of child visual objects.
        private VisualCollection _children;

        public MyVisualHost()
        {
            _children = new VisualCollection(this);
            _children.Add(CreateDrawingVisualRectangle());
            //_children.Add(CreateDrawingVisualText());
            //_children.Add(CreateDrawingVisualEllipses());
            // Add the event handler for MouseLeftButtonUp.
            this.MouseLeftButtonUp += new System.Windows.Input.MouseButtonEventHandler(MyVisualHost_MouseLeftButtonUp);
        }

        // Create a DrawingVisual that contains a rectangle.
        private DrawingVisual CreateDrawingVisualRectangle()
        {
            DrawingVisual drawingVisual = new DrawingVisual();

            // Retrieve the DrawingContext in order to create new drawing content.
            DrawingContext drawingContext = drawingVisual.RenderOpen();

            // Create a rectangle and draw it in the DrawingContext.
            Rect rect = new Rect(new System.Windows.Point(160, 100), new System.Windows.Size(320, 80));
            drawingContext.DrawLine(new Pen(Brushes.Yellow, 2), new Point(100,100),new Point(300,100) );
            drawingContext.DrawRectangle(Brushes.LightBlue, (System.Windows.Media.Pen)null, rect);
            drawingContext.DrawText(new FormattedText(
                "this is a test",
                CultureInfo.GetCultureInfo("en-us"),
                FlowDirection.LeftToRight,
                new Typeface("Verdana"),
                32,
                Brushes.Black),new Point(200,200));
            var StreamGeometry = new StreamGeometry();
            StreamGeometry.Clear();
            var points = new List<Point>()
            {
                new Point(20,20),
                new Point(40,3),
                new Point(60,200),
                new Point(0,0)
            };
            using (var ctx = StreamGeometry.Open())
            {
                ctx.BeginFigure(new Point(0,0), false, false);
                ctx.PolyLineTo(points, true, false);
            }
            drawingContext.DrawGeometry(Brushes.Yellow,new Pen(Brushes.Black,2), StreamGeometry);

            // Persist the drawing content.
            drawingContext.Close();

            return drawingVisual;
        }

        // Provide a required override for the VisualChildrenCount property.
        protected override int VisualChildrenCount
        {
            get { return _children.Count; }
        }

        // Provide a required override for the GetVisualChild method.
        protected override Visual GetVisualChild(int index)
        {
            if (index < 0 || index >= _children.Count)
            {
                throw new ArgumentOutOfRangeException();
            }

            return _children[index];
        }

        // Capture the mouse event and hit test the coordinate point value against
        // the child visual objects.
        void MyVisualHost_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            // Retrieve the coordinates of the mouse button event.
            System.Windows.Point pt = e.GetPosition((UIElement)sender);

            // Initiate the hit test by setting up a hit test result callback method.
            VisualTreeHelper.HitTest(this, null, new HitTestResultCallback(myCallback), new PointHitTestParameters(pt));
        }

        // If a child visual object is hit, toggle its opacity to visually indicate a hit.
        public HitTestResultBehavior myCallback(HitTestResult result)
        {
            if (result.VisualHit.GetType() == typeof(DrawingVisual))
            {
                if (((DrawingVisual)result.VisualHit).Opacity == 1.0)
                {
                    ((DrawingVisual)result.VisualHit).Opacity = 0.4;
                }
                else
                {
                    ((DrawingVisual)result.VisualHit).Opacity = 1.0;
                }
            }

            // Stop the hit test enumeration of objects in the visual tree.
            return HitTestResultBehavior.Stop;
        }
    }
}

summary

WPF provides rich and powerful functions, but it is known that there are some performance problems due to too many functions. Therefore, when designing and Using WPF, we must use lightweight modules as much as possible. If necessary, we can inherit them by ourselves.

Keywords: Optimize WPF microsoft

Added by coolphpdude on Thu, 09 Dec 2021 10:06:33 +0200