Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF PathGeometry update is _SLOW_

In a WPF UI I have nodes connected by bezier paths, like so:

It might be... atomic http://nv3wrg.blu.livefilestore.com/y1pIGBd33lCC6lF-9H0MqgnL40BdNEoEemZDENzgpEI1IL2j4B-qb3qS3WlxMSys28IjqNngR7mdfvQBnPzerf4cFJQj9VqHBh4/acurve.png?psid=1

When the user drags a node around, the connecting paths need to be updated in real-time. However, I've noticed some slowdown (especially if one node is connected to many others, or multiple nodes are being dragged at once). I profiled it, and the main problem appears to be here:

Proof I actually used a profiler, so please don't be all like "OMG, premature opiumzation; you are DEMON!!" http://nv3wrg.blu.livefilestore.com/y1pjRfQYuN57yei5qdUxW4Dlh4vVCzPy8TcfEzlw_8cUicfOR6BwHCTntcQbQUspRAgBdKcItC0ZcEJbIWMKaYrCtDMOtCBKB4g/profile.png?psid=1

This is the function that is called each time either the source or destination property is changed. The geometry that makes up the path seems to be being regenerated internally each time any of the control points change. Perhaps if there were a way to prevent the geometry from being regenerated until after all the relevant dependency properties have been set?

EDIT: Mart's solution to use StreamGeometry sped it up exponentially; the function is nowhere close to a bottleneck. A little Reflecting suggests that PathGeometry uses StreamGeometry internally, and every time any of the dependency properties are changed, the StreamGeometry is recalculated. So this way just cuts out the middleman. The final result is:

private void onRouteChanged()
{
    Point src = Source;
    Point dst = Destination;
    if (!src.X.isValid() || !src.Y.isValid() || !dst.X.isValid() || !dst.Y.isValid())
    {
        _shouldDraw = false;
        return;
    }

    /*
        * The control points are all laid out along midpoint lines, something like this:
        * 
        *   -------------------------------- 
        *  |          |          |          |
        *  |   SRC    |    CP1   |          |
        *  |          |          |          |
        *   -------------------------------- 
        *  |          |          |          |
        *  |          |    MID   |          |
        *  |          |          |          |
        *   ------------------------------- 
        *  |          |          |          |
        *  |          |    CP2   |    DST   |
        *  |          |          |          |
        *   -------------------------------- 
        *   
        * This causes it to be horizontal at the endpoints and vertical
        * at the midpoint.
        */

    double mx = (src.X + dst.X) / 2;
    double my = (src.Y + dst.Y) / 2;
    Point mid = new Point(mx, my);
    Point cp1 = new Point(mx, src.Y);
    Point cp2 = new Point(mx, dst.Y);

    _geometry.Clear();
    _shouldDraw = true;
    using(StreamGeometryContext ctx = _geometry.Open())
    {
        ctx.BeginFigure(src, false, false);
        ctx.QuadraticBezierTo(cp1, mid, true, false);
        ctx.QuadraticBezierTo(cp2, dst, true, false);
    }
}

The full source code of the project is available at http://zeal.codeplex.com for the curious.

like image 438
Robert Fraser Avatar asked Jul 07 '10 05:07

Robert Fraser


1 Answers

1- I would try to use StreamGeometry:

        StreamGeometry streamGeo = new StreamGeometry();
        Stopwatch sw = new Stopwatch();
        sw.Start();
        for (int i = 0; i < 10000; i++)
        {
            streamGeo.Clear();
            var ctx = streamGeo.Open();
            ctx.BeginFigure(new Point(0, 0), false, false);
            ctx.QuadraticBezierTo(new Point(10, 10), new Point(10, i), true, true);
            ctx.Close();
        }
        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds); // For 10k it took 30 ms

It looks much faster than PathGeometry+PathFigure.

When you set the Point for the QuadraticBezierSegment it recalculates everything. That's why it is slow. And more slow when it is already added to a geometry.

2- Try to use only 1 frameworkelement for all of your curves. Check this: Writing More Efficient ItemsControls

like image 200
Mart Avatar answered Oct 07 '22 14:10

Mart