Inconsistent glDrawElements

Started by USNavyFish, September 08, 2011, 22:10:32

Previous topic - Next topic

USNavyFish

Hello! First post, so I want to start off with a big Thank You to those who contribute to LWJGL. I'm relatively new to the library and to the new OpenGL 3+ specification, so it's possible that the problem I'm encountering is caused by incorrect usage, although I am writing my code following a number of fairly thorough examples (OpenGL SuperBible, Learning Modern 3D Graphics Programming, mostly).

In a nutshell:

The overridden "glDrawElements" method that directly takes an index buffer will produce the expected geometry, if and only if GL_ELEMENT_ARRAY_BUFFER is bound to zero. If the buffer is bound to anything else, an exception is thrown.

The "normal-syntax" glDrawElements method, on the other hand, will throw an exception if this buffer is NOT bound (that's the expected behavior right?). But, when I bind the GL_ELEMENT_ARRAY_BUFFER it still does not produce any geometry, whereas the overridden method uses the same indexes and does.

In other words, there are four cases. And since I'm not 100% what the expected behavior IS, I'm going to post all four of them for consideration  :)


The simplified, "Overridden" DrawElements method only works when the GL_ELEMENT_ARRAY_BUFFER is UNBOUND:

GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
GL11.glDrawElements(GL11.GL_TRIANGLES, indexData);


This case produces the expected geometry. If the GL_ELEMENT_ARRAY_BUFFER IS bound, an exception is thrown:

GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, indexBufferObject);
GL11.glDrawElements(GL11.GL_TRIANGLES, indexData);

OpenGLException: Cannot use Buffers when Element Array Buffer Object is enabled




Using the "normal" syntax, DrawElements throws an exception when the GL_ELEMENT_ARRAY_BUFFER is Unbound (this is expected right?):

GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
GL11.glDrawElements(GL11.GL_TRIANGLES, indexData.limit(), GL11.GL_INT, (long)0);

OpenGLException: Cannot use offsets when Element Array Buffer Object is disabled



But even with the proper GL_ELEMENT_ARRAY_BUFFER bound, this code doesn't work:

GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, indexBufferObject);
GL11.glDrawElements(GL11.GL_TRIANGLES, 24, GL11.GL_INT, (long)0);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);


Maybe the '24' was messing things up?  Nope, this change fixes nothing:

GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, indexBufferObject);
indexData.rewind();
GL11.glDrawElements(GL11.GL_TRIANGLES, indexData.limit(), GL11.GL_INT, (long)0);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);



I'll post the source code of this entire file below. If it turns out this is actually a bug and not improper usage on my part, I'll be happy to send over the rest of my source code if that will help expedite the bug reproduction.

Thanks for reading!

Bryan


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package bryan.terrain;


import bryan.util.ShaderProgram;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.util.vector.Matrix4f;
import org.lwjgl.util.vector.Vector3f;

/**
 *
 * @author Bryan
 */
public class Shape {
    
    private static ShaderProgram shader;
    
    private static int unifProjectionMatrix;
    private static int unifModelViewMatrix;
    
    
    static{
        shader = new ShaderProgram("\\src\\bryan\\shaders\\", "screen.vert", "screen.frag");
        
        unifProjectionMatrix = GL20.glGetUniformLocation(shader.getHandle(), "projectionMatrix");
        unifModelViewMatrix = GL20.glGetUniformLocation(shader.getHandle(), "modelViewMatrix");
    }
    
    private ObjectManager objectManager;
    
    private int vertexArrayObject;
    private int vertexBufferObject;
    private int indexBufferObject;
    
    private IntBuffer indexData;
    
    private double dtAccum;
    
    private FloatBuffer modelViewMatrixBuffer;
    private Matrix4f modelViewMatrix;
    private Vector3f position;

    
    public Shape(ObjectManager theObjectManager){
        this.objectManager = theObjectManager;
        initGLState();
        
        position = new Vector3f(0,0,-10);
        modelViewMatrix = new Matrix4f();
        modelViewMatrixBuffer = BufferUtils.createFloatBuffer(16);
    }
    
    void Logic(double dt) {
        dtAccum += dt;
    }
    
    public void Draw(double dt){
        shader.Use();

        generateModelViewMatrix();
        
        GL20.glUniformMatrix4(unifModelViewMatrix, false, modelViewMatrixBuffer);
        GL20.glUniformMatrix4(unifProjectionMatrix, false, objectManager.renderer.getProjectionMatrix());
        
        GL30.glBindVertexArray(vertexArrayObject);
        
        // Is this a bug in LWJGL? The two glDrawElement functions work differently -
        // the overidden DrawElements function will ONLY work if there is no
        // GL_ELEMENT_ARRAY_BUFFER bound, while the "normal-syntax" DrawElements
        // function requires the element array buffer to be bound. The overidden
        // function produces the expected geometry, but the "normal" one does not.
        
        // CASE 1: Only works when GL_ELEMENT_ARRAY_BUFFER is bound to Zero. 
        // If the buffer is bound, an exception is thrown.
        //GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, indexBufferObject);
        GL11.glDrawElements(GL11.GL_TRIANGLES, indexData);
        
        // Case 2: The "normal-syntax" version doesn't work at all (though it should!),
        //GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, indexBufferObject);
        //indexData.rewind();
        //GL11.glDrawElements(GL11.GL_TRIANGLES, indexData.limit(), GL11.GL_INT, (long)0);
        //GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
        
        GL30.glBindVertexArray(0);
      
        shader.Release();
    }

    private void generateModelViewMatrix() {
        modelViewMatrix.setIdentity();
        modelViewMatrix.translate(position);
        Matrix4f.mul(modelViewMatrix, objectManager.camera.getCameraTransform(), modelViewMatrix);
        modelViewMatrix.store(modelViewMatrixBuffer);
        modelViewMatrixBuffer.rewind();
    }
    
    private void initGLState() {
        FloatBuffer vertexData = generateVertexData();
        vertexArrayObject = GL30.glGenVertexArrays();
        GL30.glBindVertexArray(vertexArrayObject);
        
        vertexBufferObject = GL15.glGenBuffers();
        
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vertexBufferObject);
        GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertexData, GL15.GL_STATIC_DRAW);
        
        GL20.glEnableVertexAttribArray(0);
        GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 0, (long)0);
        
        GL20.glEnableVertexAttribArray(1);
        GL20.glVertexAttribPointer(1, 4, GL11.GL_FLOAT, false, 0, (long)(8*3*4));
        
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
        
        indexData = generateIndexData();
        
        indexBufferObject = GL15.glGenBuffers();
        GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, indexBufferObject);
        //GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indexData, GL15.GL_STATIC_DRAW);
        GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, GL11.GL_INT, GL15.GL_STATIC_DRAW);
        GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
        
        GL30.glBindVertexArray(0);
    }

    private FloatBuffer generateVertexData() {
        float[] raw = new float[]{
            +1.0f, +1.0f, +1.0f,
            -1.0f, -1.0f, +1.0f,
            -1.0f, +1.0f, -1.0f,
            +1.0f, -1.0f, -1.0f,

            -1.0f, -1.0f, -1.0f,
            +1.0f, +1.0f, -1.0f,
            +1.0f, -1.0f, +1.0f,
            -1.0f, +1.0f, +1.0f,

            0.0f, 1.0f, 0.0f, 1.0f,
            0.0f, 0.0f, 1.0f, 1.0f,
            1.0f, 0.0f, 0.0f, 1.0f,
            0.5f, 0.5f, 0.0f, 1.0f,

            0.0f, 1.0f, 0.0f, 1.0f,
            0.0f, 0.0f, 1.0f, 1.0f,
            1.0f, 0.0f, 0.0f, 1.0f,
            0.5f, 0.5f, 0.0f, 1.0f
        };
        

        FloatBuffer generatedVertexData = BufferUtils.createFloatBuffer(raw.length);
        generatedVertexData.put(raw);
        generatedVertexData.rewind();
         
        return generatedVertexData;
    }
    
    private IntBuffer generateIndexData(){
        int[] raw = new int[]{
            0, 1, 2,
            1, 0, 3,
            2, 3, 0,
            3, 2, 1,

            5, 4, 6,
            4, 5, 7,
            7, 6, 4,
            6, 7, 5
        };
        
        IntBuffer generatedIndexData = BufferUtils.createIntBuffer(raw.length);
        generatedIndexData.put(raw);
        generatedIndexData.rewind();
        
        return generatedIndexData;
    }
}

spasi

The 4th case should be working. What do you mean it doesn't work btw? Do you get an exception or simply no geometry being rendered? Try to run your code using a debug context (use new ContextAttribs().withDebug(true)) and enable LWJGL's debug too (pass -Dorg.lwjgl.util.Debug=true to the VM).

USNavyFish

Thanks for the response, spasi.

The 4th case produces no geometry. With debug mode on (I'd forgotten about setting it through the VM args, hence no exceptions were being thrown... doh!), I'm getting a 1280 exception:

Exception in thread "main" org.lwjgl.opengl.OpenGLException: Invalid enum (1280)

I've narrowed it down to this line
GL11.glDrawElements(GL11.GL_TRIANGLES, indexData.limit(), GL11.GL_INT, (long)0);




The problem is that DrawElements only takes unsigned data types, i.e. GL_UNSIGNED_SHORT:

GL11.glDrawElements(GL11.GL_TRIANGLES, indexData.limit(), GL11.GL_UNSIGNED_SHORT, (long)0);


Using the above line eliminates the exception! But, it still produces no geometry.  I'd imagine that that is because the index data is stored in a ShortBuffer, not an "Unsigned"ShortBuffer.

    private ShortBuffer generateIndexData(){
        short[] raw = new short[]{
            0, 1, 2,
            1, 0, 3,
            2, 3, 0,
            3, 2, 1,

            5, 4, 6,
            4, 5, 7,
            7, 6, 4,
            6, 7, 5
        };
        
        ShortBuffer generatedIndexData = BufferUtils.createShortBuffer(raw.length);
        generatedIndexData.put(raw);
        generatedIndexData.rewind();
        
        return generatedIndexData;
    }



Any ideas how to fix that? I'm not aware of any ushort datatypes in Java or LWJGL.



spasi

You don't need to worry about unsigned shorts. If you use an index greater than Short.MAX_VALUE, it will overflow to a negative short (in Java), but the GPU will simply read it correctly as an unsigned value.

No GL errors and no geometry rendered usually means a problem with modelview matrix (the camera that is), back-face culling or the geometry data itself.

USNavyFish

Again, I appreciate the help on this.

Unfortunately, the problem is not because of back-face culling, nor the modelview matrix or geometry, because this produces the expected geometry:

GL11.glDrawElements(GL11.GL_TRIANGLES, indexData);


While this does not:

GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, indexBufferObject);
indexData.rewind();
GL11.glDrawElements(GL11.GL_TRIANGLES, indexData.limit(), GL11.GL_UNSIGNED_SHORT, (long)0);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);


With there being no other changes to the code, obviously.

-----------------------------------------------------------------------------------------------------------------------

But here's something suspicious: I just realized I CAN get that 2nd DrawElements method working, if I buffer the index data using this method:

GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indexData, GL15.GL_STATIC_DRAW);


Whereas, the overridden glBufferData method that I was using before to buffer the indicies prevents the 2nd DrawElements method from producing geometry:

GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, GL11.GL_UNSIGNED_SHORT, GL15.GL_STATIC_DRAW);


Note that during the vertex and index buffering step, the correct VAO (glBindVertexArray) is bound. Also note that the overidden GLDrawElements function [  GL11.glDrawElements(GL11.GL_TRIANGLES, indexData);  ] work regardless of which glBufferData method I use.


So, things work, there just seems to be some wonkiness when I use the 'standard' glBufferData method (instead of LWJGL's overidden version) in conjunction with the 'standard' glDrawElements method.


The entire vertex/index initialization process is posted below in case you have any questions about my usage.  I don't mean to waste time if this is simply a usage-problem, but in the chance that there is an issue with the LWJGL code, I'd like to provide any assistance I can. Thanks again for your time!

       vertexArrayObject = GL30.glGenVertexArrays();
        GL30.glBindVertexArray(vertexArrayObject);
        
        vertexBufferObject = GL15.glGenBuffers();
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vertexBufferObject);
        vertexData = generateVertexData();
        GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertexData, GL15.GL_STATIC_DRAW);
        
        GL20.glEnableVertexAttribArray(0);
        GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 0, (long)0);
        
        GL20.glEnableVertexAttribArray(1);
        GL20.glVertexAttribPointer(1, 4, GL11.GL_FLOAT, false, 0, (long)(8*3*4));
        vertexData.rewind();
        
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
        
        indexBufferObject = GL15.glGenBuffers();
        GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, indexBufferObject);
        indexData = generateIndexData();
        
        //There is no problem when I use this method:
        GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indexData, GL15.GL_STATIC_DRAW);
        
        //There IS a problem when I use this method:
        //GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, GL11.GL_UNSIGNED_SHORT, GL15.GL_STATIC_DRAW);
        
        indexData.rewind();
        GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
        
        GL30.glBindVertexArray(0);

spasi

I think you're misunderstanding how VBOs work. You should probably read the specification again and check out more examples.

GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indexData, GL15.GL_STATIC_DRAW);


This is indeed the correct way to initialize and use a static element BO. VBOs are useless unless you upload data to them, which is done with glBufferData/glBufferSubData.

GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, GL11.GL_UNSIGNED_SHORT, GL15.GL_STATIC_DRAW)


This is initializing an empty element BO with 5123 indices (the decimal value of GL_UNSIGNED_SHORT). It's not what you want.

USNavyFish

I've been away from home for the past few days and haven't had a chance to test the code with that correction, but that's got to be it. Funny how you can stare at the same few lines of code too long, they start looking like what you expect them to look like!

Thank you for the help here, and please accept my apologies for raising a stir in this non-issue.