The Quad updating a VBO with BufferSubData

Introduction
When we’ve got our VBO set up we can draw it as many times as we like without any need to keep track of the actual data. We have uploaded it to GPU memory and OpenGL knows where that is and how to get it when necessary. However we might want to adjust our model’s vertices to do some animation for example.

Creating our VBO
We already know how to setup a VBO, we call the method “glBufferData” to allocate memory on the graphics card and upload bytes to it. We can also tell OpenGL how we are going to use this data by saying “GL_STATIC_DRAW” for instance. When we know that we are going to update the VBO we could use “GL_STREAM_DRAW” instead. Again, we are just letting OpenGL know how the data will be processed later on.

Creating our VBO looks like this, verticesFloatBuffer is filled with the vertices data:

 // Create a new Vertex Buffer Object in memory and select it (bind) vboId = GL15.glGenBuffers; GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboId); GL15.glBufferData(GL15.GL_ARRAY_BUFFER, verticesFloatBuffer, GL15.GL_STREAM_DRAW);

Updating our VBO
When updating a VBO we should make sure that OpenGL is not currently using the VBO for rendering therefore we’ll update our VBO before our rendering code. To update the VBO we simply bind it again, create the new vertex definition in bytes and upload it with “glBufferSubData”. Where “glBufferData” allocates new memory, “glBufferSubData” reuses that same memory and overwrites whatever was there in the first place. We are going to adjust our quad vertices by giving it a random offset in the X and Y direction.

 // Update vertices in the VBO, first bind the VBO GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboId);

// Apply and update vertex data for (int i = 0; i < vertices.length; i++) { TexturedVertex vertex = vertices[i]; // Define offset float offsetX = (float) (Math.cos(Math.PI * Math.random) * 0.1); float offsetY = (float) (Math.sin(Math.PI * Math.random) * 0.1); // Offset the vertex position float[] xyz = vertex.getXYZ; vertex.setXYZ(xyz[0] + offsetX, xyz[1] + offsetY, xyz[2]); // Put the new data in a ByteBuffer (in the view of a FloatBuffer) FloatBuffer vertexFloatBuffer = vertexByteBuffer.asFloatBuffer; vertexFloatBuffer.rewind; vertexFloatBuffer.put(vertex.getElements); vertexFloatBuffer.flip; GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, i * TexturedVertex.stride, 			vertexByteBuffer); // Restore the vertex data vertex.setXYZ(xyz[0], xyz[1], xyz[2]); }

// And of course unbind GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);

Notice that we update each vertex at a time, “glBufferSubData” is not meant to rewrite the entire set of bytes. Instead in the memory block of our quad (which is 4 times a vertex definition) we’ll use our “stride” (the amount of bytes for one vertex definition) to offset which bytes will be overwritten. The amount of bytes that will get overwritten in our quad buffer is the same amount of bytes that are in a single vertex buffer.

The only portion of the code responsible of updating the VBO is this line:

 GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, i * TexturedVertex.stride, 		vertexByteBuffer);

Remember that the TexturedVertex class holds all information about a single vertex including the amount of bytes it is made up of (the stride).

The result
The result is a quad which corners are constantly moving in a random fashion (run the application to see the effect):



Vertex shader

 * 1) version 150 core

in vec4 in_Position; in vec4 in_Color; in vec2 in_TextureCoord;

out vec4 pass_Color; out vec2 pass_TextureCoord;

void main(void) { gl_Position = in_Position; pass_Color = in_Color; pass_TextureCoord = in_TextureCoord; }

Fragment shader

 * 1) version 150 core

uniform sampler2D texture_diffuse;

in vec4 pass_Color; in vec2 pass_TextureCoord;

out vec4 out_Color;

void main(void) { out_Color = pass_Color; // Override out_Color with our texture pixel out_Color = texture2D(texture_diffuse, pass_TextureCoord); }

TexturedVertex class
 public class TexturedVertex { // Vertex data private float[] xyzw = new float[] {0f, 0f, 0f, 1f}; private float[] rgba = new float[] {1f, 1f, 1f, 1f}; private float[] st = new float[] {0f, 0f}; // The amount of bytes an element has public static final int elementBytes = 4; // Elements per parameter public static final int positionElementCount = 4; public static final int colorElementCount = 4; public static final int textureElementCount = 2; // Bytes per parameter public static final int positionBytesCount = positionElementCount * elementBytes; public static final int colorByteCount = colorElementCount * elementBytes; public static final int textureByteCount = textureElementCount * elementBytes; // Byte offsets per parameter public static final int positionByteOffset = 0; public static final int colorByteOffset = positionByteOffset + positionBytesCount; public static final int textureByteOffset = colorByteOffset + colorByteCount; // The amount of elements that a vertex has public static final int elementCount = positionElementCount + colorElementCount + textureElementCount; // The size of a vertex in bytes, like in C/C++: sizeof(Vertex) public static final int stride = positionBytesCount + colorByteCount + textureByteCount; // Setters public void setXYZ(float x, float y, float z) { this.setXYZW(x, y, z, 1f); }	public void setRGB(float r, float g, float b) { this.setRGBA(r, g, b, 1f); }	public void setST(float s, float t) { this.st = new float[] {s, t}; }	public void setXYZW(float x, float y, float z, float w) { this.xyzw = new float[] {x, y, z, w}; }	public void setRGBA(float r, float g, float b, float a) { this.rgba = new float[] {r, g, b, 1f}; }	// Getters public float[] getElements { float[] out = new float[TexturedVertex.elementCount]; int i = 0; // Insert XYZW elements out[i++] = this.xyzw[0]; out[i++] = this.xyzw[1]; out[i++] = this.xyzw[2]; out[i++] = this.xyzw[3]; // Insert RGBA elements out[i++] = this.rgba[0]; out[i++] = this.rgba[1]; out[i++] = this.rgba[2]; out[i++] = this.rgba[3]; // Insert ST elements out[i++] = this.st[0]; out[i++] = this.st[1]; return out; }	public float[] getXYZW { return new float[] {this.xyzw[0], this.xyzw[1], this.xyzw[2], this.xyzw[3]}; }

public float[] getXYZ { return new float[] {this.xyzw[0], this.xyzw[1], this.xyzw[2]}; }	public float[] getRGBA { return new float[] {this.rgba[0], this.rgba[1], this.rgba[2], this.rgba[3]}; }	public float[] getRGB { return new float[] {this.rgba[0], this.rgba[1], this.rgba[2]}; }	public float[] getST { return new float[] {this.st[0], this.st[1]}; } }

Application
 import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.FloatBuffer;

import org.lwjgl.BufferUtils; import org.lwjgl.LWJGLException; import org.lwjgl.input.Keyboard; import org.lwjgl.opengl.ContextAttribs; import org.lwjgl.opengl.Display; import org.lwjgl.opengl.DisplayMode; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL13; import org.lwjgl.opengl.GL15; import org.lwjgl.opengl.GL20; import org.lwjgl.opengl.GL30; import org.lwjgl.opengl.PixelFormat; import org.lwjgl.util.glu.GLU;

import de.matthiasmann.twl.utils.PNGDecoder; import de.matthiasmann.twl.utils.PNGDecoder.Format;

public class TheQuadExampleUpdateVBO { // Entry point for the application public static void main(String[] args) { new TheQuadExampleUpdateVBO; }	// Setup variables private final String WINDOW_TITLE = "The Quad: Update VBO"; private final int WIDTH = 320; private final int HEIGHT = 320; // Quad variables private int vaoId = 0; private int vboId = 0; private int vboiId = 0; private int indicesCount = 0; // Shader variables private int vsId = 0; private int fsId = 0; private int pId = 0; // Texture variables private int[] texIds = new int[] {0, 0}; private int textureSelector = 0; // Update VBO variables private TexturedVertex[] vertices = null; private ByteBuffer vertexByteBuffer = null; private ByteBuffer verticesByteBuffer = null; public TheQuadExampleUpdateVBO { // Initialize OpenGL (Display) this.setupOpenGL; this.setupQuad; this.setupShaders; this.setupTextures; while (!Display.isCloseRequested) { // Do a single loop (logic/render) this.loopCycle; // Force a maximum FPS of about 60 Display.sync(60); // Let the CPU synchronize with the GPU if GPU is tagging behind Display.update; }		// Destroy OpenGL (Display) this.destroyOpenGL; }

private void setupTextures { texIds[0] = this.loadPNGTexture("assets/stGrid1.png", GL13.GL_TEXTURE0); texIds[1] = this.loadPNGTexture("assets/stGrid2.png", GL13.GL_TEXTURE0); this.exitOnGLError("setupTexture"); }

private void setupOpenGL { // Setup an OpenGL context with API version 3.2 try { PixelFormat pixelFormat = new PixelFormat; ContextAttribs contextAtrributes = new ContextAttribs(3, 2) .withForwardCompatible(true) .withProfileCore(true); Display.setDisplayMode(new DisplayMode(WIDTH, HEIGHT)); Display.setTitle(WINDOW_TITLE); Display.create(pixelFormat, contextAtrributes); GL11.glViewport(0, 0, WIDTH, HEIGHT); } catch (LWJGLException e) { e.printStackTrace; System.exit(-1); }		// Setup an XNA like background color GL11.glClearColor(0.4f, 0.6f, 0.9f, 0f); // Map the internal OpenGL coordinate system to the entire screen GL11.glViewport(0, 0, WIDTH, HEIGHT); this.exitOnGLError("setupOpenGL"); }	private void setupQuad { // We'll define our quad using 4 vertices of the custom 'TexturedVertex' class TexturedVertex v0 = new TexturedVertex; v0.setXYZ(-0.5f, 0.5f, 0); v0.setRGB(1, 0, 0); v0.setST(0, 0); TexturedVertex v1 = new TexturedVertex; v1.setXYZ(-0.5f, -0.5f, 0); v1.setRGB(0, 1, 0); v1.setST(0, 1); TexturedVertex v2 = new TexturedVertex; v2.setXYZ(0.5f, -0.5f, 0); v2.setRGB(0, 0, 1); v2.setST(1, 1); TexturedVertex v3 = new TexturedVertex; v3.setXYZ(0.5f, 0.5f, 0); v3.setRGB(1, 1, 1); v3.setST(1, 0); vertices = new TexturedVertex[] {v0, v1, v2, v3}; // Create a FloatBufer of the appropriate size for one vertex vertexByteBuffer = BufferUtils.createByteBuffer(TexturedVertex.stride); // Put each 'Vertex' in one FloatBuffer verticesByteBuffer = BufferUtils.createByteBuffer(vertices.length * 				TexturedVertex.stride); FloatBuffer verticesFloatBuffer = verticesByteBuffer.asFloatBuffer; for (int i = 0; i < vertices.length; i++) { // Add position, color and texture floats to the buffer verticesFloatBuffer.put(vertices[i].getElements); }		verticesFloatBuffer.flip; // OpenGL expects to draw vertices in counter clockwise order by default byte[] indices = { 0, 1, 2,				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) vaoId = GL30.glGenVertexArrays; GL30.glBindVertexArray(vaoId); // Create a new Vertex Buffer Object in memory and select it (bind) vboId = GL15.glGenBuffers; GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboId); GL15.glBufferData(GL15.GL_ARRAY_BUFFER, verticesFloatBuffer, GL15.GL_STREAM_DRAW); // Put the position coordinates in attribute list 0 GL20.glVertexAttribPointer(0, TexturedVertex.positionElementCount, GL11.GL_FLOAT, 				false, TexturedVertex.stride, TexturedVertex.positionByteOffset); // Put the color components in attribute list 1 GL20.glVertexAttribPointer(1, TexturedVertex.colorElementCount, GL11.GL_FLOAT, 				false, TexturedVertex.stride, TexturedVertex.colorByteOffset); // Put the texture coordinates in attribute list 2 GL20.glVertexAttribPointer(2, TexturedVertex.textureElementCount, GL11.GL_FLOAT, 				false, TexturedVertex.stride, TexturedVertex.textureByteOffset); 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) - INDICES vboiId = GL15.glGenBuffers; GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, vboiId); GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL15.GL_STATIC_DRAW); GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0); this.exitOnGLError("setupQuad"); }	private void setupShaders { // Load the vertex shader vsId = this.loadShader("src/thequad/vertex_textured.glsl", GL20.GL_VERTEX_SHADER); // Load the fragment shader fsId = this.loadShader("src/thequad/fragment_textured.glsl", GL20.GL_FRAGMENT_SHADER); // Create a new shader program that links both shaders pId = GL20.glCreateProgram; GL20.glAttachShader(pId, vsId); GL20.glAttachShader(pId, fsId);

// Position information will be attribute 0 GL20.glBindAttribLocation(pId, 0, "in_Position"); // Color information will be attribute 1 GL20.glBindAttribLocation(pId, 1, "in_Color"); // Textute information will be attribute 2 GL20.glBindAttribLocation(pId, 2, "in_TextureCoord"); GL20.glLinkProgram(pId); GL20.glValidateProgram(pId); this.exitOnGLError("setupShaders"); }	private void logicCycle { // Texture selection while(Keyboard.next) { // Only listen to events where the key was pressed (down event) if (!Keyboard.getEventKeyState) continue; // Switch textures depending on the key released switch (Keyboard.getEventKey) { case Keyboard.KEY_1: textureSelector = 0; break; case Keyboard.KEY_2: textureSelector = 1; break; }		}		// Update vertices in the VBO, first bind the VBO GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboId); // Apply and update vertex data for (int i = 0; i < vertices.length; i++) { TexturedVertex vertex = vertices[i]; // Define offset float offsetX = (float) (Math.cos(Math.PI * Math.random) * 0.1); float offsetY = (float) (Math.sin(Math.PI * Math.random) * 0.1); // Offset the vertex position float[] xyz = vertex.getXYZ; vertex.setXYZ(xyz[0] + offsetX, xyz[1] + offsetY, xyz[2]); // Put the new data in a ByteBuffer (in the view of a FloatBuffer) FloatBuffer vertexFloatBuffer = vertexByteBuffer.asFloatBuffer; vertexFloatBuffer.rewind; vertexFloatBuffer.put(vertex.getElements); vertexFloatBuffer.flip; GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, i * TexturedVertex.stride, 					vertexByteBuffer); // Restore the vertex data vertex.setXYZ(xyz[0], xyz[1], xyz[2]); }		// And of course unbind GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); this.exitOnGLError("logicCycle"); }	private void renderCycle { GL11.glClear(GL11.GL_COLOR_BUFFER_BIT); GL20.glUseProgram(pId); // Bind the texture GL13.glActiveTexture(GL13.GL_TEXTURE0); GL11.glBindTexture(GL11.GL_TEXTURE_2D, texIds[textureSelector]); // Bind to the VAO that has all the information about the vertices GL30.glBindVertexArray(vaoId); GL20.glEnableVertexAttribArray(0); GL20.glEnableVertexAttribArray(1); GL20.glEnableVertexAttribArray(2); // 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); GL20.glDisableVertexAttribArray(1); GL20.glDisableVertexAttribArray(2); GL30.glBindVertexArray(0); GL20.glUseProgram(0); this.exitOnGLError("renderCycle"); }	private void loopCycle { // Update logic this.logicCycle; // Update rendered frame this.renderCycle; this.exitOnGLError("loopCycle"); }	private void destroyOpenGL { // Delete the texture GL11.glDeleteTextures(texIds[0]); GL11.glDeleteTextures(texIds[1]); // Delete the shaders GL20.glUseProgram(0); GL20.glDetachShader(pId, vsId); GL20.glDetachShader(pId, fsId); GL20.glDeleteShader(vsId); GL20.glDeleteShader(fsId); GL20.glDeleteProgram(pId); // Select the VAO GL30.glBindVertexArray(vaoId); // Disable the VBO index from the VAO attributes list GL20.glDisableVertexAttribArray(0); GL20.glDisableVertexAttribArray(1); // 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); this.exitOnGLError("destroyOpenGL"); Display.destroy; }	private int loadShader(String filename, int type) { StringBuilder shaderSource = new StringBuilder; int shaderID = 0; try { BufferedReader reader = new BufferedReader(new FileReader(filename)); String line; while ((line = reader.readLine) != null) { shaderSource.append(line).append("\n"); }			reader.close; } catch (IOException e) { System.err.println("Could not read file."); e.printStackTrace; System.exit(-1); }		shaderID = GL20.glCreateShader(type); GL20.glShaderSource(shaderID, shaderSource); GL20.glCompileShader(shaderID); if (GL20.glGetShader(shaderID, GL20.GL_COMPILE_STATUS) == GL11.GL_FALSE) { System.err.println("Could not compile shader."); System.exit(-1); }		this.exitOnGLError("loadShader"); return shaderID; }	private int loadPNGTexture(String filename, int textureUnit) { ByteBuffer buf = null; int tWidth = 0; int tHeight = 0; try { // Open the PNG file as an InputStream InputStream in = new FileInputStream(filename); // Link the PNG decoder to this stream PNGDecoder decoder = new PNGDecoder(in); // Get the width and height of the texture tWidth = decoder.getWidth; tHeight = decoder.getHeight; // Decode the PNG file in a ByteBuffer buf = ByteBuffer.allocateDirect(					4 * decoder.getWidth * decoder.getHeight); decoder.decode(buf, decoder.getWidth * 4, Format.RGBA); buf.flip; in.close; } catch (IOException e) { e.printStackTrace; System.exit(-1); }		// Create a new texture object in memory and bind it		int texId = GL11.glGenTextures; GL13.glActiveTexture(textureUnit); GL11.glBindTexture(GL11.GL_TEXTURE_2D, texId); // All RGB bytes are aligned to each other and each component is 1 byte GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1); // Upload the texture data and generate mip maps (for scaling) GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGB, tWidth, tHeight, 0, 				GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buf); GL30.glGenerateMipmap(GL11.GL_TEXTURE_2D); // Setup the ST coordinate system GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT); GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT); // Setup what to do when the texture has to be scaled GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, 				GL11.GL_LINEAR); GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, 				GL11.GL_LINEAR_MIPMAP_LINEAR); this.exitOnGLError("loadPNGTexture"); return texId; }	private void exitOnGLError(String errorMessage) { int errorValue = GL11.glGetError; if (errorValue != GL11.GL_NO_ERROR) { String errorString = GLU.gluErrorString(errorValue); System.err.println("ERROR - " + errorMessage + ": " + errorString); if (Display.isCreated) Display.destroy; System.exit(-1); }	} }

Credit
Mathias Verboven (moci)