Mengapa DrawingContext.DrawText Wpf begitu mahal?

Di Wpf (4.0) kotak daftar saya (menggunakan VirtualizingStackPanel) berisi 500 item. Setiap item adalah Tipe khusus

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)


}

Sekarang ketika menggerakkan bilah gulir kotak daftar bolak-balik sehingga visual setiap item dibuat setidaknya sekali penggunaan ram naik hingga 500 Mb setelah beberapa saat dan kemudian - setelah beberapa saat - kembali ke sekitar 250 Mb tetapi tetap pada level ini . Kebocoran memori? Saya pikir keuntungan dari VirtualizingStackPanel adalah visual yang tidak diperlukan/terlihat dibuang...

Pokoknya penggunaan ram ekstrim ini hanya muncul saat menggambar teks menggunakan "DrawText". Menggambar objek lain seperti "DrawEllipse" tidak menghabiskan banyak memori.

Apakah ada cara yang lebih efisien untuk menggambar banyak item teks dibandingkan menggunakan "DrawText" Drawing.Context?

Berikut contoh lengkapnya (buat saja proyek Aplikasi Wpf baru dan ganti kode window1): (Saya tahu ada FlowDocument dan FixedDocument tetapi tidak ada alternatif lain) 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>

Dan 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;

        }

    }

}

person fritz    schedule 01.11.2010    source sumber
comment
Anda dapat mencoba StreamGeometry. Yang mana bobotnya relatif ringan. msdn.microsoft.com/en-us/library/ms742199.aspx Di sisi lain. Saya harus mengatakan. DrawText adalah hal yang relatif lebih ringan. Tidak tahu mengapa ini menghabiskan banyak sumber daya. Apakah Anda memiliki contoh untuk skenario di atas?   -  person Prince Ashitaka    schedule 01.11.2010
comment
DrawingContext.DrawGlyph tampaknya jauh lebih cepat daripada DrawText.   -  person fritz    schedule 03.11.2010


Jawaban (3)


Meskipun ini tidak sepenuhnya berguna bagi Anda, pengalaman saya dengan VirtualizingStackPanel bukan berarti ia membuang objek yang tidak terlihat, namun memungkinkan objek yang tidak terlihat dibuang untuk memulihkan memori ketika aplikasi memerlukan lebih banyak memori, yang akan menghasilkan dalam penggunaan memori Anda membengkak ketika ada memori yang tersedia.

Apakah mungkin dc.DrawText mengaktifkan BuildGeometry() untuk setiap objek formattedText, dan Anda dapat membawanya ke luar loop? Saya tidak tahu seberapa banyak pekerjaan yang dilakukan BuildGeometry, tetapi mungkin saja DrawingContext hanya mampu menerima geometri, dan panggilan BuildGeometry dipanggil secara tidak perlu sebanyak 999 kali dalam sampel Anda. Lihat:

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

untuk melihat apakah ada pengoptimalan lain yang dapat Anda lakukan.

Bisakah Anda mengeluarkan beberapa data profil memori dan beberapa data pengaturan waktu dalam loop Anda untuk memberikan gambaran apakah data tersebut melambat, atau memori meningkat secara non-linier selama loop?

person David Hagan    schedule 11.01.2011
comment
Itu sebabnya DrawingContext.DrawGlyph lebih cepat daripada DrawText: ia hanya mengaktifkan BuildGemeotry sekali saat dibuat. Namun kekurangannya adalah karakter terlihat lebih buram dibandingkan dengan DrawText. - person fritz; 23.01.2011
comment
@fritz, DrawGlyphRun menghasilkan teks buram karena tidak sejajar dengan piksel perangkat untuk Anda. Anda bisa menyelaraskannya sendiri dengan kombinasi GlyphRun.ComputeAlignmentBox() dan DrawingContext.PushGuidelineSet(). - person Brian Reichle; 27.11.2013

Kontributor besar adalah fakta (berdasarkan pengalaman saya dengan GlyphRun yang menurut saya digunakan di belakang layar) bahwa ia menggunakan setidaknya 2 pencarian kamus per karakter untuk mendapatkan indeks dan lebar mesin terbang. Salah satu peretasan yang saya gunakan dalam proyek saya adalah saya menemukan offset antara nilai ASCII dan indeks mesin terbang untuk karakter alfanumerik untuk font yang saya gunakan. Saya kemudian menggunakannya untuk menghitung indeks mesin terbang untuk setiap karakter daripada mencari kamus. Itu memberi saya kecepatan yang lumayan. Juga fakta bahwa saya dapat menggunakan kembali mesin terbang untuk memindahkannya dengan transformasi terjemahan tanpa menghitung ulang semuanya atau pencarian kamus tersebut. Sistem tidak dapat melakukan peretasan ini sendiri karena tidak cukup umum untuk digunakan dalam setiap kasus. Saya membayangkan peretasan serupa dapat dilakukan untuk font lain. Saya hanya menguji dengan Arial, font lain dapat diindeks secara berbeda. Mungkin bisa lebih cepat lagi dengan font spasi tunggal karena Anda mungkin bisa berasumsi lebar mesin terbang semuanya akan sama dan hanya melakukan satu pencarian, bukan satu per karakter, tapi saya belum mengujinya.

Kontributor perlambatan lainnya adalah kode kecil ini, saya belum menemukan cara untuk meretasnya. typeface.TryGetGlyphTypeface(keluar glyphTypeface);

Ini kode saya untuk hack Arial alfanumerik saya (kompatibilitas dengan karakter lain tidak diketahui)

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;
    }
person user638350    schedule 13.08.2012

Saya menemukan solusi user638350 sangat berguna; dalam kasus saya, saya hanya menggunakan satu ukuran font sehingga optimasi berikut mengurangi waktu menjadi kurang dari 0,0000 lebih dari 20.000 frame turun dari 0,0060ms setiap frame. Sebagian besar perlambatan berasal dari 'TryGetGlyphTypeface' dan 'AdvanceWidths' sehingga keduanya di-cache. Juga, menambahkan penghitungan posisi offset dan melacak lebar total.

    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;
    }
person jv_    schedule 08.06.2015