Hi all,
I've recently started dabbling in OpenGL and was following one of the tutorials on the wiki. The following happened to me using the OpenGL context 4.0 core profile on a macOS Sierra 10.12.5 MBP with an integrated graphics card capable of running up to OpenGL 4.1, with JDK 1.8.0_121 and LWJGL 3.1.2.
The triangle tutorial is very simple and basically does the following:
- use glfw to create a window
- create a vertex shader and fragment shader
- create a VBO describing the triangle's position and color
- draw the triangle
The tutorial code ran without issues (though it took me a while to figure out I had to move GL.createCapabilities() from the render loop to my init method, before trying to create the shaders etc!) but the screen remained black. After hours I found that the reason for this was related to the GL15.glBufferData
method. I was using the overloaded version which takes a FloatBuffer as second parameter. Replacing this FloatBuffer by the backing float[] solved my issue and suddenly my triangle was visible on screen.
I tried creating the FloatBuffer in numerous ways:
- via MemoryStack.stackPush().mallocFloat(...).put(..).put(..)....flip()
- via FloatBuffer.allocate(..).put(..).put(..)....flip()
- via FloatBuffer.wrap(new float[]{...})
but in none of these cases did the triangle appear on screen. However, when using the overloaded version of GL15.glBufferData which directly takes the float[] as second parameter, the triangle is shown.
Below you can find a minimal verifiable code example (2 java files and 2 shader files) to reproduce this issue. I say issue but I'm not sure if it even is an issue, I might just be doing something wrong with the buffer, but I don't see what.
import org.lwjgl.Version;
import org.lwjgl.glfw.GLFWErrorCallback;
import org.lwjgl.glfw.GLFWVidMode;
import org.lwjgl.opengl.GL;
import org.lwjgl.system.MemoryStack;
import java.nio.IntBuffer;
import static org.lwjgl.glfw.Callbacks.glfwFreeCallbacks;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL30.*;
import static org.lwjgl.system.MemoryStack.stackPush;
import static org.lwjgl.system.MemoryUtil.NULL;
/**
* Created by RDM on 21/06/2017.
*/
public class Test3 {
// The window handle
private long window;
// the vertex array
private int vao;
// a vertex buffer object
private int vbo;
// a vertex shader
private int vertexShader;
// a fragment shader
private int fragmentShader;
// a shader program
private int shaderProgram;
public static void main(String[] args) {
new Test3().run();
}
public void run() {
System.out.println("Hello LWJGL " + Version.getVersion() + "!");
init();
loop();
System.out.println("Starting clean up routine!");
destroyShaders();
destroyVBO();
// Free the window callbacks and destroy the window
glfwFreeCallbacks(window);
glfwDestroyWindow(window);
// Terminate GLFW and free the error callback
glfwTerminate();
glfwSetErrorCallback(null).free();
}
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 (!glfwInit())
throw new IllegalStateException("Unable to initialize GLFW");
// Configure GLFW
glfwDefaultWindowHints(); // optional, the current window hints are already the default
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // the window will stay hidden after creation
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); // the window will be resizable
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
// glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
// glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
// Create the window
window = glfwCreateWindow(300, 300, "Hello World!", NULL, NULL);
if (window == NULL)
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.
glfwSetKeyCallback(window, (window, key, scancode, action, mods) -> {
if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE)
glfwSetWindowShouldClose(window, true); // We will detect this in the rendering loop
});
// Get the thread stack and push a new frame
try (MemoryStack stack = stackPush()) {
IntBuffer pWidth = stack.mallocInt(1); // int*
IntBuffer pHeight = stack.mallocInt(1); // int*
// Get the window size passed to glfwCreateWindow
glfwGetWindowSize(window, pWidth, pHeight);
// Get the resolution of the primary monitor
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
// Center the window
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
glfwMakeContextCurrent(window);
// Enable v-sync
glfwSwapInterval(1);
// This line is critical for LWJGL's interoperation with GLFW's
// OpenGL context, or any context that is managed externally.
// LWJGL detects the context that is current in the current thread,
// creates the GLCapabilities instance and makes the OpenGL
// bindings available for use.
GL.createCapabilities();
vao = glGenVertexArrays();
glBindVertexArray(vao);
glViewport(0, 0, 300, 300);
createShaders();
createVBO();
// Make the window visible
glfwShowWindow(window);
}
private void loop() {
// Set the clear color
glClearColor(0.6f, 0.6f, 0.6f, 0.0f);
// Run the rendering loop until the user has attempted to close
// the window or has pressed the ESCAPE key.
while (!glfwWindowShouldClose(window)) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear the framebuffer
// System.out.println("drawing VAO");
glDrawArrays(GL_TRIANGLES, 0, 3);
glfwSwapBuffers(window); // swap the color buffers
// Poll for window events. The key callback above will only be
// invoked during this call.
// glfwPollEvents();
// "If instead you only need to update your rendering once you have received new input, glfwWaitEvents is a better choice. It waits until at least one event has been received, putting the thread to sleep in the meantime, and then processes all received events. This saves a great deal of CPU cycles and is useful for, for example, many kinds of editing tools."
glfwWaitEvents();
}
}
private void createVBO() {
try (MemoryStack stack = MemoryStack.stackPush()) {
float[] vertexData = {-.8f, -.8f, 0, 1, 1, 0, 0, 1,
0, .8f, 0, 1, 0, 1, 0, 1,
.8f, -.8f, 0, 1, 0, 0, 1, 1};
vbo = glGenBuffers();
glBindBuffer(GL_ARRAY_BUFFER, vbo);
// glBufferData(GL_ARRAY_BUFFER, FloatBuffer.wrap(vertexData), GL_STATIC_DRAW);
glBufferData(GL_ARRAY_BUFFER, vertexData, GL_STATIC_DRAW);
int positionLocation = glGetAttribLocation(shaderProgram, "position");
int colorLocation = glGetAttribLocation(shaderProgram, "color");
int floatSize = 4;
glVertexAttribPointer(positionLocation, 4, GL_FLOAT, false, 8 * floatSize, 0);
glVertexAttribPointer(colorLocation, 4, GL_FLOAT, false, 8 * floatSize, 4 * floatSize);
glEnableVertexAttribArray(positionLocation);
glEnableVertexAttribArray(colorLocation);
int e = glGetError();
if (e != GL_NO_ERROR) {
throw new RuntimeException("Error creating VBO");
}
System.out.println("Initialized VBO (pos=" + positionLocation + ", col=" + colorLocation + ")");
}
}
private void createShaders() {
Shader vertexShader = Shader.loadShader(GL_VERTEX_SHADER, "triangle.vs");
this.vertexShader = vertexShader.getID();
System.out.println("Created vertex shader");
Shader fragmentShader = Shader.loadShader(GL_FRAGMENT_SHADER, "triangle.fs");
this.fragmentShader = fragmentShader.getID();
System.out.println("Created fragment shader");
shaderProgram = glCreateProgram();
System.out.println("Created shader program");
glAttachShader(shaderProgram, this.vertexShader);
glAttachShader(shaderProgram, this.fragmentShader);
glLinkProgram(shaderProgram);
System.out.println("Linked shader program");
if (glGetProgrami(shaderProgram, GL_LINK_STATUS) != GL_TRUE) {
throw new RuntimeException(glGetProgramInfoLog(shaderProgram));
}
glUseProgram(shaderProgram);
System.out.println("Using shader program");
}
private void destroyShaders() {
glUseProgram(0);
glDetachShader(shaderProgram, vertexShader);
glDetachShader(shaderProgram, fragmentShader);
glDeleteShader(vertexShader);
glDeleteProgram(shaderProgram);
}
private void destroyVBO() {
// position
glDisableVertexAttribArray(0);
// color
glDisableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDeleteBuffers(vbo);
glBindVertexArray(0);
glDeleteVertexArrays(vao);
}
}
I use a the Shader utility class (copied directly from the SilverTiger tutorial to lwjgl3 from the wiki) to load 2 shaders:
triangle.vs:
#version 400 core
in vec4 position;
in vec4 color;
out vec4 vertexColor;
void main() {
vertexColor = color;
gl_Position = position;
}
triangle.fs:
#version 400
in vec4 vertexColor;
out vec4 fragColor;
void main() {
fragColor = vertexColor;
}
The Shader utility class file containing Shader.loadShader:
import java.io.*;
import static org.lwjgl.opengl.GL11.GL_TRUE;
import static org.lwjgl.opengl.GL20.*;
/**
* This class represents a shader.
*
* @author Heiko Brumme
*/
public class Shader {
/**
* Stores the handle of the shader.
*/
private final int id;
/**
* Creates a shader with specified type. The type in the tutorial should be
* either <code>GL_VERTEX_SHADER</code> or <code>GL_FRAGMENT_SHADER</code>.
*
* @param type Type of the shader
*/
public Shader(int type) {
id = glCreateShader(type);
}
/**
* Creates a shader with specified type and source and compiles it. The type
* in the tutorial should be either <code>GL_VERTEX_SHADER</code> or
* <code>GL_FRAGMENT_SHADER</code>.
*
* @param type Type of the shader
* @param source Source of the shader
* @return Compiled Shader from the specified source
*/
public static Shader createShader(int type, CharSequence source) {
Shader shader = new Shader(type);
shader.source(source);
shader.compile();
return shader;
}
/**
* Loads a shader from a file.
*
* @param type Type of the shader
* @param path File path of the shader
* @return Compiled Shader from specified file
*/
public static Shader loadShader(int type, String path) {
StringBuilder builder = new StringBuilder();
File file = new File(path);
try (InputStream in = new FileInputStream(file);
// try (InputStream in = Shader.class.getResourceAsStream(path);
BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
String line;
while ((line = reader.readLine()) != null) {
builder.append(line).append("\n");
}
} catch (IOException ex) {
throw new RuntimeException("Failed to load a shader file: <" + file.getAbsolutePath() + ">"
+ System.lineSeparator() + ex.getMessage());
}
CharSequence source = builder.toString();
return createShader(type, source);
}
/**
* Sets the source code of this shader.
*
* @param source GLSL Source Code for the shader
*/
public void source(CharSequence source) {
glShaderSource(id, source);
}
/**
* Compiles the shader and checks it's status afertwards.
*/
public void compile() {
glCompileShader(id);
checkStatus();
}
/**
* Checks if the shader was compiled successfully.
*/
private void checkStatus() {
int status = glGetShaderi(id, GL_COMPILE_STATUS);
if (status != GL_TRUE) {
throw new RuntimeException(glGetShaderInfoLog(id));
}
}
/**
* Deletes the shader.
*/
public void delete() {
glDeleteShader(id);
}
/**
* Getter for the shader ID.
*
* @return Handle of this shader
*/
public int getID() {
return id;
}
}