2014-11-12 4 views
2

Я испытываю странную проблему, пытаясь использовать WPF для рендеринга ряда полилиний (64 полилинии около 400-500 вершин в каждом на холсте 2300x1024). Полилинии обновляются каждые 50 мс.WPF-рендеринг слишком медленный

По какой-то причине мой пользовательский интерфейс приложения становится очень вялым и почти не отвечает на ввод пользователя.

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

class DoubleBufferPlot 
    { 
     /// <summary> 
     /// Double-buffered point collection 
     /// </summary> 
     private readonly PointCollection[] mLineBuffer = 
     { 
      new PointCollection(), 
      new PointCollection() 
     }; 

     private int mWorkingBuffer; //index of the workign buffer (buffer being modified) 

     #region Properties 

     //Polyline displayed 
     public Polyline Display { get; private set; } 

     /// <summary> 
     /// index operator to access points 
     /// </summary> 
     /// <param name="aIndex">index</param> 
     /// <returns>Point at aIndex</returns> 
     public Point this[int aIndex] 
     { 
      get { return mLineBuffer[mWorkingBuffer][aIndex]; } 
      set { mLineBuffer[mWorkingBuffer][aIndex] = value; } 
     } 

     /// <summary> 
     /// Number of points in the working buffer 
     /// </summary> 
     public int WorkingPointCount 
     { 
      get { return mLineBuffer[mWorkingBuffer].Count; } 

      set 
      { 
       SetCollectionSize(mLineBuffer[mWorkingBuffer], value); 
      } 
     } 
     #endregion 

     public DoubleBufferPlot(int numPoints = 0) 
     { 
      Display = new Polyline {Points = mLineBuffer[1]}; 

      if (numPoints > 0) 
      { 
       SetCollectionSize(mLineBuffer[0], numPoints); 
       SetCollectionSize(mLineBuffer[1], numPoints); 
      } 
     } 

     /// <summary> 
     /// Swap working and display buffer 
     /// </summary> 
     public void Swap() 
     { 
      Display.Points = mLineBuffer[mWorkingBuffer]; //display workign buffer 

      mWorkingBuffer = (mWorkingBuffer + 1) & 1; //swap 

      //adjust buffer size if needed 
      if (Display.Points.Count != mLineBuffer[mWorkingBuffer].Count) 
      { 
       SetCollectionSize(mLineBuffer[mWorkingBuffer], Display.Points.Count); 
      } 
     } 

     private static void SetCollectionSize(IList<Point> collection, int newSize) 
     { 
      while (collection.Count > newSize) 
      { 
       collection.RemoveAt(collection.Count - 1); 
      } 

      while (collection.Count < newSize) 
      { 
       collection.Add(new Point()); 
      } 
     } 
    } 

Я обновляю рабочий буфер закадровый, а затем вызвать своп(), чтобы он показывался. Все 64 полилинии (DoubleBufferPlot.Display) добавляются в Canvas как дети.

Я использовал средство анализа Concurrency Analyzer Visual Studio, чтобы узнать, что происходит, и обнаружил, что после каждого обновления основной поток тратит 46 мс на выполнение некоторых связанных с WPF задач: System.Widnows.ContextLayoutManager.UpdateLayout() и System.Windows.Media. MediaContex.Render().

Я также обнаружил, что есть еще одна нить, которая работает почти нон-стоп рендеринга wpfgfx_v0400.dll! CPartitionThread :: ThreadMain ... wpfgfx_v0400.dll! CDrawingContext :: Рендер ... и т.д.

Я прочитал ряд статей по WPF, включая это: Can WPF render a line path with 300,000 points on it in a performance-sensitive environment? , а также эту статью http://msdn.microsoft.com/en-us/magazine/dd483292.aspx.

Я (или моя компания) стараюсь избегать DrawingVisual, так как в остальной части проекта используется API форм WPF.

Любая идея, почему это так медленно? Я даже попытался отключить сглаживание (RenderOptions.SetEdgeMode (mCanvas, EdgeMode.Aliased)), но это не очень помогло.

Почему обновление макета занимает так много времени. Кто-нибудь, кто является экспертом в составе WPF?

спасибо.

+0

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

+0

Я не добавляю и не удаляю фигуры. DoubleBufferPlot.Display добавляется один раз, и я только обновляю коллекцию точек (Polyline.Points). –

ответ

0

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

Я закончил реализацию при подходе, где я рисую полилинии только при наличии 1 или меньше вершин на пиксель. В противном случае я визуализирую вручную объект WriteableBitmap. Это на удивление гораздо более эффективно.

+1

Если формы в основном статичны, вы также можете включить на них кешированную композицию, которая эффективно выполняет одно и то же: WPF кэширует визуализированную геометрию в текстуре в памяти и только повторно тесселирует, когда визуальный признак недействителен. Проверьте свойство 'CacheMode' и класс 'BitmapCache'. Это, вероятно, проще и эффективнее, чем рендеринг программного обеспечения «WriteableBitmap». –

+0

Да, это правильно. Однако, в моем случае, формы обновляются каждые 25-50 мс. Приложение похоже на осциллограф, только вы можете просматривать сетку до 64 каналов. –

+0

Тем не менее, если вы перерисовываете их на растровую поверхность программного обеспечения в любом случае, кешированная композиция, вероятно, будет быстрее и упростит ваш код. Стоит попробовать, я думаю. –

0

Самый быстрый способ я нашел, чтобы сделать часто обновляемой геометрию, чтобы создать DrawingGroup «backingStore», выход, резервное хранилище во время OnRender(), а затем обновить эту backingStore, когда мои данные нужно обновить, используя backingStore.Open(). (см. код ниже)

В моих тестах это было более эффективно, чем использование WriteableBitmap или RenderTargetBitmap.

Если ваш пользовательский интерфейс становится невосприимчивым, как вы запускаете перерисовку каждые 50 мс? Возможно ли, что часть перерисовки занимает более 50 мс и резервное копирование сообщения-насоса с перерисованными сообщениями? Один из способов избежать этого - отключить таймер перезаписи во время цикла перерисовывания (или сделать его таймером с одним выстрелом) и включить его только в конце. Другим методом является повторная переделка во время события CompositionTarget.Rendering, которое происходит непосредственно перед перерисовкой WPF.

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();    
} 
Смежные вопросы