Hi!
I display a lot of text and I have a problem of memory leak. How can I increase the size of the cache used by TextRenderer?
Hi!
I display a lot of text and I have a problem of memory leak. How can I increase the size of the cache used by TextRenderer?
I’ve notived memory leaks with the TextRenderer too. The trick as I’ve noticed it is to only make only one for each font your using, and never make any more.
I personally use my own graphics layer but inside there is a Map<Font, TextRenderer> inside. So when I set the Font to use when drawing text it gets the TextRenderer I used last time for the same Font.
i have been annoyed by textrenderer myself, so i have written a loader and renderer for angelcode’s bmfont
http://www.angelcode.com/products/bmfont/
imo this little tool produces very nice text that can compete with textrenderer!
I reproduce the memory leak even with a single text renderer The demo “TextFlow” has the same problem but in my case, the memory leak is hugely bigger
Thank you, I will have a look at it too
You could port UnicodeFont to JOGL:
https://bob.newdawnsoftware.com/repos/slick/trunk/Slick/src/org/newdawn/slick/UnicodeFont.java
Or as mentioned, just support the BMFont (AngelCode) format and then you could use Hiero:
http://slick.cokeandcode.com/demos/hiero.jnlp
The memory leak is there in the class “TextRenderer”:
public List/*<Glyph>*/ getGlyphs(CharSequence inString) {
glyphsOutput.clear();
iter.initFromCharSequence(inString);
GlyphVector fullRunGlyphVector = font.createGlyphVector(getFontRenderContext(),
iter);
We should not create a glyph vector at each call. Who wrote this source code?
And he’s gone to Google now…
Cas
Ok I will have to understand some Sun internal classes without any help.
Woa. I would have never imagined that TextRenderer caches complete Strings and not each glyph. The “last word” in text rendering, indeed! :
UnicodeFont caches individual glyphs to the backing texture and allocates multiple backing textures as needed. Also, and this may be the best part, it doesn’t leak memory.
[quote]Woa. I would have never imagined that TextRenderer caches complete Strings and not each glyph. The “last word” in text rendering, indeed!
[/quote]
The reason it does that is for correct kerning I expect. Kerning is a pain to get right, and if you don’t things really do look wrong. Or you need a good fixed width font.
Not only that. If you mix western/arabic text (left-to-right => right-to-left) in the same sentence, things become seriously hard. Java2D’s text drawing code is almost as good as MS Office (Word). Much better than any browser I tested pulls off. Rendering glyph-by-glyph would mean you’d have to code that too, which might take a few man-years.
I worked around the kerning by drawing every single pair of glyphs next to each other using AWT and then recording the difference in position between them. Seems to work perfectly. Somehow does Arabic too - don’t know how, it just works. Apparently. I can’t read Arabic :
Cas
I realize this isn’t a UnicodeFont thread, and I promise to shut up about it soon :-*… but UnicodeFont doesn’t just output a glyph, x-advance, and then the next glyph. Instead it uses AWT’s GlyphVector to determine the layout of the entire String to be rendered glyph by glyph. This way kerning, Unicode combining marks, etc are all handled properly. Here is an example in Japanese:
You could also just pre-draw your text to a BufferedImage using Java2D and then load the image as a texture. You could also do this on the fly whilst running for small pieces of text.
My fix is below; it requires some more work but the performance is noticeably better. The most worrying point is the cache of GlyphVector instances. Let me know what you think about it:
HashMap<String, GlyphVector> fullGlyphVectorCache = new HashMap<String, GlyphVector>();
HashMap<Character, GlyphMetrics> glyphMetricsCache = new HashMap<Character, GlyphMetrics>();
public List<Glyph> getGlyphs(CharSequence inString) {
glyphsOutput.clear();
GlyphVector fullRunGlyphVector;
fullRunGlyphVector = fullGlyphVectorCache.get(inString.toString());
if (fullRunGlyphVector == null) {
iter.initFromCharSequence(inString);
fullRunGlyphVector = font.createGlyphVector(getFontRenderContext(), iter);
fullGlyphVectorCache.put(inString.toString(), fullRunGlyphVector);
}
boolean complex = (fullRunGlyphVector.getLayoutFlags() != 0);
if (complex || DISABLE_GLYPH_CACHE) {
// Punt to the robust version of the renderer
glyphsOutput.add(new Glyph(inString.toString(), false));
return glyphsOutput;
}
int lengthInGlyphs = fullRunGlyphVector.getNumGlyphs();
int i = 0;
while (i < lengthInGlyphs) {
Character letter = CharacterCache.valueOf(inString.charAt(i));
GlyphMetrics metrics = glyphMetricsCache.get(letter);
if (metrics == null) {
metrics = fullRunGlyphVector.getGlyphMetrics(i);
glyphMetricsCache.put(letter, metrics);
}
Glyph glyph = getGlyph(inString, metrics, i);
if (glyph != null) {
glyphsOutput.add(glyph);
i++;
}
else {
// Assemble a run of characters that don't fit in
// the cache
StringBuffer buf = new StringBuffer();
while (i < lengthInGlyphs
&& getGlyph(inString, fullRunGlyphVector.getGlyphMetrics(i), i) == null) {
buf.append(inString.charAt(i++));
}
glyphsOutput.add(new Glyph(buf.toString(),
// Any more glyphs after this run?
i < lengthInGlyphs));
}
}
return glyphsOutput;
}
I added this because I cannot use directly Character.valueOf(char).
private static class CharacterCache {
private CharacterCache() {
}
static final Character cache[] = new Character[127 + 1];
static {
for (int i = 0; i < cache.length; i++) {
cache[i] = new Character((char) i);
}
}
public static Character valueOf(char c) {
if (c <= 127) { // must cache
return CharacterCache.cache[c];
}
return new Character(c);
}
}
I need to remove generics too.