Sign in to follow this  
Finalspace

OpenGL How to rendering fast bitmap fonts/text

Recommended Posts

Hi there,

i have implemented signed distance field bitmap fonts creation and rendering successfully in a simple opengl sample.
The rendering is a mix of modern shader stuff and immediate mode for translating and scaling the characters, but this are not allowed in my actual game engine. Only opengl es 2.0 compatible rendering is allowed, no pushmatrix, no translate, scale etc. What is the fastest way possible to convert the following code into a 100% opengl es 2.0 compatible way - just the todo part?

 

I dont think its a good way to do for every character two matrix operations (scale and translate) and one operation to reset the matrix before the drawing. Is there a way to improve that - can these be moved to the shader somehow ?
 

	
	private void drawText(String text, float x, float y, float scale, HorizontalAlignment halign, VerticalAlignment valign) {
		// Enable alpha blending
		GL11.glEnable(GL11.GL_BLEND);

		// Enable vertex buffer - will also enabled client state for texture and vertex array
		fontVBO.enable();

		// Set white color
		GL11.glColor4f(1f, 1f, 1f, 1f);

		// Calculate text sizes in world coordinates
		float textSizeX = getTextWidth(text, scale);
		float textSizeY = getTextHeight(text, scale);

		// Calculate starting text x and y offset in world coordinates
		float sxoffset = -textSizeX * 0.5f;
		float syoffset = 0f;
		if (HorizontalAlignment.Left.equals(halign)) {
			sxoffset += textSizeX * 0.5f;
		} else if (HorizontalAlignment.Right.equals(halign)) {
			sxoffset -= textSizeX * 0.5f;
		}
		if (VerticalAlignment.Bottom.equals(valign)) {
			syoffset = textSizeY * 0.5f;
		} else if (VerticalAlignment.Top.equals(valign)) {
			syoffset = -textSizeY * 0.5f;
		}

		// Enable bitmap font texture
		fontTex.enable(0);
		
		// Enable sdf shader when enabled
		if (sdfEnabled) {
			sdfShader.enable();
			sdfShader.setUniform1i(sdfShaderTex0Location, 0);
		}
		
		// TODO: Rendering the characters - each char is moved and scaled in immediate mode which does not work with opengl es 2.0!!!
		float xoffset = sxoffset;
		float yoffset = syoffset;
		for (int i = 0; i < text.length(); i++) {
			BitmapFontChar fontChar = bitmapFonts.get(text.codePointAt(i));
			GL11.glPushMatrix();
			GL11.glTranslatef(x + xoffset + fontChar.getAdvance() * scale * 0.5f, y + yoffset, 0f);
			GL11.glScalef(scale, scale, 1f);
			GL11.glDrawArrays(GL11.GL_QUADS, fontChar.getVboOffset(), 4);
			GL11.glPopMatrix();
			xoffset += fontChar.getAdvance() * scale;
		}
		
		// Disable sdf shader when enabled
		if (sdfEnabled) {
			sdfShader.disable();
		}

		// Disable bitmap font texture
		fontTex.disable(0);

		/*
		GL11.glPolygonMode( GL11.GL_FRONT, GL11.GL_LINE );
		xoffset = sxoffset;
		yoffset = syoffset;
		for (int i = 0; i < text.length(); i++) {
			BitmapFontChar fontChar = bitmapFonts.get(text.codePointAt(i));
			GL11.glPushMatrix();
			GL11.glTranslatef(x + xoffset + fontChar.getAdvance() * 0.5f, y + yoffset, 0f);
			GL11.glDrawArrays(GL11.GL_QUADS, fontChar.getVboOffset(), 4);
			GL11.glPopMatrix();
			xoffset += fontChar.getAdvance();
			//yoffset -= 0.01f;
		}
		GL11.glPolygonMode( GL11.GL_FRONT, GL11.GL_FILL );
		*/

		// Disable vertex buffer - will also disable client state for texture and vertex array
		fontVBO.disable();

		// Disable alpha blending
		GL11.glDisable(GL11.GL_BLEND);
	}
Edited by Finalspace

Share this post


Link to post
Share on other sites

Create the matrix with some other matrix library, that is opengl compatible.

 

Then add something like this to your shader:

 

  gl_Position = u_matrix * a_position;

 

Then you should be able to set your matrix with setUniform and everything should work as before.

Share this post


Link to post
Share on other sites

I have already done this, but is this really the fastest way to do this?

				// Render chars
				float xoffset = sxoffset;
				float yoffset = syoffset;
				for (int i = 0; i < text.length(); i++) {
					TextureFontChar tfc = texFont.getChar(text.codePointAt(i));
					if (tfc == null) {
						// Char is not found, use first one which should the space character
						tfc = texFont.getChar(0);
					}
					currentMatrix.set(orthoMatrix);
					currentMatrix.translate(x + xoffset + tfc.getAdvance() * 0.5f * scale, y + yoffset, 0f);
					currentMatrix.scale(scale, scale, 1f);
					renderer.setTransform(currentMatrix);
					renderer.drawVertexArray(ElementType.Quad, tfc.getVboOffset(), 4);
					xoffset += tfc.getAdvance() * 2f * scale;
				}

Edited by Finalspace

Share this post


Link to post
Share on other sites
Can't you just set up your quads first and then call just one DrawArrays for the whole screen full of text?
You can make it that whenever you call your text drawing code, it does NOT draw it but builds up the quad array. The text can then be all drawn with one call before you swap buffers, or whenever you like of course.

Share this post


Link to post
Share on other sites

Ah, sorry, I thought you meant "least change" with fastest.

 

If you are not animating the individual characters every frame, it would be faster to generate the vertexes into a vertex-array, and then re-use that until the text has to be changed.

Edited by Olof Hedman

Share this post


Link to post
Share on other sites

Ah, sorry, I thought you meant "least change" with fastest.

 

If you are not animating the individual characters every frame, it would be faster to generate the vertexes into a vertex-array, and then re-use that until the text has to be changed.

 

This may work to modify the vertex buffer and fill it with all the letter quads - should be faster than using 2 matrix operations per letter. Why i havent thought about that.... thanks for that hint! The only thing which sucks - filling the vertex buffer with java is a pain in the pass :-(

 

This is so .... inconvenient (This is done once to create the letters from the bitmap font in the vertex buffer initialy):

// Creating a vertex buffer for all letters/quads
fontVBO = renderer.createVertexBuffer(false, vertexCount, VertexBufferFormat.TEX2_VERTEX4_PAD2);

// Creating a damn byte buffer which is required for the fill vertex buffer method
ByteBuffer vertBuffer = ByteBuffer.allocateDirect(VertexBufferFormat.TEX2_VERTEX4_PAD2.getStride() * vertexCount).order(ByteOrder.nativeOrder());
		
// We want to fill floats into the byte buffer - so we need to convert it back to a float buffer -.-
FloatBuffer fontVertices = vertBuffer.asFloatBuffer();

// Put stuff in it
...
fontVertices.put(new float[] { u, v, x, y, z, w, 0f, 0f });
...

// Finally flip the buffer to write the bytes left to the final byte buffer
// This will also reset the postition to zero
fontVertices.flip();

// And lastly fill the vertex buffer with that byte buffer content
fontVBO.fill(vertBuffer);

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this