Good day all you beautiful people,
About a week ago, i did a major refractor in my main project, trying to create abstract java classes for all my renderers and shaders, so i wouldn’t have to do this all over and over again for everything.
(Terrain, Entities, Clouds, Water, UI, …)
So, I very stupidly spent a whole day redoing all the renderers without debugging at any stage… Well, of course it stopped working and i only got part of my UI renderer to work, so I decided to try and
create a small project to test all my render pipeline stuff out safely
(Source Code: )
//The main method just calls the GameLoop() constructor
//GameLoop.java:
public class GameLoop {
Display dm;
Renderer renderer;
public GameLoop() {
init();
loop();
cleanUp();
}
private void init() {
dm = new Display();
renderer = new Renderer();
}
private void loop() {
while(!dm.shouldClose()) {
dm.render();
renderer.render();
}
}
private void cleanUp() {
dm.close();
renderer.cleanUp();
dm.destroy();
}
}
///////////////////////////////////////
//Display.java: (Straight from the lwjgl.com website, so this class SHOULD be working correctly)
public class Display {
public static int WIDTH = 1080, HEIGHT = 720;
public static final float NEAR_PLANE = 0.1f, FAR_PLANE = 1000;
public static final float FOV = 70;
private static final String TITLE = "MPS";
private static final Vector3f bgColor = new Vector3f(0.4f, 0.8f, 0.78f);
public static long window = -1l;
public Display() {
System.out.println("Starting " + TITLE + ",,,,");
init();
GL.createCapabilities();
GL11.glViewport(0, 0, WIDTH, HEIGHT);
GLFW.glfwSetFramebufferSizeCallback(window, (long window, int width, int height) -> {
GL11.glViewport(0, 0, width, height);
Display.WIDTH = width; Display.HEIGHT = height;
});
GL43.glEnable(GL43.GL_DEBUG_OUTPUT);
GL11.glClearColor(bgColor.x, bgColor.y, bgColor.z, 0);
}
private void init() {
// Setup an error callback. The default implementation
// will print the error message in System.err.
GLFWErrorCallback.createPrint(System.err).set();
// Initialize GLFW. Most GLFW functions will not work before doing this.
if ( !GLFW.glfwInit() )
throw new IllegalStateException("Unable to initialize GLFW");
// Configure GLFW
GLFW.glfwDefaultWindowHints(); // optional, the current window hints are already the default
GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE); // the window will stay hidden after creation
GLFW.glfwWindowHint(GLFW.GLFW_RESIZABLE, GLFW.GLFW_TRUE); // the window will be resizable
// Create the window
window = GLFW.glfwCreateWindow(WIDTH, HEIGHT, TITLE, 0, 0);
if ( window == -1l )
throw new RuntimeException("Failed to create the GLFW window");
// Setup a key callback. It will be called every time a key is pressed, repeated or released.
GLFW.glfwSetKeyCallback(window, (window, key, scancode, action, mods) -> {
if ( key == GLFW.GLFW_KEY_TAB && action == GLFW.GLFW_RELEASE )
GLFW.glfwSetWindowShouldClose(window, true); // We will detect this in the rendering loop
//[a bunch of callbacks to set the pressed keys in my static Controls class]
if(key == GLFW.GLFW_KEY_ESCAPE && action == GLFW.GLFW_PRESS) {
Controls.helper = true;
} if(key == GLFW.GLFW_KEY_ESCAPE && action == GLFW.GLFW_RELEASE) {
if(Controls.inMenu && Controls.helper) { Controls.helper = Controls.inMenu = false; }
if(!Controls.inMenu && Controls.helper) { Controls.helper = false; Controls.inMenu = true; }
}
});
GLFW.glfwSetCursorPosCallback(window, (window, xpos, ypos) -> {
Controls.mX = xpos;
Controls.mY = ypos;
});
// Get the thread stack and push a new frame
try ( MemoryStack stack = MemoryStack.stackPush() ) {
IntBuffer pWidth = stack.mallocInt(1); // int*
IntBuffer pHeight = stack.mallocInt(1); // int*
// Get the window size passed to glfwCreateWindow
GLFW.glfwGetWindowSize(window, pWidth, pHeight);
// Get the resolution of the primary monitor
GLFWVidMode vidmode = GLFW.glfwGetVideoMode(GLFW.glfwGetPrimaryMonitor());
// Center the window
GLFW.glfwSetWindowPos(
window,
(vidmode.width() - pWidth.get(0)) / 2,
(vidmode.height() - pHeight.get(0)) / 2
);
} // the stack frame is popped automatically
// Make the OpenGL context current
GLFW.glfwMakeContextCurrent(window);
// Enable v-sync
GLFW.glfwSwapInterval(1);
// Make the window visible
GLFW.glfwShowWindow(window);
}
public void render() {
GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_COLOR_BUFFER_BIT);
GLFW.glfwSwapBuffers(window);
GLFW.glfwPollEvents();
doCursorStuff();
}
public void doCursorStuff() {
if(!Controls.inMenu) {
GLFW.glfwSetInputMode(window, GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED);
} else {
GLFW.glfwSetInputMode(window, GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_NORMAL);
}
}
public boolean shouldClose() { return GLFW.glfwWindowShouldClose(window); }
public void close() {
GLFW.glfwSetWindowShouldClose(window, true);
}
public void destroy() {
// Free the window callbacks and destroy the window
//GLFW.glfwFreeCallbacks(window);
GLFW.glfwDestroyWindow(window);
// Terminate GLFW and free the error callback
GLFW.glfwTerminate();
GLFW.glfwSetErrorCallback(null).free();
}
}
//////////////////////////////////////////////////
//Renderer.java (This is where i suspect the error is -> perhaps something about the model loading/ binding the buffers)
// (All the unbind calls have been commented out to ensure that they're not responsible)
public class Renderer {
private List<Integer> bfers = new ArrayList<Integer>();
private List<Integer> textures = new ArrayList<Integer>();
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
protected GeometryShader shader;
protected Model model;
public Renderer() {
this.shader = new GeometryShader();
this.model = loadModel(vertices);
}
private Model loadModel(float[] vertices) {
int vao = createVAO();
storeDataInAttributeList(vao, 0, 3, vertices, true);
//GL30.glBindVertexArray(0);
return new Model(vao, vertices.length/3, true);
}
public void render() {
shader.start();
GL30.glBindVertexArray(model.vaoID());
GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, model.vertexCount());
//GL30.glBindVertexArray(0);
shader.stop();
}
/**
* Creates and binds VAO
*/
private int createVAO() {
int vaoID = GL30.glGenVertexArrays();
bfers.add(vaoID);
GL30.glBindVertexArray(vaoID);
return vaoID;
//GL20.glBufferData(GL20.GL_ARRAY_BUFFER, null, st);
}
private int createVBO() {
int vboID = GL30.glGenBuffers();
bfers.add(vboID);
GL20.glBindBuffer(GL20.GL_ARRAY_BUFFER, vboID);
return vboID;
}
private void storeDataInAttributeList(int vaoID, int attribNumber, int coordSize, float[] data, boolean staticDraw) {
int vbo = createVBO();
//GL20.glBindBuffer(GL20.GL_ARRAY_BUFFER, vbo);
GL20.glBufferData(GL20.GL_ARRAY_BUFFER, storeDataInFloatBuffer(data), staticDraw ? GL20.GL_STATIC_DRAW : GL20.GL_DYNAMIC_DRAW);
GL20.glVertexAttribPointer(attribNumber, coordSize, GL11.GL_FLOAT, false, 0, 0);
GL20.glEnableVertexAttribArray(attribNumber);
//GL20.glBindBuffer(GL20.GL_ARRAY_BUFFER, 0);
}
private FloatBuffer storeDataInFloatBuffer(float[] data) {
FloatBuffer buffer = BufferUtils.createFloatBuffer(data.length);
buffer.put(data);
buffer.flip(); //write mode -> read mode
return buffer;
}
public void cleanUp() {
for(int i : bfers)
GL30.glDeleteVertexArrays(i);
for(int i : textures)
GL15.glDeleteTextures(i);
}
}
///////////////////////////
//vertexShader
#version 330 core
layout (location = 0) in vec3 position;
void main(void) {
gl_Position = vec4(position, 1);
}
////////////////////////////
//fragmentShader
#version 330 core
out vec4 out_Color;
void main(void) {
out_Color = vec4(1, 0, 0, 1);
}
/////////////////////
//GeometryShader.java
public class GeometryShader {
private static final String VERTEX_FILE = "src/main/java/de/magiczerda/geometry/impl_renderer/geometry_vertexShader.glsl";
private static final String GEOMETRY_FILE = "src/main/java/de/magiczerda/geometry/impl_renderer/geometry_geometryShader.glsl";
private static final String FRAGMENT_FILE = "src/main/java/de/magiczerda/geometry/impl_renderer/geometry_fragmentShader.glsl";
protected int vsID = -1, gsID = -1, fsID = -1, pID = -1;
public GeometryShader() {
this.vsID = loadShader(VERTEX_FILE, GL20.GL_VERTEX_SHADER);
//this.gsID = loadShader(GEOMETRY_FILE, GL40.GL_GEOMETRY_SHADER);
this.fsID = loadShader(FRAGMENT_FILE, GL20.GL_FRAGMENT_SHADER);
this.pID = GL20.glCreateProgram();
GL20.glAttachShader(pID, vsID);
//GL20.glAttachShader(pID, gsID);
GL20.glAttachShader(pID, fsID);
GL20.glValidateProgram(pID);
GL20.glLinkProgram(pID);
int ls = GL20.glGetProgrami(pID, GL20.GL_LINK_STATUS);
System.out.println("Shader Link status: " + ls); //1
if(ls == GL11.GL_FALSE) {
String info = GL20.glGetProgramInfoLog(pID);
System.out.println(info);
GL20.glDeleteProgram(pID);
}
/*start();
bindAttributes();
stop();*/
}
public void start() {
GL20.glUseProgram(pID);
}
public void stop() {
GL20.glUseProgram(0);
}
/*protected void bindAttribute(int attribute, String variableName) {
GL20.glBindAttribLocation(pID, attribute, variableName);
}
protected void bindAttributes() {
for(String s : new String[] { "0 position" })
bindAttribute(Integer.parseInt(s.split(" ")[0]), s.split(" ")[1]);
}*/
private int loadShader(String file, int type) {
StringBuilder shaderSource = new StringBuilder();
try {
BufferedReader reader = new BufferedReader(new FileReader(file));
String line;
while ((line = reader.readLine()) != null) {
shaderSource.append(line).append("//\n");
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
int shaderID = GL20.glCreateShader(type);
GL20.glShaderSource(shaderID, shaderSource);
//System.out.println(shaderSource);
GL20.glCompileShader(shaderID);
if (GL20.glGetShaderi(shaderID, GL20.GL_COMPILE_STATUS) == GL11.GL_FALSE) {
System.err.println("Could not compile shader!");
System.out.println(GL20.glGetShaderInfoLog(shaderID, 5000));
System.exit(-1);
}
return shaderID;
}
public void cleanUp() {
stop(); //stop the shaders
GL20.glDetachShader(pID, vsID); //detach the vertex shader from the shader program
if(gsID != -1)
GL20.glDetachShader(pID, gsID);
GL20.glDetachShader(pID, fsID); //detach the fragment shader from the shader program
GL20.glDeleteProgram(vsID); //delete the vertex shader
if(gsID != -1)
GL20.glDeleteShader(gsID);
GL20.glDeleteShader(fsID); //delete the fragment shader
GL20.glDeleteProgram(pID); //delete the shader program
}
}
GLFW and OpenGL initialization is successful
(no i didn’t forget the glfwMakeContextCurrent() or createCapabilities() method calls)
The shaders are compiled correctly, the shader program id is never -1 (as specified)
And the vaoID is set as well, but the triangle never shows up. (Neither with GL_POINTS, GL_LINES or any of the STRIP variants)
By exclusion, i think the mistake must be occurring while i am loading the model, but I really don’t know for sure. OpenGL does not give me any error messages either, no log is created, no nothing at all
I got three questions about all this:
1.: This is how I understand the process:
1.1. Init GLFW & OpenGL Context
1.2. Compile the shaders
1.3. Load the model (2-3 order should not matter)
1.4. Start the render loop
=> glfwPollEvents(), glfwSwapBuffers(),
start shader, (send uniform data), render, stop shader
1.5. Clean all shaders, vaos, vbos, fbos and textures up
=> Did i understand this wrong or is my implementation wrong?
Does the shader only need to be started once or does it need to be started and stopped per render call?
I suppose the latter is only true when you have multiple shader stages to deal with
Most importantly though, what am i doing wrong here? I honestly don’t think i can possibly find the answer myself because I’ve been redoing this over and over again and at this point, the error must be completely engrained into my brain ^^
I’m feeling real stupid right now because I’ve set up a bunch of lwjgl projects, but i guess this is just dunning-kruger hitting me with full force and shoving me into the valley of despair (which is a good thing)
Thank y’all for your time and patience with me ^^