为什么WPF中的帧率不规则且不限于监视刷新?

Tri*_*tan 20 wpf animation

我在一个简单的WPF动画中测量帧之间的时间.穿孔器说该应用程序的执行速度约为60fps,因此我预计帧之间的时间约为16.6ms,偏差很小.

    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;
    }
Run Code Online (Sandbox Code Playgroud)

有两件事让我惊讶:

  • 帧之间的时间相当不规则
  • 帧之间的时间约为8毫秒.我曾预料到显示器的刷新率会设置帧之间的时间下限(即每帧之间60Hz = 16.6ms,任何更快的都没有意义).

DefaultFrameRate

Y - 以滴答为单位的帧之间的时间(10,000滴答= 1ms)
X - 帧计数

可能的混杂因素

  • 定时器不准确
  • 如果CompositionTarget.Rendering实际上与单个帧的绘制无关

我正在使用的项目:SimpleWindow.zip

===编辑

Markus指出我可以使用RenderingEventArgs.RenderingTime.Ticks而不是DateTime.Now.Ticks.我重复了这次跑步并得到了截然不同的结果.唯一的区别是计时方法:

DateTime.Now.Ticks

DefaultFrameRate

RenderingEventArgs.RenderingTime.Ticks

在此输入图像描述

来自RenderingEventArgs的数据产生的数据更接近预期的16.6ms /帧,并且它是一致的.

  • 我不确定为什么DateTime.Now和RenderingEventArgs会产生如此不同的数据.
  • 假设RenderingEventArgs正在产生正确的时间,那么这些时间不是预期的16.6ms仍然有点令人不安.

如果显示器每16.6ms更新一次并且WPF每14.9ms更新一次,我们可以预期会出现导致撕裂的竞争条件.也就是说,当显示器试图读取图像时,大约每第10帧WPF将尝试写入其图像.

Chr*_*age 20

我向WPF团队提出了这个问题,这里是我给出的回复的摘要:

从UI线程计算帧速率很困难.WPF将UI线程与渲染线程分离.UI线程将呈现:

  • 只要某些东西被标记为脏,我们就会降低渲染优先级.这可能比刷新率更频繁地发生.

  • 如果动画处于挂起状态(或者有人挂钩了CompositionTarget.Rendering事件),我们将在渲染线程中的每个存在之后在UI线程上进行渲染.这涉及推进时序树,以便动画计算其新值.

因此,每个"帧"可以多次引发CompositionTarget.Rendering事件.我们在RenderingEventArgs中报告预期的"帧时间",并且应用程序应该仅在报告的帧时间发生变化时执行"每帧"工作.

请注意,UI线程正在做很多事情,因此假设CompositionTarget.Rendering事件处理程序以可靠的节奏运行是不可靠的.我们使用的模型(解耦两个线程)意味着UI线程可能稍微落后,因为它正在计算未来帧时间的动画.

特别感谢Dwayne Need向我解释这个问题.

  • 真棒.我在哪里可以学到更多这样的东西?有一本好书吗?我一直无法找到'引擎盖'的WPF信息.一个问题是,因为WPF非常容易(相对于GDI +等),有很多人在撰写关于它的人并不真正理解他们在做什么,但仍然写下来.过滤掉似乎有一些质量差的信息. (5认同)

the*_*bit 8

首先 - 'Christopher Bennage的答案有一个很好的解释并提供解决方案的提示:

"当报告的帧时间发生变化时,只执行"每帧"工作"

这有点难,因为RenderingEventArgs被隐藏为正常的EventArgs并且必须完成转换.

为了使这更容易一些,可以在"EVAN'S CODE CLUNKERS"中找到一个方便的解决方案 http://evanl.wordpress.com/2009/12/06/efficient-optimal-per-frame-eventing-in-wpf/

我拿了他的代码并修改了一下.现在只需要我剪切,将类添加到您的项目中,使用CompositionTargetEx您使用的是CompositionTarget,你很好:)

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); 
    } 
}
Run Code Online (Sandbox Code Playgroud)