Hot questions for Using Lightweight Java Game Library in true type fonts

Question:

The LWJGL3 library contains bindings to STB TrueType and other libraries made by Sean Barrett.

In order to modify the packing API provided by this library to render SDF glyphs into the backing texture instead of normal bitmaps, I am reproducing the texture-rendering code from the library in java.

I managed to get it to almost work but I am hitting a stumbling stone where I am getting mangled garbage data for the very top-left corner of the texture. I am somewhat confident that the error must be located somewhere in the code for my version of the stbtt__h_prefilter(...), as this is where the assertion fails.

Edit: I forgot to take into consideration the current buffer position when doing read/write operations on the buffer. Now I still have some garbage data in the bitmap, but it's more evenly distributed.

In fact looking at the updated second picture it seems that somehow the very left-most part of every glyph is shifted half the glyph height down. I cannot find out where or why it happens, especially considering that the bitmap processing works on each glyph individually after it is rendered into the font, so to my understanding the next line of glyphs should just overwrite this..?

Bitmap generated by the original library:

Bitmap generated by my version (see the offset half-lines cutting into some letters):

Addendum: Bitmap generated by my version without the prefilter_... methods:

Below you find my versions of the methods from the library. The originals can be found here.


The references to STB... functions refer to the generated bindings form lwjgl3.

private static boolean packFontRangesRenderIntoRectsSDF(
                  STBTTPackContext context, STBTTFontinfo fontinfo,
                  STBTTPackRange.Buffer ranges, STBRPRect.Buffer rects) {

    int i, j, k;
    boolean returnValue = true;

    int curr_hOversample = context.h_oversample();
    int curr_vOversample = context.v_oversample();

    k = 0;
    for(i = 0 ; i < ranges.remaining() ; i++) {
        float fh = ranges.get(i).font_size();
        float scale = fh > 0.0f ? stbtt_ScaleForPixelHeight(fontinfo, fh) : stbtt_ScaleForMappingEmToPixels(fontinfo, -fh);
        float recip_h, recip_v, sub_x, sub_y;

        curr_hOversample = STBTTPackRange.nh_oversample(ranges.get(i).address()) & 0xFF;
        curr_vOversample = STBTTPackRange.nv_oversample(ranges.get(i).address()) & 0xFF;

        recip_h = 1.0f / (float)curr_hOversample;
        recip_v = 1.0f / (float)curr_vOversample;

        sub_x = __oversample_shift(curr_hOversample);
        sub_y = __oversample_shift(curr_vOversample);

        for(j = 0 ; j < ranges.get(i).num_chars() ; j++) {
            STBRPRect r = rects.get(k);
            if(r.was_packed()) {
                STBTTPackedchar bc = ranges.get(i).chardata_for_range().get(j);

                IntBuffer advance = ByteBuffer.allocateDirect(Integer.BYTES)
                                              .order(ByteOrder.nativeOrder())
                                              .asIntBuffer();
                IntBuffer lsb = ByteBuffer.allocateDirect(Integer.BYTES)
                                          .order(ByteOrder.nativeOrder())
                                          .asIntBuffer();

                IntBuffer x0 = ByteBuffer.allocateDirect(Integer.BYTES)
                                         .order(ByteOrder.nativeOrder())
                                         .asIntBuffer();
                IntBuffer x1 = ByteBuffer.allocateDirect(Integer.BYTES)
                                         .order(ByteOrder.nativeOrder())
                                         .asIntBuffer();
                IntBuffer y0 = ByteBuffer.allocateDirect(Integer.BYTES)
                                         .order(ByteOrder.nativeOrder())
                                         .asIntBuffer();
                IntBuffer y1 = ByteBuffer.allocateDirect(Integer.BYTES)
                                         .order(ByteOrder.nativeOrder())
                                         .asIntBuffer();

                int codepoint = ranges.get(i).array_of_unicode_codepoints() == null ? ranges.get(i).first_unicode_codepoint_in_range() + j : ranges.get(i).array_of_unicode_codepoints().get(j);
                int glyph = stbtt_FindGlyphIndex(fontinfo, codepoint);
                int pad = context.padding();

                r.x((short) (r.x() + pad));
                r.y((short) (r.y() + pad));
                r.w((short) (r.w() - pad));
                r.h((short) (r.h() - pad));
                stbtt_GetGlyphHMetrics(fontinfo, glyph, advance, lsb);
                stbtt_GetGlyphBitmapBox(fontinfo, glyph,
                                        scale * curr_hOversample,
                                        scale * curr_vOversample,
                                        x0, y0, x1, y1);
                //TODO replace below with SDF func
                ByteBuffer buff = context.pixels(context.height() * context.width());
                buff.position(r.x() + r.y() * context.stride_in_bytes());

                stbtt_MakeGlyphBitmapSubpixel(fontinfo, buff,
                                              r.w() - curr_hOversample + 1,
                                              r.h() - curr_vOversample + 1,
                                              context.stride_in_bytes(),
                                              scale * curr_hOversample,
                                              scale * curr_vOversample,
                                              0, 0,
                                              glyph);

                if(curr_hOversample > 1) {
                    //FIXME __h_prefilter(..) function
                    buff.position(r.x() + r.y() * context.stride_in_bytes());
                    __h_prefilter(buff,
                                  r.w(), r.h(), context.stride_in_bytes(),
                                  curr_hOversample);
                }

                if(curr_vOversample > 1) {
                    //FIXME __v_prefilter(..) function
                    buff.position(r.x() + r.y() * context.stride_in_bytes());
                    __v_prefilter(buff,
                                  r.w(), r.h(), context.stride_in_bytes(),
                                  curr_vOversample);
                }

                bc.x0(r.x());
                bc.y0(r.y());
                bc.x1((short) (r.x() + r.w()));
                bc.y1((short) (r.y() + r.h()));
                bc.xadvance(scale * advance.get(0));
                bc.xoff((float) (x0.get(0) * recip_h + sub_x));
                bc.yoff((float) (y0.get(0) * recip_v + sub_y));
                bc.xoff2((x0.get(0) + r.w()) * recip_h + sub_x);
                bc.yoff2((y0.get(0) + r.h()) * recip_v + sub_y);
            } else {
                returnValue = false;
            }

            ++k;
        }
    }

    return returnValue;
}

//copy of stbtt__oversample_shift(..) as it's inaccessible
private static float __oversample_shift(int oversample) {
    if(oversample == 0) {
        return 0.0f;
    }

    return (float)-(oversample - 1) / (2.0f * (float)oversample);
}

private static final int MAX_OVERSAMPLE = 8;
private static final int __OVER_MASK = MAX_OVERSAMPLE - 1;

private static void __h_prefilter(ByteBuffer pixels, int w, int h, int stride_in_bytes, int kernel_width) {
    final int pixels_offset = pixels.position();
    int pixelstride = 0;

    byte[] buffer = new byte[MAX_OVERSAMPLE];
    int safe_w = w - kernel_width;
    int j;

    Arrays.fill(buffer, 0, MAX_OVERSAMPLE, (byte)0);

    for(j = 0 ; j < h ; j++) {
        int i;
        int total;
        Arrays.fill(buffer, 0, kernel_width, (byte)0);

        total = 0;

        for(i = 0 ; i <= safe_w ; i++) {
            total += Byte.toUnsignedInt(pixels.get(pixels_offset + (pixelstride + i))) - Byte.toUnsignedInt(buffer[i & __OVER_MASK]);
            buffer[(i + kernel_width) & __OVER_MASK] = pixels.get(pixels_offset + (pixelstride + i));
            pixels.put(pixels_offset + (pixelstride + i), (byte) Integer.divideUnsigned(total, kernel_width));
        }

        for(; i < w ; ++i) {
//                if(Byte.toUnsignedInt(pixels.get(pixels_offset + (pixelstride + i))) != 0) {
//                    throw new RuntimeException("Expected '0' but was '" + Byte.toUnsignedInt(pixels.get(pixels_offset + (pixelstride + i))) + "'");
//                }

            total -= Byte.toUnsignedInt(buffer[i & __OVER_MASK]);
            pixels.put(pixels_offset + (pixelstride + i), (byte) Integer.divideUnsigned(total, kernel_width));
        }

        pixelstride += stride_in_bytes;
    }
}

private static void __v_prefilter(ByteBuffer pixels, int w, int h, int stride_in_bytes, int kernel_width) {
    final int pixels_offset = pixels.position();
    int pixelstride = 0;

    byte[] buffer = new byte[MAX_OVERSAMPLE];
    int safe_h = h - kernel_width;
    int j;
    Arrays.fill(buffer, 0, MAX_OVERSAMPLE, (byte)0);

    for(j = 0 ; j < w ; j++) {
        int i;
        int total;
        Arrays.fill(buffer, 0, kernel_width, (byte)0);

        total = 0;

        for(i = 0 ; i <= safe_h ; i++) {
            total += Byte.toUnsignedInt(pixels.get(pixels_offset + ((pixelstride + i) * stride_in_bytes))) - Byte.toUnsignedInt(buffer[i & __OVER_MASK]);
            buffer[(i + kernel_width) & __OVER_MASK] = pixels.get(pixels_offset + ((pixelstride + i) * stride_in_bytes));
            pixels.put(pixels_offset + ((pixelstride + i) * stride_in_bytes), (byte) Integer.divideUnsigned(total, kernel_width));
        }

        for(; i < h ; ++i) {
//                if(Byte.toUnsignedInt(pixels.get(pixels_offset + ((pixelstride + i) * stride_in_bytes))) != 0) {
//                    throw new RuntimeException("Expected '0' but was '" + Byte.toUnsignedInt(pixels.get(pixels_offset + ((pixelstride + i) * stride_in_bytes))) + "'");
//                }

            total -= Byte.toUnsignedInt(buffer[i & __OVER_MASK]);
            pixels.put(pixels_offset + ((pixelstride + i) * stride_in_bytes), (byte) Integer.divideUnsigned(total, kernel_width));
        }

        pixelstride += 1;
    }
}

Answer:

It seems to work out fine when I remove the offset from the __v_prefilter(..) method.

Thus changing final int pixels_offset = pixels.position(); to final int pixels_offset = 0; (or removing it altogether from the code).

I say it seems because I have not done any bitwise comparisons of the produced maps between my, now working, and the original code. There are just no, to me at least, discernible mangled bits in the texture anymore.

Question:

The LWJGL3 library contains bindings to STB TrueType and other libraries made by Sean Barrett.

In order to understand and use that library I wanted to convert the code in the oversampling demo from using...

stbtt_PackFontRange(pc, ttf, 0, 16, 32, chardata);

...to instead using...

STBTTPackRange.Buffer packRanges = STBTTPackRange.malloc(1);
packRanges.put(STBTTPackRange.malloc().set(16, 32, null, 96, chardata));
...
stbtt_PackFontRanges(pc, ttf, 0, packRanges);

...which yields an empty texture.

From the understanding I could gather by reading the documentations and looking at the examples in the stb repo as well as in the lwjgl repo what I am doing should be working, yet it does not.

I modified the below class from the original ~1 line at a time to the point where I switched from using the stbtt_PackFontRange(...) method to using the stbtt_PackFontRanges(...) method.


The full load_fonts() method for context:

private void load_fonts() {
    font_tex = glGenTextures();
    chardata = STBTTPackedchar.malloc(96);
    chardataIndices = new HashMap<>();

    for(int i = 0 ; i < chardata.remaining() ; i++) {
        chardataIndices.put(i + 32, i);
    }

    STBTTPackRange.Buffer packRanges = STBTTPackRange.malloc(1);
    packRanges.put(STBTTPackRange.malloc().set(16, 32, null, 96, chardata));

    try (STBTTPackContext pc = STBTTPackContext.malloc()) {
        ByteBuffer ttf = ioResourceToByteBuffer("demo/monof55.ttf", 512 * 1024);

        ByteBuffer bitmap = BufferUtils.createByteBuffer(BITMAP_W * BITMAP_H);

        stbtt_PackBegin(pc, bitmap, BITMAP_W, BITMAP_H, 0, 1, NULL);

        stbtt_PackSetOversampling(pc, 1, 1);

        //below method works
        stbtt_PackFontRange(pc, ttf, 0, 16, 32, chardata);

        //below method works not
        //stbtt_PackFontRanges(pc, ttf, 0, packRanges);

        stbtt_PackEnd(pc);

        glBindTexture(GL_TEXTURE_2D, font_tex);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, BITMAP_W, BITMAP_H, 0, GL_ALPHA, GL_UNSIGNED_BYTE, bitmap);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

The complete modified TruetypeOversample class, can be easiest executed by cloning the lwjgl3 repo and replacing the class with the same name:

/*
 * Copyright LWJGL. All rights reserved.
 * License terms: https://www.lwjgl.org/license
 */
package org.lwjgl.demo.stb;

import static org.lwjgl.demo.glfw.GLFWUtil.glfwInvoke;
import static org.lwjgl.demo.util.IOUtil.ioResourceToByteBuffer;
import static org.lwjgl.glfw.Callbacks.glfwFreeCallbacks;
import static org.lwjgl.glfw.GLFW.GLFW_FALSE;
import static org.lwjgl.glfw.GLFW.GLFW_KEY_B;
import static org.lwjgl.glfw.GLFW.GLFW_KEY_ESCAPE;
import static org.lwjgl.glfw.GLFW.GLFW_KEY_V;
import static org.lwjgl.glfw.GLFW.GLFW_RELEASE;
import static org.lwjgl.glfw.GLFW.GLFW_RESIZABLE;
import static org.lwjgl.glfw.GLFW.GLFW_TRUE;
import static org.lwjgl.glfw.GLFW.GLFW_VISIBLE;
import static org.lwjgl.glfw.GLFW.glfwCreateWindow;
import static org.lwjgl.glfw.GLFW.glfwDefaultWindowHints;
import static org.lwjgl.glfw.GLFW.glfwGetPrimaryMonitor;
import static org.lwjgl.glfw.GLFW.glfwGetVideoMode;
import static org.lwjgl.glfw.GLFW.glfwInit;
import static org.lwjgl.glfw.GLFW.glfwMakeContextCurrent;
import static org.lwjgl.glfw.GLFW.glfwPollEvents;
import static org.lwjgl.glfw.GLFW.glfwSetErrorCallback;
import static org.lwjgl.glfw.GLFW.glfwSetFramebufferSizeCallback;
import static org.lwjgl.glfw.GLFW.glfwSetKeyCallback;
import static org.lwjgl.glfw.GLFW.glfwSetWindowPos;
import static org.lwjgl.glfw.GLFW.glfwSetWindowShouldClose;
import static org.lwjgl.glfw.GLFW.glfwSetWindowSizeCallback;
import static org.lwjgl.glfw.GLFW.glfwShowWindow;
import static org.lwjgl.glfw.GLFW.glfwSwapBuffers;
import static org.lwjgl.glfw.GLFW.glfwSwapInterval;
import static org.lwjgl.glfw.GLFW.glfwTerminate;
import static org.lwjgl.glfw.GLFW.glfwWindowHint;
import static org.lwjgl.glfw.GLFW.glfwWindowShouldClose;
import static org.lwjgl.opengl.GL11.GL_ALPHA;
import static org.lwjgl.opengl.GL11.GL_BLEND;
import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_CULL_FACE;
import static org.lwjgl.opengl.GL11.GL_DEPTH_TEST;
import static org.lwjgl.opengl.GL11.GL_LIGHTING;
import static org.lwjgl.opengl.GL11.GL_LINEAR;
import static org.lwjgl.opengl.GL11.GL_MODELVIEW;
import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA;
import static org.lwjgl.opengl.GL11.GL_PROJECTION;
import static org.lwjgl.opengl.GL11.GL_QUADS;
import static org.lwjgl.opengl.GL11.GL_SRC_ALPHA;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_MAG_FILTER;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_MIN_FILTER;
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE;
import static org.lwjgl.opengl.GL11.glBegin;
import static org.lwjgl.opengl.GL11.glBindTexture;
import static org.lwjgl.opengl.GL11.glBlendFunc;
import static org.lwjgl.opengl.GL11.glClear;
import static org.lwjgl.opengl.GL11.glClearColor;
import static org.lwjgl.opengl.GL11.glColor3f;
import static org.lwjgl.opengl.GL11.glDisable;
import static org.lwjgl.opengl.GL11.glEnable;
import static org.lwjgl.opengl.GL11.glEnd;
import static org.lwjgl.opengl.GL11.glGenTextures;
import static org.lwjgl.opengl.GL11.glLoadIdentity;
import static org.lwjgl.opengl.GL11.glMatrixMode;
import static org.lwjgl.opengl.GL11.glOrtho;
import static org.lwjgl.opengl.GL11.glTexCoord2f;
import static org.lwjgl.opengl.GL11.glTexImage2D;
import static org.lwjgl.opengl.GL11.glTexParameteri;
import static org.lwjgl.opengl.GL11.glTranslatef;
import static org.lwjgl.opengl.GL11.glVertex2f;
import static org.lwjgl.opengl.GL11.glViewport;
import static org.lwjgl.stb.STBTruetype.stbtt_GetPackedQuad;
import static org.lwjgl.stb.STBTruetype.stbtt_PackBegin;
import static org.lwjgl.stb.STBTruetype.stbtt_PackEnd;
import static org.lwjgl.stb.STBTruetype.stbtt_PackFontRange;
import static org.lwjgl.stb.STBTruetype.stbtt_PackSetOversampling;
import static org.lwjgl.system.MemoryUtil.NULL;
import static org.lwjgl.system.MemoryUtil.memAllocFloat;
import static org.lwjgl.system.MemoryUtil.memFree;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.HashMap;
import java.util.Map;

import org.lwjgl.BufferUtils;
import org.lwjgl.glfw.GLFWErrorCallback;
import org.lwjgl.glfw.GLFWVidMode;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GLUtil;
import org.lwjgl.stb.STBTTAlignedQuad;
import org.lwjgl.stb.STBTTPackContext;
import org.lwjgl.stb.STBTTPackRange;
import org.lwjgl.stb.STBTTPackedchar;
import org.lwjgl.system.Callback;

/**
 * STB Truetype oversampling demo.
 *
 * <p>This is a Java port of <a href="https://github.com/nothings/stb/blob/master/tests/oversample/main.c">https://github
 * .com/nothings/stb/blob/master/tests/oversample/main.c</a>.</p>
 */
public final class TruetypeOversample {

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

    // ----

    private final STBTTAlignedQuad q  = STBTTAlignedQuad.malloc();
    private final FloatBuffer      xb = memAllocFloat(1);
    private final FloatBuffer      yb = memAllocFloat(1);

    private long window;

    private Callback debugProc;

    // ----

    private int ww = 1600;
    private int wh = 768;

    private int fbw = ww;
    private int fbh = wh;

    private int font_tex;

    private STBTTPackedchar.Buffer chardata;
    //Map<char, index_in_chardata_buffer>
    private Map<Integer, Integer> chardataIndices;

    private boolean black_on_white;

    private boolean show_tex;

    private TruetypeOversample() {
    }

    public static void main(String[] args) {
        new TruetypeOversample().run("STB Truetype Oversample Demo");
    }

    private void load_fonts() {
        font_tex = glGenTextures();
        chardata = STBTTPackedchar.malloc(96);
        chardataIndices = new HashMap<>();

        for(int i = 0 ; i < chardata.remaining() ; i++) {
            chardataIndices.put(i + 32, i);
        }

        STBTTPackRange.Buffer packRanges = STBTTPackRange.malloc(1);
        packRanges.put(STBTTPackRange.malloc().set(
                16,
                32,
                null,
                96,
                chardata));

        try (STBTTPackContext pc = STBTTPackContext.malloc()) {
            ByteBuffer ttf = ioResourceToByteBuffer("demo/monof55.ttf", 512 * 1024);

            ByteBuffer bitmap = BufferUtils.createByteBuffer(BITMAP_W * BITMAP_H);

            stbtt_PackBegin(pc, bitmap, BITMAP_W, BITMAP_H, 0, 1, NULL);

            stbtt_PackSetOversampling(pc, 1, 1);

            //below method works
            stbtt_PackFontRange(pc, ttf, 0, 16, 32, chardata);

            //below method works not
//            stbtt_PackFontRanges(pc, ttf, 0, packRanges);

            stbtt_PackEnd(pc);

            glBindTexture(GL_TEXTURE_2D, font_tex);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, BITMAP_W, BITMAP_H, 0, GL_ALPHA, GL_UNSIGNED_BYTE, bitmap);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private void draw_init() {
        glDisable(GL_CULL_FACE);
        glDisable(GL_TEXTURE_2D);
        glDisable(GL_LIGHTING);
        glDisable(GL_DEPTH_TEST);

        glViewport(0, 0, fbw, fbh);
        if (black_on_white) {
            glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
        } else {
            glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
        }
        glClear(GL_COLOR_BUFFER_BIT);

        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(0.0, ww, wh, 0.0, -1.0, 1.0);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
    }

    private static void drawBoxTC(float x0, float y0, float x1, float y1, float s0, float t0, float s1, float t1) {
        glTexCoord2f(s0, t0);
        glVertex2f(x0, y0);
        glTexCoord2f(s1, t0);
        glVertex2f(x1, y0);
        glTexCoord2f(s1, t1);
        glVertex2f(x1, y1);
        glTexCoord2f(s0, t1);
        glVertex2f(x0, y1);
    }

    private void print(float x, float y, int font, String text) {
        xb.put(0, x);
        yb.put(0, y);

        chardata.position(0);

        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, font_tex);

        glBegin(GL_QUADS);
        for (int i = 0; i < text.length(); i++) {
            stbtt_GetPackedQuad(
                    chardata,
                    BITMAP_W, BITMAP_H,
                    chardataIndices.get((int)text.charAt(i)),
                    xb, yb,
                    q,
                    true);
            drawBoxTC(
                q.x0(), q.y0(), q.x1(), q.y1(),
                q.s0(), q.t0(), q.s1(), q.t1()
            );
        }
        glEnd();
    }

    private void draw_world() {
        float x = 20;

        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

        if (black_on_white) {
            glColor3f(0.0f, 0.0f, 0.0f);
        } else {
            glColor3f(1.0f, 1.0f, 1.0f);
        }

        print(80, 30, 0, "Controls:");
        if (black_on_white) {
            print(100, 210, 0, "B: toggle to white-on-black");
        } else {
            print(100, 210, 0, "B: toggle to black-on-white");
        }
        print(100, 235, 0, "V: view font texture");

        print(80, 300, 0, "Current fontsize: 16 pixels");

        if (show_tex) {
            glBegin(GL_QUADS);
            drawBoxTC(200, 400, 200 + BITMAP_W, 300 + BITMAP_H, 0, 0, 1, 1);
            glEnd();
        } else {
            glMatrixMode(GL_MODELVIEW);
            glTranslatef(200, 350, 0);

            print(x, 100, 0, "This is a test");
            print(x, 130, 0, "Now is the time for all good men to come to the aid of their country.");
            print(x, 160, 0, "The quick brown fox jumps over the lazy dog.");
        }
    }

    private void draw() {
        draw_init();
        draw_world();
        glfwSwapBuffers(window);
    }

    private void loopmode(float dt) {
        if (dt > 0.25f) {
            dt = 0.25f;
        }
        if (dt < 0.01f) {
            dt = 0.01f;
        }

        draw();
    }

    private void windowSizeChanged(long window, int width, int height) {
        this.ww = width;
        this.wh = height;
    }

    private void framebufferSizeChanged(long window, int width, int height) {
        this.fbw = width;
        this.fbh = height;
    }

    private void createWindow(String title) {
        GLFWErrorCallback.createPrint().set();
        if (!glfwInit()) {
            throw new IllegalStateException("Unable to initialize GLFW");
        }

        glfwDefaultWindowHints();
        glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
        glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);

        this.window = glfwCreateWindow(ww, wh, title, NULL, NULL);
        if (window == NULL) {
            throw new RuntimeException("Failed to create the GLFW window");
        }

        glfwSetWindowSizeCallback(window, this::windowSizeChanged);
        glfwSetFramebufferSizeCallback(window, this::framebufferSizeChanged);

        glfwSetKeyCallback(window, (window, key, scancode, action, mods) -> {
            if (action == GLFW_RELEASE) {
                return;
            }

            switch (key) {
                case GLFW_KEY_ESCAPE:
                    glfwSetWindowShouldClose(window, true);
                    break;
                case GLFW_KEY_V:
                    show_tex = !show_tex;
                    break;
                case GLFW_KEY_B:
                    black_on_white = !black_on_white;
                    break;
            }
        });

        // Center window
        GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());

        glfwSetWindowPos(
            window,
            (vidmode.width() - ww) / 2,
            (vidmode.height() - wh) / 2
        );

        // Create context
        glfwMakeContextCurrent(window);
        GL.createCapabilities();
        debugProc = GLUtil.setupDebugMessageCallback();

        glfwSwapInterval(1);
        glfwShowWindow(window);

        glfwInvoke(window, this::windowSizeChanged, this::framebufferSizeChanged);
    }

    private void run(String title) {
        try {
            createWindow(title);
            load_fonts();

            long time = System.nanoTime();
            while (!glfwWindowShouldClose(window)) {
                glfwPollEvents();

                long  t  = System.nanoTime();
                float dt = (float)((t - time) / 1000000000.0);
                time = t;

                loopmode(dt);
            }
        } finally {
            try {
                destroy();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void destroy() {
        chardata.free();

        if (debugProc != null) {
            debugProc.free();
        }

        glfwFreeCallbacks(window);
        glfwTerminate();
        glfwSetErrorCallback(null).free();

        memFree(yb);
        memFree(xb);

        q.free();
    }
}

Answer:

Flip the buffer...

packRanges.flip()

Even though the underlying library is written in C, this wrapper is written in Java and uses Java Buffers to pass data to the underlying lib. Thus after writing to the buffer you need to flip() it to prepare it for reading operations.

It doesn't matter that LWJGL does not modify the buffer.position() when reading, it will still use the position as a start-point.

Question:

So i am making a 3d game and i am working on 2d on 3d right now but there's just one problem before I'm finished with it and that is: I can't draw any 2d quads after i have drawed a text? Here's the renderer code:

changeto2D();
    for (Face2D face : tds){
        face.initializeEdges();
        GL11.glBegin(GL11.GL_QUADS);
        GL11.glColor4f(face.c.red, face.c.green, face.c.blue, (float) Math.sin(Math.toRadians(face.transparency)));
        for (Location l : face.edges){
            GL11.glVertex2f(l.x, l.y);
        }
        GL11.glEnd();
    }
    for (Text t : texts){
        t.draw();
    }
    fps++;
    for (GUI gui : openGUIs){
        gui.draw();
    }
    for (GUI gui : removing){
        if (openGUIs.contains(gui)) openGUIs.remove(gui);
    }
    removing.clear();

This is the code for changeTo2D();:

public void changeto2D() {
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, Display.getWidth(), Display.getHeight(), 0, 1, -1);
    glMatrixMode(GL_MODELVIEW);
    GL11.glViewport(0, 0, Display.getDisplayMode().getWidth(), Display.getDisplayMode().getHeight());
}

And to draw a text:

public Text(Location loc, String text, float size, Color color){
    try {
        InputStream inputStream = ResourceLoader.getResourceAsStream("res\\AGENCYR.TTF");

        Font awtFont2 = Font.createFont(Font.TRUETYPE_FONT, inputStream);
        awtFont2 = awtFont2.deriveFont(size);
        font = new TrueTypeFont(awtFont2, false);
    } catch (Exception e) {
        e.printStackTrace();
    }
    c = new org.newdawn.slick.Color(color.red, color.green, color.blue);
    this.loc = loc;
    this.text = text;
    this.size = size;
}
public void draw(){
    font.drawString(loc.x, loc.y, text, c);
    if (td != null){
        td.draw();
    }
}

And now here is what my problem is: I want to make a GUI that contains a Quad but it won't draw even thought the other quads draw perfectly fine? I've also tried putting the code for all the quads after drawing text them the quads won't draw at all.


Answer:

I fixed it, just made myself another renderering technique. Drawing all quads first and the texts at the end.