Hello Guest

Weird issue occuring when rendering fonts with stbtt_truetype

  • 8 Replies
  • 2570 Views
Hi!

I experience some weird behaviour when rendering a BakedFont object in OpenGL.

I have written some code that uses stbtt_truetype's BakedFont in order to render a String with the specified font.
However, for days I have been trying to solve a very weird issue when it comes to the ByteBuffers.

The code below works fine. It does not crash or do anything else crazy.
However, if I add
Code: [Select]
stbtt_FreeBitMap(ttf) after the
Code: [Select]
stbtt_BakeFontBitmap(...) call - it crashes.
The same thing occurs if I free the bitmap object after the
Code: [Select]
glTexImage2D(...)] call.
Not only this, but it also crashes if I remove the ByteBuffer ttf as a parameter into the Font object.
Basically, it seems like the ttf object has be kept alive in order for it to NOT crash.

Just to be clear, it does not crash in the creating process - but rather during the rendering process.
I can't see any visible flaws in my code (but there must be... because it crashes), and I have difficulties understanding the crash report from Java.
Quote
EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x00007ffe7c96829a, pid=9772, tid=17808
This tells me that I access memory that is NULL (I guess?). This makes be believe that the program tries to access the data after it has been removed.
Here is a pastebin link to the full crash report:
https://pastebin.pl/view/aa818779

The creating process:

Code: [Select]
/** Creates a Font object with a fixed size from a path to a .ttf file.
     * @param path - the path to the .ttf file.
     * @param fontSize - the size in pixels
     * @return the generated Font object that is needed upon rendering.
     */
    @SuppressWarnings("unused")
    public Font loadFont(final String path, final int fontSize) {

        //Convert .ttf file to data we can deal with, that being a ByteBuffer.
        ByteBuffer ttf = Resources.loadFileAsByteBuffer(path);
        STBTTFontinfo info = STBTTFontinfo.create();

        if(ttf == null || !STBTruetype.stbtt_InitFont(info,ttf)) {
            throw new IllegalStateException("Failed to initialize font information. Check the log for exact reason.");
        }

        try (MemoryStack stack = stackPush()) {
            IntBuffer pAscent  = stack.mallocInt(1);
            IntBuffer pDescent = stack.mallocInt(1);
            IntBuffer pLineGap = stack.mallocInt(1);

            stbtt_GetFontVMetrics(info, pAscent, pDescent, pLineGap);

        }

        //Capacity is the ASCII max
        STBTTBakedChar.Buffer charData = STBTTBakedChar.malloc(215);

        int bitmapWidth  = fontSize * 16;
        int bitmapHeight = fontSize * 16;

        ByteBuffer bitmap = BufferUtils.createByteBuffer(bitmapWidth * bitmapHeight);

        stbtt_BakeFontBitmap(ttf, fontSize, bitmap, bitmapWidth, bitmapHeight, 32, charData);
        int texId = glGenTextures();
        glBindTexture(GL_TEXTURE_2D, texId);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R,GL_ONE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G,GL_ONE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B,GL_ONE);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA8, bitmapWidth, bitmapHeight, 0, GL_ALPHA, GL_UNSIGNED_BYTE, bitmap);

        glBindTexture(GL_TEXTURE_2D, 0);

        Texture2D texture2D = new Texture2D(texId, bitmapWidth, bitmapHeight);

        return new Font(fontSize,ttf,info,charData,texture2D);
    }

This returns a Font object. This contains the information that we want to keep until later. Such as the STBTTFontInfo.

The rendering process
Code: [Select]
/** Draws a string with a specified font,color and transform to the screen.
     *
     * @param font - the Font object. This contains necessary information regarding the font that is used during rendering.
     * @param text - the string that is to be rendered.
     * @param color - the RGBA color.
     * @param transform - the Matrix4f transform.
     */
    public void drawText(final Font font, final String text, final Vector4f color,
                         final Matrix4f transform) {

        FloatBuffer x = BufferUtils.createFloatBuffer(1),y = BufferUtils.createFloatBuffer(1);
        STBTTAlignedQuad q = STBTTAlignedQuad.create();
        for(int i = 0; i < text.length(); i++) {
            stbtt_GetBakedQuad(font.getCharData(), font.getBitmapWidth(), font.getBitMapHeight(), text.charAt(i) - 32,
                    x, y, q, false);

            Vertex[] vertices = new Vertex[]{
                    new Vertex(new Vector2f( q.x0(), q.y0()), color, transform, new Vector2f(q.s0(), q.t0()),
                            font.getTexture2d().getRenderSlot()),

                    new Vertex(new Vector2f(q.x1(), q.y0()), color, transform, new Vector2f(q.s1(), q.t0()),
                            font.getTexture2d().getRenderSlot()),

                    new Vertex(new Vector2f(q.x1(), q.y1()), color, transform, new Vector2f(q.s1(), q.t1()),
                            font.getTexture2d().getRenderSlot()),

                    new Vertex(new Vector2f(q.x0(), q.y1()), color, transform, new Vector2f(q.s0(), q.t1()),
                            font.getTexture2d().getRenderSlot())
            };

            DefaultModel model = new DefaultModel(vertices, quadIndices);

            batchRenderer.addToDrawCall(model, font.getTexture2d(), defaultShader);
        }

This renders a String with the created font. A Vertex object simply contains the following data: (Vector2f pos, Vector4f color, Matrix4f transform, Vector2f textureCoords).
These vertices are then put into a Model object which is then inserted into a batch rendering system. All the batches are drawn at the end of each render call.

Does anyone know why it crashes? If so, how can I modify my code to make it stop? I tried using getCodePointBitmap(...) but it crashes as well.
If you need additional information, feel free to ask!

Thanks in advance :)


« Last Edit: April 28, 2022, 10:03:25 by Dubstepzedd »

*

Offline spasi

  • *****
  • 2261
    • WebHotelier
Re: Weird issue occuring when rendering fonts with stbtt_truetype
« Reply #1 on: May 05, 2022, 08:29:55 »
Hey Dubstepzedd,

Your analysis is correct, the ttf buffer must be kept alive for as long as you're rendering. stb_truetype does not copy the buffer internally and calls like stbtt_GetBakedQuad access its data directly.

Re: Weird issue occuring when rendering fonts with stbtt_truetype
« Reply #2 on: May 05, 2022, 08:47:32 »
Hey Dubstepzedd,

Your analysis is correct, the ttf buffer must be kept alive for as long as you're rendering. stb_truetype does not copy the buffer internally and calls like stbtt_GetBakedQuad access its data directly.


Thanks for your reply Spasi!

So there is nothing wrong with my code? I thought you were supposed to free some buffers - but maybe that's just in C/C++?

*

Offline spasi

  • *****
  • 2261
    • WebHotelier
Re: Weird issue occuring when rendering fonts with stbtt_truetype
« Reply #3 on: May 05, 2022, 09:34:53 »
I can't see any obvious problem in the code above. I'm assuming that Resources.loadFileAsByteBuffer returns a ByteBuffer that has been allocated with ByteBuffer.allocateDirect, which returns a GC-managed buffer. You just have to make sure that there's a strong reference to that buffer while rendering, otherwise the GC will eventually free it (at an unspecified, usually surprising, point in time).

Re: Weird issue occuring when rendering fonts with stbtt_truetype
« Reply #4 on: May 05, 2022, 13:46:06 »
I can't see any obvious problem in the code above. I'm assuming that Resources.loadFileAsByteBuffer returns a ByteBuffer that has been allocated with ByteBuffer.allocateDirect, which returns a GC-managed buffer. You just have to make sure that there's a strong reference to that buffer while rendering, otherwise the GC will eventually free it (at an unspecified, usually surprising, point in time).

Exactly, it converts it into a ByteBuffer. I will post the code for it in case you want to take a look. I have yet to experience such runtime errors. :)

Code: [Select]
/** Loads a file in the resource folder as an InputStream.
     *
     * @param file - the name or path of the file that is to be loaded.
     * @return an InputStream containing the loaded data.
     */
    public static InputStream loadFileAsInputStream(String file) {
        return ClassLoader.getSystemResourceAsStream(file);
    }

    /** Loads a file in the resource folder as a ByteBuffer object
     *
     * @param file - the name or path of the file that is to be loaded.
     * @return a ByteBuffer object containing the loaded data. Returns null if the file does not exist.
     */
    public static ByteBuffer loadFileAsByteBuffer(final String file) {
        InputStream s = loadFileAsInputStream(file);

        if(s == null) {
            LOGGER.error("Given file or path does not exist in the resources folder. File: " + file);
            return null;
        }

        try {
            byte[] data = s.readAllBytes();
            //LWJGL only accepts DIRECT NIO Buffers, so we must fulfill their requirements.
            ByteBuffer buffer = ByteBuffer.allocateDirect(data.length);
            buffer.put(data);

            buffer.flip();

            return buffer;

        }
        catch (IOException e) {
            e.printStackTrace();
            LOGGER.error("Failed to read contents of file: " + file);
            return null;
        }

    }

When am I supposed to free the ttf buffer? When exiting the application?

Re: Weird issue occuring when rendering fonts with stbtt_truetype
« Reply #5 on: May 05, 2022, 13:52:55 »
I can't see any obvious problem in the code above. I'm assuming that Resources.loadFileAsByteBuffer returns a ByteBuffer that has been allocated with ByteBuffer.allocateDirect, which returns a GC-managed buffer. You just have to make sure that there's a strong reference to that buffer while rendering, otherwise the GC will eventually free it (at an unspecified, usually surprising, point in time).

Is ttf the only buffer that I can't free? I just tried to free the temporary bitmap and it crashed due to an EXCEPTION_ACCESS_VIOLATION. Does glTexImage2D(...) free it? Because I can't access it after
that method call.

Thanks, Liam

*

Offline spasi

  • *****
  • 2261
    • WebHotelier
Re: Weird issue occuring when rendering fonts with stbtt_truetype
« Reply #6 on: May 05, 2022, 16:12:43 »
No, glTexImage2D does not free anything. Buffers created by ByteBuffer.allocateDirect are freed automatically by the GC, when the object (that holds the offheap pointer) is no longer accessible. Freeing such buffers explicitly is not possible, because it would cause a double-free (and a crash). I recommend reading https://blog.lwjgl.org/memory-management-in-lwjgl-3/.

Re: Weird issue occuring when rendering fonts with stbtt_truetype
« Reply #7 on: May 05, 2022, 17:26:09 »
No, glTexImage2D does not free anything. Buffers created by ByteBuffer.allocateDirect are freed automatically by the GC, when the object (that holds the offheap pointer) is no longer accessible. Freeing such buffers explicitly is not possible, because it would cause a double-free (and a crash). I recommend reading https://blog.lwjgl.org/memory-management-in-lwjgl-3/.

Well, That explains the crashes...

What if you use ByteBuffer.createByteBuffer(...)? Is that also directly freed by the garbage collector? I read in the article that you sent that allocateDirect(...) is "horrible". So I guess I have to change that accordingly.

Re: Weird issue occuring when rendering fonts with stbtt_truetype
« Reply #8 on: May 05, 2022, 17:47:02 »
No, glTexImage2D does not free anything. Buffers created by ByteBuffer.allocateDirect are freed automatically by the GC, when the object (that holds the offheap pointer) is no longer accessible. Freeing such buffers explicitly is not possible, because it would cause a double-free (and a crash). I recommend reading https://blog.lwjgl.org/memory-management-in-lwjgl-3/.

Thanks for linking the article. I replaced the ByteBuffer.allocateDirect(...) method from the old LWJGL with the class that was recommended in the article: org.lwjgl.system.libc.Stdlib.

EDIT:
This fixed the stbtt_freeBitmap issue regarding the temporary bitmap ByteBuffer. It can now be freed without crashing.

Thanks for the help Spasi!
« Last Edit: May 05, 2022, 17:54:32 by Dubstepzedd »