LWJGL 3 and Slick2D?

Started by DGK, June 03, 2018, 22:00:43

Previous topic - Next topic

DGK

I have spent the last hour looking for either a replacement for Slick2D-Utils (to draw Strings / etc) or finding a 'fixed' version that works with LWJGL 3, and to no avail I can get none of them to work. I always end up with an error related to the GLContext or something along the lines, and attempted to fix this issue by creating the font before or after the GLFW Window Creation. Everything else in my game works, but trying to implement TrueTypeFont in my project is becoming very difficult for me.

Is there a working version for Slick-Utils for LWJGL 3.x.x or any replacement for this (like a library that will allow me to draw text similar to Slick2D?)

This would be fantastic.

Thanks.

Cornix

LWJGL3 can be configured to come with a binding to stb_trutype which gives you access to a solid font rendering API.

Here is the code that I have used in my own project for font rendering. There might be a little bit more functionality than what you need so you might want to cut a few lines of code out:

Font class:
import static org.lwjgl.opengl.GL11.GL_ALPHA;
import static org.lwjgl.opengl.GL11.GL_LINEAR;
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 java.io.File;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.stb.STBTTBakedChar;
import org.lwjgl.stb.STBTTFontinfo;
import org.lwjgl.stb.STBTruetype;
import org.lwjgl.system.MemoryStack;

public class StbTtFontResource {
	
	public static final int CHAR_DATA_MALLOC_SIZE = 96;
	public static final int FONT_TEX_W = 512;
	public static final int FONT_TEX_H = FONT_TEX_W;
	public static final int BAKE_FONT_FIRST_CHAR = 32;
	public static final int GLYPH_COUNT = CHAR_DATA_MALLOC_SIZE;
	
	protected final STBTTBakedChar.Buffer charData;
	protected final STBTTFontinfo fontInfo;
	protected final int fontHeight;
	protected final int texGlName;
	protected final float ascent;
	protected final float descent;
	protected final float lineGap;
	protected final String fontName;
	protected boolean disposed;
	
	public StbTtFontResource(File ttfFile, int fontHeight) {
		fontName = ttfFile.getName();
		this.fontHeight = fontHeight;
		charData = STBTTBakedChar.malloc(CHAR_DATA_MALLOC_SIZE);
		fontInfo = STBTTFontinfo.create();
		int texGlName = 0;
		float ascent = 0, descent = 0, lineGap = 0;
		try {
			ByteBuffer ttfFileData = GlfwPRoot.loadFileToByteBuffer(ttfFile);
			ByteBuffer texData = BufferUtils.createByteBuffer(FONT_TEX_W * FONT_TEX_H);
			int result = STBTruetype.stbtt_BakeFontBitmap(ttfFileData, fontHeight, texData, FONT_TEX_W, FONT_TEX_H, BAKE_FONT_FIRST_CHAR, charData);
			if (result < 1) {
				System.err.println("stbtt_BakeFontBitmap failed with return value: "+result);
			}
			
			try (MemoryStack stack = MemoryStack.stackPush()) {
				STBTruetype.stbtt_InitFont(fontInfo, ttfFileData);
				float pixelScale = STBTruetype.stbtt_ScaleForPixelHeight(fontInfo, fontHeight);
				
				IntBuffer bufAscent = stack.ints(0);
				IntBuffer bufDescent = stack.ints(0);
				IntBuffer bufLineGap = stack.ints(0);
				STBTruetype.stbtt_GetFontVMetrics(fontInfo, bufAscent, bufDescent, bufLineGap);
				ascent = bufAscent.get(0) * pixelScale;
				descent = bufDescent.get(0) * pixelScale;
				lineGap = bufLineGap.get(0) * pixelScale;
			}
			
			texGlName = GL11.glGenTextures();
			GL11.glBindTexture(GL_TEXTURE_2D, texGlName);
			GL11.glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, FONT_TEX_W, FONT_TEX_H, 0, GL_ALPHA, GL_UNSIGNED_BYTE, texData);
			GL11.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			GL11.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		} catch (Exception e) {
			e.printStackTrace();
		}
		this.texGlName = texGlName;
		this.ascent = ascent;
		this.descent = descent;
		this.lineGap = lineGap;
	}
	
	public int getGlName() {
		return texGlName;
	}
	
	public STBTTBakedChar.Buffer getBakedCharData() {
		return charData;
	}
	
	public float getAscent() {
		return ascent;
	}
	
	public float getDescent() {
		return descent;
	}
	
	public float getLineGap() {
		return lineGap;
	}
	
	public void dispose() {
		if (disposed) {
			return;
		}
		disposed = true;
		charData.free();
		fontInfo.free();
		if (texGlName != 0) {
			GL11.glDeleteTextures(texGlName);
		}
	}
	
	public boolean isDisposed() {
		return disposed;
	}
	
	@Override
	protected void finalize() {
		dispose();
	}
	
	public String getName() {
		return fontName;
	}
	
	public int getPixelSize() {
		return fontHeight;
	}
	
	public static int getCodePoint(String text, int length, int index, IntBuffer out) {
		char c1 = text.charAt(index);
		if (Character.isHighSurrogate(c1) && index + 1 < length) {
			char c2 = text.charAt(index + 1);
			if (Character.isLowSurrogate(c2)) {
				out.put(0, Character.toCodePoint(c1, c2));
				return 2;
			}
		}
		out.put(0, c1);
		return 1;
	}
	
	public PSize getSize(String text, MutablePSize result) {
		int width = 0;
		int idx = 0;
		int length = text.length();
		try (MemoryStack stack = MemoryStack.stackPush()) {
			IntBuffer codePnt	= stack.mallocInt(1);
			IntBuffer adv		= stack.mallocInt(1);
			IntBuffer lsb		= stack.mallocInt(1);
			
			while (idx < length) {
				idx += StbTtFontResource.getCodePoint(text, length, idx, codePnt);
				int codepoint = codePnt.get(0);
				
				STBTruetype.stbtt_GetCodepointHMetrics(fontInfo, codepoint, adv, lsb);
				width += adv.get(0);
			}
		}
		width = (int) (width * STBTruetype.stbtt_ScaleForPixelHeight(fontInfo, fontHeight) + 0.5f);
		if (result == null) {
			return new ImmutablePSize(width, fontHeight);
		} else {
			result.set(width, fontHeight);
			return result;
		}
	}
	
	public static class GlyphDim {
		public final float width, height;
		public GlyphDim(float x0, float y0, float x1, float y1) {
			width = Math.abs(x0 - x1);
			height = Math.abs(y0 - y1);
		}
		public GlyphDim(float w, float h) {
			width = w;
			height = h;
		}
	}
	
}


Rendering code:
public final void drawString(StbTtFontResource fontRes, String text, float x, float y) {
		int fontSize = fontRes.getPixelSize();
		y += fontRes.getAscent();
		try (MemoryStack stack = MemoryStack.stackPush()) {
			FloatBuffer bufX = stack.floats(x);
			FloatBuffer bufY = stack.floats(y);
			
			STBTTAlignedQuad q = STBTTAlignedQuad.mallocStack(stack);
			STBTTBakedChar.Buffer charData = fontRes.getBakedCharData();
			
			GL11.glEnable(GL_TEXTURE_2D);
			GL11.glBindTexture(GL_TEXTURE_2D, fontRes.getGlName());
			
			GL11.glBegin(GL11.GL_TRIANGLES);
			GL11.glColor4f(curColorR, curColorG, curColorB, curColorA);
			
			int firstCP = StbTtFontResource.BAKE_FONT_FIRST_CHAR;
			int lastCP = StbTtFontResource.BAKE_FONT_FIRST_CHAR + StbTtFontResource.GLYPH_COUNT - 1;
			for (int i = 0; i < text.length(); i++) {
				int codePoint = text.codePointAt(i);
				if (codePoint == '\n') {
					bufX.put(0, x);
					bufY.put(0, y + bufY.get(0) + fontSize);
					continue;
				} else if (codePoint < firstCP || codePoint > lastCP) {
					continue;
				}
				STBTruetype.stbtt_GetBakedQuad(charData,
						StbTtFontResource.FONT_TEX_W, StbTtFontResource.FONT_TEX_H,
						codePoint - firstCP,
						bufX, bufY, q, true);
				
				GL11.glTexCoord2f(q.s0(), q.t0());
				GL11.glVertex2f(q.x0(), q.y0());
				
				GL11.glTexCoord2f(q.s0(), q.t1());
				GL11.glVertex2f(q.x0(), q.y1());
				
				GL11.glTexCoord2f(q.s1(), q.t1());
				GL11.glVertex2f(q.x1(), q.y1());
				
				GL11.glTexCoord2f(q.s1(), q.t1());
				GL11.glVertex2f(q.x1(), q.y1());
				
				GL11.glTexCoord2f(q.s1(), q.t0());
				GL11.glVertex2f(q.x1(), q.y0());
				
				GL11.glTexCoord2f(q.s0(), q.t0());
				GL11.glVertex2f(q.x0(), q.y0());
			}
			GL11.glEnd();
			
			GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
			GL11.glDisable(GL11.GL_TEXTURE_2D);
		}
	}

DGK

Edit: I imported STB but I am getting errors for GlfwPRoot and MutablePSize.

Cornix

These classes are not necessary for the general functionality of text rendering. Obviously you can not just copy & paste the code above an expect it to work in your project. I gave it to you as a basis for your own implementation. The method to load a file into a buffer looks like this:
public static ByteBuffer loadFileToByteBuffer(File file) throws IOException {
		ByteBuffer buffer;
		try (FileInputStream fis = new FileInputStream(file);
				FileChannel fc = fis.getChannel();)
		{
			buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
		}
		return buffer;
	}

I am pretty sure I have taken it either from the LWJGL3 wiki or the forums.