Search code examples
javalwjglvboopengl-es-3.0

OpenGL glBufferSubData - can't get it to work


I need to update my mesh with new vertices. I create the VBO as such (initially it gets created with only one vertex in it):

public Mesh(float[] vertex, int size)
{
    texture = null;
    meshType = 1;           //will draw lines

    FloatBuffer verticesBuffer = null;
    IntBuffer indicesBuffer = null;
    int vboID;

    try
    {
        vertexCount = size;

        vaoID = glGenVertexArrays();
        glBindVertexArray(vaoID);

        vboIDList = new ArrayList<>();

        // Vertices VBO generation
        vboID = glGenBuffers();
        vboIDList.add(vboID);
        verticesBuffer = MemoryUtil.memAllocFloat(size * 3);        // !!! Must Be manually freed!
        verticesBuffer.put(vertex).flip();
        glBindBuffer(GL_ARRAY_BUFFER, vboID);
        glBufferData(GL_ARRAY_BUFFER, verticesBuffer, GL_STATIC_DRAW);
        glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);
        vertexAttrArrCount += 1;

        // Indices VBO generation
        vboID = glGenBuffers();
        vboIDList.add(vboID);
        indicesBuffer = MemoryUtil.memAllocInt(size);             // !!! Must be manually freed!
        indicesBuffer.put(new int[]{0}).flip();
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboID);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL_STATIC_DRAW);

        // unbinding
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glBindVertexArray(0);
    }
    finally
    {
        if (verticesBuffer != null)
        {
            MemoryUtil.memFree(verticesBuffer);                             // Freeing vertex buffer
        }

        if (indicesBuffer != null)
        {
            MemoryUtil.memFree(indicesBuffer);                              // Freeing indices buffer
        }
    }

}

then I want to update the VBO buffer and write new vertices into it. Note that I do create VBO to have enough space for my new vertices, and I do control that it doesn't get overfilled. I also control how many elements I draw with each render call, so I don't draw the empty 0/0/0 vertices.

My problem is, this code WORKS:

public void updateVBO(float[] vertices, int[] indices, int size)
{
    if (meshType == 1)
    {
        lineCount = size;

        FloatBuffer subDataF = null;
        IntBuffer subDataI = null;
        int vboID;

        try
        {
            //System.out.printf("Adding vertex (%f, %f, %f) to position %d\n",vertex.x,vertex.y,vertex.z,position);
            vboID = vboIDList.get(0);
            //float[] vert = new float[]{vertex.x, vertex.y, vertex.z};
            subDataF = MemoryUtil.memAllocFloat(vertices.length);        // !!! Must Be manually freed!
            subDataF.put(vertices).flip();
            glBindBuffer(GL_ARRAY_BUFFER, vboID);
            glBufferData(GL_ARRAY_BUFFER, subDataF, GL_STATIC_DRAW);
            glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);

            vboID = vboIDList.get(1);
            //int[] index = new int[]{ position };
            subDataI = MemoryUtil.memAllocInt(indices.length);        // !!! Must Be manually freed!
            subDataI.put(indices).flip();
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboID);
            glBufferData(GL_ELEMENT_ARRAY_BUFFER, subDataI, GL_STATIC_DRAW);

            //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        }
        finally
        {
            if (subDataF != null)
            {
                MemoryUtil.memFree(subDataF);
            }
            if (subDataI != null)
            {
                MemoryUtil.memFree(subDataI);
            }
        }
    }
}

so when I pass the entire vertices array, and re-allocate VBO memory from scratch - it draws exactly what I need it to. However I would like to use glBufferSubData, so that I don't re-allocate the momory each time I add new vertex. And this code DOESN'T WORK:

public void addVertex(Vector3f vertex, int position)
{
    if (meshType == 1)
    {
        FloatBuffer subDataF = null;
        IntBuffer subDataI = null;
        int vboID;

        lineCount = position+1;

        try
        {
            System.out.printf("Adding vertex (%f, %f, %f) to position %d\n",vertex.x,vertex.y,vertex.z,position);
            vboID = vboIDList.get(0);
            float[] vert = new float[]{vertex.x, vertex.y, vertex.z};
            subDataF = MemoryUtil.memAllocFloat(3);        // !!! Must Be manually freed!
            subDataF.put(vert).flip();
            glBindBuffer(GL_ARRAY_BUFFER, vboID);
            glBufferSubData(GL_ARRAY_BUFFER, position * 3 * 4, subDataF);
            glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);

            vboID = vboIDList.get(1);
            int[] index = new int[]{ position };
            subDataI = MemoryUtil.memAllocInt(1);        // !!! Must Be manually freed!
            subDataI.put(index).flip();
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboID);
            glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, position * 4, subDataI);

            glBindBuffer(GL_ARRAY_BUFFER, 0);
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        }
        finally
        {
            if (subDataF != null)
            {
                MemoryUtil.memFree(subDataF);
            }
            if (subDataI != null)
            {
                MemoryUtil.memFree(subDataI);
            }
        }
    }
}

Also I'm aware it's not optimized the way I create the floatbuffer and intbuffer, I just want to get it to work before I clean this up. I was trying a bunch of things, so the last piece of code is weird because of that.

Still, I don't understand what I'm doing wrong. I did check that I pass all the data correctly, and that the position (and offset) seem to be calculated how they should be. And it just doesn't draw anything, while when I use glBufferData it does.

Could someone explain where I'm making a mistake?

After all suggestions, here's what I end up with, but it still doesn't work at all:

public void addVertex(Vector3f vertex, int position)
{
    if (meshType == 1)
    {
        FloatBuffer subDataF = null;
        IntBuffer subDataI = null;
        int vboID;

        lineCount = position+1;

        try
        {
            System.out.printf("Adding vertex (%f, %f, %f) to position %d\n",vertex.x,vertex.y,vertex.z,position);
            vboID = vboIDList.get(0);
            float[] vert = new float[]{vertex.x, vertex.y, vertex.z};
            subDataF = MemoryUtil.memAllocFloat(3);        // !!! Must Be manually freed!
            subDataF.put(vert).flip();
            glBindBuffer(GL_ARRAY_BUFFER, vboID);
            glBufferSubData(GL_ARRAY_BUFFER, (long)(position * 3 * 4), (FloatBuffer)subDataF);
            glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);

            vboID = vboIDList.get(1);
            int[] index = new int[]{ position };
            subDataI = MemoryUtil.memAllocInt(1);        // !!! Must Be manually freed!
            subDataI.put(index).flip();

            glBindVertexArray(vaoID);
            glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, (long)(position * 4), (IntBuffer)subDataI);
        }
        finally
        {
            if (subDataF != null)
            {
                MemoryUtil.memFree(subDataF);
            }
            if (subDataI != null)
            {
                MemoryUtil.memFree(subDataI);
            }
        }
    }
}

I did check that VAO ID is correct.


Solution

  • Just as I thought, it was something stupid, and not at all connected to VAO binding and such.

    The thing is, when I create VBO initially, I do it like this:

    // Vertices VBO generation
            vboID = glGenBuffers();
            vboIDList.add(vboID);
            verticesBuffer = MemoryUtil.memAllocFloat(size * 3);        // !!! Must Be manually freed!
            verticesBuffer.put(vertex).flip();
            glBindBuffer(GL_ARRAY_BUFFER, vboID);
            glBufferData(GL_ARRAY_BUFFER, verticesBuffer, GL_STATIC_DRAW);
            glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);
            vertexAttrArrCount += 1;
    

    I assumed that because I allocate buffer for size*3 floats, it will be of that size, and when I put it in the VBO - it will allocate size*3*4 bytes, i.e. enough for size*3 floats.

    Turns out Nope! Because I put only one vertex (3 floats) into the buffer - it will allocate only that amount of space. So when I later try to use glBufferSubData - it only has spaces for 3 floats on the GPU, and naturally doesn't put the values where I need them. I'm actually surprised it doesn't flat-out crash on me.

    To fix this, at the moment I did this instead:

    // Vertices VBO generation
            ...
            verticesBuffer.put(vertex).put(new float[size*3 - 3]).flip();
            ...
    

    So basically I'm manually putting an empty array into the FloatBuffer, and that ensures that the buffer is the right size.

    Here's the result: Constructor:

    public Mesh(float[] vertex, int size)
    {
        texture = null;
        meshType = 1;           //will draw lines
    
        FloatBuffer verticesBuffer = null;
        IntBuffer indicesBuffer = null;
        int vboID;
    
        try
        {
            vertexCount = size;
    
            vaoID = glGenVertexArrays();
            glBindVertexArray(vaoID);
    
            vboIDList = new ArrayList<>();
    
            // Vertices VBO generation
            vboID = glGenBuffers();
            vboIDList.add(vboID);
            verticesBuffer = MemoryUtil.memAllocFloat(size * 3);        // !!! Must Be manually freed!
            verticesBuffer.put(vertex).put(new float[size*3 - 3]).flip();
            glBindBuffer(GL_ARRAY_BUFFER, vboID);
            glBufferData(GL_ARRAY_BUFFER, verticesBuffer, GL_STATIC_DRAW);
            glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);
            vertexAttrArrCount += 1;
    
            // Indices VBO generation
            vboID = glGenBuffers();
            vboIDList.add(vboID);
            indicesBuffer = MemoryUtil.memAllocInt(size);             // !!! Must be manually freed!
            indicesBuffer.put(new int[size]).flip();                  // I need the first element 0 anyway
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboID);
            glBufferData(GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL_STATIC_DRAW);
    
            // unbinding
            glBindBuffer(GL_ARRAY_BUFFER, 0);
            glBindVertexArray(0);
        }
        finally
        {
            if (verticesBuffer != null)
            {
                MemoryUtil.memFree(verticesBuffer);                             // Freeing vertex buffer
            }
    
            if (indicesBuffer != null)
            {
                MemoryUtil.memFree(indicesBuffer);                              // Freeing indices buffer
            }
        }
    
    }
    

    And then updating:

    public void addVertex(Vector3f vertex, int position)
    {
        if (meshType == 1)
        {
            FloatBuffer subDataF = null;
            IntBuffer subDataI = null;
            int vboID;
    
            lineCount = position+1;
    
            try
            {
                vboID = vboIDList.get(0);
                float[] vert = new float[]{vertex.x, vertex.y, vertex.z};
                subDataF = MemoryUtil.memAllocFloat(vert.length);        // !!! Must Be manually freed!
                subDataF.put(vert).flip();
                glBindBuffer(GL_ARRAY_BUFFER, vboID);
                glBufferSubData(GL_ARRAY_BUFFER, (long)(position * 3 * 4), (FloatBuffer)subDataF);
                glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);
    
                vboID = vboIDList.get(1);
                int[] index = new int[]{ position };
                subDataI = MemoryUtil.memAllocInt(index.length);        // !!! Must Be manually freed!
                subDataI.put(index).flip();
                glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboID);
                glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, (long)(position * 4), (IntBuffer)subDataI);
    
                glBindBuffer(GL_ARRAY_BUFFER, 0);
                glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
            }
            finally
            {
                if (subDataF != null)
                {
                    MemoryUtil.memFree(subDataF);
                }
                if (subDataI != null)
                {
                    MemoryUtil.memFree(subDataI);
                }
            }
        }
    }
    

    And it works. Do note that the code is a bit dirty, I didn't clean it up before posting an answer.