25

I'm measuring the time between frames in a simple WPF animation. Perforator says the app performs at ~60fps, so I expected the time between frames to be ~16.6ms with little deviation.

    public MainWindow()
    {
    ...
        CompositionTarget.Rendering += Rendering;
    }

    List<long> FrameDurations = new List<long>();
    private long PreviousFrameTime = 0;
    private void Rendering(object o, EventArgs args)
    {
        FrameDurations.Add(DateTime.Now.Ticks - PreviousFrameTime);
        PreviousFrameTime = DateTime.Now.Ticks;
    }

Two things surprised me:

  • Time between frames is fairly irregular
  • Time between frames is ~8ms. I had expected that the monitor's refresh rate would set a lower bound on time between frames (ie. 60Hz = 16.6ms between each frame, and anything faster is pointless).

DefaultFrameRate

Y - Time between frames in ticks (10,000 ticks = 1ms)
X - Frame count

Possible confounding factors

  • Timer inaccuracy
  • If CompositionTarget.Rendering doesn't actually correlate to the drawing of a single frame

The project I'm using: SimpleWindow.zip

===Edit

Markus pointed out I could be using RenderingEventArgs.RenderingTime.Ticks instead of DateTime.Now.Ticks. I repeated the run and got very different results. The only difference is timing method:

DateTime.Now.Ticks

DefaultFrameRate

RenderingEventArgs.RenderingTime.Ticks

enter image description here

Data from RenderingEventArgs produced data much closer the expected 16.6ms/frame, and it is consistent.

  • I'm not sure why DateTime.Now and RenderingEventArgs would produce such very different data.
  • Assuming RenderingEventArgs is producing correct times, it's still a bit disconcerting that those times are not the expected 16.6ms.

If the display is updating every 16.6ms and WPF is updating every 14.9ms, we can expect a race condition that would result in tearing. That is to say, roughly every 10th frame WPF will be trying to write its image while the display is trying to read the image.

Tristan
  • 1,466
  • 1
  • 16
  • 24
  • The Stopwatch class is the way to go to avoid timer inaccuracy – Martin Booth Apr 28 '11 at 01:12
  • this 'might' be useful: http://rhnatiuk.wordpress.com/2008/12/21/wpf-video-playback-problems/ - I am not sure how relevant this issue is but I think it still exists today. – Patrick Klug Apr 28 '11 at 07:03
  • 3
    @Tristan @Martin time measuring isn't needed at all! you can [cast EventArgs to RenderingEventArgs to get the RenderTime](http://msdn.microsoft.com/en-us/library/system.windows.media.compositiontarget.rendering(VS.95).aspx) – Markus Hütter Apr 28 '11 at 10:32
  • @Patrick - I suspect Roman is on to something. I wish his article included data. Without data, his article reads like an opinion (educated opinion, but still not 'fact'). – Tristan Apr 28 '11 at 16:28
  • @Markus - Of course! I feel dumb. Thank you :) – Tristan Apr 28 '11 at 16:28
  • If one generates a video frame in WPF and the display output device is in the middle of a frame, will WPF delay the application code until the start of the next frame and then display the data, or will WPF immediately make a snapshot of the display data and return, leaving a driver task to copy the data to the display buffer at the appropriate time? My hunch would be that it's doing the latter, which would mean that the times at which renders complete would be random, even if they were being displayed synchronized with physical output. – supercat Apr 28 '11 at 19:13
  • @SuperCat - any idea how I can test your theory? – Tristan Apr 28 '11 at 21:08
  • @Tristan: Check the results against a StopWatch object. The time returned from DateTime.Now may not be precise; a StopWatch object used on a single CPU core should be much better. Note that on some multi-core machines, a StopWatch object created on one core and read on another may be off by a certain amount which will usually be constant for a given pair of cores unless the machine is restarted. – supercat Apr 28 '11 at 22:29

3 Answers3

28

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.

Christopher Bennage
  • 2,578
  • 2
  • 22
  • 32
  • 7
    Awesome. Where can I learn more stuff like this? Is there a good book? I've been having trouble finding 'under-the-hood' WPF information. One problem is that because WPF is so easy (relative to GDI+ etc), there are a lot of people writing about it who don't really understand what they are doing, but still write about it. There's seems to be a fair bit of poor quality information to filter out. – Tristan Jun 06 '11 at 18:43
10

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); 
    } 
}
thewhiteambit
  • 1,365
  • 16
  • 31
4

WPF is not designed to be a constant frame rate rendering system. WPF renders to screen when the elements in the screen are marked as changed. The rendering system runs as a message loop so there is no way to ensure that a frame will be rendered at specific intervals.

Vicro
  • 565
  • 3
  • 14
  • 1
    Your answer makes sense, and is consistent with everything I'm seeing. Can you provide any data or means to backup your assertion? (Also, thanks for your time. I do appreciate it) – Tristan May 11 '11 at 18:05
  • 2
    You can check this video on the WPF Architecture in Channel 9 from one of the designers of WPF (They talk a lot about Avalon, it was the code name of WPF before its release)[link](http://channel9.msdn.com/Shows/Going+Deep/Greg-Schechter-Windows-Presentation-FoundationWPF-Architecture) – Vicro May 12 '11 at 02:38
  • 2
    Also I found some info on [MSDN](http://msdn.microsoft.com/en-us/library/ms748373.aspx#visual_rendering_behavior) about the Rendering Behavior. – Vicro May 12 '11 at 02:40