Search code examples
javaopenglbufferrenderinglwjgl

LWJGL Vertex data seems corrupted


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.

RenderDoc vertex data result

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, ]

Solution

  • 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);
        }
    
    }