I am trying to render a triangle to see how LWJGL works. Each frame, I reset the vertex data ByteBuffer
and write 3 vertices to it directly. Then I call buffer.flip()
to ready the data to be uploaded to the GPU and call glBufferData(...)
and finally glDrawArrays(...)
, but no triangle shows. Using the debug program RenderDoc I was able to look at the vertex data that was supposedly uploaded and it definitely doesn't seem right.
As you can see, each position is extremely small (like 41 zero's after the the .
). I don't see any errors, even with the GLFW error callbacks and debug context set up.
All Java code:
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWErrorCallback;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GLUtil;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
public class MinimalExample {
private static void debugPrintErrors() {
System.out.println("-> DEBUG PRINT ERRORS");
int error;
while ((error = GL30.glGetError()) != GL30.GL_NO_ERROR) {
StringBuilder b = new StringBuilder(" ");
switch (error) {
case GL30.GL_INVALID_ENUM -> b.append("INVALID_ENUM");
case GL30.GL_INVALID_VALUE -> b.append("INVALID_VALUE");
case GL30.GL_INVALID_OPERATION -> b.append("INVALID_OP");
case GL30.GL_INVALID_FRAMEBUFFER_OPERATION -> b.append("INVALID_FB_OP");
}
System.out.println(b);
}
}
private static String readResource(String res) {
try {
InputStream is = MinimalExample.class.getResourceAsStream(res);
String s = new String(is.readAllBytes(), StandardCharsets.UTF_8);
is.close();
return s;
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
// vertex data buffer
private static final ByteBuffer buf = ByteBuffer.allocateDirect(4096);
// shader program
static int program;
// render objects
static int vao;
static int vbo;
public static void main(String[] args) {
// set buffer limit
buf.limit(4096).position(0);
// init glfw and create window
GLFW.glfwInit();
long window = GLFW.glfwCreateWindow(500, 500, "Hello", 0, 0);
// create GL
GLFW.glfwMakeContextCurrent(window);
GL.createCapabilities();
GLUtil.setupDebugMessageCallback(System.out);
GLFW.glfwSetErrorCallback(GLFWErrorCallback.createPrint(System.out));
// create vertex objects
vao = GL30.glGenVertexArrays();
vbo = GL30.glGenBuffers();
GL30.glBindVertexArray(vao);
GL30.glBindBuffer(GL30.GL_ARRAY_BUFFER, vbo);
GL30.glVertexAttribPointer(0, 3, GL30.GL_FLOAT, false, 7 * 4, 0);
GL30.glVertexAttribPointer(1, 4, GL30.GL_FLOAT, false, 7 * 4, 7 * 3);
GL30.glEnableVertexAttribArray(0);
GL30.glEnableVertexAttribArray(1);
// compile and link shaders
int vertexShader = GL30.glCreateShader(GL30.GL_VERTEX_SHADER);
int fragmentShader = GL30.glCreateShader(GL30.GL_FRAGMENT_SHADER);
GL30.glShaderSource(vertexShader, readResource("/test.vsh"));
GL30.glShaderSource(fragmentShader, readResource("/test.fsh"));
GL30.glCompileShader(vertexShader);
GL30.glCompileShader(fragmentShader);
program = GL30.glCreateProgram();
GL30.glAttachShader(program, vertexShader);
GL30.glAttachShader(program, fragmentShader);
GL30.glLinkProgram(program);
// render loop
while (!GLFW.glfwWindowShouldClose(window)) {
// poll events
GLFW.glfwPollEvents();
// clear screen
GL30.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
GL30.glClear(GL30.GL_COLOR_BUFFER_BIT);
// render
render();
// swap buffers
GLFW.glfwSwapBuffers(window);
}
}
static void render() {
// put vertex data
// manual to simulate graphics library
putVec3(0.25f, 0.25f, 1f); putVec4(1.0f, 0.0f, 0.0f, 1.0f);
putVec3(0.75f, 0.25f, 1f); putVec4(0.0f, 1.0f, 0.0f, 1.0f);
putVec3(0.50f, 0.75f, 1f); putVec4(0.0f, 0.0f, 1.0f, 1.0f);
buf.flip();
// bind program
GL30.glUseProgram(program);
// bind vertex array
GL30.glBindVertexArray(vao);
GL30.glEnableVertexAttribArray(0);
GL30.glEnableVertexAttribArray(1);
// upload graphics data and draw
GL30.glBindBuffer(GL30.GL_ARRAY_BUFFER, vbo);
GL30.glBufferData(GL30.GL_ARRAY_BUFFER, buf, GL30.GL_STATIC_DRAW);
GL30.glDrawArrays(GL30.GL_TRIANGLES, 0, 3);
// reset vertex data buffer
buf.position(0);
buf.limit(buf.capacity());
}
//////////////////////////////////////////
static void putVec3(float x, float y, float z) {
buf.putFloat(x);
buf.putFloat(y);
buf.putFloat(z);
}
static void putVec4(float x, float y, float z, float w) {
buf.putFloat(x);
buf.putFloat(y);
buf.putFloat(z);
buf.putFloat(w);
}
}
All shader code (merged into one block for convenience, actually two files in reality):
/*
test.vsh
*/
#version 330 core
in layout(location = 0) vec3 position;
in layout(location = 1) vec4 col;
out layout(location = 0) vec4 fColor;
void main() {
gl_Position = vec4(position * 1000, 1);
fColor = col;
}
/*
test.fsh
*/
#version 330 core
in layout(location = 0) vec4 fColor;
out vec4 outColor;
void main() {
outColor = fColor;
}
Edit: I know the contents of the ByteBuffer
are correct, checking them each frame yields:
[ 0.25, 0.25, 1.0, 1.0, 0.0, 0.0, 1.0, 0.75, 0.25, 1.0, 0.0, 1.0, 0.0, 1.0, 0.5, 0.75, 1.0, 0.0, 0.0, 1.0, 1.0, ]
There are 3 problems.
The first problem is buf
. ByteBuffer::allocateDirect
allocates a BIG_ENDIAN buffer, but OpenGL is a C library, which is LITTLE_ENDIAN. So it must be allocated with BufferUtils::createByteBuffer
or MemoryUtil::memAlloc
.
The second problem is the offset of glVertexAttribPointer
. It should be 4 * 3
for 3 floats.
The third problem is your vertex shader. The input position was multiplied by 1000, which is out of the viewport. To avoid this, you just have to remove the multiplication or use a projection matrix.
gl_Position = vec4(position /* * 1000 */, 1);
BTW I recommend to use ByteBuffer::clear
to reset the vertex data buffer, because it is a builtin method.
Fixed Java code:
import org.lwjgl.BufferUtils;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWErrorCallback;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GLUtil;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
public class MinimalExample {
private static void debugPrintErrors() {
System.out.println("-> DEBUG PRINT ERRORS");
int error;
while ((error = GL30.glGetError()) != GL30.GL_NO_ERROR) {
StringBuilder b = new StringBuilder(" ");
switch (error) {
case GL30.GL_INVALID_ENUM -> b.append("INVALID_ENUM");
case GL30.GL_INVALID_VALUE -> b.append("INVALID_VALUE");
case GL30.GL_INVALID_OPERATION -> b.append("INVALID_OP");
case GL30.GL_INVALID_FRAMEBUFFER_OPERATION -> b.append("INVALID_FB_OP");
}
System.out.println(b);
}
}
private static String readResource(String res) {
try {
InputStream is = MinimalExample.class.getResourceAsStream(res);
String s = new String(is.readAllBytes(), StandardCharsets.UTF_8);
is.close();
return s;
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
// vertex data buffer
private static final ByteBuffer buf = BufferUtils.createByteBuffer(4096);
// shader program
static int program;
// render objects
static int vao;
static int vbo;
public static void main(String[] args) {
// set buffer limit
buf.limit(4096).position(0);
// init glfw and create window
GLFW.glfwInit();
long window = GLFW.glfwCreateWindow(500, 500, "Hello", 0, 0);
// create GL
GLFW.glfwMakeContextCurrent(window);
GL.createCapabilities();
GLUtil.setupDebugMessageCallback(System.out);
GLFW.glfwSetErrorCallback(GLFWErrorCallback.createPrint(System.out));
// create vertex objects
vao = GL30.glGenVertexArrays();
vbo = GL30.glGenBuffers();
GL30.glBindVertexArray(vao);
GL30.glBindBuffer(GL30.GL_ARRAY_BUFFER, vbo);
GL30.glVertexAttribPointer(0, 3, GL30.GL_FLOAT, false, 7 * 4, 0);
GL30.glVertexAttribPointer(1, 4, GL30.GL_FLOAT, false, 7 * 4, 4 * 3);
GL30.glEnableVertexAttribArray(0);
GL30.glEnableVertexAttribArray(1);
// compile and link shaders
int vertexShader = GL30.glCreateShader(GL30.GL_VERTEX_SHADER);
int fragmentShader = GL30.glCreateShader(GL30.GL_FRAGMENT_SHADER);
GL30.glShaderSource(vertexShader, readResource("/test.vsh"));
GL30.glShaderSource(fragmentShader, readResource("/test.fsh"));
GL30.glCompileShader(vertexShader);
GL30.glCompileShader(fragmentShader);
program = GL30.glCreateProgram();
GL30.glAttachShader(program, vertexShader);
GL30.glAttachShader(program, fragmentShader);
GL30.glLinkProgram(program);
// render loop
while (!GLFW.glfwWindowShouldClose(window)) {
// poll events
GLFW.glfwPollEvents();
// clear screen
GL30.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
GL30.glClear(GL30.GL_COLOR_BUFFER_BIT);
// render
render();
// swap buffers
GLFW.glfwSwapBuffers(window);
}
}
static void render() {
// put vertex data
// manual to simulate graphics library
putVec3(0.25f, 0.25f, 1f); putVec4(1.0f, 0.0f, 0.0f, 1.0f);
putVec3(0.75f, 0.25f, 1f); putVec4(0.0f, 1.0f, 0.0f, 1.0f);
putVec3(0.50f, 0.75f, 1f); putVec4(0.0f, 0.0f, 1.0f, 1.0f);
buf.flip();
// bind program
GL30.glUseProgram(program);
// bind vertex array
GL30.glBindVertexArray(vao);
GL30.glEnableVertexAttribArray(0);
GL30.glEnableVertexAttribArray(1);
// upload graphics data and draw
GL30.glBindBuffer(GL30.GL_ARRAY_BUFFER, vbo);
GL30.glBufferData(GL30.GL_ARRAY_BUFFER, buf, GL30.GL_STATIC_DRAW);
GL30.glDrawArrays(GL30.GL_TRIANGLES, 0, 3);
// reset vertex data buffer
buf.clear();
}
//////////////////////////////////////////
static void putVec3(float x, float y, float z) {
buf.putFloat(x);
buf.putFloat(y);
buf.putFloat(z);
}
static void putVec4(float x, float y, float z, float w) {
buf.putFloat(x);
buf.putFloat(y);
buf.putFloat(z);
buf.putFloat(w);
}
}