2010-11-01 30 views
7

Trong Wpf (4.0) hộp danh sách của tôi (sử dụng VirtualizingStackPanel) chứa 500 mục. Mỗi mục là một loại tùy chỉnhTại sao DrawingContext.DrawText của Wpf lại đắt đến vậy?

class Page : FrameworkElement 
... 
protected override void OnRender(DrawingContext dc) 
{ 
    // Drawing 1000 single characters to different positions 
    //(formattedText is a static member which is only instantiated once and contains the string "A" or "B"...) 
    for (int i = 0; i < 1000; i++) 
    dc.DrawText(formattedText, new Point(....)) 


    // Drawing 1000 ellipses: very fast and low ram usage 
    for (int i = 0; i < 1000; i++)  
    dc.DrawEllipse(Brushes.Black, null, new Point(....),10,10) 


} 

Bây giờ khi di chuyển thanh cuộn của ListBox lui để mỗi của thị giác mục được tạo ra ít nhất một lần sử dụng ram đi lên đến 500 Mb sau một thời gian và sau đó - sau một thời gian - quay trở lại ca 250 Mb nhưng vẫn ở cấp độ này. Bộ nhớ bị rò rỉ ? Tôi nghĩ rằng lợi thế của một VirtualizingStackPanel là hình ảnh mà không cần thiết/có thể nhìn thấy được xử lý ...

Dù sao, sử dụng ram cực đoan này chỉ xuất hiện khi vẽ văn bản bằng cách sử dụng "DrawText". Vẽ các đối tượng khác như "DrawEllipse" không tiêu thụ quá nhiều bộ nhớ.

Có cách nào hiệu quả hơn để vẽ nhiều mục văn bản hơn bằng cách sử dụng "DrawText" của Drawing.Context không?

Đây là mẫu đầy đủ (chỉ cần tạo một dự án WPF Application mới và thay thế mã Window1): (Tôi biết có FlowDocument và FixedDocument nhưng họ không thay thế) XAML:

<Window x:Class="WpfApplication1.Window1" 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
Title="Window1" Height="900" Width="800"> 
<Grid Background="Black"> 
    <ListBox Name="lb" ScrollViewer.CanContentScroll="True" Background="Black"> 
     <ListBox.ItemsPanel> 
      <ItemsPanelTemplate> 
       <VirtualizingStackPanel Orientation="Horizontal" /> 
      </ItemsPanelTemplate> 
     </ListBox.ItemsPanel> 
    </ListBox> 
</Grid> 
</Window> 

Và các Window1.xaml.cs:

public partial class Window1 : Window 
{ 
    readonly ObservableCollection<FrameworkElement> collection = new ObservableCollection<FrameworkElement>(); 

    public Window1() 
    { 
     InitializeComponent(); 

     for (int i = 0; i < 500; i++) 
     { 
      collection.Add(new Page(){ Width = 500, Height = 800 }); 
     } 

     lb.ItemsSource = collection; 
    } 
} 

public class Page : FrameworkElement 
{ 
    static FormattedText formattedText = new FormattedText("A", CultureInfo.GetCultureInfo("en-us"), 
               FlowDirection.LeftToRight, 
               new Typeface(new FontFamily("Arial").ToString()), 
               12,Brushes.Black); 
    protected override void OnRender(DrawingContext dc) 
    { 
     dc.DrawRectangle(Brushes.White, null, new Rect(0, 0, Width, Height)); 
     double yOff = 0; 
     for (int i = 0; i < 1000; i++) // draw 1000 "A"s 
     { 
      dc.DrawText(formattedText, new Point((i % 80) * 5, yOff)); 
      if (i % 80 == 0) yOff += 10; 

     } 

    } 

} 
+0

Bạn có thể thử StreamGeometry. Đó là trọng lượng tương đối nhẹ. http://msdn.microsoft.com/en-us/library/ms742199.aspx Mặt khác. Tôi phải nói. DrawText là thứ ít được cân nhắc hơn. Không biết tại sao nó lại lấy nhiều tài nguyên. Bạn có mẫu nào cho kịch bản trên không? –

+0

DrawingContext.DrawGlyph có vẻ nhanh hơn nhiều so với DrawText. – fritz

Trả lời

1

trong khi điều này là không hoàn toàn hữu ích cho bạn, kinh nghiệm của tôi với VirtualizingStackPanel không là nó disposes của các đối tượng không theo quan điểm, nhưng nó cho phép đối tượng không theo quan điểm là d được thiết lập để phục hồi bộ nhớ khi ứng dụng cần nhiều bộ nhớ hơn, điều này sẽ dẫn đến việc sử dụng bộ nhớ của bạn khi có bộ nhớ.

Có thể dc.DrawText đang kích hoạt BuildGeometry() cho mỗi đối tượng được định dạng và bạn có thể mang nó ra ngoài vòng lặp không? Tôi không biết BuildGeometry có bao nhiêu công việc, nhưng có thể DrawingContext chỉ có khả năng chấp nhận hình học và cuộc gọi BuildGeometry đang được gọi không cần thiết 999 lần trong mẫu của bạn. Có một cái nhìn tại địa chỉ:

http://msdn.microsoft.com/en-us/library/system.windows.media.formattedtext.aspx

để xem liệu có bất kỳ tối ưu hóa khác bạn có thể thực hiện.

Bạn có thể xuất một số dữ liệu hồ sơ bộ nhớ và một số dữ liệu định thời trong vòng của bạn để cho biết cảm giác làm chậm hoặc bộ nhớ tăng theo kiểu phi tuyến trong vòng lặp không?

+0

Đó là lý do tại sao DrawingContext.DrawGlyph nhanh hơn DrawText: nó chỉ khởi chạy BuildGemeotry một lần khi tạo. Nhưng disadvnatage là các nhân vật trông mờ hơn so với DrawText. – fritz

+1

@fritz, DrawGlyphRun tạo ra văn bản mờ vì nó không phù hợp với pixel của thiết bị cho bạn. Bạn có thể căn chỉnh nó với một sự kết hợp của 'GlyphRun.ComputeAlignmentBox()' và 'DrawingContext.PushGuidelineSet()'. –

6

Một đóng góp lớn là sự kiện (dựa trên kinh nghiệm của tôi với GlyphRun mà tôi nghĩ rằng được sử dụng phía sau hậu trường) mà nó sử dụng ít nhất 2 từ điển tra cứu cho mỗi ký tự để có được chỉ số glyph và chiều rộng. Một hack tôi sử dụng trong dự án của tôi là tôi đã tìm ra sự bù đắp giữa giá trị ASCII và chỉ mục glyph cho các ký tự chữ và số cho phông chữ mà tôi đang sử dụng. Sau đó tôi sử dụng để tính toán các chỉ số glyph cho mỗi ký tự thay vì làm từ điển tra cứu. Điều đó đã cho tôi một tốc độ khá tốt. Ngoài ra thực tế là tôi có thể tái sử dụng các glyph chạy di chuyển nó xung quanh với một biến đổi dịch mà không tính toán lại tất cả mọi thứ hoặc những tra cứu từ điển. Hệ thống không thể thực hiện việc hack này vì nó không phải là đủ chung để được sử dụng trong mọi trường hợp. Tôi tưởng tượng một hack tương tự có thể được thực hiện cho các phông chữ khác. Tôi chỉ được thử nghiệm với Arial, các phông chữ khác có thể được lập chỉ mục khác nhau. Có thể thậm chí còn nhanh hơn với phông chữ đơn cách vì bạn có thể giả định chiều rộng glyph sẽ giống nhau và chỉ thực hiện một cái nhìn thay vì một ký tự cho mỗi ký tự, nhưng tôi chưa thử nghiệm điều này.

Người đóng góp chậm khác là mã nhỏ này, tôi chưa tìm ra cách hack nó. typeface.TryGetGlyphTypeface (ra glyphTypeface);

Đây là mã của tôi cho hack Arial chữ và số của tôi (khả năng tương thích với các nhân vật khác chưa biết)

public GlyphRun CreateGlyphRun(string text,double size) 
    { 
     Typeface typeface = new Typeface("Arial"); 
     GlyphTypeface glyphTypeface; 
     if (!typeface.TryGetGlyphTypeface(out glyphTypeface)) 
      throw new InvalidOperationException("No glyphtypeface found");   

     ushort[] glyphIndexes = new ushort[text.Length]; 
     double[] advanceWidths = new double[text.Length]; 

     for (int n = 0; n < text.Length; n++) { 
      ushort glyphIndex = (ushort)(text[n] - 29); 
      glyphIndexes[n] = glyphIndex; 
      advanceWidths[n] = glyphTypeface.AdvanceWidths[glyphIndex] * size; 
     } 

     Point origin = new Point(0, 0); 

     GlyphRun glyphRun = new GlyphRun(glyphTypeface, 0, false, size, glyphIndexes, origin, advanceWidths, null, null, null, 
             null, null, null); 
     return glyphRun; 
    } 
+0

Mã đẹp để bắt đầu. Cảm ơn! – LionAM

1

tôi tìm thấy giải pháp user638350 để thể rất hữu ích; trong trường hợp của tôi, tôi chỉ sử dụng một kích thước phông chữ để tối ưu hóa sau giảm thời gian xuống dưới 0.0000 trên 20.000 khung hình xuống từ 0,0060ms mỗi khung hình. Hầu hết sự chậm lại là từ 'TryGetGlyphTypeface' và 'AdvanceWidths' và do đó cả hai được lưu trữ. Ngoài ra, thêm tính toán một vị trí bù đắp và theo dõi tổng chiều rộng.

private static Dictionary<ushort,double> _glyphWidths = new Dictionary<ushort, double>(); 
    private static GlyphTypeface _glyphTypeface; 
    public static GlyphRun CreateGlyphRun(string text, double size, Point position) 
    { 
     if (_glyphTypeface == null) 
     { 
      Typeface typeface = new Typeface("Arial"); 
      if (!typeface.TryGetGlyphTypeface(out _glyphTypeface)) 
       throw new InvalidOperationException("No glyphtypeface found");     
     } 

     ushort[] glyphIndexes = new ushort[text.Length]; 
     double[] advanceWidths = new double[text.Length]; 

     var totalWidth = 0d; 
     double glyphWidth; 

     for (int n = 0; n < text.Length; n++) 
     { 
      ushort glyphIndex = (ushort)(text[n] - 29); 
      glyphIndexes[n] = glyphIndex; 

      if (!_glyphWidths.TryGetValue(glyphIndex, out glyphWidth)) 
      { 
       glyphWidth = _glyphTypeface.AdvanceWidths[glyphIndex] * size; 
       _glyphWidths.Add(glyphIndex, glyphWidth); 
      } 
      advanceWidths[n] = glyphWidth; 
      totalWidth += glyphWidth; 
     } 

     var offsetPosition = new Point(position.X - (totalWidth/2), position.Y - 10 - size); 

     GlyphRun glyphRun = new GlyphRun(_glyphTypeface, 0, false, size, glyphIndexes, offsetPosition, advanceWidths, null, null, null, null, null, null); 

     return glyphRun; 
    } 
Các vấn đề liên quan