WPF Animation has Tearing and Flicker
Asked Answered
A

3

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

Ankara answered 26/4, 2011 at 22:42 Comment(9)
Are you running on Win7 or XP?Fetor
I've seen it in XP but not in Win7. Sorry couldn't helpFetor
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 :)Reliant
The flicker and tearing can be seen with just one square. Squares are animated using storyboards.Ankara
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. msdn.microsoft.com/en-us/library/aa970912.aspxHoneyhoneybee
WPF has some issues when it comes to animations. See also #3002771Miquelmiquela
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"?Gretchengrete
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.Goolsby
Did you try removing tryEnter and try catch or replacing them with some condition checking?Plassey
A
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.

Adele answered 14/5, 2011 at 18:23 Comment(0)
H
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)

Haeres answered 20/7, 2011 at 8:50 Comment(1)
Peforator says nothing in the window is software rendered. I've tried forcing software rendering and performanc is worse.Ankara
A
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.

Adele answered 14/5, 2011 at 18:23 Comment(0)
B
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);
    }
}
Blockage answered 2/5, 2011 at 16:45 Comment(3)
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.Ankara
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.Ugly
I removed it by animating just one rectangle. It still flickered and tore.Ankara

© 2022 - 2024 — McMap. All rights reserved.