WPF Animation имеет разрывы и мерцание - PullRequest
13 голосов
/ 27 апреля 2011

У меня проблемы с разрывом и мерцанием в анимации WPF.У меня есть игрушечное приложение, которое демонстрирует проблемы.Приложение анимирует квадраты по экрану.Края квадратов показывают разрывы, и анимация в целом не выглядит гладкой.

Perforator показывает> 60 кадров в секунду, ~ 10 МБ видеопамяти, 0 IRT.

Я пробовал это на двух новых компьютерах высшего класса, и оба показывают ту же плохую анимацию (> 1 ГБ vram, четырехъядерный процессор и т. Д.).

SimpleWindow.zip

Ответы [ 3 ]

2 голосов
/ 20 июля 2011

Вы абсолютно уверены, что ваш код работает с аппаратным ускорением?Пожалуйста, посмотрите в этот список: http://blogs.msdn.com/b/jgoldb/archive/2010/06/22/software-rendering-usage-in-wpf.aspx.

Если это так - учитывая то, что у вас есть оборудование Ubercool - вы можете попробовать запустить его на CPU вместо GPU.Вы можете применить это, установив для RenderMode значение SoftwareOnly (элемент 6 в списке, указанном выше)

1 голос
/ 14 мая 2011

Я поднял этот вопрос с командой WPF, и в итоге они сказали, что считают, что есть некоторые проблемы с плавностью анимации, которые можно улучшить.

Они также добавили:

Мы очень стараемся планировать нашу сцену обновления в синхронизации с VBlank, чтобы получить очень регулярные, надежные анимации. любой работа в потоке пользовательского интерфейса может мешать жесткий. В этом примере они используя DispatcherTimers, который график работать над потоком пользовательского интерфейса, чтобы создать новый раскадровки, удаление старых элементов и т. д.

Они также продемонстрировали чисто декларативную версию анимации, и она показалась мне более гладкой. Особая благодарность Dwayne Need за эту информацию.

0 голосов
/ 02 мая 2011

Причина, по которой это происходит, заключается в том, что создается много объектов, которым должен принадлежать поток пользовательского интерфейса, и все эти вызовы, а также добавление их в контейнер пользовательского интерфейса проходят через основной поток.

Я даже пытался создать версию с управлением потоками вместо таймеров, но это ничего не изменило, поскольку все объекты FrameWorkElement должны создаваться с помощью Dispatcher.Invoke.

Создание раскадровок и beginStoryboard + EventTrigger все должно быть сделано в потоке пользовательского интерфейса. Это то, что блокирует беглость.

К сожалению, при таком дизайне невозможно добиться работы без мерцания: /

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);
    }
}
...