How to conceptually design a LWJGL Class

Hello everybody,

Short version of this question: Is there an example / tutorial that explains how to develop a LWJGL application using custom shaders and VBOs and sticks mostly to openGL 2.0 standards?
I would ask a more specific question if I could - but I’m completely lost :slight_smile: so help would really be appreciated a lot!

I am currently porting an AWT application to LWJGL, and I have some problems and I would be very happy for some advice.

Intro:
Earlier I played around with JOGL and was able to render some models using glBegin / glEnd “brackets”. After looking at some other code I realized that this is not the state of the art methodology.
So I would like to focus on GL 2.0 standards.

My setup & expectations:

  • I developed an interface which is implemented by my “old” AWT classes. I would like to keep them as I put a lot of work and optimization into them. This interface is now to be implemented by a LWJGL context.
  • The mentioned interface should ideally implement three functions: “addSprite” (to load textures etc.), “renderSpriteAt” (to prepare rendering) and “render”, which does the rendering for all sprites etc.
  • I developed my own shaders - as I have very project specific requirements as to how the data I send to the graphics card is to be handled.
  • I would like to use “state of the art” openGL, meaning VBOs and basically not have to transfer lots of data between RAM and VRAM unless “addSprite” is called.

addSprite: Takes a color map and a normal map, copies their contents to VRAM and keeps the ID to this data in an array, hashmap, whatsoever, for later use. The color map holds some information about effects (blinking, bumpmap strength etc.) in the alpha channel. That’s no problem I think, my fragment shader will take care of that.
renderSpriteAt: This function will be called several times during preparation of a single frame. Here’s basically my problem. See also below.
render: This function should finally render all sprites (and also some other stuff) and display the frame.

My problem:
A sprite in my context consists of a quad, two textures (IDs, color and normal map), and angle and a center (pivot point for rotation).
My renderSpriteAt function currently works like this:


		GL20.glUseProgram(BumpmapProgram); // use my custom shader for this sprite
		Sprite S = Sprites.get(spriteID);
		if (S == null)
			return;
		int[] spriteDimensions = S.getSize(); // the width and height of the QUAD to render
		int[] spriteTexture = S.getTexIDs(); // the IDs of the two maps in VRAM
		GL20.glUniform1i(maTextureHandle, spriteTexture[0]); // let the fragment shader know which color map to use
		GL20.glUniform1i(maNormalsHandle, spriteTexture[1]); // let the fragment shader know which normal map to use
		IntBuffer vert=makeBuffer(new int[]{0,0,0,0,spriteDimensions[1],0,spriteDimensions[0],spriteDimensions[1],0,spriteDimensions[0],0,0}); // prepare a VBO to describe the QUADs vertices
		GL20.glVertexAttribPointer(maPositionHandle, 4, false, false, 3*INT_SIZE_BYTES, vert); // load said VBO to VRAM
		GL20.glEnableVertexAttribArray(maPositionHandle); // enable it... (for whatever reason...)
		GL20.glVertexAttribPointer(maTexturePosHandle, 4, false, false, 3*INT_SIZE_BYTES, vert); // same VBO is used for the UV coordinates
		GL20.glEnableVertexAttribArray(maTexturePosHandle); // again, enable the thingy
				
		int primitiveID=GL15.glGenBuffers(); // now make a new buffer
		IntBuffer Primitives=makeBuffer(new int[]{0,1,2,3}); // load the vertex indices to this buffer
		GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, primitiveID); // Bind it...
		GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, Primitives, GL15.GL_STREAM_DRAW); // ...and store the data in VRAM
		
		GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, primitiveID); // Bind the index buffer again...
		GL11.glDrawElements(GL11.GL_QUADS, 4, GL11.GL_INT, 0); // to draw the quads mentioned therein.

OK, now I know / believe to know that this probably does not make sense. Or does it? Please, enlighten me as to how such a method should be setup and whether it’s even possible avoiding the hideous glBegin / glEnd brackets the way I did…

I really tried to get some documentation on the combination “own texture, own shader, VBO, and GL20”… But it was hopeless.
Please enlighten me!

Thanks in advance to clarify my view…
Kind regards, J

Hi

Someone wrote some interesting tutorials about these aspects for both OpenGL bindings:
http://www.sjonesart.com/gl.php

Hi again!

Goussej’s hint was helpful, and he also told me to have a look at the GTGE sources.

Before I start copying and pasting those code-snippets I would like to understand, why the below example yields a black screen.

I know that I have a different approach than you see in other openGL sources, but I would like to understand why mine doesn’t work.

Thanks a lot, LWJGL Pros!


import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;

public class LWJGLGraphics
{
	private static final String BumpMapVertexShader =
									"uniform mat4 normal_matrix;\n"
											+ "uniform mat4 proj_matrix;\n"
											+ "attribute vec4 aPosition;\n"
											+ "void main() {\n"
											+ "  gl_Position = proj_matrix * normal_matrix * aPosition;\n"
											+ "}\n";
	private static final String BumpMapFragmentShader =
								"void main() {\n"
								+ "  gl_FragColor = vec4(0.5,0.5,0.0,0.5);\n"
								+ "}\n";

	private static final int FLOAT_SIZE_BYTES = 4;
	private static final int INT_SIZE_BYTES = 4;
	private static final int SHORT_SIZE_BYTES = 2;
	
	private static int BumpmapProgram;
	private static int mProjMatrixHandle;
	private static int muNOMatrixHandle;
	private static int maPositionHandle;
	private static boolean isInitialized = false;

	public static void main(String[] argv)
	{
		try
		{
			init(640, 480, "Test");
		}
		catch(LWJGLException e)
		{
			e.printStackTrace();
		}
		while (!Display.isVisible())
		{
			try
			{
				Thread.sleep(200);
			} catch (InterruptedException e)
			{}
		}
		while(!Display.isCloseRequested())
		{
			try
			{
				Thread.sleep(100);
			}
			catch(InterruptedException e)
			{}
			drawColorfulRectangle(-25,-25);
			render();
		}
		Display.destroy();
	}
	
	public static void drawColorfulRectangle(int cx, int cy)
	{
		// USE MY SHADERS
		GL20.glUseProgram(BumpmapProgram);
		// Define vertices (of a quad)
		IntBuffer vert = makeIntBuffer(new int[] { -cx,       -cy, 0,
												   -cx,    -cy+50, 0,
												   -cx+80, -cy+50, 0,
												   -cx+80, -cy,    0, });
		vert.position(0);
		GL20.glEnableVertexAttribArray(maPositionHandle);
		GL20.glVertexAttribPointer(maPositionHandle, 4, false, false, 3 * INT_SIZE_BYTES, vert);
		// index buffer 
		ShortBuffer Primitives = makeShortBuffer(new short[] { 0, 1, 2, 3 });
		GL11.glDrawElements(GL11.GL_QUADS, Primitives);
	}
	
	private static void init(int width, int height, String title) throws LWJGLException
	{
		// WINDOW STUFF
		int hw = width / 2;
		int hh = height / 2;
		Display.setLocation(Display.getDisplayMode().getWidth() / 2 - hw, Display.getDisplayMode().getHeight() / 2 - hh);
		Display.setDisplayMode(new DisplayMode(width, height));
		Display.setTitle(title);
		Display.create();
		
		// CREATE, COMPILE SHADERS, PROGRAM
		BumpmapProgram = createProgram(BumpMapVertexShader, BumpMapFragmentShader);
		GL20.glValidateProgram(BumpmapProgram);
		System.out.println("BumpMap Program: " + BumpmapProgram);
		
		// GET POINTERS TO ATTRIBUTES, UNIFORMS
		maPositionHandle = GL20.glGetAttribLocation(BumpmapProgram, "aPosition");
		mProjMatrixHandle = GL20.glGetUniformLocation(BumpmapProgram, "proj_matrix");
		muNOMatrixHandle = GL20.glGetUniformLocation(BumpmapProgram, "normal_matrix");
		System.out.println("Position: " + maPositionHandle);
		System.out.println("Normal Mat: " + muNOMatrixHandle);
		System.out.println("Projection Mat: " + mProjMatrixHandle);

		// PROJECTION STUFF (leave everything standard)
		float[] normal_matrix = new float[16];
		float[] proj_matrix = new float[16];
		FloatBuffer Normal = makeFloatBuffer(normal_matrix);
		FloatBuffer Projection = makeFloatBuffer(proj_matrix);
		GL11.glGetFloat(GL11.GL_PROJECTION_MATRIX, Projection);
		GL11.glGetFloat(GL11.GL_MODELVIEW_MATRIX, Normal);
		GL20.glUniformMatrix4(mProjMatrixHandle, false, Projection);
		GL20.glUniformMatrix4(muNOMatrixHandle, false, Normal);
	}

	private static void render()
	{
		if (isInitialized)
			Display.update();
	}

	private static int loadShader(int shaderType, String source)
	{
		int shader = GL20.glCreateShader(shaderType);
		String errors;
		if (shader != 0)
		{
			GL20.glShaderSource(shader, source);
			GL20.glCompileShader(shader);
		}
		errors = GL20.glGetShaderInfoLog(shader, 1000).trim();
		if (errors.length() > 0)
			System.err.println("Shader errors: " + errors);
		return shader;
	}

	private static int createProgram(String vertexSource, String fragmentSource)
	{
		String errors;
		int vertexShader = loadShader(GL20.GL_VERTEX_SHADER, vertexSource);
		int fragmentShader = loadShader(GL20.GL_FRAGMENT_SHADER, fragmentSource);
		System.out.println("VertexShader: " + vertexShader);
		System.out.println("FragmentShader: " + fragmentShader);
		int program = GL20.glCreateProgram();
		if (program != 0)
		{
			GL20.glAttachShader(program, vertexShader);
			GL20.glAttachShader(program, fragmentShader);
			GL20.glLinkProgram(program);
		}
		errors=GL20.glGetProgramInfoLog(BumpmapProgram, 1000).trim();
		if(errors.length()>0)
			System.err.println("Program errors:" + errors);
		GL20.glGetActiveAttrib(program, 0, 1000); // required as dummy invocation
		return program;
	}


	private static IntBuffer makeIntBuffer(int[] data)
	{
		int l = data.length;
		IntBuffer I = ByteBuffer.allocateDirect(l * INT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asIntBuffer();
		I.put(data);
		I.flip();
		return I;
	}

	private static ShortBuffer makeShortBuffer(short[] data)
	{
		int l = data.length;
		ShortBuffer I = ByteBuffer.allocateDirect(l * SHORT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asShortBuffer();
		I.put(data);
		I.flip();
		return I;
	}

	private static FloatBuffer makeFloatBuffer(float[] data)
	{
		int l = data.length;
		FloatBuffer I = ByteBuffer.allocateDirect(l * FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
		I.put(data);
		I.flip();
		return I;
	}
}

Before jumping into OpenGL with LWJGL, I’d recommend you run quickly through the ‘LWJGL Basics Tutorial Series’. Should help clarify some of the LWJGL specific bits the rest is mostly just pure OpenGL (and OpenAL, OpenCL).

Thanks Kappa for taking the time to respond.
I went through these tutorials, and of course I noticed the big difference to my code (the tutorial does not only show a BLACK screen…)

Which part of my code is so desperately wrong that you say I should go through said tutorial? :slight_smile:

EDIT: Please note that what I have provided above is a “boiled down to the problem” version. I’m not really only painting a quad in my real code. But I supposed I wouldn’t get any hints if I posted 100k lines of code…

Nothing majorly wrong with your code. Just noticed some small oddities, like the use of the following in the game loop:

try
            {
                Thread.sleep(100);
            }
            catch(InterruptedException e)
            {}

That would be roughly equivalent to using the following in the same place

Display.sync(10); // run at 10fps

also you shouldn’t need the following bit of code

while (!Display.isVisible())
        {
            try
            {
                Thread.sleep(200);
            } catch (InterruptedException e)
            {}
        }

lastly if you don’t want all those GL11. and GL20. bits on all of the OpenGL calls you can just add the following bit of code under the imports:

import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL20.*;

and then you’ll be able to use something like glUseProgram(BumpmapProgram); instead of GL20.glUseProgram(BumpmapProgram);

Thanks for elaborating.

Explanation:

10 fps: Yes, but as I said, this is not my real code. It’s just to show my problem.
while(!Display.isVisible): Ok, thank’s for the hint. I put this line there earlier while still debugging. It turns out that a typo in my shaders was the problem, not this :-). Will remove it.
imports: I know. I do this because I want to know at what version these functions have been added to openGL. Or is there any speed/performance advantage when I use static imports?

EDIT: Nevertheless, any hint as to why the quad doesn’t show?
EDIT 2: I worked over the code and was able to boil it down even a bit more. This time with static imports…
EDIT 3: Is there possibly any incentive I could offer to a lwjgl guru to go through my code? Money? Women? Men? An emu? :smiley:


import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL20.*;

public class LWJGLGraphics
{
	private static final String BumpMapVertexShader =
									"uniform mat4 normal_matrix;\n"
											+ "uniform mat4 proj_matrix;\n"
											+ "attribute vec4 aPosition;\n"
											+ "void main() {\n"
											+ "  gl_Position = proj_matrix * normal_matrix * aPosition;\n"
											+ "}\n";
	private static final String BumpMapFragmentShader =
								"void main() {\n"
								+ "  gl_FragColor = vec4(1,0.5,0.0,1);\n"
								+ "}\n";

	private static final int FLOAT_SIZE_BYTES = 4;
	private static final int INT_SIZE_BYTES = 4;
	private static final int SHORT_SIZE_BYTES = 2;
	
	private static int BumpmapProgram;
	private static int mProjMatrixHandle;
	private static int muNOMatrixHandle;
	private static int maPositionHandle;

	public static void main(String[] argv)
	{
		try
		{
			init(640, 480, "Test");
		}
		catch(LWJGLException e)
		{
			e.printStackTrace();
			System.exit(0);
		}
		while(!Display.isCloseRequested())
		{
			drawColorfulRectangle(25,25);
			Display.update();
		}
		Display.destroy();
	}
	
	public static void drawColorfulRectangle(int cx, int cy)
	{
		// USE MY SHADERS
		glUseProgram(BumpmapProgram);
		// Define vertices (of a quad)
		IntBuffer vert = makeIntBuffer(new int[] { -cx,       -cy, 0,
												   -cx,    -cy+50, 0,
												   -cx+80, -cy+50, 0,
												   -cx+80, -cy,    0, });
		vert.position(0);
		glEnableVertexAttribArray(maPositionHandle);
		glVertexAttribPointer(maPositionHandle, 4, false, false, 3 * INT_SIZE_BYTES, vert);
		// index buffer 
		ShortBuffer Primitives = makeShortBuffer(new short[] { 0, 1, 2, 3 });
		glDrawElements(GL_QUADS, Primitives);
	}
	
	private static void init(int width, int height, String title) throws LWJGLException
	{
		// WINDOW STUFF
		int hw = width / 2;
		int hh = height / 2;
		Display.setLocation(Display.getDisplayMode().getWidth() / 2 - hw, Display.getDisplayMode().getHeight() / 2 - hh);
		Display.setDisplayMode(new DisplayMode(width, height));
		Display.setTitle(title);
		Display.create();
				
		// CREATE, COMPILE SHADERS, PROGRAM
		BumpmapProgram = createProgram(BumpMapVertexShader, BumpMapFragmentShader);
		glValidateProgram(BumpmapProgram);
		
		// GET POINTERS TO ATTRIBUTES, UNIFORMS
		maPositionHandle = glGetAttribLocation(BumpmapProgram, "aPosition");
		mProjMatrixHandle = glGetUniformLocation(BumpmapProgram, "proj_matrix");
		muNOMatrixHandle = glGetUniformLocation(BumpmapProgram, "normal_matrix");

		// PROJECTION STUFF (leave everything standard)
		float[] normal_matrix = new float[16];
		float[] proj_matrix = new float[16];
		FloatBuffer Normal = makeFloatBuffer(normal_matrix);
		FloatBuffer Projection = makeFloatBuffer(proj_matrix);
		glGetFloat(GL_PROJECTION_MATRIX, Projection);
		glGetFloat(GL_MODELVIEW_MATRIX, Normal);
		glUniformMatrix4(mProjMatrixHandle, false, Projection);
		glUniformMatrix4(muNOMatrixHandle, false, Normal);
	}

	private static int loadShader(int shaderType, String source)
	{
		int shader = glCreateShader(shaderType);
		String errors;
		if (shader != 0)
		{
			glShaderSource(shader, source);
			glCompileShader(shader);
		}
		errors = glGetShaderInfoLog(shader, 1000).trim();
		if (errors.length() > 0)
			System.err.println("Shader errors: " + errors);
		return shader;
	}

	private static int createProgram(String vertexSource, String fragmentSource)
	{
		String errors;
		int vertexShader = loadShader(GL_VERTEX_SHADER, vertexSource);
		int fragmentShader = loadShader(GL_FRAGMENT_SHADER, fragmentSource);
		int program = glCreateProgram();
		if (program != 0)
		{
			glAttachShader(program, vertexShader);
			glAttachShader(program, fragmentShader);
			glLinkProgram(program);
		}
		errors=glGetProgramInfoLog(BumpmapProgram, 1000).trim();
		if(errors.length()>0)
			System.err.println("Program errors:" + errors);
		return program;
	}

	private static IntBuffer makeIntBuffer(int[] data)
	{
		int l = data.length;
		IntBuffer I = ByteBuffer.allocateDirect(l * INT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asIntBuffer();
		I.put(data);
		I.flip();
		return I;
	}

	private static ShortBuffer makeShortBuffer(short[] data)
	{
		int l = data.length;
		ShortBuffer I = ByteBuffer.allocateDirect(l * SHORT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asShortBuffer();
		I.put(data);
		I.flip();
		return I;
	}

	private static FloatBuffer makeFloatBuffer(float[] data)
	{
		int l = data.length;
		FloatBuffer I = ByteBuffer.allocateDirect(l * FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
		I.put(data);
		I.flip();
		return I;
	}
}

OK, I solved my problem.