Search code examples
javaopengllwjgl

Why does LWJGL (OpenGL) drawElements not draw?


I attempted to follow the LWJGL 3.2+ Tutorial on drawElements and get my LWJGL application to draw a quad. My code runs successfully but doesn't draw anything (apart from the basic window), no matter where I run my loopCycle method that should draw the quad. I assume it has to do with the change from Display (Tutorial) to GLFW (my code)? I saw some posts talking about Projection, View and Model matrices that I do not use (afaik), is that the issue why it doesn't display?

package org.tempest.game;

import org.lwjgl.*;
import org.lwjgl.glfw.*;
import org.lwjgl.opengl.*;
import org.lwjgl.system.*;

import java.nio.*;

import static org.lwjgl.glfw.Callbacks.*;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.system.MemoryStack.*;
import static org.lwjgl.system.MemoryUtil.*;

public class Graphics {

    // The window handle
    private long window;

    // Window setup
    private final String WINDOW_TITLE = "Test";
    // 1920x1080, 1600x900 and 1200x675 are all 16:9 ratios
    private final int WIDTH = 320;
    private final int HEIGHT = 240;

    // Quad variables
    private int vaoId = 0;
    private int vboId = 0;
    private int vboiId = 0;
    private int indicesCount = 0;

    public static void main(String[] args) {
        new Graphics().run();
    }

    public void run() {
        System.out.println("Hello LWJGL " + Version.getVersion() + "!");
        init();

        setupQuad();
        loop();
        destroyOpenGL();

        // 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);
        glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);

        // Create the window
        window = glfwCreateWindow(WIDTH, HEIGHT, WINDOW_TITLE, 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 with 1
        glfwSwapInterval(0);

        // Make the window visible
        glfwShowWindow(window);
    }

    private void loop() {
        // Initialize variables for fps calculation
        long time_start = System.nanoTime();
        int frames = 0;
        final double check_fps_time = 1d;

        // Set the clear color
        glClearColor(0.2f, 0.2f, 0.2f, 0.0f);
        // TODO Where to initialize this?
        //GL11.glViewport(0, 0, WIDTH, HEIGHT);

        // 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

            glfwSwapBuffers(window); // swap the color buffers

            // Count, calculate and display fps
            frames++;
            long time_now = System.nanoTime();
            if ((double)(time_now - time_start)/1000000000 > check_fps_time) {
                int fps_prediction = (int)(frames/check_fps_time);
                System.out.println("FPS: " + fps_prediction);
                frames = 0;
                time_start = time_now;
            }

            // Poll for window events. The key callback above will only be
            // invoked during this call.
            glfwPollEvents();

            loopCycle();
        }
    }

    public void setupQuad() {
        GL.createCapabilities();
        // Vertices, the order is not important.
        float[] vertices = {
                -0.5f, 0.5f, 0f,    // Left top         ID: 0
                -0.5f, -0.5f, 0f,   // Left bottom      ID: 1
                0.5f, -0.5f, 0f,    // Right bottom     ID: 2
                0.5f, 0.5f, 0f      // Right left       ID: 3
        };
        // Sending data to OpenGL requires the usage of (flipped) byte buffers
        FloatBuffer verticesBuffer = BufferUtils.createFloatBuffer(vertices.length);
        verticesBuffer.put(vertices);
        verticesBuffer.flip();

        // OpenGL expects to draw vertices in counter clockwise order by default
        byte[] indices = {
                // Left bottom triangle
                0, 1, 2,
                // Right top triangle
                2, 3, 0
        };
        indicesCount = indices.length;
        ByteBuffer indicesBuffer = BufferUtils.createByteBuffer(indicesCount);
        indicesBuffer.put(indices);
        indicesBuffer.flip();

        // Create a new Vertex Array Object in memory and select it (bind)
        // A VAO can have up to 16 attributes (VBOs) assigned to it by default
        vaoId = GL30.glGenVertexArrays();
        GL30.glBindVertexArray(vaoId);

        // Create a new Vertex Buffer Object in memory and select it (bind)
        // A VBO is a collection of Vectors which in this case resemble the location of each vertex.
        vboId = GL15.glGenBuffers();
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboId);
        GL15.glBufferData(GL15.GL_ARRAY_BUFFER, verticesBuffer, GL15.GL_STATIC_DRAW);
        // Put the VBO in the attributes list at index 0
        GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 0, 0);
        // Deselect (bind to 0) the VBO
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
        // Deselect (bind to 0) the VAO
        GL30.glBindVertexArray(0);

        // Create a new VBO for the indices and select it (bind)
        vboiId = GL15.glGenBuffers();
        GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, vboiId);
        GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL15.GL_STATIC_DRAW);
        // Deselect (bind to 0) the VBO
        GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
    }

    public void loopCycle() {
        GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);

        // Bind to the VAO that has all the information about the vertices
        GL30.glBindVertexArray(vaoId);
        GL20.glEnableVertexAttribArray(0);

        // Bind to the index VBO that has all the information about the order of the vertices
        GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, vboiId);

        // Draw the vertices
        GL11.glDrawElements(GL11.GL_TRIANGLES, indicesCount, GL11.GL_UNSIGNED_BYTE, 0);

        // Put everything back to default (deselect)
        GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
        GL20.glDisableVertexAttribArray(0);
        GL30.glBindVertexArray(0);
    }
    public void destroyOpenGL() {
        // Disable the VBO index from the VAO attributes list
        GL20.glDisableVertexAttribArray(0);

        // Delete the vertex VBO
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
        GL15.glDeleteBuffers(vboId);

        // Delete the index VBO
        GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
        GL15.glDeleteBuffers(vboiId);

        // Delete the VAO
        GL30.glBindVertexArray(0);
        GL30.glDeleteVertexArrays(vaoId);
    }

    public int getWIDTH() {
        return WIDTH;
    }

    public int getHEIGHT() {
        return HEIGHT;
    }
}

I am a beginner and probably there are a number of things I need to look into to make this work. I would love to hear some guidance on what to look into to get my application to do something so I can take things from there. Thank you so much! :)


Solution

  • There is at least one issue with this code- it calls clear/draw/swap in the wrong order. Basically with OpenGL, the main loop should call clear() first, draw some things, and then call swapBuffers() to display the buffer contents.

    The example instead: calls clear (ok, clear the buffer), swaps the buffers (here a blank window is shown, since the buffer is cleared), and then draws a bunch of stuff to the buffer. But the buffer contents is never displayed (since in the next cycle, the first operation is clear() again).

    Below the slightly modified code; it draws a white rectangle - I am not totally sure about the usage of glBindBuffer (I used drawLine and drawTriangle in the past), but it's a start.

    package sample;
    
    import org.lwjgl.*;
    import org.lwjgl.glfw.*;
    import org.lwjgl.opengl.*;
    import org.lwjgl.system.*;
    import java.nio.*;
    import static org.lwjgl.glfw.Callbacks.*;
    import static org.lwjgl.glfw.GLFW.*;
    import static org.lwjgl.opengl.GL11.*;
    import static org.lwjgl.system.MemoryStack.*;
    import static org.lwjgl.system.MemoryUtil.*;
    
    public class DrawExample {
    
      // The window handle
      private long window;
    
      // Window setup
      private final String WINDOW_TITLE = "Test";
      // 1920x1080, 1600x900 and 1200x675 are all 16:9 ratios
      private final int WIDTH = 320;
      private final int HEIGHT = 240;
    
      // Quad variables
      private int vaoId = 0;
      private int vboId = 0;
      private int vboiId = 0;
      private int indicesCount = 0;
    
      public static void main(String[] args) {
        new DrawExample().run();
      }
    
      public void run() {
        System.out.println("Hello LWJGL " + Version.getVersion() + "!");
        init();
    
        setupQuad();
        loop();
        destroyOpenGL();
    
        // 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);
        glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
    
        // Create the window
        window = glfwCreateWindow(WIDTH, HEIGHT, WINDOW_TITLE, 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 resolution of the primary monitor
        GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
        // 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);
    
    
    
          // 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 with 1
        glfwSwapInterval(1);
    
        // Make the window visible
        glfwShowWindow(window);
      }
    
      private void loop() {
        // Initialize variables for fps calculation
        long time_start = System.nanoTime();
        int frames = 0;
        final double check_fps_time = 1d;
    
        // Set the clear color
        glClearColor(0.2f, 0.2f, 0.2f, 0.0f);
        // TODO Where to initialize this?
        // GL11.glViewport(0, 0, WIDTH, HEIGHT);
    
        // Run the rendering loop until the user has attempted to close
        // the window or has pressed the ESCAPE key.
        while (!glfwWindowShouldClose(window)) {
          // Count, calculate and display fps
          frames++;
          long time_now = System.nanoTime();
          if ((double) (time_now - time_start) / 1000000000 > check_fps_time) {
            int fps_prediction = (int) (frames / check_fps_time);
            System.out.println("FPS: " + fps_prediction);
            frames = 0;
            time_start = time_now;
          }
    
          // Poll for window events. The key callback above will only be
          // invoked during this call.
          glfwPollEvents();
    
          loopCycle();
          glfwSwapBuffers(window); // swap the color buffers
    
        }
      }
    
      public void setupQuad() {
        GL.createCapabilities();
        // Vertices, the order is not important.
        float[] vertices = {-0.5f, 0.5f, 0f, // Left top ID: 0
            -0.5f, -0.5f, 0f, // Left bottom ID: 1
            0.5f, -0.5f, 0f, // Right bottom ID: 2
            0.5f, 0.5f, 0f // Right left ID: 3
        };
        // Sending data to OpenGL requires the usage of (flipped) byte buffers
        FloatBuffer verticesBuffer = BufferUtils.createFloatBuffer(vertices.length);
        verticesBuffer.put(vertices);
        verticesBuffer.flip();
    
        // OpenGL expects to draw vertices in counter clockwise order by default
        byte[] indices = {
            // Left bottom triangle
            0, 1, 2,
            // Right top triangle
            2, 3, 0};
        indicesCount = indices.length;
        ByteBuffer indicesBuffer = BufferUtils.createByteBuffer(indicesCount);
        indicesBuffer.put(indices);
        indicesBuffer.flip();
    
        // Create a new Vertex Array Object in memory and select it (bind)
        // A VAO can have up to 16 attributes (VBOs) assigned to it by default
        vaoId = GL30.glGenVertexArrays();
        GL30.glBindVertexArray(vaoId);
    
        // Create a new Vertex Buffer Object in memory and select it (bind)
        // A VBO is a collection of Vectors which in this case resemble the location of each vertex.
        vboId = GL15.glGenBuffers();
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboId);
        GL15.glBufferData(GL15.GL_ARRAY_BUFFER, verticesBuffer, GL15.GL_STATIC_DRAW);
        // Put the VBO in the attributes list at index 0
        GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 0, 0);
        // Deselect (bind to 0) the VBO
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
        // Deselect (bind to 0) the VAO
        GL30.glBindVertexArray(0);
    
        // Create a new VBO for the indices and select it (bind)
        vboiId = GL15.glGenBuffers();
        GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, vboiId);
        GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL15.GL_STATIC_DRAW);
        // Deselect (bind to 0) the VBO
        GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
      }
    
      public void loopCycle() {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
        // Bind to the VAO that has all the information about the vertices
        GL30.glBindVertexArray(vaoId);
        GL20.glEnableVertexAttribArray(0);
    
        // Bind to the index VBO that has all the information about the order of the vertices
        GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, vboiId);
    
        // Draw the vertices
        GL11.glDrawElements(GL11.GL_TRIANGLES, indicesCount, GL11.GL_UNSIGNED_BYTE, 0);
    
        // Put everything back to default (deselect)
        GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
        GL20.glDisableVertexAttribArray(0);
        GL30.glBindVertexArray(0);
      }
    
      public void destroyOpenGL() {
        // Disable the VBO index from the VAO attributes list
        GL20.glDisableVertexAttribArray(0);
    
        // Delete the vertex VBO
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
        GL15.glDeleteBuffers(vboId);
    
        // Delete the index VBO
        GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
        GL15.glDeleteBuffers(vboiId);
    
        // Delete the VAO
        GL30.glBindVertexArray(0);
        GL30.glDeleteVertexArrays(vaoId);
      }
    
      public int getWIDTH() {
        return WIDTH;
      }
    
      public int getHEIGHT() {
        return HEIGHT;
      }
    }