Подтвердить что ты не робот

Быстрая 2D-графика в WPF

Мне нужно нарисовать большое количество 2D-элементов в WPF, таких как линии и полигоны. Их положение также необходимо постоянно обновлять.

Я рассмотрел многие ответы, которые в основном предлагали использовать DrawingVisual или переопределять функцию OnRender. Чтобы протестировать эти методы, я реализовал простую систему частиц, отображающую 10000 эллипсов, и я считаю, что производительность чертежа по-прежнему ужасна с использованием обоих этих подходов. На моем ПК я не могу получить намного больше 5-10 кадров в секунду. что совершенно неприемлемо, если учесть, что я легко рисую 1/2 миллиона частиц плавно, используя другие технологии.

Итак, мой вопрос: я бегу против технического ограничения здесь WPF или я что-то упускаю? Есть ли что-то еще, что я могу использовать? любые предложения приветствуются.

Здесь код, который я пробовал

содержимое MainWindow.xaml:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="500" Width="500" Loaded="Window_Loaded">
    <Grid Name="xamlGrid">

    </Grid>
</Window>

содержимое MainWindow.xaml.cs:

using System.Windows.Threading;

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }


        EllipseBounce[]     _particles;
        DispatcherTimer     _timer = new DispatcherTimer();

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {

            //particles with Ellipse Geometry
            _particles = new EllipseBounce[10000];

            //define area particles can bounce around in
            Rect stage = new Rect(0, 0, 500, 500);

            //seed particles with random velocity and position
            Random rand = new Random();

            //populate
            for (int i = 0; i < _particles.Length; i++)
            {
               Point pos = new Point((float)(rand.NextDouble() * stage.Width + stage.X), (float)(rand.NextDouble() * stage.Height + stage.Y));
               Point vel = new Point((float)(rand.NextDouble() * 5 - 2.5), (float)(rand.NextDouble() * 5 - 2.5));
                _particles[i] = new EllipseBounce(stage, pos, vel, 2);
            }

            //add to particle system - this will draw particles via onrender method
            ParticleSystem ps = new ParticleSystem(_particles);


            //at this element to the grid (assumes we have a Grid in xaml named 'xmalGrid'
            xamlGrid.Children.Add(ps);

            //set up and update function for the particle position
            _timer.Tick += _timer_Tick;
            _timer.Interval = new TimeSpan(0, 0, 0, 0, 1000 / 60); //update at 60 fps
            _timer.Start();

        }

        void _timer_Tick(object sender, EventArgs e)
        {
            for (int i = 0; i < _particles.Length; i++)
            {
                _particles[i].Update();
            }
        }
    }

    /// <summary>
    /// Framework elements that draws particles
    /// </summary>
    public class ParticleSystem : FrameworkElement
    {
        private DrawingGroup _drawingGroup;

        public ParticleSystem(EllipseBounce[] particles)
        {
            _drawingGroup = new DrawingGroup();

            for (int i = 0; i < particles.Length; i++)
            {
                EllipseGeometry eg = particles[i].EllipseGeometry;

                Brush col = Brushes.Black;
                col.Freeze();

                GeometryDrawing gd = new GeometryDrawing(col, null, eg);

                _drawingGroup.Children.Add(gd);
            }

        }


        protected override void OnRender(DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);

            drawingContext.DrawDrawing(_drawingGroup);
        }
    }

    /// <summary>
    /// simple class that implements 2d particle movements that bounce from walls
    /// </summary>
    public class SimpleBounce2D
    {
        protected Point     _position;
        protected Point     _velocity;
        protected Rect     _stage;

        public SimpleBounce2D(Rect stage, Point pos,Point vel)
        {
            _stage = stage;

            _position = pos;
            _velocity = vel;
        }

        public double X
        {
            get
            {
                return _position.X;
            }
        }


        public double Y
        {
            get
            {
                return _position.Y;
            }
        }

        public virtual void Update()
        {
            UpdatePosition();
            BoundaryCheck();
        }

        private void UpdatePosition()
        {
            _position.X += _velocity.X;
            _position.Y += _velocity.Y;
        }

        private void BoundaryCheck()
        {
            if (_position.X > _stage.Width + _stage.X)
            {
                _velocity.X = -_velocity.X;
                _position.X = _stage.Width + _stage.X;
            }

            if (_position.X < _stage.X)
            {
                _velocity.X = -_velocity.X;
                _position.X = _stage.X;
            }

            if (_position.Y > _stage.Height + _stage.Y)
            {
                _velocity.Y = -_velocity.Y;
                _position.Y = _stage.Height + _stage.Y;
            }

            if (_position.Y < _stage.Y)
            {
                _velocity.Y = -_velocity.Y;
                _position.Y = _stage.Y;
            }
        }
    }


    /// <summary>
    /// extend simplebounce2d to add ellipse geometry and update position in the WPF construct
    /// </summary>
    public class EllipseBounce : SimpleBounce2D
    {
        protected EllipseGeometry _ellipse;

        public EllipseBounce(Rect stage,Point pos, Point vel, float radius)
            : base(stage, pos, vel)
        {
            _ellipse = new EllipseGeometry(pos, radius, radius);
        }

        public EllipseGeometry EllipseGeometry
        {
            get
            {
                return _ellipse;
            }
        }

        public override void Update()
        {
            base.Update();
            _ellipse.Center = _position;
        }
    }
}
4b9b3361

Ответ 1

Я полагаю, что предоставленный пример кода в значительной степени хорош и демонстрирует ограничения фреймворка. В моих измерениях, которые я профилировал, средняя стоимость 15-25 мс связана с накладными расходами рендеринга. По сути, мы говорим здесь только о модификации свойства центра (dependency-), что довольно дорого. Я предполагаю, что это дорого, потому что оно напрямую передает изменения в mil-core.

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

Лучший альтернативный подход в этой ситуации - прибегнуть к D3DImage, который является элементом Windows Presentation Foundation для представления информации, отображаемой с помощью DirectX. Обычно говорят, что подход должен быть эффективным, с точки зрения производительности.

Ответ 2

Вы можете попробовать WriteableBitmap и создать изображение, используя более быстрый код в фоновом потоке. Тем не менее, единственное, что вы можете сделать с этим - скопировать данные растровых изображений, так что вам нужно либо закодировать свои собственные примитивные процедуры рисования, либо (что может даже работать в вашем случае) создать "штамповый" образ, который вы копируете повсюду ваши частицы идти...

Ответ 3

В формах окон такие вещи заставляют меня вернуться к:

  • Установить Visible = False для контейнера самого высокого уровня (например, холста самой формы)
  • Нарисуйте много.
  • Установить видимый = True

Не уверен, поддерживает ли WPF это.

Ответ 4

Самый быстрый способ рисования WPF, который я нашел, это:

  • создать DrawingGroup "backingStore".
  • во время OnRender(), нарисуйте мою группу чертежей в контекст чертежа
  • в любое время, я хочу, backingStore.Open() и рисовать в него новые графические объекты

Удивительная вещь для меня, поступающая из Windows.Forms.. заключается в том, что я могу обновить свою DrawingGroup после того, как добавлю ее в DrawingContext во время OnRender(). Это обновление существующих сохраненных команд рисования в дереве чертежей WPF и запуск эффективной перерисовки.

В простом приложении я закодирован как в Windows.Forms, так и в WPF (SoundLevelMonitor), этот метод эмпирически кажется очень похожим в производительности до непосредственного чертежа OnPaint() GDI.

Я думаю, что WPF сделал dis-service, вызвав метод OnRender(), его можно было бы лучше назвать AccumulateDrawingObjects()

В основном это выглядит так:

DrawingGroup backingStore = new DrawingGroup();

protected override void OnRender(DrawingContext drawingContext) {      
    base.OnRender(drawingContext);            

    Render(); // put content into our backingStore
    drawingContext.DrawDrawing(backingStore);
}

// I can call this anytime, and it'll update my visual drawing
// without ever triggering layout or OnRender()
private void Render() {            
    var drawingContext = backingStore.Open();
    Render(drawingContext);
    drawingContext.Close();            
}

Я также попытался использовать RenderTargetBitmap и WriteableBitmap, как для Image.Source, так и написан непосредственно в DrawingContext. Вышеуказанный метод выполняется быстрее.

Ответ 5

Вот некоторые из вещей, которые вы можете попробовать: (Я попробовал их с вашим образцом и, похоже, выглядел быстрее (по крайней мере, в моей системе)).

  • Используйте Canvas вместо Grid (если у вас нет других причин). Play BitmapScalingMode и CachingHint:

    <Canvas Name="xamlGrid" RenderOptions.BitmapScalingMode="LowQuality" RenderOptions.CachingHint="Cache" IsHitTestVisible = "False">
    
    </Canvas>
    
  • Добавить StaticResource для кисти, используемой в GeometryDrawing:

    <SolidColorBrush x:Key="MyBrush" Color="DarkBlue"/>
    

в использовании кода:

    GeometryDrawing gd = new GeometryDrawing((SolidColorBrush)this.FindResource("MyBrush"), null, eg);

Надеюсь, это поможет.