WPF 动画存在撕裂和闪烁

发布于 2024-11-03 15:40:37 字数 333 浏览 4 评论 0原文

我在 WPF 动画中遇到撕裂和闪烁问题。我有一个玩具应用程序可以演示这些问题。该应用程序在屏幕上呈现方块动画。方块的边缘出现撕裂现象,整个动画感觉不平滑。

Perforator 显示 >60fps、~10mb 视频内存、0 IRT。

我已经在两台新的高端计算机上尝试过此操作,并且都显示相同的糟糕动画(> 1gb vram、四核等)。

SimpleWindow.zip

I'm having trouble with tearing and flickering in WPF animations. I have a toy app that demonstrates the problems. The app animates squares across the screen. The edges of the squares show tearing and the animation as a whole does not feel smooth.

Perforator shows >60fps, ~10mb video memory, 0 IRTs.

I have tried this on two new high end computers and both show the same poor animation (>1gb vram, quad core etc).

SimpleWindow.zip

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

冬天的雪花 2024-11-10 15:40:37

您绝对确定您的代码正在运行硬件加速吗?请查看此列表:http://blogs.msdn.com/b/jgoldb/archive/2010/06/22/software-rendering-usage-in-wpf.aspx

如果是这样 - 考虑到您拥有超酷的硬件 - 您可以尝试在 CPU 而不是 GPU 上运行它。您可以通过将 RenderMode 设置为 SoftwareOnly 来强制执行此操作(上面链接的列表中的第 6 项)

Are you absolutely sure your Code is running hardware accelerated? Please look into this list : http://blogs.msdn.com/b/jgoldb/archive/2010/06/22/software-rendering-usage-in-wpf.aspx.

If so - given that ubercool hardware you got - you could try running it on CPU instead of GPU. You can enforce that by setting the RenderMode to SoftwareOnly (Item 6 in the list linked to above)

雨轻弹 2024-11-10 15:40:37

我向 WPF 团队提出了这个问题,总而言之,他们说他们认为动画平滑度存在一些可以改进的问题。

他们还补充道:

我们非常努力地安排我们的场景
与 VBlank 同步更新以获得
非常规律、可靠的动画。任何
UI 线程上的工作可能会干扰
艰难的。在这个例子中,他们是
使用 DispatcherTimers 进行调度
在 UI 线程上工作以创建新的
故事板、删除旧元素等。

的动画,对我来说看起来更流畅。特别感谢 Dwayne Need 提供此信息。

I raised this question with WPF team and in summary they said that they believe that there are some glitches with animation smoothness that could be improved.

They also added:

We try very hard to schedule our scene
updates in sync with the VBlank to get
very regular, reliable animations. Any
work on the UI thread can interfere
tough. In this example, they are
using DispatcherTimers which schedule
work onto the UI thread to create new
storyboards, remove old elements, etc.

They also demonstrated a purely declarative version of the animations, and it appeared smoother to me. Special thanks to Dwayne Need for this information.

蔚蓝源自深海 2024-11-10 15:40:37

造成撕裂的原因是创建了许多必须由 UI 线程拥有的对象,并且所有这些调用以及将它们添加到 UI 容器都将通过主线程。

我什至尝试制作线程驱动版本而不是计时器,但这并没有改变任何内容,因为所有 FrameWorkElement 对象都必须使用 Dispatcher.Invoke 创建。

Storyboard 和 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);
    }
}

The reason why this has tearing is that a lot of object that must be UI thread owned are created and all those calls plus adding them to UI container is going through the main thread.

I even tried to make a Thread driven version instead of Timers, but that did not change anything since all FrameWorkElement objects must be created with Dispatcher.Invoke.

The creation of storyboards and beginStoryboard + EventTrigger all must be done on the Ui thread. This is what is blocking the fluency.

Unfortunately with this design there is no way of achieving flickerfree operation :/

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);
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文