2014-06-09 4 views
11

나는 DrawText을 사용하여 WPF에서 많은 수의 텍스트를 만든 다음 단일 Canvas에 추가하려고합니다.WPF에서 성능이 저하 된 이유

MouseWheel 이벤트에서 화면을 다시 그려야하는데 성능이 약간 느리다는 것을 알았 기 때문에 객체가 생성 된 시간을 측정했으며 1 밀리 초 미만이었습니다!

그래서 문제가 될 수 있습니까? 오랫동안 나는 어딘가에서 실제로 시각을 만들고 추가하지 않고 시간이 걸리는 Rendering이라는 것을 읽었을 것입니다. 여기

내가에만 필수 부품 포함 시켰습니다, 나는 텍스트 개체를 만드는 데 사용하고 코드입니다 :

public class ColumnIdsInPlan : UIElement 
    { 
    private readonly VisualCollection _visuals; 
    public ColumnIdsInPlan(BaseWorkspace space) 
    { 
     _visuals = new VisualCollection(this); 

     foreach (var column in Building.ModelColumnsInTheElevation) 
     { 
      var drawingVisual = new DrawingVisual(); 
      using (var dc = drawingVisual.RenderOpen()) 
      { 
       var text = "C" + Convert.ToString(column.GroupId); 
       var ft = new FormattedText(text, cultureinfo, flowdirection, 
              typeface, columntextsize, columntextcolor, 
              null, TextFormattingMode.Display) 
       { 
        TextAlignment = TextAlignment.Left 
       }; 

       // Apply Transforms 
       var st = new ScaleTransform(1/scale, 1/scale, x, space.FlipYAxis(y)); 
       dc.PushTransform(st); 

       // Draw Text 
       dc.DrawText(ft, space.FlipYAxis(x, y)); 
      } 
      _visuals.Add(drawingVisual); 
     } 
    } 

    protected override Visual GetVisualChild(int index) 
    { 
     return _visuals[index]; 
    } 

    protected override int VisualChildrenCount 
    { 
     get 
     { 
      return _visuals.Count; 
     } 
    } 
} 

을 그리고이 코드는 MouseWheel 이벤트가 발생 될 때마다 실행됩니다 :

var columnsGroupIds = new ColumnIdsInPlan(this); 
MyCanvas.Children.Clear(); 
FixedLayer.Children.Add(columnsGroupIds); 

무엇이 범인이 될 수 있습니까? 패닝 동안

는 또한 문제가 있습니다 :

private void Workspace_MouseMove(object sender, MouseEventArgs e) 
    { 
     MousePos.Current = e.GetPosition(Window); 
     if (!Window.IsMouseCaptured) return; 
     var tt = GetTranslateTransform(Window); 
     var v = Start - e.GetPosition(this); 
     tt.X = Origin.X - v.X; 
     tt.Y = Origin.Y - v.Y; 
    } 
+1

당신은 그림 텍스트를 사용하지 않아야합니다. 당신이해야 할 일은 템플릿을'Label' 또는'Textblock'에 적용하고'ItemControl'에 넣고 자동으로 그릴 수 있도록 문자열 배열을 피드하는 것입니다. – Franck

+0

@Franck Thanks Frank, 각 텍스트의 위치와 회전은 어떨까요? – Vahid

+0

템플릿에 모두 포함되어 있습니다. WPF는 X, Y, Z를 뒤집기 위해'ScaleTransform'을 할 수있게 해줍니다. (마지막으로 3dviewport의 경우), 회전은 사용할 태그의 이름이 무엇인지를 기억하지 못합니다.하지만 확실히 변환 그룹에 있습니다. . databinded 개체의 속성에 대한 margin/padding base의 현명한 간단한 오프셋 위치는 충분합니다. – Franck

답변

15

나는 현재 동일한 문제가있을 가능성이 있으며 나는 예상치 못한 것을 발견했다. WriteableBitmap에 렌더링하고 사용자가 스크롤 (줌) 및 팬하여 렌더링 된 것을 변경하도록 허용합니다. 줌과 패닝 모두 움직임이 고르지 않아 렌더링이 너무 오래 걸리는 것으로 생각했습니다. 일부 계측 후에는 30-60 fps로 렌더링한다는 것을 확인했습니다. 사용자가 확대/축소 또는 이동하는 방법에 관계없이 렌더링 시간이 늘어나지 않으므로 초퍼성이 다른 곳에서 발생해야합니다.

대신 OnMouseMove 이벤트 처리기를 살펴 보았습니다. WriteableBitmap은 초당 30-60 번 업데이트되지만 MouseMove 이벤트는 초당 1-2 회만 발생합니다. WriteableBitmap의 크기를 줄이면 MouseMove 이벤트가 더 자주 발생하고 팬 작업이 부드럽게 나타납니다. 따라서 choppiness는 실제로 MouseMove 이벤트가 고르지 않고 렌더링되지 않습니다 (예 : WriteableBitmap이 7-10 프레임을 렌더링하고 MouseMove 이벤트가 발생하면 WriteableBitmap이 새로 이동 한 이미지의 7-10 프레임을 렌더링 함). , 등).

MouseableGitPosition (this)을 사용하여 WriteableBitmap이 업데이트 될 때마다 마우스 위치를 폴링하여 팬 작업을 추적하려고했습니다. 그러나 반환 된 마우스 위치가 새 값으로 변경되기 전에 7-10 프레임에서 동일하기 때문에 동일한 결과가 나타납니다.

나는 다음 폴링에게 PInvoke를 서비스 GetCursorPos like in this SO answer 예를 사용하여 마우스 위치 시도 :

[DllImport("user32.dll")] 
[return: MarshalAs(UnmanagedType.Bool)] 
static extern bool GetCursorPos(out POINT lpPoint); 

[StructLayout(LayoutKind.Sequential)] 
public struct POINT 
{ 
    public int X; 
    public int Y; 

    public POINT(int x, int y) 
    { 
     this.X = x; 
     this.Y = y; 
    } 
} 

를이 실제로 트릭을했다. GetCursorPos는 호출 될 때마다 (마우스가 움직일 때마다) 새 위치를 반환하므로 사용자가 패닝하는 동안 각 프레임이 약간 다른 위치에 렌더링됩니다. 같은 종류의 choppiness가 MouseWheel 이벤트에 영향을 미치는 것으로 보이며, 나는 그 이벤트를 해결하는 방법을 모릅니다.

따라서 시각적 트리를 효율적으로 유지 관리하는 것에 대한 위의 조언이 모두 우수하지만, 성능 문제는 마우스 이벤트 빈도를 방해하는 결과 일 수 있습니다. 제 경우에는 어떤 이유로 렌더링이 마우스 이벤트가 평상시보다 훨씬 느리게 업데이트되고 실행되는 것으로 보입니다. 이 부분적인 해결 방법보다는 진정한 해결책을 찾으면 이것을 업데이트하겠습니다.


편집는 : 좋아, 나는이 좀 더 파고 나는 내가 지금 무슨 일이 일어나고 있는지 이해 생각합니다. 보다 자세한 코드 샘플을 사용하여 설명하겠습니다.

this MSDN article.에 설명 된대로 CompositionTarget.Rendering 이벤트를 처리하도록 등록하여 프레임 단위로 비트 맵을 렌더링합니다. 기본적으로 UI가 렌더링 될 때마다 내 비트 맵을 업데이트 할 수 있도록 내 코드가 호출됩니다. 이것은 본질적으로 당신이하는 렌더링과 동일합니다. 단지 비주얼 엘리먼트를 어떻게 설정했는지에 따라 렌더링 코드가 뒤에서 호출된다는 것입니다. 그리고 렌더링 코드는 볼 수있는 곳입니다. OnMouseMove 이벤트를 재정 의하여 마우스의 위치에 따라 변수를 업데이트합니다.

public class MainWindow : Window 
{ 
    private System.Windows.Point _mousePos; 
    public Window() 
    { 
    InitializeComponent(); 
    CompositionTarget.Rendering += CompositionTarget_Rendering; 
    } 

    private void CompositionTarget_Rendering(object sender, EventArgs e) 
    { 
    // Update my WriteableBitmap here using the _mousePos variable 
    } 

    protected override void OnMouseMove(MouseEventArgs e) 
    { 
    _mousePos = e.GetPosition(this); 
    base.OnMouseMove(e); 
    } 
} 

문제는 렌더링 시간이 더 걸립니다, MouseMove 이벤트 (모든 마우스 이벤트, 정말이) 훨씬 덜 빈번하게 호출되는 점이다. 렌더링 코드에 15ms가 걸리면 MouseMove 이벤트가 몇 밀리 초마다 호출됩니다. 렌더링 코드에 30ms가 걸리면 MouseMove 이벤트가 밀리 초마다 호출됩니다. 왜 이런 일이 일어나는가에 대한 내 이론은 WPF 마우스 시스템이 값을 업데이트하고 마우스 이벤트를 발생시키는 동일한 스레드에서 렌더링이 일어나고 있다는 것입니다. 이 스레드의 WPF 루프에는 한 조건에서 렌더링이 너무 오래 걸리면 마우스 업데이트를 건너 뛸 수있는 조건부 논리가 있어야합니다. 내 렌더링 코드가 모든 단일 프레임에서 "너무 오래"걸릴 때 문제가 발생합니다. 그런 다음 렌더링이 프레임 당 15 여분의 ms를 필요로하기 때문에 인터페이스가 약간 느려지는 대신 15 밀리 초의 렌더링 시간으로 인해 마우스 업데이트 사이에 수백 밀리 초의 지연이 생겨 인터페이스가 크게 혼란스럽게됩니다.

앞서 언급 한 PInvoke 해결 방법은 기본적으로 WPF 마우스 입력 시스템을 우회합니다. 렌더링이 발생할 때마다 소스로 곧바로 이동하므로 WPF 마우스 입력 시스템을 굶주리게해도 더 이상 비트 맵이 올바르게 업데이트되지 않습니다.

public class MainWindow : Window 
{ 
    private System.Windows.Point _mousePos; 
    public Window() 
    { 
    InitializeComponent(); 
    CompositionTarget.Rendering += CompositionTarget_Rendering; 
    } 

    private void CompositionTarget_Rendering(object sender, EventArgs e) 
    { 
    POINT screenSpacePoint; 
    GetCursorPos(out screenSpacePoint); 

    // note that screenSpacePoint is in screen-space pixel coordinates, 
    // not the same WPF Units you get from the MouseMove event. 
    // You may want to convert to WPF units when using GetCursorPos. 
    _mousePos = new System.Windows.Point(screenSpacePoint.X, 
             screenSpacePoint.Y); 
    // Update my WriteableBitmap here using the _mousePos variable 
    } 

    [DllImport("user32.dll")] 
    [return: MarshalAs(UnmanagedType.Bool)] 
    static extern bool GetCursorPos(out POINT lpPoint); 

    [StructLayout(LayoutKind.Sequential)] 
    public struct POINT 
    { 
    public int X; 
    public int Y; 

    public POINT(int x, int y) 
    { 
     this.X = x; 
     this.Y = y; 
    } 
    } 
} 

이 방법은 그러나, 내 마우스 이벤트 (MouseDown,을 MouseWheel 등)의 나머지가 해결되지 않았고, 내 마우스 입력의 모든이의 PInvoke 접근 방식을 복용에 치열하지 않았다, 그래서 나는 결정 WPF 마우스 입력 시스템을 굶기 만하면됩니다. 내가 끝내기 만하면 실제로 업데이트해야 할 때 WriteableBitmap을 업데이트하는 것이 었습니다. 일부 마우스 입력이 영향을 줄 때만 업데이트하면됩니다. 결과적으로 마우스 입력 한 프레임을 수신하고 다음 프레임에서 비트 맵을 업데이트하지만 업데이트가 수 밀리 초가 지나치게 오래 걸리므로 동일한 프레임에서 더 많은 마우스 입력을받지 못하고 다음 프레임을 수신하게됩니다. 마우스를 다시 입력 할 필요가 없으므로 마우스 입력.가변 길이 프레임 시간이 단지 일종의 평균이기 때문에 렌더링 시간이 길어질수록 훨씬 선형적이고 합리적인 성능 저하가 발생합니다.

public class MainWindow : Window 
{ 
    private System.Windows.Point _mousePos; 
    private bool _bitmapNeedsUpdate; 
    public Window() 
    { 
    InitializeComponent(); 
    CompositionTarget.Rendering += CompositionTarget_Rendering; 
    } 

    private void CompositionTarget_Rendering(object sender, EventArgs e) 
    { 
    if (!_bitmapNeedsUpdate) return; 
    _bitmapNeedsUpdate = false; 
    // Update my WriteableBitmap here using the _mousePos variable 
    } 

    protected override void OnMouseMove(MouseEventArgs e) 
    { 
    _mousePos = e.GetPosition(this); 
    _bitmapNeedsUpdate = true; 
    base.OnMouseMove(e); 
    } 
} 

자신의 특정 상황에이 같은 지식을 번역 : 나는 캐싱의 몇 가지 유형을 시도 할 것입니다 성능 문제가 발생할하여 복잡한 형상에 대한. 예를 들어, Geometry 자체가 변경되지 않거나 자주 변경되지 않으면 RenderTargetBitmap으로 렌더링 한 다음 Geometry 자체를 추가하는 대신 RenderTargetBitmap을 비주얼 트리에 추가하십시오. 이렇게하면 WPF에서 렌더링 경로를 수행 할 때 원시 기하 데이터에서 픽셀 데이터를 다시 작성하는 대신 해당 비트 맵을 blit 처리 만하면됩니다.

+0

감사합니다 Dan, 여기서 제공 한 정보는 나에게 큰 가치가 있습니다. 또한 그림 비주얼 등을 그려서 문제를 해결했지만 화면에 많은 수의 지오메트리가있을 때 여전히 큰 프로젝트에 대해 잘게 자릅니다. 위에서 제공 한 코드를 이해하는 데 문제가 있습니다. 샘플을 제공 할 수 있습니까? 내 년이 아니라면 이것은 진정으로 내 하루를 만들 것입니다! – Vahid

+0

필자는 내 대답을 세부 사항 및 상황에 대한 더 나은 아이디어로 업데이트했습니다. –

4

가능성이 범인 당신이 밖으로 취소하고 각 휠 이벤트에 시각적 트리를 재 구축하는 것이 사실이다. 자신의 게시물에 따르면, 해당 트리에는 "많은 수의"텍스트 요소가 포함되어 있습니다. 들어오는 각 이벤트마다 해당 텍스트 요소 각각을 다시 작성하고, 다시 포맷하고, 측정하고, 결국 렌더링해야합니다. 이것이 간단한 텍스트 크기 조정을 수행하는 방법이 아닙니다.

FormattedText 요소에 ScaleTransform을 설정하는 대신 텍스트가 포함 된 요소에 하나를 설정하십시오. 필요에 따라 RenderTransform 또는 LayoutTransform을 설정할 수 있습니다. 그런 다음 바퀴 이벤트를 받으면 Scale 속성을 적절하게 조정하십시오. 각 이벤트의 텍스트를 다시 작성하지 마십시오.

나는 또한 다른 사람이 추천 한 것을 수행하고 ItemsControl을 열 목록에 바인딩하고 그런 방식으로 텍스트를 생성합니다. 이 작업을 직접 수행해야하는 이유는 없습니다.

+0

내 마지막 코멘트를 보았습니까? 움직이는 동안/패닝 중에는 시각적 트리를 재구성하지 않아도됩니다. 각 객체에 'ScaleTransform'을 적용해야합니다. 왜냐하면 모든 객체가 화면상의 특정 위치에 있기 때문입니다. 그리고 나는 다른 사람들과의 관계에서 그 비율을 유지할 필요가있다. – Vahid

+0

실제로 팬이되는 시각적 하위 트리와 패닝을 수행하는 방법을 확인해야합니다. –

+0

텍스트 그리기는 항상 _ 비쌉니다. – Gusdor

2

@Vahid : WPF 시스템이 [retained graphics]을 사용 중입니다. 결국해야 할 일은 "이전 프레임과 비교하여 변경된 사항"만 전송하는 시스템을 고안하는 것입니다. 에 새 개체를 생성해서는 안됩니다. "객체 생성시 0 초 소요"가 아니라 렌더링 및 시간에 어떤 영향을 미치는지에 관한 것입니다. 그것은 WPF가 캐싱을 사용하여 작업하도록하는 것입니다.

렌더링을 위해 GPU에 새 개체 보내기 = 느림. 어떤 객체가 이동했는지 알려주는 업데이트 만 GPU로 전송 = .

또한, 성능 (Multithreaded UI: HostVisual - Dwayne Need)을 개선하기 위해 임의의 스레드 비주얼 을 만들 수있다. 즉, 프로젝트가 3D로 현저히 복잡하다면 WPF가 단순히 잘라내지 않을 가능성이 있습니다. DirectX ..를 직접 사용하면 훨씬 더 효과적입니다!

기사의 일부

나는 당신이 & 이해 읽을 제안 :

[Writing More Efficient ItemsControls - Charles Petzold]을 - 하나의 WPF에서 더 나은 그리기 속도를 달성하는 방법을 과정을 이해합니다.

UI가 왜 뒤떨어져 있는지에 대해서는 Dan의 답변이있는 것 같습니다. WPF가 처리 할 수있는 것 이상을 렌더링하려고하면 입력 시스템이 손상됩니다.

+0

크리스에게 감사드립니다. WPF가 보존 된 그래픽을 사용하고 있지만 패닝에 왜 영향을 미치는지 이해합니다. 패닝하는 동안 새 그래픽을 만들지 않으므로 아무것도 렌더링되지 않는다고 가정합니다. 아니면 내가 틀렸어? – Vahid

+0

제가 제안한 해결책을 시도했지만, 당신은 내 다른 질문을 볼 수 있습니다 : http://stackoverflow.com/questions/27584324/slow-pan-and-zoom-in-wpf하지만 여전히 문제를 해결하지 못했습니다 : ( – Vahid

+0

@Vahid : 더 이상 얻었습니까? 지금 정확히 어디에 있고, 무엇을했는지보기가 어렵습니다. –

관련 문제