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:
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
:
<Window x:Class="FrameRateTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:FrameRateTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<Storyboard x:Key="sb" RepeatBehavior="Forever">
<DoubleAnimation Storyboard.TargetName="tt"
Storyboard.TargetProperty="X" From="-250" To="250"
Duration="0:0:5" BeginTime="0:0:0"/>
<DoubleAnimation Storyboard.TargetName="tt"
Storyboard.TargetProperty="X" From="250" To="-250"
Duration="0:0:5" BeginTime="0:0:5"/>
</Storyboard>
</Window.Resources>
<Grid>
<Button HorizontalAlignment="Left" VerticalAlignment="Top" Content="Toggle Animation" Click="ToggleAnimation_Click" />
<Ellipse Width="30" Height="30" Fill="Red" HorizontalAlignment="Center" VerticalAlignment="Center">
<Ellipse.RenderTransform>
<TranslateTransform x:Name="tt" />
</Ellipse.RenderTransform>
</Ellipse>
</Grid>
</Window>
MainWindow.xaml.cs
:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace FrameRateTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
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<double> _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<double>(windowSize);
}
public void AddSample(double newSample)
{
if (!double.IsNaN(newSample))
{
if (_samples.Count == _windowSize)
{
_sum -= _samples.Dequeue();
}
_samples.Enqueue(newSample);
_sum += newSample;
}
}
}
}