LWJGL / lwjgl3

LWJGL is a Java library that enables cross-platform access to popular native APIs useful in the development of graphics (OpenGL, Vulkan, bgfx), audio (OpenAL, Opus), parallel computing (OpenCL, CUDA) and XR (OpenVR, LibOVR, OpenXR) applications.
https://www.lwjgl.org
BSD 3-Clause "New" or "Revised" License
4.82k stars 640 forks source link

Crash caused by Nuklear #986

Closed zipCoder933 closed 1 month ago

zipCoder933 commented 5 months ago

Version

3.3.2

Platform

Windows x64

JDK

OpenJDK Runtime Environment Corretto-17.0.11.9.1

Module

org.lwjgl.nuklear.Nuklear

Bug description

I experienced mildly frequent crashes on my LWJGL project. I discovered by reading the hid_pid_err.log that the crashes are coming from Nuklear nnk_convert() method.

Attached is the jvm error log: hs_err_pid11372.log

The code (in my application) that keeps causing the crash is right here:

import static org.lwjgl.nuklear.Nuklear.*;
import org.lwjgl.nuklear.*;
import static org.lwjgl.system.MemoryUtil.*;

{
. . .

            // load draw vertices & elements directly into vertex + element buffer
            ByteBuffer vertices = Objects.requireNonNull(glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY, max_vertex_buffer, null));
            ByteBuffer elements = Objects.requireNonNull(glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY, max_element_buffer, null));
            try (MemoryStack stack = stackPush()) {
                // fill convert configuration
                NkConvertConfig config = NkConvertConfig.calloc(stack)
                        .vertex_layout(VERTEX_LAYOUT)
                        .vertex_size(20)
                        .vertex_alignment(4)
                        .tex_null(null_texture)
                        .circle_segment_count(22)
                        .curve_segment_count(22)
                        .arc_segment_count(22)
                        .global_alpha(1.0f)
                        .shape_AA(AA)
                        .line_AA(AA);

                // setup buffers to load vertices and elements
                NkBuffer vbuf = NkBuffer.malloc(stack);
                NkBuffer ebuf = NkBuffer.malloc(stack);

                nk_buffer_init_fixed(vbuf, vertices/*, max_vertex_buffer*/);
                nk_buffer_init_fixed(ebuf, elements/*, max_element_buffer*/);
                nk_convert(ctx, cmds, vbuf, ebuf, config);//<------------ This line is causing keeps causing a JVM crash
            }
            glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
            glUnmapBuffer(GL_ARRAY_BUFFER);

. . .
}

Stacktrace or crash log output

From the his_err_pid.log:

# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.

Current thread (0x0000023bccebaf20):  JavaThread "main" [_thread_in_native, id=17560, stack(0x0000004582100000,0x0000004582200000)]

Stack: [0x0000004582100000,0x0000004582200000],  sp=0x00000045821fe600,  free space=1017k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
C  [lwjgl_stb.dll+0x2e567]

Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
J 3525  org.lwjgl.stb.STBTruetype.nstbtt_GetPackedQuad(JIIIJJJI)V (0 bytes) @ 0x0000023bdc70289b [0x0000023bdc702820+0x000000000000007b]
J 4228 c2 org.lwjgl.nuklear.NkQueryFontGlyphCallbackI.callback(JJ)V (68 bytes) @ 0x0000023bdc7c79dc [0x0000023bdc7c7780+0x000000000000025c]
v  ~StubRoutines::call_stub
J 5363  org.lwjgl.nuklear.Nuklear.nnk_convert(JJJJJ)I (0 bytes) @ 0x0000023bdc88a7fe [0x0000023bdc88a7a0+0x000000000000005e]
J 5361 c1 org.lwjgl.nuklear.Nuklear.nk_convert(Lorg/lwjgl/nuklear/NkContext;Lorg/lwjgl/nuklear/NkBuffer;Lorg/lwjgl/nuklear/NkBuffer;Lorg/lwjgl/nuklear/NkBuffer;Lorg/lwjgl/nuklear/NkConvertConfig;)I (39 bytes) @ 0x0000023bd504250c [0x0000023bd5041f00+0x000000000000060c]
J 4723 c1 com.xbuilders.window.NKWindow.NKrender(III)V (645 bytes) @ 0x0000023bd4a55eb4 [0x0000023bd4a538e0+0x00000000000025d4]
J 5799 c1 com.xbuilders.engine.ui.gameScene.GameUI.draw()V (122 bytes) @ 0x0000023bd52f8044 [0x0000023bd52f7560+0x0000000000000ae4]
J 5625 c1 com.xbuilders.engine.gameScene.GameScene.render()V (157 bytes) @ 0x0000023bd524667c [0x0000023bd5245b20+0x0000000000000b5c]
J 5345 c1 com.xbuilders.game.Main.render()V (22 bytes) @ 0x0000023bd4d886c4 [0x0000023bd4d885e0+0x00000000000000e4]
j  com.xbuilders.game.Main.run()V+55
j  com.xbuilders.game.Main.main([Ljava/lang/String;)V+75
v  ~StubRoutines::call_stub

siginfo: EXCEPTION_ACCESS_VIOLATION (0xc0000005), reading address 0x0000023bc3fcbff4
Spasi commented 5 months ago

Hey @zipCoder933,

I'm guessing it's related to this.

zipCoder933 commented 4 months ago

I did that and it still crashes sometimes: hs_err_pid8720.log

Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
J 2879  org.lwjgl.stb.STBTruetype.nstbtt_GetPackedQuad(JIIIJJJI)V (0 bytes) @ 0x00000210a571341b [0x00000210a57133a0+0x000000000000007b]
J 2955 c2 org.lwjgl.nuklear.NkQueryFontGlyphCallbackI.callback(JJ)V (68 bytes) @ 0x00000210a571aa5c [0x00000210a571a800+0x000000000000025c]
v  ~StubRoutines::call_stub
J 3038  org.lwjgl.nuklear.Nuklear.nnk_convert(JJJJJ)I (0 bytes) @ 0x00000210a572707e [0x00000210a5727020+0x000000000000005e]
J 3090 c1 org.lwjgl.nuklear.Nuklear.nk_convert(Lorg/lwjgl/nuklear/NkContext;Lorg/lwjgl/nuklear/NkBuffer;Lorg/lwjgl/nuklear/NkBuffer;Lorg/lwjgl/nuklear/NkBuffer;Lorg/lwjgl/nuklear/NkConvertConfig;)I (39 bytes) @ 0x000002109e03e70c [0x000002109e03e100+0x000000000000060c]
J 4009 c1 com.xbuilders.window.NKWindow.NKrender()V (639 bytes) @ 0x000002109dda3bfc [0x000002109dda15c0+0x000000000000263c]
J 4107 c1 com.xbuilders.engine.ui.gameScene.GameUI.draw()V (125 bytes) @ 0x000002109dd5b084 [0x000002109dd5a500+0x0000000000000b84]
J 4075 c1 com.xbuilders.engine.gameScene.GameScene.render()V (227 bytes) @ 0x000002109dad8bc4 [0x000002109dad7580+0x0000000000001644]
J 3069 c1 com.xbuilders.game.Main.render()V (22 bytes) @ 0x000002109e02f7c4 [0x000002109e02f6e0+0x00000000000000e4]
j  com.xbuilders.game.Main.<init>()V+294
j  com.xbuilders.game.Main.main([Ljava/lang/String;)V+197
v  ~StubRoutines::call_stub

Here is a more complete chunk of code containing the source of the crashes.


public abstract class NKWindow extends BaseWindow {
. . .

    //This prevents the byteBuffers from being garbage collected before the render is done with them, thus preventing a JVM crash
    private ByteBuffer vertices, elements; //Used to fix the crash bug (https://github.com/LWJGL/lwjgl3/issues/986)
    private NkBuffer vbuf, ebuf; //Also stored out of scope to fix the crash bug
    private NkConvertConfig config;//Also stored out of scope to fix the crash bug

    private static final int AA = NK_ANTI_ALIASING_ON;
    private static final int max_vertex_buffer = MAX_VERTEX_BUFFER;
    private static final int max_element_buffer = MAX_ELEMENT_BUFFER;

    public void NKrender() {
        try (MemoryStack stack = stackPush()) {
            enableNuklear();
            // setup program
            glUseProgram(shader.getID());
            glUniform1i(uniform_tex, 0);
            glUniformMatrix4fv(uniform_proj, false, stack.floats(2.0f / getWidth(), 0.0f, 0.0f, 0.0f,
                    0.0f, -2.0f / getHeight(), 0.0f, 0.0f,
                    0.0f, 0.0f, -1.0f, 0.0f,
                    -1.0f, 1.0f, 0.0f, 1.0f
            ));
            GL11.glViewport(0, 0, getDisplay_width(), getDisplay_height());
        }

        {
            // convert from command queue into draw list and draw to screen

            // allocate vertex and element buffer
            glBindVertexArray(vao);
            glBindBuffer(GL_ARRAY_BUFFER, vbo);
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);

            glBufferData(GL_ARRAY_BUFFER, max_vertex_buffer, GL_STREAM_DRAW);
            glBufferData(GL_ELEMENT_ARRAY_BUFFER, max_element_buffer, GL_STREAM_DRAW);

            // load draw vertices & elements directly into vertex + element buffer
            vertices = Objects.requireNonNull(glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY, max_vertex_buffer, null));
            elements = Objects.requireNonNull(glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY, max_element_buffer, null));
            try (MemoryStack stack = stackPush()) {
                // fill convert configuration
                config = NkConvertConfig.calloc(stack)
                        .vertex_layout(VERTEX_LAYOUT)
                        .vertex_size(20)
                        .vertex_alignment(4)
                        .tex_null(null_texture)
                        .circle_segment_count(22)
                        .curve_segment_count(22)
                        .arc_segment_count(22)
                        .global_alpha(1.0f)
                        .shape_AA(AA)
                        .line_AA(AA);

                // setup buffers to load vertices and elements
                vbuf = NkBuffer.malloc(stack);//TODO: If we keep getting crashes, maybe this has to be stored out of scope too?
                ebuf = NkBuffer.malloc(stack);

                nk_buffer_init_fixed(vbuf, vertices/*, max_vertex_buffer*/);
                nk_buffer_init_fixed(ebuf, elements/*, max_element_buffer*/);
                nk_convert(ctx, cmds, vbuf, ebuf, config);//<----------------------- This line is causing keeps causing a JVM crash
            }
            glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
            glUnmapBuffer(GL_ARRAY_BUFFER);

            // iterate over and execute each draw command
            float fb_scale_x = (float) getDisplay_width() / (float) getWidth();
            float fb_scale_y = (float) getDisplay_height() / (float) getHeight();

            long offset = NULL;
            for (NkDrawCommand cmd = nk__draw_begin(ctx, cmds); cmd != null; cmd = nk__draw_next(cmd, cmds, ctx)) {
                if (cmd.elem_count() == 0) {
                    continue;
                }
                glBindTexture(GL_TEXTURE_2D, cmd.texture().id());
                glScissor((int) (cmd.clip_rect().x() * fb_scale_x),
                        (int) ((getHeight() - (int) (cmd.clip_rect().y() + cmd.clip_rect().h())) * fb_scale_y),
                        (int) (cmd.clip_rect().w() * fb_scale_x),
                        (int) (cmd.clip_rect().h() * fb_scale_y)
                );
                glDrawElements(GL_TRIANGLES, cmd.elem_count(), GL_UNSIGNED_SHORT, offset);
                offset += cmd.elem_count() * 2;
            }
            nk_clear(ctx);
            nk_buffer_clear(cmds);
        }
        disableNuklear();
    }
. . .
}
octylFractal commented 4 months ago

Things allocated using MemoryStack are freed when the stack is closed, i.e. at the end of the try (...) {}. Storing them in a variable won't change this. You should allocate them using MemoryUtil or BufferUtils (and properly free them later) if the lifecycle won't fit into a try.

zipCoder933 commented 4 months ago

Ok. Where is the safest place to free up the memory?

Do I even need to do it at all? Im assuming that i need to use MemoryUtil.memFree() but i think bytebuffers are garbage collected

octylFractal commented 4 months ago

When it should be freed depends on what's using it, generally you should try to free it as soon as possible after it is done being used -- read the documentation of each function or the library to determine when that is.

Anything allocated with MemoryUtil must be freed using it, or it will leak. Buffers allocated with BufferUtils will be freed when garbage collected, but this may not happen immediately.

See https://github.com/LWJGL/lwjgl3-wiki/wiki/1.3.-Memory-FAQ.

Spasi commented 4 months ago

Hey @zipCoder933,

I did that and it still crashes sometimes:

The vertex & element buffers are unrelated to the problem. Those are stored by the OpenGL driver, the Java-side buffers are gone when you call glUnmapBuffer and you don't need to keep them around.

See the post I linked above please. The crash happens in the NkUserFont::query callback, you need to look at the code that sets up font rendering. There should be a call to stbtt_InitFont somewhere, passing the font data as a buffer. There should be a strong reference to that buffer for as long as you're rendering with the font.

zipCoder933 commented 3 months ago

So I followed the advice in the other bug report that you posted (https://github.com/LWJGL/lwjgl3/issues/535#issuecomment-590983833) However the same crash still occurs. I put the fontBuffer in a public static context so that it isnt possible for it to become garbage collected.

Theme.java

public class Theme{ 
    public static ByteBuffer fontBuffer;

    public static void initialize(NkContext context, boolean largerFonts) throws IOException {
        fontBuffer = NKFontUtils.loadFontData("fonts\\Press_Start_2P\\PressStart2P-Regular.ttf", 512 * 1024);

        font_24 = NKFontUtils.TTF_assignToNewTexture(fontBuffer, largerFonts ? 26 : 24);
        font_22 = NKFontUtils.TTF_assignToNewTexture(fontBuffer, largerFonts ? 24 : 22);
        font_18 = NKFontUtils.TTF_assignToNewTexture(fontBuffer, largerFonts ? 20 : 18);
        font_12 = NKFontUtils.TTF_assignToNewTexture(fontBuffer, largerFonts ? 14 : 12);
        font_10 = NKFontUtils.TTF_assignToNewTexture(fontBuffer, largerFonts ? 12 : 10);
        font_9 = NKFontUtils.TTF_assignToNewTexture(fontBuffer, largerFonts ? 10 : 9);
...
  }
}

NKFontUtils.java

The loadFontData method uses the LWJGL demo repository's "ioResourceToByteBuffer()" method

public class NKFontUtils {

    final static int BITMAP_W = 1024;
    final static int BITMAP_H = 1024;

    public static final ArrayList<ByteBuffer> fontBuffers = new ArrayList<>();

    public static ByteBuffer loadFontData(String path) throws IOException {
        ByteBuffer buff = ioResourceToByteBuffer(path, 512 * 1024);
        fontBuffers.add(buff);
        return buff;
    }

    public static NkUserFont TTF_assignToNewTexture(final ByteBuffer fontBytes, int fontHeight) {
        NkUserFont font = NkUserFont.create();

        int fontTexID = glGenTextures();
        TextureUtils.addTexture(fontTexID);

        STBTTFontinfo fontInfo = STBTTFontinfo.create();
        STBTTPackedchar.Buffer cdata = STBTTPackedchar.create(95);

        float scale;
        float descent;

        try (MemoryStack stack = stackPush()) {
            stbtt_InitFont(fontInfo, fontBytes);
            scale = stbtt_ScaleForPixelHeight(fontInfo, fontHeight);

            IntBuffer d = stack.mallocInt(1);
            stbtt_GetFontVMetrics(fontInfo, null, d, null);
            descent = d.get(0) * scale;

            ByteBuffer bitmap = memAlloc(BITMAP_W * BITMAP_H);

            STBTTPackContext pc = STBTTPackContext.malloc(stack);
            stbtt_PackBegin(pc, bitmap, BITMAP_W, BITMAP_H, 0, 1, NULL);
            stbtt_PackSetOversampling(pc, 4, 4);
            stbtt_PackFontRange(pc, fontBytes, 0, fontHeight, 32, cdata);
            stbtt_PackEnd(pc);

            // Convert R8 to RGBA8
            ByteBuffer texture = memAlloc(BITMAP_W * BITMAP_H * 4);
            for (int i = 0; i < bitmap.capacity(); i++) {
                texture.putInt((bitmap.get(i) << 24) | 0x00FFFFFF);
            }
            texture.flip();

            glBindTexture(GL_TEXTURE_2D, fontTexID);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, BITMAP_W, BITMAP_H, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, texture);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

            memFree(texture);
            memFree(bitmap);
        }

        font
                .width((handle, h, text, len) -> {
                    float text_width = 0;
                    try (MemoryStack stack = stackPush()) {
                        IntBuffer unicode = stack.mallocInt(1);

                        int glyph_len = nnk_utf_decode(text, memAddress(unicode), len);
                        int text_len = glyph_len;

                        if (glyph_len == 0) {
                            return 0;
                        }

                        IntBuffer advance = stack.mallocInt(1);
                        while (text_len <= len && glyph_len != 0) {
                            if (unicode.get(0) == NK_UTF_INVALID) {
                                break;
                            }

                            /* query currently drawn glyph information */
                            stbtt_GetCodepointHMetrics(fontInfo, unicode.get(0), advance, null);
                            text_width += advance.get(0) * scale;

                            /* offset next glyph */
                            glyph_len = nnk_utf_decode(text + text_len, memAddress(unicode), len - text_len);
                            text_len += glyph_len;
                        }
                    }
                    return text_width;
                })
                .height(fontHeight)
                .query((handle, font_height, glyph, codepoint, next_codepoint) -> {
                    try (MemoryStack stack = stackPush()) {
                        FloatBuffer x = stack.floats(0.0f);
                        FloatBuffer y = stack.floats(0.0f);

                        STBTTAlignedQuad q = STBTTAlignedQuad.malloc(stack);
                        IntBuffer advance = stack.mallocInt(1);

                        stbtt_GetPackedQuad(cdata, BITMAP_W, BITMAP_H, codepoint - 32, x, y, q, false);
                        stbtt_GetCodepointHMetrics(fontInfo, codepoint, advance, null);

                        NkUserFontGlyph ufg = NkUserFontGlyph.create(glyph);

                        ufg.width(q.x1() - q.x0());
                        ufg.height(q.y1() - q.y0());
                        ufg.offset().set(q.x0(), q.y0() + (fontHeight + descent));
                        ufg.xadvance(advance.get(0) * scale);
                        ufg.uv(0).set(q.s0(), q.t0());
                        ufg.uv(1).set(q.s1(), q.t1());
                    }
                })
                .texture(it -> it
                        .id(fontTexID));

        return font;
    }
}
Spasi commented 3 months ago

I'm out of ideas, could you prepare an MCVE please?

zipCoder933 commented 3 months ago

Here is a working MRE: NuklearMRE.zip

The challenge is that this crash occurs VERY sparsely in my main application, and so, this makes it very difficult for me to try to reproduce the error in MRE code as well.

The only way I can give you a crash log is if i open this application enough times that Maybe I get lucky and the crash resurfaces?? (As a note, the "hello world" button in the nuklear UI makes the font buffer null, so possibly might cause the crash to occur?)

Anyways, I'm not sure if opening and closing the window many times per second might help?...

Spasi commented 3 months ago

It reproduces the crash with two changes:

  1. Comment-out fontBuffers.add(buff); at NkFontUtils.java:56. Setting fontBuffer to null won't do anything if there's another strong reference to the instance.
  2. Add Runtime.getRuntime().gc(); to the main loop. It's very hard to reproduce such a crash without actual memory pressure, the GC will never run.

With these two changes, font rendering breaks as soon as I press the button and the program usually crashes a few seconds later.

If your application still crashes after making sure there's always a strong reference to the font data, then it must be another unrelated issue.

zipCoder933 commented 3 months ago

Interesting. The MRE now indeed crashes when the byteBuffer is garbage collected, but the trace in the crash log isn't coming from nk_convert like in my application... This one is coming from nk_begin:

Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
J 1108  org.lwjgl.stb.STBTruetype.nstbtt_GetCodepointHMetrics(JIJJ)V (0 bytes) @ 0x0000022de6c8c3f6 [0x0000022de6c8c3a0+0x0000000000000056]
J 1236 c2 com.xbuilders.window.nuklear.NKFontUtils.lambda$TTF_assignToNewTexture$0(Lorg/lwjgl/stb/STBTTFontinfo;FJFJI)F (190 bytes) @ 0x0000022de6c99278 [0x0000022de6c99080+0x00000000000001f8]
J 1180 c1 com.xbuilders.window.nuklear.NKFontUtils$$Lambda$63+0x0000000800c308d8.invoke(JFJI)F (18 bytes) @ 0x0000022ddf9d97a4 [0x0000022ddf9d9720+0x0000000000000084]
J 1179 c1 org.lwjgl.nuklear.NkTextWidthCallbackI.callback(JJ)V (62 bytes) @ 0x0000022ddf9da9dc [0x0000022ddf9da020+0x00000000000009bc]
v  ~StubRoutines::call_stub
j  org.lwjgl.nuklear.Nuklear.nnk_begin(JJJI)Z+0
j  org.lwjgl.nuklear.Nuklear.nk_begin(Lorg/lwjgl/nuklear/NkContext;Ljava/lang/CharSequence;Lorg/lwjgl/nuklear/NkRect;I)Z+38
j  com.xbuilders.app.TopMenu.render()V+60

Here is a trace of the crash log coming from my real application:

Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
J 3489  org.lwjgl.stb.STBTruetype.nstbtt_GetPackedQuad(JIIIJJJI)V (0 bytes) @ 0x000002517616d09b [0x000002517616d020+0x000000000000007b]
J 3575 c2 org.lwjgl.nuklear.NkQueryFontGlyphCallbackI.callback(JJ)V (68 bytes) @ 0x0000025176178594 [0x0000025176178360+0x0000000000000234]
v  ~StubRoutines::call_stub
J 3646  org.lwjgl.nuklear.Nuklear.nnk_convert(JJJJJ)I (0 bytes) @ 0x00000251761833fe [0x00000251761833a0+0x000000000000005e]
J 3889 c2 com.xbuilders.window.NKWindow.NKrender()V (623 bytes) @ 0x00000251761ac058 [0x00000251761ab9c0+0x0000000000000698]
J 4787 c1 com.xbuilders.engine.ui.gameScene.GameUI.draw()V (125 bytes) @ 0x000002516e55e804 [0x000002516e55dc80+0x0000000000000b84]
J 4756 c1 com.xbuilders.engine.gameScene.GameScene.render()V (191 bytes) @ 0x000002516e453eac [0x000002516e453200+0x0000000000000cac]
J 3675 c1 com.xbuilders.game.Main.render()V (22 bytes) @ 0x000002516ebb6844 [0x000002516ebb6760+0x00000000000000e4]
j  com.xbuilders.game.Main.<init>()V+294
j  com.xbuilders.game.Main.main([Ljava/lang/String;)V+222
v  ~StubRoutines::call_stub

Either way, there are indeed 2 strong references to the font byteBuffer in my application, so the font bytebuffer must not be what is causing the crash.

octylFractal commented 3 months ago

Specifically because this is crashing in GetPackedQuad, it must be from some memory access in that function. The only pointers used by it are chardata, xpos, ypos, and q. Since you just stack allocated the last three, I think that only chardata could possibly be in an invalid state. However, it isn't freed, as all callbacks are GC roots, so there is a strong reference to it as long as the owning NkUserFont is not freed, which it appears to not be in your MRE at least (held by static field). But, there is a memory access violation that can occur in your code even without any freed memory!

You take codepoint - 32 without checking if codepoint is at least 32. This means you can pass a negative index to GetPackedQuad, which isn't caught by LWJGL's checks. This results in GetPackedQuad accessing below the allocated object, which could occasionally result in an access error. This could be caused by something as simple as the tab or newline character appearing in some text somewhere, as those both have a codepoint below 32.

Try adding a check in your query callback to ensure that no codepoint is below 32 when you call that method. Whether you hard-crash or just return some dummy data in ufg, just make sure not to call the GetPackedQuad method.