为什么WPF中的帧速率不规则,并且不限于监视器刷新?
我在一个简单的WPF动画中测量帧间的时间。Perforator说该应用程序的执行速度约为60fps,因此我预计帧间时间约为16.6ms,偏差很小为什么WPF中的帧速率不规则,并且不限于监视器刷新?,wpf,animation,Wpf,Animation,我在一个简单的WPF动画中测量帧间的时间。Perforator说该应用程序的执行速度约为60fps,因此我预计帧间时间约为16.6ms,偏差很小 public MainWindow() { ... CompositionTarget.Rendering += Rendering; } List<long> FrameDurations = new List<long>(); private long Pre
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;
}
public主窗口()
{
...
CompositionTarget.Rendering+=呈现;
}
List FrameDurations=新列表();
private long PreviousFrameTime=0;
私有void呈现(对象o、事件args args)
{
添加(DateTime.Now.Ticks-PreviousFrameTime);
PreviousFrameTime=DateTime.Now.Ticks;
}
有两件事让我吃惊:
- 两帧之间的时间相当不规则
- 帧间时间约为8毫秒。我原以为显示器的刷新率会设置帧间时间的下限(即每帧之间的时间为60Hz=16.6ms,任何更快的都是毫无意义的)
X帧计数 可能的混杂因素
- 定时器误差
- 如果CompositionTarget.Rendering实际上与单个帧的绘图不相关
- 我不知道为什么DateTime.Now和RenderingEventArgs会产生如此不同的数据
- 假设RenderingEventArgs生成正确的时间,这些时间不是预期的16.6ms仍然有点令人不安
如果显示器每16.6ms更新一次,WPF每14.9ms更新一次,我们可以预期会出现导致撕裂的竞争条件。也就是说,当显示器试图读取图像时,大约每10帧WPF将尝试写入其图像。WPF不是设计为恒定帧速率渲染系统的。当屏幕中的元素标记为已更改时,WPF将渲染到屏幕。渲染系统作为消息循环运行,因此无法确保以特定的间隔渲染帧。我向WPF团队提出了这个问题,下面是我得到的答复摘要: 从UI计算帧率 这条线很难。WPF解耦 渲染线程中的UI线程。 UI线程将呈现:
- 每当有东西被标记为脏的时候,我们就把水排干进行渲染 优先。这种情况可能更经常发生 比刷新率高
- 如果动画处于挂起状态(或者如果有人钩住了 CompositionTarget.Rendering事件)我们 之后将在UI线程上呈现 渲染线程中的每个存在。 这包括提前时间安排 树,以便动画计算新的 价值观
特别感谢德韦恩需要向我解释这一点。首先-”克里斯托弗·本纳奇的回答有很好的解释,并提供了解决方案的提示: 仅在报告的帧时间更改时执行“每帧”工作 这有点困难,因为RenderingEventArgs作为普通EventArgs隐藏,必须进行转换 为了使这一点更容易,可以在“EVAN的代码破烂”中找到一个方便的解决方案 我把他的代码修改了一下。现在只需将我的截图添加到您的项目中,使用CompositionTargetEx您是否使用了CompositionTarget,您很好:)
公共静态类CompositionTargetEx{
私有静态时间跨度_last=TimeSpan.0;
私有静态事件事件处理程序\u帧更新;
公共静态事件事件处理程序呈现{
添加{
如果(_frameupdateing==null)
CompositionTarget.Rendering+=CompositionTarget\u Rendering;
_帧更新+=值;
}
删除{
_帧更新-=值;
如果(_frameupdateing==null)
CompositionTarget.Rendering-=CompositionTarget\u Rendering;
}
}
静态void CompositionTarget_呈现(对象发送方,事件参数e){
RenderingEventArgs args=(RenderingEventArgs)e;
如果(args.RenderingTime==\u last)
返回;
_last=args.RenderingTime;\u帧更新(发送方,args);
}
}
Stopwatch类是避免计时器不准确的方法这“可能”很有用:-我不确定这个问题有多重要,但我认为它今天仍然存在。@Tristan@Martin时间测量根本不需要!你可以@Patrick-我怀疑Roman有什么发现。我希望他的文章包括数据。没有数据,他的文章读起来像是一种观点(受过教育的观点,但仍然不是‘事实’)。@Markus-当然!我觉得自己很笨。谢谢:)你的回答很有道理,而且与我所看到的一切都是一致的。您能否提供任何数据或方法来备份您的断言?(同时,感谢您的时间。我非常感谢)您可以在WPF A上查看此视频
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);
}
}