Hello.
I am a noob to LWJGL and OpenGL and failing to draw my first triangle.
Like many before me I am following a tutorial writing my own 3D Game Engine (https://www.youtube.com/playlist?list=PLEETnX-uPtBXP_B2yupUKlflXBznWIlL5 (https://www.youtube.com/playlist?list=PLEETnX-uPtBXP_B2yupUKlflXBznWIlL5)).
Since it is already very old I have been trying to rewrite some parts since I am using LWJGL 3.
To cap it all I am developing this on a Windows machine as well as Mac OS X.
I arrived at the point where I want to render a triangle in my window. In several tutorials it is mentioned that drawing a triangle will only work on Mac OS X using shaders and VAOs. I set everything up so far but I wasn't able to run it on Windows yet, just on my Mac.
I am quite desperate now since I feel I have searched the whole internet for my problem and tried different approaches rewriting my code, without success. I have little hope that someone wants to look through my code. I am not sure if I made a mistake setting up my window and OpenGL, maybe there is a mistake. Also, when I try to glValidateProgram() I get an error.
Feel free to ask more questions, since I am not sure how I can better explain my problem. Maybe the code speaks for itself:
Main Class:
package com.base.engine;
// add -XstartOnFirstThread to VM Options in Run/Edit Config...
public class MainComponent
{
private static int WIDTH = 800;
private static int HEIGHT = 600;
private static final String TITLE = "3D Engine";
public static final double FRAME_CAP = 5000.0; // How many updates per second
private boolean isRunning; // Is engine running
private Game game;
public MainComponent(Window window)
{
System.out.println(RenderUtil.getOpenGLVersion());
RenderUtil.initGraphics(window);
isRunning = false;
game = new Game();
}
public void start(Window window)
{
if(isRunning)
return;
run(window);
}
public void stop()
{
if(!isRunning)
return;
isRunning = false;
}
private void run(Window window)
{
int frames = 0; // Increases for every render
long frameCounter = 0;
final double frameTime = 1.0/FRAME_CAP; // Amount of time one frame takes
long lastTime = Time.getTime(); // Start time of previous frame rendering
double unprocessedTime = 0; // Keep track of how much frames need to be updated
isRunning = true;
while(isRunning)
{
boolean render = false; // Indicate if frame needs to be rendered
long startTime = Time.getTime(); // Start time of current frame rendering
long passedTime = startTime - lastTime; // Time it took to render last frame
lastTime = startTime; // Buffer start time of current frame rendering
unprocessedTime += passedTime/(double)Time.SECOND;
frameCounter += passedTime;
// Render while frame still needs to be updated
while(unprocessedTime > frameTime)
{
render = true;
unprocessedTime -= frameTime; // Update unprocessed time
// Stop engine if window close is requested
if(window.closeRequested())
stop();
Time.setDelta(frameTime); // Update delta
// Update the game
game.input();
Input.update(window.getWindow()); // Update the input for every frame
game.update();
// Print amount of frames passed per second
if(frameCounter >= Time.SECOND)
{
System.out.println(frames);
frames = 0;
frameCounter = 0;
}
}
// Render if frame needs to be updated
if(render)
{
render(window);
frames++;
}
else
{
// Sleep 1ms if there is no frame needed to be rendered
try
{
Thread.sleep(1);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
cleanUp(window); // Clean up after run
}
private void render(Window window)
{
RenderUtil.clearScreen();
game.render();
window.update();
window.render();
}
private void cleanUp(Window window)
{
window.destroy();
}
public static void main(String[] args)
{
Window window = new Window(WIDTH, HEIGHT);
window.createWindow(TITLE);
MainComponent engine = new MainComponent(window);
engine.start(window);
}
}
Window Class:
package com.base.engine;
import static org.lwjgl.glfw.GLFW.*; // Allows window creation
import static org.lwjgl.opengl.GL11.*; // Access to GL defines
import static org.lwjgl.system.MemoryUtil.*; // Access to NULL
import org.lwjgl.glfw.GLFWVidMode; // Primary monitor dependencies
import org.lwjgl.glfw.GLFWWindowSizeCallback;
public class Window {
private long window;
private int width, height;
private boolean fullscreen;
/*private boolean hasResized;
private GLFWWindowSizeCallback windowSizeCallback;
private Input input;*/
public Window(int width, int height) {
setSize(width, height);
setFullscreen(false);
}
public void createWindow(String title) {
// Throw error if glfw init fails
if (!glfwInit()) {
System.err.println("GLFW initialization failed!");
}
// Define OpenGL Core Profile
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
// Request an OpenGL debug context
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
// Allow window to be resizeable
glfwWindowHint(GLFW_RESIZABLE, GL_TRUE);
// Create window
window = glfwCreateWindow(width, height, title, NULL, NULL);
// Check if window creation was successful
if (window == NULL) {
System.err.println("Could not create window.");
}
// Create vidmode object for primary monitor detection
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
// Set initial position of window
glfwSetWindowPos(window, 100, 100);
// Set context of GLFW
glfwMakeContextCurrent(window);
// Show window
glfwShowWindow(window);
}
public void update() {
glfwPollEvents();
}
public void render() {
glfwSwapBuffers(window);
}
public void destroy()
{
glfwDestroyWindow(window);
}
public boolean closeRequested()
{
return glfwWindowShouldClose(window);
}
public void setSize(int width, int height)
{
this.width = width;
this.height = height;
}
public void setFullscreen(boolean fullscreen)
{
this.fullscreen = fullscreen;
}
public long getWindow()
{
return window;
}
public int getWidth()
{
return width;
}
public int getHeight()
{
return height;
}
}
Game Class:
package com.base.engine;
public class Game
{
private MeshLoader loader;
private Mesh mesh;
private Shader shader;
public Game()
{
Vertex[] data = new Vertex[] { new Vertex(new Vector3f(-0.5f, 0.5f, 0)),
new Vertex(new Vector3f(-0.5f, -0.5f, 0)),
new Vertex(new Vector3f(0.5f, -0.5f, 0)) };
loader = new MeshLoader();
mesh = loader.LoadToVAO(data);
shader = new Shader();
//mesh.addVertices(data);
shader.addVertexShader(ResourceLoader.loadShader("basicVertex.vsh"));
shader.addFragmentShader(ResourceLoader.loadShader("basicFragment.fsh"));
shader.compileShader();
shader.validateShader();
}
public void input()
{
}
public void update()
{
}
public void render()
{
shader.bind();
mesh.draw();
}
}
Render Util Class:
package com.base.engine;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GLCapabilities;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL30.*;
public class RenderUtil
{
public static void clearScreen()
{
GL.createCapabilities(); // Get current context
//TODO: Stencil Buffer
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
public static void initGraphics(Window window)
{
GL.createCapabilities(); // Get current context
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glFrontFace(GL_CW); // Every face drawn in clockwise order is frontfaced
glCullFace(GL_BACK); // Cull backfaces
glEnable(GL_CULL_FACE); // Enable culling
glEnable(GL_DEPTH_TEST); // Do depth comparision and update depth buffer
//TODO: Depth Clamp
glEnable(GL_FRAMEBUFFER_SRGB); // Assume input colors (being written) are in linear colorspace
// Map the internal OpenGL coordinate system to the entire screen
glViewport(0, 0, window.getWidth(), window.getHeight());
}
public static String getOpenGLVersion()
{
GL.createCapabilities(); // Get current context
return glGetString(GL_VERSION);
}
}
Mesh Class:
package com.base.engine;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL30.*;
public class Mesh
{
private int vao; // Vertex Array Object
// private int vbo; // Vertex Buffer Object: handle (pointer) that holds vertex data like (position, normals, etc.)
private int size; // Size of mesh: number of vertices * vertex size
public Mesh(int vao, int size)
{
//vao = glGenVertexArrays();
//vbo = glGenBuffers(); // Generates buffer object names
//size = 0;
this.vao = vao;
this.size = size;
}
public void draw()
{
glBindVertexArray(vao);
glEnableVertexAttribArray(0);
glDrawArrays(GL_TRIANGLES, 0, size);
glDisableVertexAttribArray(0);
glBindVertexArray(0);
}
}
Mesh Loader Class:
package com.base.engine;
import java.util.*;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL30.*;
public class MeshLoader
{
private List<Integer> vaos = new ArrayList<Integer>();
private List<Integer> vbos = new ArrayList<Integer>();
public Mesh LoadToVAO(Vertex[] vertices)
{
int size = vertices.length * Vertex.SIZE;
int vao = createVAO();
storeDataInAttribList(0, vertices);
unbindVAO();
return new Mesh(vao, size);
}
public void cleanUp()
{
for(int vao: vaos)
{
glDeleteVertexArrays(vao);
}
for(int vbo: vbos)
{
glDeleteBuffers(vbo);
}
}
private int createVAO()
{
int vao = glGenVertexArrays();
vaos.add(vao);
glBindVertexArray(vao);
return vao;
}
private void storeDataInAttribList(int attribNum, Vertex[] vertices)
{
int vbo = glGenBuffers();
vbos.add(vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, Util.createFlippedBuffer(vertices), GL_STATIC_DRAW);
glVertexAttribPointer(attribNum, 3, GL_FLOAT, false, Vertex.SIZE * 4, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
private void unbindVAO()
{
glBindVertexArray(0);
}
}
Shader Class:
package com.base.engine;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL32.*;
public class Shader
{
private int program;
public Shader() {
program = glCreateProgram();
if (program == 0)
{
System.err.println("Shader creation failed: Could not find valid memory location in constructor.");
System.exit(1);
}
}
public void bind()
{
glUseProgram(program);
}
public void addVertexShader(String text)
{
addProgram(text, GL_VERTEX_SHADER);
}
public void addFragmentShader(String text)
{
addProgram(text, GL_FRAGMENT_SHADER);
}
public void addGeometryShader(String text)
{
addProgram(text, GL_GEOMETRY_SHADER);
}
public void compileShader()
{
glLinkProgram(program);
if(glGetProgrami(program, GL_LINK_STATUS) == 0)
{
System.err.println(glGetShaderInfoLog(program, 1024));
System.exit(1);
}
//ERROR???
/* glValidateProgram(program);
if(glGetProgrami(program, GL_VALIDATE_STATUS) == 0)
{
System.err.println(glGetShaderInfoLog(program, 1024));
System.exit(1);
}*/
}
public void validateShader()
{
glValidateProgram(program);
if(glGetProgrami(program, GL_VALIDATE_STATUS) == GL_FALSE)
{
System.out.println("Shader validation failed.");
System.err.println(glGetShaderInfoLog(program, 1024));
System.exit(1);
}
}
private void addProgram(String text, int type)
{
int shader = glCreateShader(type);
if(shader == 0)
{
System.err.println("Shader creation failed: Could not find valid memory location when adding shader.");
System.exit(1);
}
glShaderSource(shader, text);
glCompileShader(shader);
if(glGetShaderi(shader, GL_COMPILE_STATUS) == 0)
{
System.err.println(glGetShaderInfoLog(shader, 1024));
System.exit(1);
}
glAttachShader(program, shader);
}
}
Resource Loader Class:
package com.base.engine;
import java.io.BufferedReader;
import java.io.FileReader;
public class ResourceLoader
{
/*
* Load specified shader program
*/
public static String loadShader(String filename)
{
StringBuilder shaderSource = new StringBuilder(); // Responsible for loading text of shader program
BufferedReader shaderReader = null;
try
{
shaderReader = new BufferedReader(new FileReader("./res/shaders/" + filename));
String line;
// Load text to StringBuilder while BufferedReader still reads text from source
while((line = shaderReader.readLine()) != null)
{
shaderSource.append(line).append("\n");
}
shaderReader.close();
}
catch (Exception e)
{
e.printStackTrace();
System.exit(1);
}
return shaderSource.toString();
}
}