WPF动画有撕裂和闪烁

Posted

技术标签:

【中文标题】WPF动画有撕裂和闪烁【英文标题】:WPF Animation has Tearing and Flicker 【发布时间】:2011-08-13 10:20:11 【问题描述】:

我在 WPF 动画中出现撕裂和闪烁的问题。我有一个演示问题的玩具应用程序。该应用程序在屏幕上为正方形设置动画。方块的边缘出现撕裂,整个动画感觉不流畅。

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

我已经在两台新的高端计算机上尝试过这个,它们都显示出同样糟糕的动画(>1gb vram、四核等)。

SimpleWindow.zip

【问题讨论】:

你运行的是Win7还是XP? 我在 XP 中看到过,但在 Win7 中没有。抱歉帮不上忙 你用的是什么动画?你同时动画多少个方块?我在动画性能方面也遇到了一些问题......这可能很棘手:) 只用一个正方形就可以看到闪烁和撕裂。方块使用故事板制作动画。 尝试禁用 WPF 的硬件加速,看看是否能解决问题。如果是这样,请尝试更新您的视频卡驱动程序并重新启用加速。 msdn.microsoft.com/en-us/library/aa970912.aspx 【参考方案1】:

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

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

storyboards 和 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);
    

【讨论】:

感谢您的意见,马里诺。如果您的理论是正确的,那么为单个对象设置动画应该不会闪烁且不会撕裂。也就是说,如果 Rectangles 和 Storyboards 的创建是问题的根源,那么删除这些创建应该可以解决问题。但它没有:一个 Rectangle 仍然闪烁和流泪。 是的,这是我的理论,但我可能是错的。您可以尝试不在画布上删除和添加项目吗?这可能会阻塞 UI。一种设计更改,您可以在 XAML 或表单加载中将所有可能的 relangles 添加到画布上。并通过将它们向右平移并在屏幕外将背面向左平移来为它们设置动画。 我只为一个矩形设置动画将其删除。它仍然闪烁和撕裂。【参考方案2】:

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

他们还补充说:

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

他们还演示了动画的纯声明版本,我觉得它看起来更流畅。特别感谢 Dwayne Need 提供此信息。

【讨论】:

【参考方案3】:

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

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

【讨论】:

Peforator 表示窗口中没有任何内容是软件渲染的。我试过强制软件渲染,性能更差。

以上是关于WPF动画有撕裂和闪烁的主要内容,如果未能解决你的问题,请参考以下文章

WPF动画 - Loading加载动画

如何解决抽屉动画期间的显示撕裂问题

SurfaceView drawText 闪烁

闪烁动画问题

动画时透明UITableViewCell闪烁背景

css3 动画硬闪烁(帧之间没有淡入淡出)