WPF 动画存在撕裂和闪烁问题

13

我在WPF动画中遇到了撕裂和闪烁的问题。我有一个演示这些问题的玩具应用程序。该应用程序将正方形动画移到屏幕上。正方形的边缘会出现撕裂,整个动画也不流畅。

Perforator显示帧率超过60fps、视频内存约10mb、0个IRTs。

我已经在两台新的高端计算机上尝试过,并且两者都显示出相同的差劲动画(超过1gb vram,四核等)。

SimpleWindow.zip


你是在运行Win7还是XP? - evanb
你正在使用什么类型的动画?同时有多少个正方形在运动?我也遇到了一些动画性能的问题...这可能有点棘手 :) - harri
只需一个正方形就可以看到闪烁和撕裂。 正方形使用故事板进行动画处理。 - Tristan
WPF在动画方面存在一些问题。参见https://dev59.com/lk7Sa4cB1Zd3GeqP2VDv - Goran
我想知道你是否在使用“tearing”和“flicker”这两个词时,它们的意思和通常情况下一样。我运行了你的示例,结果看起来很糟糕,但我没有看到“tearing”- 为了确认我们理解一致,请您澄清一下您认为“tearing”是什么意思? - Ian Griffiths
显示剩余4条评论
3个回答

2

Peforator在窗口中没有任何软件渲染。我尝试强制使用软件渲染,但性能更差。 - Tristan

1
我向WPF团队提出了这个问题,他们总结说他们认为动画平滑度存在一些小问题,可以改进。
他们还补充道:
“我们非常努力地将场景更新的时间安排在VBlank的同步中,以获得非常规律、可靠的动画效果。但是UI线程上的任何工作都可能会干扰它。在这个例子中,他们使用DispatcherTimers来调度工作到UI线程上,以创建新的storyboards,删除旧元素等。”
他们还演示了一个纯声明式版本的动画,我觉得它更加流畅。特别感谢Dwayne Need提供这些信息。

0

这个问题的原因在于创建了许多必须由 UI 线程拥有的对象,所有这些调用以及将它们添加到 UI 容器中都通过主线程进行。

我甚至尝试过使用线程驱动版本代替计时器,但是由于所有 FrameWorkElement 对象都必须使用 Dispatcher.Invoke 创建,所以这并没有改变任何事情。

故事板的创建、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);
    }
}

感谢您的输入,马里诺。如果您的理论是正确的,那么动画单个对象应该是不会出现闪烁和撕裂的。也就是说,如果创建矩形和故事板是问题的根源,那么删除这些创建应该可以解决问题。但事实并非如此:一个单独的矩形仍然会闪烁和撕裂。 - Tristan
是的,那是我的理论,但我可能错了。你能试着不要在画布上添加或删除项目吗?这可能会阻塞用户界面。一种设计变更是在XAML或表单加载时将所有可能的矩形添加到画布中,并通过将它们向右平移并在屏幕外时将它们放回左侧来对它们进行动画处理。 - Marino Šimić
1
我通过动画移除了一个矩形,但仍然出现闪烁和撕裂。 - Tristan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接