Trying to make a font renderer system...

Started by Yuri6037, October 23, 2013, 15:18:39

Previous topic - Next topic

Yuri6037

Hello,

Because of limitations, performence issues and blockages I need to kill the library I was using : Slick2D ! Currently I have finished the RenderEngine (rendering math forms, mounting colors and textures)...

Now the problem is text rendering... So my idea would be to create a 1024 by 1024 font texture in ascii (.png), devide it into squares that matches ascii char code like name ! But i don't know how to do that in LWJGL !

I only need that and the whole renderer system is terminated, it only rest to change the references to Graphics (Slick2d) with the unique instance of RenderEngine...


Please help me,
Yuri6037

Yuri6037

Nobody has an idea... Nobody can help ? You know how to render math forms, and you have helped me in the past. But you don't know how to render fonts ?
Strange...

I will never finish the new version of my game !

quew8

What do you mean by "Maths Forms"? As in mathematical notation? How did you do that? I'm not really sure how the two would be different.

As for your idea (which is an established way of doing it) you load the whole thing as one texture and then mess around with the texture coords when you draw. This is a very general description so please feel free to ask specific questions. It's just that there's no point me going into any detail since 90% of it you'll probably already know.

juemisorg

Hi.

Here is my lwjgl's font rendering. Font's image come the web. I have another version that work with a png and xml files created by BmGenerator.
I hope it can help you.
Thank's.

package test6_Fonte;

import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL13.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL30.*;

import java.nio.FloatBuffer;

import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.ContextAttribs;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.PixelFormat;

/**
 * Gestion des fontes<br>
 * 
 * Date Création : 25/08/2012<br>
 * Date Modification : 26/08/2012
 */
public class Fonte {

	/**
	 * Nombre de caractères max par chaine affichée
	 */
	private static int NB_CARS_MAX = 256;
	/**
	 * Nombre de float par caractère : 6 faces de 4 éléments
	 */
	private static int NB_FLOAT_CHAR = 24;
	/**
	 * Texture de la fonte
	 */
	Texture texture;
	/**
	 * Shader d'affichage de la fonte
	 */
	Shader shader;
	/**
	 * VAO
	 */
	int vaoID;
	/**
	 * VBO
	 */
	int vboTextureID;
	/**
	 * Buffer
	 */
	FloatBuffer vertexFB;

	/**
	 * Constructeur
	 */
	public Fonte() {
		final String f = "Fonte.Fonte : ";
		texture = Texture.loadFromFile("_Images/Fonte.png");
		shader = new ShaderText("_Shaders/vTexte.vert", "_Shaders/fTexture.frag");
		shader.create();
		vertexFB = BufferUtils.createFloatBuffer(NB_CARS_MAX * NB_FLOAT_CHAR);

		System.out.println(f + " ok");
	}

	/**
	 * 
	 */
	public void createBuffer() {
		String f = "createBuffer";

		// Création d'un VAO qui va contenir un VBO
		vaoID = glGenVertexArrays();
		MethodesStatic.checkError(f + "glGenVertexArrays");

		// On va travailler avec le VOA 
		glBindVertexArray(vaoID);
		MethodesStatic.checkError(f + "glBindVertexArray");

		// Création d'un VBO
		vboTextureID = glGenBuffers();
		MethodesStatic.checkError(f + "glGenBuffers - vboTextureID");

		// On va travailler avec ce VBO
		glBindBuffer(GL_ARRAY_BUFFER, vboTextureID);
		MethodesStatic.checkError(f + "glBindBuffer - vboVertexID");

		// Active et positionne les diverses propriétés d'un vertex
		glEnableVertexAttribArray(0);
		MethodesStatic.checkError(f + "glEnableVertexAttribArray - 0");
		glVertexAttribPointer(0, 4, GL_FLOAT, false, 16, 0);
		MethodesStatic.checkError(f + "glVertexAttribPointer - 0");

		// Upload les vertex (points) dans le VBO - Ici réserve de la mémoire
		glBufferData(GL_ARRAY_BUFFER, NB_CARS_MAX * NB_FLOAT_CHAR * 4, GL_DYNAMIC_DRAW);
		MethodesStatic.checkError(f + "glBufferData");

		// Fin enregistrement
		glBindVertexArray(0);

		System.out.println(f + " ok");
		MethodesStatic.checkError(f + "glBindVertexArray 0");
	}

	/**
	 * Efface proprement la fonte
	 */
	public void delete() {
		final String f = "Fonte.delete : ";

		texture.delete();
		shader.delete();

		// On s'assure qu'on utilise plus le VOA
		glDisableVertexAttribArray(0);
		MethodesStatic.checkError(f + "glDisableVertexAttribArray - 0");

		// Supprime du VAO le lien vers le VBO
		glBindBuffer(GL_ARRAY_BUFFER, 0);
		MethodesStatic.checkError(f + "glBindBuffer");

		// Supprime le VBO
		glDeleteBuffers(vboTextureID);
		MethodesStatic.checkError(f + "glDeleteBuffers vboTextureID");

		// Finalement, supprime le VOA
		glDeleteVertexArrays(vaoID);
		MethodesStatic.checkError(f + "glDeleteVertexArrays");

		System.out.println(f + " ok");
	}

	/**
	 * Affiche une chaine
	 * 
	 * @param _str Chaîne à afficher dont la longueur (non testée) doit être inférieure à NB_CARS_MAX (256)
	 * @param _x Position X
	 * @param _y Position Y
	 */
	public void drawString(String _str, float _x, float _y) {
		final String f = "Fonte.drawString : ";

		//
		// Construit le buffer avec les triangles qui vont former le texte.
		// Deux triangles par caractère
		//
		float SCREEN_SCALE_X = 2.0f / (float) Display.getWidth();
		float SCREEN_SCALE_Y = 2.0f / (float) Display.getHeight();

		float TEXTURE_DELTA = 1.0f / 94f;
		float CAR_X = texture.tx * SCREEN_SCALE_X * TEXTURE_DELTA;
		float CAR_Y = texture.ty * SCREEN_SCALE_Y;

		float posX = _x * SCREEN_SCALE_X - 1f;
		float posY = -_y * SCREEN_SCALE_Y + 1f;

		byte[] bytes = _str.getBytes();

		// Pour chaque caractère
		vertexFB.clear();
		for (int i = 0; i < bytes.length; i++) {

			float sx1 = (float) (bytes[i] - 33) * TEXTURE_DELTA;
			float sx2 = sx1 + TEXTURE_DELTA;

			// Triangle 1
			vertexFB.put(posX);
			vertexFB.put(posY);
			vertexFB.put(sx1);
			vertexFB.put(0);

			vertexFB.put(posX);
			vertexFB.put(posY - CAR_Y);
			vertexFB.put(sx1);
			vertexFB.put(1);

			vertexFB.put(posX + CAR_X);
			vertexFB.put(posY);
			vertexFB.put(sx2);
			vertexFB.put(0);

			// Triangle 2
			vertexFB.put(posX);
			vertexFB.put(posY - CAR_Y);
			vertexFB.put(sx1);
			vertexFB.put(1);

			vertexFB.put(posX + CAR_X);
			vertexFB.put(posY - CAR_Y);
			vertexFB.put(sx2);
			vertexFB.put(1);

			vertexFB.put(posX + CAR_X);
			vertexFB.put(posY);
			vertexFB.put(sx2);
			vertexFB.put(0);

			posX += CAR_X;
		}
		vertexFB.flip();

		// On va travailler avec le VBO
		glBindBuffer(GL_ARRAY_BUFFER, vboTextureID);
		MethodesStatic.checkError(f + "glBindBuffer - vboVertexID");

		// Upload les vertex (points) dans le VBO
		glBufferSubData(GL_ARRAY_BUFFER, 0, vertexFB);
		MethodesStatic.checkError(f + "glBufferSubData");

		// On ne travaille plus avec ce VBO
		glBindBuffer(GL_ARRAY_BUFFER, 0);
		MethodesStatic.checkError(f + "glBindBuffer - 0");

		//
		// Dessine
		// 
		// On utilise le Shader d'affichage du texte
		glUseProgram(shader.programID);
		MethodesStatic.checkError(f + "glUseProgram shader.pixelShaderID");

		// Active l'unité 0 des textures
		glActiveTexture(GL_TEXTURE0);
		MethodesStatic.checkError(f + "glActiveTexture");

		// Utilise la texture de la fonte			
		glBindTexture(GL_TEXTURE_2D, texture.textureID);
		MethodesStatic.checkError(f + "glBindTexture");

		// Bind le VAO
		glBindVertexArray(vaoID);
		MethodesStatic.checkError(f + "glBindVertexArray voaID");

		// Dessine
		glDrawArrays(GL_TRIANGLES, 0, vertexFB.remaining() >> 2);
		MethodesStatic.checkError(f + "glDrawArrays");

		// Fin d'utilisation du VAO
		glBindVertexArray(0);
		MethodesStatic.checkError(f + "glBindVertexArray 0");

		// On n'utilise plus le shader
		glUseProgram(0);
		MethodesStatic.checkError(f + "glUseProgram 0");
	}

	/**
	 * Point d'entré du programme
	 * 
	 * @param args Arguments du programme
	 */
	public static void main(String[] args) {

		final int screenX = 1400;
		final int screenY = 900;

		PixelFormat pixelFormat = new PixelFormat();
		ContextAttribs contextAtrributes = new ContextAttribs(4, 2);
		contextAtrributes.withForwardCompatible(true);
		contextAtrributes.withProfileCore(true);

		try {
			Display.setDisplayMode(new DisplayMode(screenX, screenY));
			Display.create(pixelFormat, contextAtrributes);
			String str = "4.2";
			if (!str.equals(glGetString(GL_VERSION).substring(0, 3))) {
				throw new LWJGLException("Version OpenGL incompatible");
			}
			Display.setTitle(Fonte.class.getName());
		} catch (Exception e) {
			System.out.println("initOpenGL : " + e.toString());
			System.exit(-1);
		}

		glViewport(0, 0, screenX, screenY);
		glClearColor(0.8f, 0.2f, 0.8f, 0f);
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

		Fonte ft = new Fonte();
		ft.createBuffer();

		int nbImage = 0;
		long start = System.nanoTime();
		while (!Display.isCloseRequested()) {

			long debut = System.nanoTime();
			glClear(GL_COLOR_BUFFER_BIT);
			for (int i = 30; i < screenY - 24; i += 24) {
				ft.drawString("abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXY01234", 0, i);
			}
			nbImage++;

			long now = System.nanoTime();
			float duree = (float) (now - debut) / 1000000.0f;
			float dureeTotal = (float) (now - start) / 1000000.0f;
			String str = (int) duree + " ms - " + (int) (1000.0f / duree) + " FPS - " + (int) (1000.0f * (float) nbImage / dureeTotal) + " Moyenne";
			ft.drawString(str, 0, 0);
			//			
			//Display.sync(120);
			Display.update();

		}

		// 1800 FPS

		long duree = (System.nanoTime() - start) / 1000000;
		System.out.println("\nDurée: " + duree + "ms = " + 1000 * nbImage / duree + " FPS\n");

		//ft.delete();
		Display.destroy();
	}
}


The vertex shader

#version 420 core

// Description d'un point (vertex) 
layout (location = 0) in vec4 inPositionTexture;

// Données passées au pixel shader
out vec2 inOutTextureCoord;

void main(void) {
    gl_Position = vec4(inPositionTexture.xy, 0, 1);    
    inOutTextureCoord = inPositionTexture.zw;
}


The pixel shader

#version 420 core

// Données issues du vertex shader
in vec2 inOutTextureCoord;

// Couleur finale
out vec4 finalColor;

// Donnée issue du programme 
uniform sampler2D texture;

void main(void) {
    finalColor = texture2D(texture, inOutTextureCoord);
}

Yuri6037

@Quew8
Hi, that's was a long time !
In effect, because i'm French it's complicated for me to explain something good in english... So excuse me, i will try to explain better now :
I would like to create a class called FontRenderer that will be used by the game to draw text at the screen...

For the "math forms", i  have to say that for example it refer to the functions renderQuad, renderUnfilledQuad, renderTriangle or renderRound in the game RenderEngine, it means respectively draw a quad, draw an unfilled quad, draw a triangle or draw a round at screen.

I wan't another time to say that i'm completly sorry for this incomprehention, that's because i'm French !
I had to leave french programming forums because of excessive number of kikoolols, noob, no brain's kid and many other reasons, in plus in all french programming forums nobody know how to use LWJGL (they don't know what is glVertex2f, and when you ask what class is to use when you wan't OpenGL 1.1 they answer org.lwjgl.Display). I hope now you understand the problem...


@juemisorg
Good code, i don't know if i will take that because i'm searching for a 1024 (i prefer 4096 but that exced my Ram) font reader not a 256 (too bad quality) font reader...
Thank you very much, you helped a little !

Yuri6037 !

quew8

OK. Call them "shapes" or "geometry", not "maths forms." I do know where you're coming from - the french "formes" - but otherwise you will seriously confuse people. Or at least you will me. I've often imagined how hard it must be to learn programming or an API from another language. Now on to serious matters.

So first of all take a look at the OpenGL texture coordinate system. Yes the Y-Axis is upside down. Maybe you already knew this, I'm just saying it in case you didn't to save you the annoyance. As it happens I think that'll help you here... So from the picture you can see how you might get the texture coordinates of a particular grid square.



Now what you need then is a way to access the appropriate texture coordinate from the ascii code of the character. Personally, if it was me I would store the locations in an ArrayList and just look them up each time. You could also however work them out each time if you so wished.

A little example of how to draw them then. This assumes the grid you use is 8 across by 16 down (8 * 16 = 128). I know there aren't actually 128 characters you'll want to draw but you get the picture.

public void drawLine(String line, float xPos, float yPos, float charWidth, float charHeight) {
    bindTexture();
    float x = xPos;
    glBegin(GL_QUADS);
    for(int i = 0; i < line.length; i++) {
        int code = (int) line.charAt(i);
        int gridX = code % 16;  //Works out the position in the grid of this character.
        int gridY = ( code - gridX ) / 16;
        float texX = (float) gridX / 8; //Works out the texture coord of the upper left corner of the char.
        float texY = (float) gridY / 16;

        glTexCoord2f(texX, texY); //Upper Left Corner
        glVertex2f(xPos, yPos);

        glTexCoord2f(texX, texY + (1 / 16) ); //Bottom Left Corner
        glVertex2f(xPos, yPos - charHeight);

        glTexCoord2f(texX + (1 / 8), texY + (1 / 16) ); //Bottom Right Corner
        glVertex2f(xPos + charWidth, yPos - charHeight);

        glTexCoord2f(texX + (1 / 8), texY); // Top Right Corner
        glVertex2f(xPos + charWidth, yPos);
    }
    glEnd();
}


I haven't tested this code and it has been years since I've used immediate mode so I am the first to admit that it might not quite work. But I hope it should give you a good idea.

quew8


Yuri6037

I think i will Slick Util...
I can't find any way to do what i'm searching for !
I hope one day i will arrive to something ! But if i arrive to something that's will not a font of 1024 by 1024 (it's impossible to get one character on a 1024 by 1024 grid) !

If you have some other things, I will take...

I have tried many many many things for that but nothing works, your code too !

Cornix

Maybe you should study the openGL functions a little bit more.
A font rendering system is actually quite easy to write yourself. I didnt have any problem writing mine.
What exactly are you having problems with, and why would you need a 1024x1024 texture for that?

Yuri6037

Thanks for answer. The problem is that I can't find any working way to do a font renderer. I have tried tutorials but tutorials gived me code which the game not rendering, game crash or game enter in a loop wich can not be stopped until use CTRL + ALT + SUPPRESS !

Why wan't I to do a 1024 by 1024 font rendering ?
For the moment all my textures are in HD so I don't won't to see any pixelisation effect.

And you are saying that making a Font Renderer is easy... Not at all it's extremely complicated like the render engine wich I have done (without any help) !


You are telling study OpenGL !
You are funny !
Yeah I won't so much to study OpenGL. But nobody, no working tutorials, no website nothing interessant about OpenGL ! Only this forum I have found !!!
So if you have a few sites to learn I will take them quickly !

Thank you again for answer,
Yuri6037


Cornix

You didnt find anything about openGL? This sounds highly dubious. There are more tutorials about openGL then you have cells in your body.

The premise of a font renderer is simple. You have a texture with the glyphs, and you have to map those glyphs on quads.
I would suggest using some kind of a map, for example a hashmap, to map characters and texture cutouts.
Then, iterate through the string you want to render, for each letter you fetch the cutout from the map and save it in a vbo.
Here is an example code. I wrote this code in 10 minutes and I didnt test it, but it should be enough to give you a general idea:
public class FontRenderer {
	
	// Both a float and an int use 4 bytes.
	private static final int FLOAT_BYTE_SIZE = 4;
	private static final int INT_BYTE_SIZE = 4;
	
	// Each quad is one glyph and needs 4 vertices.
	private static final int VERTICES_PER_QUAD = 4;
	// Each quad is split up into 2 triangles, thus we need 6 indices.
	private static final int INDICES_PER_QUAD = 6;
	
	private static final int VERTEX_COUNT = 4;
	private static final int TEXCOORD_COUNT = 4;
	
	// These correspond to the cutouts for the glyphs in our map.
	private static final int CUTOUT_X = 0;
	private static final int CUTOUT_Y = 1;
	private static final int CUTOUT_WIDTH = 2;
	private static final int CUTOUT_HEIGHT = 3;
	
	// We save our glyphs in this map. It maps character to a float array with 4 elements = x, y, width and height.
	private HashMap<Character, float[]> cutouts = new HashMap<>();
	private int vboID = 0;
	private int iboID = 0;
	private int size = 0;
	private int stride = (VERTEX_COUNT + TEXCOORD_COUNT) * FLOAT_BYTE_SIZE;
	
	public FontRenderer() {
		vboID = GL15.glGenBuffers();
		iboID = GL15.glGenBuffers();
	}
	
	public void addGlyph(Character character, float x, float y, float width, float height) {
		cutouts.put(character, new float[] {x, y, width, height});
	}
	
	public void makeVBO(String text, float x, float y, float z) {
		/*
		 * If you want to draw a quad with 2 triangles you need 6 indices.
		 * These are 0, 1, 2 for the first triangle, and 2, 3, 0 for the second one.
		 */
		size = text.length() * INDICES_PER_QUAD;
		
		/*
		 * For each vertex we put: 
		 * x, y, z, w = 1
		 * u, v, 0, 1
		 * Each letter needs 4 vertices.
		 */
		float[] vertices = new float[text.length() * (VERTEX_COUNT + TEXCOORD_COUNT) * VERTICES_PER_QUAD];
		int[] indices = new int[size];
		
		int offsetVertices = 0;
		int offsetIndices = 0;
		
		for (int letterID = 0; letterID < text.length(); letterID++) {
			Character letter = text.charAt(letterID);
			// This array is always 4 elements in size: x, y, width and height.
			float[] cutout = cutouts.get(letter);
			
			// Upper left corner
			vertices[offsetVertices++] = x;
			vertices[offsetVertices++] = y;
			vertices[offsetVertices++] = z;
			vertices[offsetVertices++] = 1;
			
			vertices[offsetVertices++] = cutout[CUTOUT_X];
			vertices[offsetVertices++] = cutout[CUTOUT_Y];
			vertices[offsetVertices++] = 0;
			vertices[offsetVertices++] = 1;
			
			// Lower left corner
			vertices[offsetVertices++] = x;
			vertices[offsetVertices++] = y + cutout[CUTOUT_HEIGHT];
			vertices[offsetVertices++] = z;
			vertices[offsetVertices++] = 1;
			
			vertices[offsetVertices++] = cutout[CUTOUT_X];
			vertices[offsetVertices++] = cutout[CUTOUT_Y] + cutout[CUTOUT_HEIGHT];
			vertices[offsetVertices++] = 0;
			vertices[offsetVertices++] = 1;
			
			// Lower right corner
			vertices[offsetVertices++] = x + cutout[CUTOUT_WIDTH];
			vertices[offsetVertices++] = y + cutout[CUTOUT_HEIGHT];
			vertices[offsetVertices++] = z;
			vertices[offsetVertices++] = 1;
			
			vertices[offsetVertices++] = cutout[CUTOUT_X] + cutout[CUTOUT_WIDTH];
			vertices[offsetVertices++] = cutout[CUTOUT_Y] + cutout[CUTOUT_HEIGHT];
			vertices[offsetVertices++] = 0;
			vertices[offsetVertices++] = 1;
			
			// Upper right corner
			vertices[offsetVertices++] = x + cutout[CUTOUT_WIDTH];
			vertices[offsetVertices++] = y;
			vertices[offsetVertices++] = z;
			vertices[offsetVertices++] = 1;
			
			vertices[offsetVertices++] = cutout[CUTOUT_X] + cutout[CUTOUT_WIDTH];
			vertices[offsetVertices++] = cutout[CUTOUT_Y];
			vertices[offsetVertices++] = 0;
			vertices[offsetVertices++] = 1;
			
			// We make sure that the next letter will be drawn to the right of this letter.
			x += cutout[CUTOUT_WIDTH];
			
			// Indices
			indices[offsetIndices++] = letterID * VERTICES_PER_QUAD + 0;
			indices[offsetIndices++] = letterID * VERTICES_PER_QUAD + 1;
			indices[offsetIndices++] = letterID * VERTICES_PER_QUAD + 2;
			indices[offsetIndices++] = letterID * VERTICES_PER_QUAD + 2;
			indices[offsetIndices++] = letterID * VERTICES_PER_QUAD + 3;
			indices[offsetIndices++] = letterID * VERTICES_PER_QUAD + 0;
		}
		assert offsetVertices == vertices.length;
		
		bind();
		vertexData(vertices);
		indexData(indices);
	}
	
	public void bind() {
		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboID);
		GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, iboID);
		
		GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY);
		GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
		
		GL11.glVertexPointer(VERTEX_COUNT, GL11.GL_FLOAT, stride, 0);
		GL11.glTexCoordPointer(TEXCOORD_COUNT, GL11.GL_FLOAT, stride, VERTEX_COUNT * FLOAT_BYTE_SIZE);
	}
	
	public void dispose() {
		GL15.glDeleteBuffers(vboID);
		GL15.glDeleteBuffers(iboID);
	}
	
	public void vertexData(float[] data) {
		FloatBuffer buffer = ByteBuffer.allocateDirect(data.length * FLOAT_BYTE_SIZE)
				.order(ByteOrder.nativeOrder()).asFloatBuffer();
		buffer.put(data);
		buffer.flip();
		
		GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW);
	}
	
	public void indexData(int[] data) {
		IntBuffer buffer = ByteBuffer.allocateDirect(data.length * INT_BYTE_SIZE)
				.order(ByteOrder.nativeOrder()).asIntBuffer();
		buffer.put(data);
		buffer.flip();
		
		GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW);
	}
	
	public void render() {
		bind();
		GL11.glDrawElements(GL11.GL_TRIANGLES, size, GL11.GL_UNSIGNED_INT, 0);
	}
	
}


You need some kind of implementation for textures too.
This is not a very efficient implementation. Many things can be done much better then here, but it should be enough to understand the concept behind it.

Yuri6037

Thanks,
I will try your code ! And don't worry about texture implementation. I have maked a void in my render engine wich mount textures into OpenGL and saves the id of it in a hashMap !


Yuri6037

I have maked this code but nothing appear to the screen :
   
    public void drawLine(int texture, String line, float xPos, float yPos, float charWidth, float charHeight) {
        renderEngine.bindTexture(texture);
        glBegin(GL_QUADS);
        for(int i = 0; i < line.length(); i++) {
            int code = (int) line.charAt(i);
            int gridX = code % 16;  //Works out the position in the grid of this character.
            int gridY = ( code - gridX ) / 16;
            float texX = (float) gridX / 16; //Works out the texture coord of the upper left corner of the char.
            float texY = (float) gridY / 16;

            glTexCoord2f(texX, texY); //Upper Left Corner
            glVertex2f(xPos, yPos);

            glTexCoord2f(texX, texY + (1 / 16) ); //Bottom Left Corner
            glVertex2f(xPos, yPos - charHeight);

            glTexCoord2f(texX + (1 / 16), texY + (1 / 16)); //Bottom Right Corner
            glVertex2f(xPos + charWidth, yPos - charHeight);

            glTexCoord2f(texX + (1 / 16), texY); // Top Right Corner
            glVertex2f(xPos + charWidth, yPos);
        }
        glEnd();
    }

Yuri6037