Why is Frame Rate in WPF Irregular and Not Limited To Monitor Refresh?

Solution 1:

I raised this question with the WPF team and here is a summary of the response I was given:

Calculating the framerate from the UI thread is difficult. WPF decouples the UI thread from the render thread. The UI thread will render:

  • Whenever something is marked as dirty and we drain down to Render priority. This can happen more often than the refresh rate.

  • If an animation is pending (or if someone hooked the CompositionTarget.Rendering event) we will render on the UI thread after every present from the render thread. This involves advancing the timing tree so animations calculate their new values.

Because of this, the CompositionTarget.Rendering event can be raised multiple times per “frame”. We report the intended “frame time” in the RenderingEventArgs, and applications should only do “per-frame” work when the reported frame time changes.

Note that the UI thread is doing many things, so it is not reliable to assume the CompositionTarget.Rendering event handler runs at a reliable cadence. The model we use (decoupling the two threads) means that the UI thread can be a little behind, since it is calculating animations for a future frame time.

Special thanks to Dwayne Need for explaining this to me.

Solution 2:

First - 'Christopher Bennage's-Answer has a good explanation and provides a hint to a solution:

"Only do “per-frame” work when the reported frame time changes"

This is a little bit hard, since the RenderingEventArgs are hidden as a normal EventArgs and a cast has to be done.

To make this a little bit easyer, a convinient solution can be found in "EVAN'S CODE CLUNKERS" http://evanl.wordpress.com/2009/12/06/efficient-optimal-per-frame-eventing-in-wpf/

I took his code and modified it a bit. Now just take my snipped, add the class to your Project, use CompositionTargetEx were you used CompositionTarget and you are fine :)

public static class CompositionTargetEx { 
    private static TimeSpan _last = TimeSpan.Zero; 
    private static event EventHandler<RenderingEventArgs> _FrameUpdating; 
    public static event EventHandler<RenderingEventArgs> Rendering { 
        add { 
            if (_FrameUpdating == null)                 
                CompositionTarget.Rendering += CompositionTarget_Rendering;
            _FrameUpdating += value; 
        } 
        remove { 
            _FrameUpdating -= value; 
            if (_FrameUpdating == null)                
                CompositionTarget.Rendering -= CompositionTarget_Rendering; 
        } 
    } 
    static void CompositionTarget_Rendering(object sender, EventArgs e) { 
        RenderingEventArgs args = (RenderingEventArgs)e;
        if (args.RenderingTime == _last) 
            return;
        _last = args.RenderingTime; _FrameUpdating(sender, args); 
    } 
}