dotnet / dotnet/wpf

CompositionTarget.Rendering event slows down when there are no UI updates

Open
#1,908 15 comments 2 reactions 0 assignees View on GitHub
Enhancement Requested
Dominant language
C#
Stars
7.7k
Forks
1.3k
Avg merge
1d 11h
Merged PRs (30d)
61

Description

* .NET Core Version:3.0.100-rc1-014190
* Windows version: 1903
* Does the bug reproduce also in WPF for .NET Framework 4.8?: Yes
* Is this bug related specifically to tooling in Visual Studio (e.g. XAML Designer, Code editing, etc...)? No



**Problem description:**
When relying on CompositionTarget.Rendering, I've noticed that the event throttles down to around 50fps when there are no UI updates. As soon as UI updates are occuring, the event goes back up to firing at 60fps.

This becomes problematic when you're doing performance metrics and want to ensure performance stays a consistent 60fps, but when things stops moving, it shows in the performance numbers as really bad performance.

Notice here where no UI updates are done, the framerate is around 50fps. Once animation is started, the framerate goes up to 60:
![Untitled Project](https://user-images.githubusercontent.com/1378165/65013271-61135e80-d8cf-11e9-9d16-ef5b00b85b30.gif)

**Actual behavior:**
Clock timer slows down.

**Expected behavior:**
Event sticks to 60fps

**Minimal repro:**
Use the toggle button to turn the animation on and off. Monitor the output window for framerate updates every 0.5 seconds.

`MainWindow.xaml`:
```xml
















```

`MainWindow.xaml.cs`:
```cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace FrameRateTest
{
///
/// Interaction logic for MainWindow.xaml
///
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
CompositionTarget.Rendering += OnRender;
stopwatch.Start();
}

Stopwatch stopwatch = new Stopwatch();
TimeSpan lastFrame = TimeSpan.Zero;
MovingAverage avr = new MovingAverage(60);
int frame = 0;
public void OnRender(object sender, EventArgs e)
{
frame++;
var ts = stopwatch.Elapsed;
avr.AddSample((ts - lastFrame).TotalMilliseconds);
lastFrame = ts;
if (frame % 30 == 0)
{
Debug.WriteLine((1000 / avr.Average).ToString("0.000"));
}
}
bool isAnimating;
public void ToggleAnimation_Click(object sender, RoutedEventArgs e)
{
var sb = (Storyboard)Resources["sb"];
if (isAnimating)
sb.Begin();
else
sb.Stop();
isAnimating = !isAnimating;

}
}

public class MovingAverage
{
private Queue _samples;
private int _windowSize;
private double _sum;

public double Average
{
get
{
int cnt = _samples.Count;
return cnt != 0 ? _sum / cnt : double.NaN;
}
}

public MovingAverage(int windowSize)
{
_windowSize = windowSize;
_samples = new Queue(windowSize);
}

public void AddSample(double newSample)
{
if (!double.IsNaN(newSample))
{
if (_samples.Count == _windowSize)
{
_sum -= _samples.Dequeue();
}
_samples.Enqueue(newSample);
_sum += newSample;
}
}
}
}
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.