TextRenderer and memory issues

classic Classic list List threaded Threaded
11 messages Options
Reply | Threaded
Open this post in threaded view
|

TextRenderer and memory issues

r.jaoui
Hello !

I'm currently working a memory leak that I can't understand.

It has to do with the TextRenderer class. Basically, in a specific JOGL application, inside a panel listener I have a TextRenderer Object that manages... well, text rendering.

The issue is that this Object isn't created in the init() method since I need to be able to render with different fonts, that I can't know in advance. For now, I have the following function that "updates" the TextRenderer (and that may run at every frame of the application) :

private void initTextRenderer() {
        if(context.listener.textRenderer == null) {
                context.listener.textRenderer = new TextRenderer(currentStyle.currentFont.getFont(), true, true);
        }
        //The else condition is simplified here, in the real code it checks if the fonts are actually the same but not
        //necessarily the same instance...
        else if(!context.listener.textRenderer.getFont().equals(currentStyle.currentFont.getFont())) {
                context.listener.textRenderer.dispose();
                context.listener.textRenderer = new TextRenderer(currentStyle.currentFont.getFont(), true, true);
        }
}

The issue is then that creating a new TextRenderer, even if disposing of the previous one beforehand, seems to cause a memory leak that I can't wrap my head around. This memory leak is pretty slow and only starts occuring after a few seconds (although at this point this method has already been called hundreds of times).

Is there a way to modify this code so that I can update the TextRenderer (or even just update the Font Object it encapsulates) to be able to use it as a text renderer for multiple fonts (again here it is important to emphasize that I can't have a TextRenderer for each font since i dont know in advance the number of fonts, and this number may be strictly increasing as time goes by...).

If not, is there another way to render text in JOGL that wouldn't suffer from this problem ?

Thanks !
Reply | Threaded
Open this post in threaded view
|

Re: TextRenderer and memory issues

gouessej
Administrator
Hello

Either use the text renderer of the graph API within JOGL instead of using the legacy text renderer or use any engine or framework based on JOGL that provides its own text rendering. LibGDX, jMonkeyEngine, Java3D and JogAmp's Ardor3D Continuation do that.

I fixed a memory leak many years ago in the text renderer you use. I'm afraid, feel free to investigate but I won't fix the one you've found.
Julien Gouesse | Personal blog | Website
Reply | Threaded
Open this post in threaded view
|

Re: TextRenderer and memory issues

r.jaoui
Thanks ! I'll try using the graph text renderer (the TextRegionUtil class ?)

Looking at the documentation, I see that the font is directly passed as an argument when rendering text, which seems perfect, but in some cases I also need to work with the Metrics (to get the Ascent, Descent...), is there a way to get a FontRenderContext from the TextRegionUtil or to construct one from it ? Or is it better to simply use FontMetrics directly ?

Thanks :)
Reply | Threaded
Open this post in threaded view
|

Re: TextRenderer and memory issues

r.jaoui
So I don't think that I can use the Graph API for any of this, since
- It renders 3D text and I need a renderer that is coherent with a specific font size (if the font size is 30, I need a 30px high font. This may be solvable using the pixelSize of course, but I don't see how).
- More importantly, it creates artifacts with a lot of font. Here is an example from my code, notice the A :

I also saw this question on this same forum : TextRegionUtil rendering artifacts.

I can't use any external engine/framework for this, so is there a way to natively render text (basically, to write a text renderer), or even easier : is there somewhere where I can find the TextRenderer source code to use it as inspiration for a custom text renderer ?

Again, thanks a lot for the help :)
Reply | Threaded
Open this post in threaded view
|

Re: TextRenderer and memory issues

gouessej
Administrator
I remind you that JOGL is open source:
https://jogamp.org/cgit/jogl.git/tree/
Enjoy.

You already use a third party API, I don't see why you can't use an engine or a framework. Some engines allow to reuse plain JOGL rendering. For example, JogAmp's Ardor3D Continuation uses the render delegates to achieve that.

You can use GLUT in JOGL but it's not really future proof.
Julien Gouesse | Personal blog | Website
Reply | Threaded
Open this post in threaded view
|

Re: TextRenderer and memory issues

farrellf
In reply to this post by r.jaoui
Sounds like the memory leak I experienced a while ago:
http://forum.jogamp.org/TextRenderer-memory-leak-td4039967.html

I didn't find an easy way to fix TextRenderer, and like you also found out, the graph API's text render has a bunch of problems that made it unusable for me.

I ended up writing my own 2D text rendering code. It's very primitive (only 2D, only 3 font sizes, only black text, only horizontal or vertical text, etc.) but works perfectly for my use cases.

So unfortunately, like Julien said, you'll have to use another library (or DIY it.)

My code isn't meant to be a library, but you can probably rip out the parts you need and work it into your codebase. My code works with OpenGL 3.2+ and OpenGL ES 3.2.

Here is what my text rendering looks like:
https://youtu.be/FqfgBnCdrTo?t=30

Functions for drawing text and getting the text width (in pixels) start here:
https://github.com/farrellf/TelemetryViewer/blob/master/Telemetry%20Viewer/src/OpenGL.java#L486

This function updates the texture atlases. Uncomment the lines at the end to see what the atlas looks like:
https://github.com/farrellf/TelemetryViewer/blob/master/Telemetry%20Viewer/src/OpenGL.java#L822

Info about the typical text height (in pixels) is around here:
https://github.com/farrellf/TelemetryViewer/blob/master/Telemetry%20Viewer/src/OpenGL.java#L1658

The shader compilation stuff is here (among all of the other code not relevant to text rendering...)
https://github.com/farrellf/TelemetryViewer/blob/master/Telemetry%20Viewer/src/OpenGL.java#L1687

That whole file (OpenGL.java) is my mini abstraction layer for OpenGL. Good luck!

-Farrell
Reply | Threaded
Open this post in threaded view
|

Re: TextRenderer and memory issues

r.jaoui
I had a look in your code, I'm fairly new to OpenGL and I don't think I'll be able to write something like this for now :'(

I know that it would technically be possible (albeit terribly slow) to render text using BufferedImage Graphics and then using the image's raster to create an OpenGL texture, and I could perhaps use this to create a glyphs array for any Font that I need (as bitmap glyphs), but this seems very slow, very heavy in ressource usage and the necessity to delete the textures after usage complicates a lot this approach in my case (this would mean having active ressource management for my fonts).

So I'm a bit stuck for now. Perhaps there is a standalone library that does this ? If not I pretty much have no solution...

I'll try to look a bit more in depth in your code though, thanks :)
Reply | Threaded
Open this post in threaded view
|

Re: TextRenderer and memory issues

r.jaoui
This post was updated on .
Hm well I may have a solution and I just need to make sure that it's not totally ridiculous and slow.

So I have already created 2D polygon renderer for both the odd-even winding rule and the non-zero winding rule for another project (using the stencil test, and benchmarking them show that they're pretty fast. They could of course be better but they're still good enough for now), as well as (CPU-based) adaptative tesselation of paths (calculating the curvature of the path to get a point density with the current screen "resolution" in mind), and I just saw that from an AWT Font objet it is possible to get single glyphs as Shape objects, containing lines, quadratic and cubic sections and so on... I made a quick Swing test (non OpenGL) to see if what I had in mind seems possible (note that I am not using the tesselation here, and only rendering the quadratic and cubic sections as lines) :



The left text is rendered using Swing, the left one is only rendering the corresponding Shape Object (as I said, not tessellated yet). The colors correspond to different types of segments (red for closing loop sections, yellow for cubic sections, green for lines and blue for quadratic sections). Would this be a possible way to render text in OpenGL (therefore only using basic rendering such as with triangles and so on) or would either the gathering of the Shape or it's rendering be way too slow (also considering that I don't need for a book to be rendered to the screen at each frame).

Note that if I were to use this, I would save into my Font objects the array of shapes at once so that when rendering text, only the actual path would have to be tesselated and drawn, but I won't need to gather the vertices themselves every time. I assume that even if this may be slower than the more "modern" approaches, it should still be better than storing an array of bitmap glyphs...

EDIT : I'm adding the section of code that manages rendering the shape,

        public void drawCharShape(Graphics g, char c, int x, int y) {
                g.setColor(new Color(0, 0, 0, 40));
                g.drawString(Character.toString(c), x, y);
                GlyphVector v = f.createGlyphVector(new FontRenderContext(null, false, false), Character.toString(c));
                Shape glyph = v.getGlyphOutline(0);
                PathIterator pi = glyph.getPathIterator(null);
                double[] cache = new double[4];
                double[] pos = new double[6];
                while(!pi.isDone()) {
                        int type = pi.currentSegment(pos);
                        //DRAW THE CURRENT SECTION
                        switch(type) {
                        case PathIterator.SEG_CLOSE:
                                g.setColor(new Color(255, 0, 0));
                                g.drawLine((int) cache[0]+x, (int) cache[1]+y, (int) pos[0]+x, (int) pos[1]+y);
                                break;
                        case PathIterator.SEG_CUBICTO:
                                g.setColor(new Color(255, 255, 0));
                                g.drawLine((int) cache[2]+x, (int) cache[3]+y, (int) pos[4]+x, (int) pos[5]+y);
                                g.drawLine((int) pos[0]+x, (int) pos[1]+y, (int) pos[4]+x, (int) pos[5]+y);
                                break;
                        case PathIterator.SEG_LINETO:
                                g.setColor(new Color(0, 255, 0));
                                g.drawLine((int) cache[2]+x, (int) cache[3]+y, (int) pos[0]+x, (int) pos[1]+y);
                                break;
                        case PathIterator.SEG_QUADTO:
                                g.setColor(new Color(0, 0, 255));
                                g.drawLine((int) cache[2]+x, (int) cache[3]+y, (int) pos[2]+x, (int) pos[3]+y);
                                break;
                        }
                        g.setColor(new Color(0, 0, 0));
                        //UPDATE CACHE POSITIONS
                        switch(type) {
                        case PathIterator.SEG_CUBICTO:
                                g.fillOval((int) pos[0]+x-2, (int) pos[1]+y-2, 5, 5);
                                g.fillOval((int) pos[2]+x-2, (int) pos[3]+y-2, 5, 5);
                                g.fillOval((int) pos[4]+x-2, (int) pos[5]+y-2, 5, 5);
                                cache[2] = pos[4];
                                cache[3] = pos[5];
                                break;
                        case PathIterator.SEG_LINETO:
                                g.fillOval((int) pos[0]+x-2, (int) pos[1]+y-2, 5, 5);
                                cache[2] = pos[0];
                                cache[3] = pos[1];
                                break;
                        case PathIterator.SEG_MOVETO:
                                g.fillOval((int) pos[0]+x-2, (int) pos[1]+y-2, 5, 5);
                                cache[0] = pos[0];
                                cache[1] = pos[1];
                                cache[2] = pos[0];
                                cache[3] = pos[1];
                                break;
                        case PathIterator.SEG_QUADTO:
                                g.fillOval((int) pos[0]+x-2, (int) pos[1]+y-2, 5, 5);
                                g.fillOval((int) pos[2]+x-2, (int) pos[3]+y-2, 5, 5);
                                cache[2] = pos[2];
                                cache[3] = pos[3];
                                break;
                        }
                        pi.next();
                }
        }


Thanks again for any feedback :)
Reply | Threaded
Open this post in threaded view
|

Re: TextRenderer and memory issues

r.jaoui
Alright so just to close out this subject, the very simple text renderer I just built with this idea works perfectly, for any AWT font, any color (transparency works), with full antialiasing...

I'll benchmark it tomorrow to make sure that it isn't completely slow as compared to other options, and if not, it seems to work very well (joined is a picture of the renderer working, using the wackiest and most "intricate" font I could find in my computer, with transparency, anti-aliasing... The number of rendered vertices here is 5238, and with no tessellation of the edges, the minimum number with this font would be 4662, so this is pretty close to optimal. Memory usage is also pretty low and constant (here, 150 MB with the rest of the application).



Finally, here are two picture showing the font tessellation with different precisions :




Thanks all for your help :)
Reply | Threaded
Open this post in threaded view
|

Re: TextRenderer and memory issues

Martin
In reply to this post by r.jaoui
Hi!

This post may be interesting for anyone willing to fix TextRenderer memory issues.

An existing improvement of TextRenderer was never merged. I build it and it is much better. See the readme to get it.

Martin
Reply | Threaded
Open this post in threaded view
|

Re: TextRenderer and memory issues

yaqiang
I also experienced TextRenderer memory leak issues when rendering string using new TextRenderer objects. My solution is using only one TextRenderer object and update it when new font needed.

Update TextRenderer in the display function and dispose and null it at the end of the function (https://github.com/meteoinfo/MeteoInfo/blob/master/meteoinfo-chart/src/main/java/org/meteoinfo/chart/jogl/GLPlot.java#L1224-L1320).

With a new font, following updateTextRender method is used.

The TextRenderer update function (https://github.com/meteoinfo/MeteoInfo/blob/master/meteoinfo-chart/src/main/java/org/meteoinfo/chart/jogl/GLPlot.java#L2267-L2285):

    void updateTextRender(Font font) {
        boolean newTR = false;
        if (this.textRenderer == null) {
            newTR = true;
        } else {
            if (!this.textRenderer.getFont().equals(font)) {
                //this.textRenderer.dispose();
                newTR = true;
            }
        }
        if (newTR) {
            if (this.dpiScale == 1) {
                textRenderer = new TextRenderer(font, true, true);
            } else {
                textRenderer = new TextRenderer(new Font(font.getFontName(), font.getStyle(),
                        (int) (font.getSize() * this.dpiScale)), true, true);
            }
        }
    }

It works fine at present. Any better solution is welcome.
www.meteothink.org