我在做什么 wrong/missing 以从 stbtt_PackFontRange 切换到 stbtt_PackFontRanges?

What am I doing wrong/missing to switch from stbtt_PackFontRange to stbtt_PackFontRanges?

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

为了理解和使用该库,我想将过采样演示中的代码从使用...

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

...改为使用...

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

...产生空纹理。

根据我的理解,我可以通过阅读文档和查看 stb 存储库以及 lwjgl 存储库中的示例来收集我正在做的事情应该有效,但它没有。

我修改了下面的 class 从原来的 ~1 行到我从使用 stbtt_PackFontRange(...) 方法切换到使用 stbtt_PackFontRanges(...) 方法的点。


上下文的完整 load_fonts() 方法:

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);
    }
}

完整修改的 TruetypeOversample class,可以通过克隆 lwjgl3 repo 并用相同的名称替换 class 来最简单地执行:

/*
 * 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();
    }
}

翻转缓冲区...

packRanges.flip()

尽管底层库是用 C 编写的,但此包装器是用 Java 编写的,并使用 Java 缓冲区将数据传递到底层库。因此,在写入缓冲区后,您需要 flip() 为读取操作做准备。

LWJGL阅读时不修改buffer.position()也没关系,还是会以位置为起点