Fonts don't render properly if more than 1 file is used?

Started by RJ, January 16, 2016, 09:20:20

Previous topic - Next topic

RJ

When I load just 1 font file everything runs smooth. I load 5 different font sizes to 1 buffer for each font. If I try to load another font it works but it renders incorrectly

Code:
package stbtests;

import static org.lwjgl.demo.util.IOUtil.*;
import static org.lwjgl.glfw.Callbacks.*;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.stb.STBTruetype.*;
import static org.lwjgl.system.MemoryUtil.*;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;

import org.lwjgl.BufferUtils;
import org.lwjgl.glfw.GLFWErrorCallback;
import org.lwjgl.glfw.GLFWFramebufferSizeCallback;
import org.lwjgl.glfw.GLFWKeyCallback;
import org.lwjgl.glfw.GLFWVidMode;
import org.lwjgl.glfw.GLFWWindowSizeCallback;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GLUtil;
import org.lwjgl.stb.STBTTAlignedQuad;
import org.lwjgl.stb.STBTTPackContext;
import org.lwjgl.stb.STBTTPackedchar;
import org.lwjgl.system.libffi.Closure;

public class StbTrueTypeFontTest {
	
	private final String testString;
	private final int[] scale;
	private float fontHeight;
	private int fontID;
	private final GLFWErrorCallback errorfun;
	private final GLFWWindowSizeCallback windowSizefun;
	private final GLFWFramebufferSizeCallback framebufferSizefun;
	private final GLFWKeyCallback keyfun;
	private long window;
	private int ww = 800;
	private int wh = 600;
	private Closure debugProc; 
	
	int BITMAP_W, BITMAP_H;
	int texID[];
	STBTTPackedchar.Buffer chardata[];
	STBTTAlignedQuad q[];
	
	public StbTrueTypeFontTest () {
		testString = "The Quick Brown Fox Jumped Over The Lazy Dog.";
		scale = new int[] {20, 40, 60, 80, 100};
		fontHeight = 18;
		fontID = getFontID (fontHeight);
		errorfun = GLFWErrorCallback.createPrint();

		windowSizefun = new GLFWWindowSizeCallback() {
			@Override
			public void invoke(long window, int width, int height) {
				StbTrueTypeFontTest.this.ww = width;
				StbTrueTypeFontTest.this.wh = height;

				glMatrixMode(GL_PROJECTION);
				glLoadIdentity();
				glOrtho(0.0, width, height, 0.0, -1.0, 1.0);
				glMatrixMode(GL_MODELVIEW);
			}
		};

		framebufferSizefun = new GLFWFramebufferSizeCallback() {
			@Override
			public void invoke(long window, int width, int height) {
				glViewport(0, 0, width, height);
			}
		};

		keyfun = new GLFWKeyCallback() {
			@Override
			public void invoke(long window, int key, int scancode, int action, int mods) {
				//ctrlDown = (mods & GLFW_MOD_CONTROL) != 0; /** Set modifiers this way **/
				if ( action == GLFW_RELEASE )
					return;

				switch ( key ) {
					case GLFW_KEY_ESCAPE:
						glfwSetWindowShouldClose(window, GLFW_TRUE);
						break;
					case GLFW_KEY_KP_ADD:
					case GLFW_KEY_EQUAL:
						fontHeight += 2;
						if (fontHeight > 100) fontHeight = 100;
						fontID = getFontID(fontHeight);
						break;
					case GLFW_KEY_KP_SUBTRACT:
					case GLFW_KEY_MINUS:
						fontHeight -= 2;
						if (fontHeight < 10) fontHeight = 10;
						fontID = getFontID(fontHeight);
						break;
					case GLFW_KEY_0:
					case GLFW_KEY_KP_0:
							fontHeight = scale[fontID];
							fontID = getFontID(fontHeight);
						break;
				}
			}
		};
	}
	
	private int getFontID (float fontHeight) {
		for (int i = 0; i < scale.length; i++) {
			if (fontHeight <= scale[i]) { System.out.println("Returning: " + i); return i; }
		}
		return -1;
	}
	
	private void createFont(File[] files) {
		int size = files.length;
		texID = new int[size];
		chardata = new STBTTPackedchar.Buffer[size];
		q = new STBTTAlignedQuad[size];
		
		for (int i = 0; i < size; i++) {
			
		texID[i] = glGenTextures();
		chardata[i] = STBTTPackedchar.mallocBuffer(5 * 128);
		q[i] = STBTTAlignedQuad.malloc();
				try {
					String path = files[i].getPath ().replaceAll("\\\\", "/");
					System.out.println(path);
					
					ByteBuffer ttf = ioResourceToByteBuffer(path, 160 * 1024); 
					ByteBuffer bitmap = BufferUtils.createByteBuffer(BITMAP_W * BITMAP_H);

					STBTTPackContext pc = STBTTPackContext.malloc();
					stbtt_PackBegin(pc, bitmap, BITMAP_W, BITMAP_H, 0, 1, null);
			
					for (int s = 0; s < scale.length; s++) {
						chardata[i].position(s * 128 + 32);
						stbtt_PackSetOversampling(pc, 3, 1);
						stbtt_PackFontRange(pc, ttf, 0, scale[s], 32, 95, chardata[i]);
					}
				
					stbtt_PackEnd(pc);
					pc.free();

					glBindTexture(GL_TEXTURE_2D, texID[i]);
					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 drawText() {
		FloatBuffer x = BufferUtils.createFloatBuffer(1);
		FloatBuffer y = BufferUtils.createFloatBuffer(1);
		
		float scaleFactor = fontHeight / scale[fontID];
		
		System.out.println("ScaleFactor: " + scaleFactor + ", Scale: "+ scale[fontID] +  ", fontHeight: " + fontHeight + ", FontID: " + fontID + ", Addition: " + ((fontID + 1f) * 3f));
		
		int font = 0; int style = 0;
		
		chardata[0].position (fontID * 128 + 32);
		glColor3f(1f,1f,0f);
		glPushMatrix();
		// Zoom
		glScalef(scaleFactor, scaleFactor, 1f);
		// Scroll
		glTranslatef(fontID <= 1 ? 0f : fontID <= 3 ? -1f : fontID == 4 ? -2f : 0f, scale[fontID] * 0.5f + ((fontID + 1f) * 3f), 0f);

		x.put(0, 0.0f);
		y.put(0, 0.0f);
		glBegin(GL_QUADS);
		for ( int i = 0; i < testString.length(); i++ ) {
			char c = testString.charAt(i);
			if ( c == '\n' ) {
				y.put(0, y.get(0) + scale[fontID]);
				x.put(0, 0.0f);
				continue;
			} else if ( c < 32 || 128 <= c )
				continue;

			stbtt_GetPackedQuad(chardata[0], BITMAP_W, BITMAP_H, c - 32, x, y, q[0], 1);

			glTexCoord2f(q[0].getS0(), q[0].getT0());
			glVertex2f(q[0].getX0(), q[0].getY0());

			glTexCoord2f(q[0].getS1(), q[0].getT0());
			glVertex2f(q[0].getX1(), q[0].getY0());

			glTexCoord2f(q[0].getS1(), q[0].getT1());
			glVertex2f(q[0].getX1(), q[0].getY1());

			glTexCoord2f(q[0].getS0(), q[0].getT1());
			glVertex2f(q[0].getX0(), q[0].getY1());
		}
		glEnd();

		glPopMatrix();

	}
	
	private void loop() {
		BITMAP_W = 1536;
		BITMAP_H = 1536;

		createFont(new File("fonts/Arial/").listFiles ());

		glClearColor(43f / 255f, 43f / 255f, 43f / 255f, 0f); // BG color
		glColor3f(169f / 255f, 183f / 255f, 198f / 255f); // Text color

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

		while ( glfwWindowShouldClose(window) == GLFW_FALSE ) {
			glfwPollEvents();

			glClear(GL_COLOR_BUFFER_BIT);

			drawText();
			
			glfwSwapBuffers(window);
		}
		for (int i = 0; i < q.length; i++)
		q[i].free();
		for (int i = 0; i < chardata.length; i++)
		memFree(chardata[i]);

		glfwDestroyWindow(window);
	}
	
	private void init(String title) {
		errorfun.set();
		if ( glfwInit() != GLFW_TRUE )
			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");

		windowSizefun.set(window);
		framebufferSizefun.set(window);
		keyfun.set(window);

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

		glfwSetWindowPos( window, (vidmode.getWidth() - ww) / 2, (vidmode.getHeight() - wh) / 2 );

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

		glfwSwapInterval(1);
		glfwShowWindow(window);
		glfwInvoke(window, windowSizefun, framebufferSizefun);
	}
	
	private void destroy() {
		if ( debugProc != null )
			debugProc.release();
		keyfun.release();
		framebufferSizefun.release();
		windowSizefun.release();
		glfwTerminate();
		errorfun.release();
	}
	
	private void run(String title) {
		try {
			init(title);

			loop();
		} finally {
			try {
				destroy();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void main(String[] args) {
		new StbTrueTypeFontTest().run("STBTrueTypeFontTest");
	}

}

bobjob

1 concern is that currently your BITMAP_W and BITMAP_H are not results of the power of 2.
BITMAP_W = 1536;
BITMAP_H = 1536;
eg. 512, 1024, 2048

Generally its good practice to have only 1 font, and 1 size in each image file. So keep a seperate font class for each possible settings.

The reason for this:

1: is that your will rarely need to mix multiple sizes together in the same line of text, so no need to reduce the calls to bind the texture by having them on the same image.

2: you can choose an efficient Bitmap width and height size after loading the single size at a time, by visually checking it.

3: its great for dynamic loading of fonts, in case you want to display a font while loading non-critical fonts in the background.

Also, personally I only use the over sampling size of 2x2 as I find it the most clear, and best for scaling of text if need be. From what I can see, it seems you went with 3x1.

But from what I can tell, the main issue you are having is that your bitmap size is too small for what you are inputting in your code.

Its a great idea to be able to render the texture of the font to the screen in your testing demo, so you can get an idea of home much space on the image has been used up for debugging purposes, before implementing those font settings into your actual application. There is a great LWJGL demo that does just that.

RJ

Fixed. The problem: I didn't call
glBindTexture(GL_TEXTURE_2D, fonts[style].getTexID ());

bobjob

Oops! didn't notice, you where using an array of textures.

bobjob

Went over your code again, and realized I was still right the first time. The code will still have problems supporting so many texture scales on the 1 texture. You will notice if you try use a font of scale 100, the font will not display correctly.