我在WPF动画中出现撕裂和闪烁问题。我有一个玩具应用程序来演示这些问题。该应用程序在屏幕上设置了正方形动画。方块的边缘显示撕裂,整个动画感觉不平滑。
穿孔器显示> 60fps,~10mb视频内存,0 IRT。
我在两台新的高端计算机上尝试了这一点,并且都显示了相同的不良动画(> 1gb vram,quad core等)。
答案 0 :(得分:2)
您是否完全确定您的代码正在运行硬件加速?请查看此列表:http://blogs.msdn.com/b/jgoldb/archive/2010/06/22/software-rendering-usage-in-wpf.aspx。
如果是这样 - 假设你得到了ubercool硬件 - 你可以尝试在CPU而不是GPU上运行它。您可以通过将RenderMode设置为SoftwareOnly(链接到上面的列表中的第6项)来强制执行此操作
答案 1 :(得分:1)
我向WPF团队提出了这个问题,总而言之,他们说他们认为动画平滑性有一些可以改进的故障。
他们还补充道:
我们非常努力地安排我们的场景 获得与VBlank同步的更新 非常规律,可靠的动画。任何 UI线程上的工作可能会干扰 强硬。在这个例子中,他们是 使用调度的DispatcherTimers 在UI线程上工作以创建新的 故事板,删除旧元素等。
他们还展示了动画的纯粹声明版本,对我来说它看起来更顺畅。特别感谢Dwayne Need提供此信息。
答案 2 :(得分:0)
这种撕裂的原因是必须创建UI线程的很多对象,并且所有这些调用以及将它们添加到UI容器都是通过主线程。
我甚至尝试使用Thread驱动版本而不是Timers,但这并没有改变任何东西,因为必须使用Dispatcher.Invoke创建所有FrameWorkElement对象。
故事板和beginStoryboard + EventTrigger的创建都必须在Ui线程上完成。这就是阻碍流畅性的原因。
不幸的是,这种设计无法实现无闪烁操作:/
using System;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Media.Animation;
using System.Windows.Threading;
using System.Collections.Generic;
namespace SimpleWindow
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow
{
readonly SolidColorBrush _fillBrush = new SolidColorBrush(Colors.CadetBlue);
// Timers
//private DispatcherTimer _addItemsTimer;
//private DispatcherTimer _removeItemsTimer;
private Thread _addItemsTimer;
private Thread _removeItemsTimer;
private volatile bool formClosing = false;
private readonly TimeSpan _addInterval = TimeSpan.FromSeconds(0.21);
private readonly TimeSpan _removeInterval = TimeSpan.FromSeconds(1);
public MainWindow()
{
InitializeComponent();
Closing += MainWindow_Closing;
Loaded += OnLoaded;
}
void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
formClosing = true;
//_addItemsTimer.Join();
//_removeItemsTimer.Join();
}
private void OnLoaded(object o, RoutedEventArgs args)
{
_addItemsTimer = new Thread((ThreadStart)delegate() {
while (!formClosing)
{
Thread.Sleep(_addInterval);
AddItems();
}
});
_removeItemsTimer = new Thread((ThreadStart)delegate()
{
while (!formClosing)
{
Thread.Sleep(_removeInterval);
RemoveOffScreenItems();
}
});
_addItemsTimer.SetApartmentState(ApartmentState.STA);
_addItemsTimer.Start();
_removeItemsTimer.SetApartmentState(ApartmentState.STA);
_removeItemsTimer.Start();
WindowState = WindowState.Maximized;
}
//private static DispatcherTimer CreateTimer(TimeSpan interval, EventHandler handler)
//{
// var timer = new DispatcherTimer();
// timer.Interval = interval;
// timer.Tick += handler;
// timer.Start();
// return timer;
//}
// Timer callback
private readonly Rectangle _canvasChildrenLock = new Rectangle();
public void AddItems()
{
lock (_canvasChildrenLock)
{
Dispatcher.Invoke((Action)delegate() {
var rect = CreateRectangle();
rect.Triggers.Add(BeginStoryboardEventTrigger(CreateStoryboard()));
MainCanvas.Children.Add(rect);
});
}
}
private static EventTrigger BeginStoryboardEventTrigger(Storyboard storyboard)
{
var beginStoryboard = new BeginStoryboard {Storyboard = storyboard};
var eventTrigger = new EventTrigger(LoadedEvent);
eventTrigger.Actions.Add(beginStoryboard);
return eventTrigger;
}
// Timer callback
public void RemoveOffScreenItems()
{
lock (_canvasChildrenLock)
{
var itemsToRemove = (List<FrameworkElement>)Dispatcher.Invoke((Func<List<FrameworkElement>>)delegate()
{
return (from FrameworkElement element in MainCanvas.Children
let topLeft = new Point((double)element.GetValue(Canvas.LeftProperty), (double)element.GetValue(Canvas.TopProperty))
where IsOffScreen(topLeft)
select element).ToList();
});
if (itemsToRemove == null) return;
foreach (FrameworkElement element in itemsToRemove)
{
Dispatcher.Invoke((Action)delegate() { MainCanvas.Children.Remove(element); });
}
}
}
private bool IsOffScreen(Point pt)
{
return
pt.X > MainCanvas.ActualWidth ||
pt.Y < 0 || pt.Y > MainCanvas.ActualHeight;
}
private Rectangle CreateRectangle()
{
var rect = new Rectangle
{
Width = 100,
Height = 100,
Fill = _fillBrush
};
return rect;
}
private const double OffScreenPosition = 100;
private const double AnimationDuration = 2;
private Storyboard CreateStoryboard()
{
var xAnimation = CreateDoubleAnimationForTranslation();
xAnimation.From = -OffScreenPosition;
xAnimation.To = MainCanvas.ActualWidth + OffScreenPosition;
Storyboard.SetTargetProperty(xAnimation, new PropertyPath(Canvas.LeftProperty));
var yAnimation = CreateDoubleAnimationForTranslation();
yAnimation.From = MainCanvas.ActualHeight * Rand.NextDouble();
yAnimation.To = MainCanvas.ActualHeight * Rand.NextDouble();
Storyboard.SetTargetProperty(yAnimation, new PropertyPath(Canvas.TopProperty));
var storyboard = new Storyboard();
storyboard.Children.Add(xAnimation);
storyboard.Children.Add(yAnimation);
storyboard.Freeze();
return storyboard;
}
private DoubleAnimation CreateDoubleAnimationForTranslation()
{
var animation = (DoubleAnimation)Dispatcher.Invoke((Func<DoubleAnimation>)delegate()
{
return new DoubleAnimation
{
Duration = TimeSpan.FromSeconds(AnimationDuration),
EasingFunction = new ShiftedQuadraticEase() { EasingMode = EasingMode.EaseInOut }
};
});
return animation;
}
private static readonly Random Rand = new Random(DateTime.Now.Millisecond);
}
}