| 2. Enter libGDX - Vertex Buffer Object

In the last section we specified our points and described their data structure with Vertex Attributes.

Step 2 - Create buffer to upload vector list to and tell OGL to use it

Now we need to put them in a location that the graphics card (GPU) has access to and then let the GPU know that they are there. This is where the 'Vertex Buffer Object (VBO)' makes an appearance. As shown before, with plain Java and OGL we specify a VBO like this:

ByteBuffer verticesBB = ByteBuffer.allocateDirect( vertices.length * 4);
verticesBB.order( ByteOrder.nativeOrder() );
fBuffer = verticesBB.asFloatBuffer();
fBuffer.put( vertices );
fBuffer.position( 0 );

vbo = Gdx.gl.glGenBuffer();
Gdx.gl.glBindBuffer( Gdx.gl.GL_ARRAY_BUFFER, vbo );
Gdx.gl.glBufferData( Gdx.gl.GL_ARRAY_BUFFER, vertices.length * 4, vertexBuffer, Gdx.gl.GL_DYNAMIC_DRAW );

This uses Java's ByteBuffer class (excellently explained at Java Code Geeks) to approximate C++'s ability to create pointers to data. This ability to work with chunks of bytes is necessary to implement the stride and offset variables OGL uses to interact with the VBO according to our Vertex Attribute settings we specified in the last section. LibGDX uses its interface class VertexData.java and three classes that implement it, VertexBufferObject.java, VertexBufferObjectSubData.java, VertexBufferObjectWithVAO.java, to abstract this away. All of those classes can be found here, in the /badlogic/gdx/graphics/glutils folder. We'll focus on the VertexBufferObject.java class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class VertexBufferObject implements VertexData {
 private VertexAttributes attributes;
 private FloatBuffer buffer;
 private ByteBuffer byteBuffer;
 private boolean ownsBuffer;
 private int bufferHandle;
 private int usage;
 boolean isDirty = false;
 boolean isBound = false;
}

The two class properties we want to notice are 'attributes' and 'buffer'. 'attributes' holds our vertex attributes we created in the last section. The 'buffer' variable holds the VBO we'll create and load our vertices into. This class also provides a set of functions to let us interact with our buffer object. The three functions we want to focus on are the class constructor, setVertices() and bind(). Let's start with the class constructor:

public VertexBufferObject (boolean isStatic, int numVertices, VertexAttributes attributes)

The 'isStatic' parameter lets us tell libGDX how to set the OGL memory usage parameter (GL_STATIC_DRAW or GL_DYNAMIC_DRAW). These values tell OGL to utilize different memory types on the GPU depending on how often the vertex data is updated. This parameter is needed for the '.glBufferData' function call in the initial code post. We'll see this function again in a second.  'numVertices' wants to know how many 'points in space' we specified in our original vertices array. If you remember, each vertex has 3 values for space and 4 for color and we described 3 vertices. That's a total of (3 + 4) * 3 or 21 float values. To get the number of vertices in an array, we divide the array length (21 in our case) by the number of floats-per-vertex (7 in our case), to get the correct number of vertices (3) for the constructor. And lastly, 'attributes' just wants our VertexAttributes object we created previously. The constructor then internally creates a ByteBuffer for us:


1
2
3
4
5
6
7
8
public VertexBufferObject (boolean isStatic, int numVertices, VertexAttributes attributes) {
    bufferHandle = Gdx.gl20.glGenBuffer();

    ByteBuffer data = BufferUtils.newUnsafeByteBuffer(attributes.vertexSize * numVertices);
    data.limit(0);
    setBuffer(data, true, attributes);
    setUsage(isStatic ? GL20.GL_STATIC_DRAW : GL20.GL_DYNAMIC_DRAW);
}

This is where libGDX maps to the top 6 lines in the initial code paste. Here's the libGDX functions in use:

1
2
3
vertisees = new VertexBufferObject( false, (vertices.length/7), vAs );
vertisees.bind( shaderProgram ); //internally calls gdx.gl.bindbuffer && gdx.gl.glBufferData
vertisees.setVertices( vertices, 0, vertices.length );

As you can see, the constructor call is followed up by the two other functions we want to look at, bind() and setVertices(). Here's the relevant snippet of the bind() function:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public void bind (ShaderProgram shader, int[] locations) {
    final GL20 gl = Gdx.gl20;

    gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, bufferHandle);
    if (isDirty) {
        byteBuffer.limit(buffer.limit() * 4);
        gl.glBufferData(GL20.GL_ARRAY_BUFFER, byteBuffer.limit(), byteBuffer, usage);
        isDirty = false;
    }

    final int numAttributes = attributes.size();
    if (locations == null) {
        for (int i = 0; i < numAttributes; i++) {
            final VertexAttribute attribute = attributes.get(i);
            final int location = shader.getAttributeLocation(attribute.alias);
            if (location < 0) continue;
            shader.enableVertexAttribute(location);

            shader.setVertexAttribute(location, attribute.numComponents, attribute.type, attribute.normalized,
                    attributes.vertexSize, attribute.offset);
        }

    }
    isBound = true;
}

Bind() takes a 'ShaderProgram' as a variable. When we cover shaders, we'll see what this is. Also, take note of .glBindBuffer(), .glBufferData(), .enableVertexAttribute(), and .setVertexAttribute(). These are OGL commands that tell OGL where the data is and how to access it and this function is where and how libGDX maps to them. It might help to return here to the initial post and see where/how these functions are called with just Java and OGL and then come back and see how libGDX internally calls them.

Last up is the setVertices() function. We call this to copy our vertex data into the VBO we just created. It takes our array of values 'float[] vertices', offset parameter into that array 'int offset', and the number of values we are copying 'int count'. We are copying our entire array, so our 'offset' is zero and the 'count' is the length of our array.

And with this, we've now seen where and how the Java and OGL commands of the initial code paste for this section are internally utilized by libGDX.

Next.