In Wpf (4.0) my listbox (using a VirtualizingStackPanel) contains 500 items. Each item is of a custom Type
class Page : FrameworkElement
...
protected over
A big contributor is the fact(based on my experience with GlyphRun which I think gets used behind the scenes) that it uses at least 2 dictionary look-ups per character to get the glyph index and width. One hack I used in my project was I figured out the offset between the ASCII value and the glyph index for alphanumeric characters for the font I was using. I then used that to calculate the glyph indexes for each character rather than doing the dictionary look up. That gave me a decent speed up. Also the fact that I could reuse the glyph run moving it around with a translate transform without recalculating everything or those dictionary lookups. The system can't do this hack on it's own because it is not generic enough to be used in every case. I imagine a similar hack could be done for other fonts. I only tested with Arial,other fonts could be indexed differently. May be able to go even faster with a mono-spaced font since you may be able to assume the glyph widths would all be the same and only do one look up rather than one per character, but I haven't tested this.
The other slow down contributor is this little code, I haven't figured out how to hack it yet. typeface.TryGetGlyphTypeface(out glyphTypeface);
Here is my code for my alphanumeric Arial hack(compatibility with other characters unknown)
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;
}
While this isn't entirely useful to you, my experience with VirtualizingStackPanel isn't that it disposes of objects not in view, but that it allows objects not in view to be disposed to recover memory when the application needs more memory, which should result in your memory usage ballooning when there is memory available.
Is it possible that dc.DrawText is firing BuildGeometry() for each formattedText object, and that you can bring that outside the loop? I don't know how much work BuildGeometry is, but it's possible that the DrawingContext is only capable of accepting geometry, and the BuildGeometry call is being called unnecessarily 999 times in your sample. Have a look at:
http://msdn.microsoft.com/en-us/library/system.windows.media.formattedtext.aspx
to see whether there are any other optimizations you can make.
Can you output some memory profile data and some timing data within your loops to give a sense of whether it's slowing down, or the memory is increasing in a non-linear fashion during the loop?
I found user638350's solution to be very useful; in my case i only use one font size so the following optimizations reduced the time to less than 0.0000 over 20,000 frames down from 0.0060ms every frame. Most of the slow down is from 'TryGetGlyphTypeface' and 'AdvanceWidths' and so these two are cached. Also, added calculating an offset position and tracking a total width.
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;
}