2014-11-12 3 views
2

WPF를 사용하여 여러 폴리 라인 (2300x1024 캔버스에서 각각 400-500 정점의 폴리 라인 64 개)을 렌더링하는 데 이상한 문제가 발생했습니다. 폴리 라인은 50ms마다 업데이트됩니다.WPF 렌더링이 너무 느립니다.

어째서 내 응용 프로그램 UI가 매우 부진하고 사용자 입력에 거의 응답하지 않습니다.

나는이 표시되는 동안 포인트 모음을 업데이트 방지하기 위해 다음과 같은 클래스에 사용하고 있습니다 :

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에 자식으로 추가됩니다.

Visual Studio Concurrency Analyzer 도구를 사용하여 진행 상황을 확인하고 각 업데이트 후에 메인 스레드가 46ms에 WPF 관련 작업 (System.Windows.ContextLayoutManager.UpdateLayout() 및 System.Windows.Media)을 소비하는 것을 확인했습니다. MediaContext.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.

저는 프로젝트의 나머지 부분에서 WPF 셰이프 API를 사용하기 때문에 DrawingVisual을 피하려고 노력하고 있습니다 (또는 내 회사).

왜 이렇게 느린가요? 나는 심지어 앤티 앨리어싱 (RenderOptions.SetEdgeMode (mCanvas, EdgeMode.Aliased))을 비활성화하려고했지만 너무 도움이되지 못했습니다.

왜 레이아웃 업데이트가 오래 걸릴까요? WPF 내부 전문가가 누구입니까?

대단히 감사합니다.

+0

반복적으로 모양을 추가하거나 제거하는 것은 빠르지 않을 것입니다. 셰이프는 캔바스의 시각적 범위를 추가하거나 제거 할 때마다 다시 계산해야한다는 점에서 WPF에 대한 컨트롤과 같습니다. –

+0

도형을 추가하거나 제거하지 않습니다. DoubleBufferPlot.Display가 한 번 추가되고 포인트 콜렉션 (Polyline.Points) 만 업데이트됩니다. –

답변

0

DrawingVisual을 포함한 다른 접근법을 시도한 후에 많은 정점이있는 폴리선 그리기가 너무 비효율적 인 것처럼 보입니다.

픽셀 당 1 개 이하의 정점이있을 때만 폴리 라인을 그립니다. 그렇지 않으면 수동으로 WriteableBitmap 객체로 렌더링합니다. 이것은 놀랍게도 훨씬 더 효율적입니다.

+1

셰이프가 대부분 정적 인 경우에도 캐싱 된 컴포지션을 활성화 할 수 있으므로 동일한 작업을 효과적으로 수행 할 수 있습니다. WPF는 렌더링 된 지오메트리를 메모리 내 텍스처에 캐시하고 비주얼이 무효화 될 때만 다시 테셀레이션합니다. 'CacheMode' 속성과'BitmapCache' 클래스를 확인하십시오. 아마도'WriteableBitmap' 소프트웨어에 렌더링하는 것보다 쉽고 효율적입니다. –

+0

네, 맞습니다. 그러나, 제 경우에는 모양이 25-50ms마다 업데이트됩니다. 이 애플리케이션은 오실로스코프와 유사하므로 최대 64 개의 채널 그리드 만 볼 수 있습니다. –

+0

그래도 어쨌든 소프트웨어 비트 맵 표면에 다시 그려 넣으면 캐시 된 구성이 더 빨라지고 코드를 더 간단하게 유지할 수 있습니다. 노력할만한 가치가 있다고 생각합니다. –

0

내가 자주 업데이트 형상을 그리는 발견 한 가장 빠른 방법은 OnRender() 동안 저장소를 백업 DrawingGroup "backingStore", 출력을 생성하고, 내 데이터가 backingStore.Open()를 사용하여 업데이트 할 필요가있을 때 그 backingStore을 업데이트하는 것입니다. (아래 코드 참조)

내 테스트에서이 방법은 WriteableBitmap 또는 RenderTargetBitmap을 사용하는 것보다 효율적입니다.

UI가 응답하지 않는 경우 어떻게 50ms마다 다시 그리기를 트리거합니까? 다시 그리기 중 일부가 50ms보다 오래 걸리고 다시 그리기 메시지로 메시지 펌프를 백업 할 수 있습니까? 이 문제를 방지하는 한 가지 방법은 다시 그리기 루프 중에 다시 그리기 타이머를 끄거나 (또는 ​​원샷 타이머로 만들기 위해) 끝에서 만 활성화하는 것입니다. 또 다른 방법은 WPF 다시 그리기 직전에 발생하는 CompositionTarget.Rendering 이벤트 중에 다시 그리기를 수행하는 것입니다.

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