2013-04-19 1 views
28

Мне нужно нарисовать большое количество 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; 
     } 
    } 
} 
+5

Я просто делал некоторые тесты, переопределяя 'OnRender()' и бросая в 10000 случайных 'drawingContext.DrawLine()'. Я обнаружил, что это делает огромную разницу в производительности только с помощью [Freezing Freezables] (http://msdn.microsoft.com/en-us/library/ms750509.aspx), таких как «Ручка» и «Кисть». –

+0

ОК, спасибо, что попробуем. кроме пера (который является нулевым в моей реализации), а Brush - что-нибудь еще, что нужно заморозить? –

+1

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

ответ

-4

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

  • Использование холста вместо сетки (если у вас нет других причин). Играть 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); 

Я надеюсь, что это помогает.

+1

-1. Как будет улучшаться качество кастинга? а также положить замороженный замороженный ('Brushes.Black') как' StaticResource' не поможет. –

+0

@HighCore: Кастинга вообще следует избегать. Однако в этом случае он либо хорош, либо лучше, чем создание кисти для каждого элемента. Я думаю, вы должны проверить его, прежде чем судить об этом! Лучше использовать StaticResource в стиле/шаблоне, но это будет связано с изменением способа создания частиц. – FHnainia

+3

Простите, неправда. 'System.Windows.Media.Brushes.Black' - это статический экземпляр, поэтому, когда вы ссылаетесь на него, вы не« создаете новый каждый раз », а фактически используете один и тот же. Что, кстати, уже заморожено. –

10

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

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

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

+0

D3DImage - звучит многообещающе, заглянет в него. Спасибо! –

3

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

+0

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

+0

Я тестировал различные функции рисования, сначала рендеринг в растровое изображение, чем только рисование этого растрового изображения (не полагаясь на функции рисования wpf), намного быстрее, чем другие методы wpf. – Gorkem

0

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

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

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

0

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

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

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

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

Я думаю, WPF сделал Дис-сервис с помощью вызова метода 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. Вышеуказанный метод выполняется быстрее.

 Смежные вопросы

  • Нет связанных вопросов^_^