13

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

Tristan
  • 1,466
  • 1
  • 16
  • 24
  • Are you running on Win7 or XP? – evanb Apr 26 '11 at 22:44
  • I've seen it in XP but not in Win7. Sorry couldn't help – evanb Apr 27 '11 at 21:03
  • What kind of animation are you using? How many squares are you animating at the same time? I had some trouble with animation performance as well ... it can be tricky :) – harri Apr 29 '11 at 07:33
  • The flicker and tearing can be seen with just one square. Squares are animated using storyboards. – Tristan Apr 29 '11 at 17:03
  • Try disabling hardware acceleration for WPF and see if that resolves the issue. If it does, try updating your video card drivers and re-enabling the acceleration. http://msdn.microsoft.com/en-us/library/aa970912.aspx – Dan Bryant May 14 '11 at 19:10
  • WPF has some issues when it comes to animations. See also http://stackoverflow.com/questions/3002271/smooth-text-animation-marquee-using-wpf – Goran May 16 '11 at 07:24
  • I'm wondering if you're using "tearing" and "flicker" to mean what they normally mean. I've run your example and it looks terrible, but I'm not seeing tearing - just to make sure that we're on the same page, could you clarify what you consider to be "tearing"? – Ian Griffiths Jul 06 '11 at 18:14
  • I see no tearing or flicker in your example on Win 7. Sorry I can't help with that. Why are you using Monitor? DispatcherTimer.Tick happens on the Dispatcher's thread, so there is no concurrency problem that I see. – Ryan Russell Jul 26 '11 at 23:34
  • Did you try removing `tryEnter` and `try catch` or replacing them with some condition checking? – Bizhan Dec 07 '11 at 19:19

3 Answers3

2

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)

Sebastian Edelmeier
  • 4,095
  • 3
  • 39
  • 60
  • Peforator says nothing in the window is software rendered. I've tried forcing software rendering and performanc is worse. – Tristan Jul 26 '11 at 15:47
1

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.

Christopher Bennage
  • 2,578
  • 2
  • 22
  • 32
0

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);
    }
}
Marino Šimić
  • 7,318
  • 1
  • 31
  • 61
  • Thanks for the input, Marino. If your theory is correct, then animating a single object should be flicker and tear free. That is to say, if creation of Rectangles and Storyboards is the source of the problem, then removing those creations should remove the problem. But it doesn't: a single Rectangle still flickers and tears. – Tristan May 02 '11 at 19:26
  • Yes that is my theory but I could be wrong. Can you try to not remove and add items on the canvas? That is something that could be blockign the UI. A design change where you add all the possible reclangles to the canvas alre4ady in XAML or form load. and animate them by translating them to the right and when offscreen put the back to the left. – Marino Šimić May 04 '11 at 13:28
  • 1
    I removed it by animating just one rectangle. It still flickered and tore. – Tristan May 04 '11 at 16:14