/*******************************************************************************
* Copyright 2011 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.badlogic.gdx.tests;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.GL30;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.graphics.VertexAttributes;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.IndexBufferObjectSubData;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.graphics.glutils.VertexBufferObjectWithVAO;
import com.badlogic.gdx.graphics.glutils.VertexData;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.WindowedMean;
import com.badlogic.gdx.tests.utils.GdxTest;
import com.badlogic.gdx.utils.BufferUtils;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.gdx.utils.StringBuilder;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
public class VBOWithVAOPerformanceTest extends GdxTest {
ShaderProgram shader;
Texture texture;
Matrix4 matrix = new Matrix4();
Mesh oldVBOWithVAOMesh;
Mesh newVBOWithVAOMesh;
SpriteBatch batch;
BitmapFont bitmapFont;
StringBuilder stringBuilder;
WindowedMean newCounter = new WindowedMean(100);
WindowedMean oldCounter = new WindowedMean(100);
WindowedMean newCounterStress = new WindowedMean(100);
WindowedMean oldCounterStress = new WindowedMean(100);
@Override
public void create () {
if (Gdx.gl30 == null) {
throw new GdxRuntimeException("GLES 3.0 profile required for this test");
}
String vertexShader = "attribute vec4 a_position; \n" + "attribute vec4 a_color;\n" + "attribute vec2 a_texCoord0;\n"
+ "uniform mat4 u_worldView;\n" + "varying vec4 v_color;" + "varying vec2 v_texCoords;"
+ "void main() \n" + "{ \n" + " v_color = a_color; \n"
+ " v_texCoords = a_texCoord0; \n" + " gl_Position = u_worldView * a_position; \n"
+ "} \n";
String fragmentShader = "#ifdef GL_ES\n" + "precision mediump float;\n" + "#endif\n" + "varying vec4 v_color;\n"
+ "varying vec2 v_texCoords;\n" + "uniform sampler2D u_texture;\n" + "void main() \n"
+ "{ \n" + " gl_FragColor = v_color * texture2D(u_texture, v_texCoords);\n"
+ "}";
shader = new ShaderProgram(vertexShader, fragmentShader);
if (shader.isCompiled() == false) {
Gdx.app.log("ShaderTest", shader.getLog());
Gdx.app.exit();
}
int numSprites = 1000;
int maxIndices = numSprites * 6;
int maxVertices = numSprites * 6;
VertexAttribute[] vertexAttributes = new VertexAttribute[] {VertexAttribute.Position(), VertexAttribute.ColorUnpacked(), VertexAttribute.TexCoords(0)};
VertexBufferObjectWithVAO newVBOWithVAO = new VertexBufferObjectWithVAO(false, maxVertices, vertexAttributes);
OldVertexBufferObjectWithVAO oldVBOWithVAO = new OldVertexBufferObjectWithVAO(false, maxVertices, vertexAttributes);
IndexBufferObjectSubData newIndices = new IndexBufferObjectSubData(false, maxIndices);
IndexBufferObjectSubData oldIndices = new IndexBufferObjectSubData(false, maxIndices);
newVBOWithVAOMesh = new Mesh(newVBOWithVAO, newIndices, false) {};
oldVBOWithVAOMesh = new Mesh(oldVBOWithVAO, oldIndices, false) {};
float[] vertexArray = new float[maxVertices * 9];
int index = 0;
int stride = 9 * 6;
for (int i = 0; i < numSprites; i++) {
addRandomSprite(vertexArray, index);
index += stride;
}
short[] indexArray = new short[maxIndices];
for (short i = 0; i < maxIndices; i++) {
indexArray[i] = i;
}
newVBOWithVAOMesh.setVertices(vertexArray);
newVBOWithVAOMesh.setIndices(indexArray);
oldVBOWithVAOMesh.setVertices(vertexArray);
oldVBOWithVAOMesh.setIndices(indexArray);
texture = new Texture(Gdx.files.internal("data/badlogic.jpg"));
batch = new SpriteBatch();
bitmapFont = new BitmapFont();
stringBuilder = new StringBuilder();
}
private void addRandomSprite (float[] vertArray, int currentIndex) {
float width = MathUtils.random(0.05f, 0.2f);
float height = MathUtils.random(0.05f, 0.2f);
float x = MathUtils.random(-1f, 1f);
float y = MathUtils.random(-1f, 1f);
float r = MathUtils.random();
float g = MathUtils.random();
float b = MathUtils.random();
float a = MathUtils.random();
vertArray[currentIndex++] = x;
vertArray[currentIndex++] = y;
vertArray[currentIndex++] = 0;
vertArray[currentIndex++] = r;
vertArray[currentIndex++] = g;
vertArray[currentIndex++] = b;
vertArray[currentIndex++] = a;
vertArray[currentIndex++] = 0;
vertArray[currentIndex++] = 1;
vertArray[currentIndex++] = x + width;
vertArray[currentIndex++] = y;
vertArray[currentIndex++] = 0;
vertArray[currentIndex++] = r;
vertArray[currentIndex++] = g;
vertArray[currentIndex++] = b;
vertArray[currentIndex++] = a;
vertArray[currentIndex++] = 1;
vertArray[currentIndex++] = 1;
vertArray[currentIndex++] = x + width;
vertArray[currentIndex++] = y + height;
vertArray[currentIndex++] = 0;
vertArray[currentIndex++] = r;
vertArray[currentIndex++] = g;
vertArray[currentIndex++] = b;
vertArray[currentIndex++] = a;
vertArray[currentIndex++] = 1;
vertArray[currentIndex++] = 0;
vertArray[currentIndex++] = x + width;
vertArray[currentIndex++] = y + height;
vertArray[currentIndex++] = 0;
vertArray[currentIndex++] = r;
vertArray[currentIndex++] = g;
vertArray[currentIndex++] = b;
vertArray[currentIndex++] = a;
vertArray[currentIndex++] = 1;
vertArray[currentIndex++] = 0;
vertArray[currentIndex++] = x;
vertArray[currentIndex++] = y + height;
vertArray[currentIndex++] = 0;
vertArray[currentIndex++] = r;
vertArray[currentIndex++] = g;
vertArray[currentIndex++] = b;
vertArray[currentIndex++] = a;
vertArray[currentIndex++] = 0;
vertArray[currentIndex++] = 0;
vertArray[currentIndex++] = x;
vertArray[currentIndex++] = y;
vertArray[currentIndex++] = 0;
vertArray[currentIndex++] = r;
vertArray[currentIndex++] = g;
vertArray[currentIndex++] = b;
vertArray[currentIndex++] = a;
vertArray[currentIndex++] = 0;
vertArray[currentIndex++] = 1;
}
@Override
public void render () {
Gdx.gl20.glViewport(0, 0, Gdx.graphics.getBackBufferWidth(), Gdx.graphics.getBackBufferHeight());
Gdx.gl20.glClearColor(0.2f, 0.2f, 0.2f, 1);
Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT);
Gdx.gl20.glEnable(GL20.GL_TEXTURE_2D);
Gdx.gl20.glEnable(GL20.GL_BLEND);
Gdx.gl20.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
texture.bind();
shader.begin();
shader.setUniformMatrix("u_worldView", matrix);
shader.setUniformi("u_texture", 0);
long beforeOld = System.nanoTime();
oldVBOWithVAOMesh.render(shader, GL20.GL_TRIANGLES);
Gdx.gl.glFlush();
oldCounter.addValue((System.nanoTime() - beforeOld));
shader.end();
Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT);
texture.bind();
shader.begin();
shader.setUniformMatrix("u_worldView", matrix);
shader.setUniformi("u_texture", 0);
long beforeNew = System.nanoTime();
newVBOWithVAOMesh.render(shader, GL20.GL_TRIANGLES);
Gdx.gl.glFlush();
newCounter.addValue((System.nanoTime() - beforeNew));
shader.end();
Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT);
texture.bind();
shader.begin();
shader.setUniformMatrix("u_worldView", matrix);
shader.setUniformi("u_texture", 0);
long beforeOldStress = System.nanoTime();
for (int i = 0; i < 100; i++)
oldVBOWithVAOMesh.render(shader, GL20.GL_TRIANGLES);
Gdx.gl.glFlush();
oldCounterStress.addValue((System.nanoTime() - beforeOldStress));
shader.end();
Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT);
texture.bind();
shader.begin();
shader.setUniformMatrix("u_worldView", matrix);
shader.setUniformi("u_texture", 0);
long beforeNewStress = System.nanoTime();
for (int i = 0; i < 100; i++)
newVBOWithVAOMesh.render(shader, GL20.GL_TRIANGLES);
Gdx.gl.glFlush();
newCounterStress.addValue((System.nanoTime() - beforeNewStress));
shader.end();
batch.begin();
stringBuilder.setLength(0);
stringBuilder.append("O Mean Time: ");
stringBuilder.append(oldCounter.getMean());
bitmapFont.draw(batch, stringBuilder, 0, 200);
stringBuilder.setLength(0);
stringBuilder.append("N Mean Time: ");
stringBuilder.append(newCounter.getMean());
bitmapFont.draw(batch, stringBuilder, 0, 200 - 20);
float oldMean = oldCounter.getMean();
float newMean = newCounter.getMean();
float meanedAverage = newMean/oldMean;
stringBuilder.setLength(0);
stringBuilder.append("New VBO time as a percentage of Old Time: ");
stringBuilder.append(meanedAverage);
bitmapFont.draw(batch, stringBuilder, 0, 200 - 40);
stringBuilder.setLength(0);
stringBuilder.append("Stress: O Mean Time: ");
stringBuilder.append(oldCounterStress.getMean());
bitmapFont.draw(batch, stringBuilder, 0, 200 - 80);
stringBuilder.setLength(0);
stringBuilder.append("Stress: N Mean Time: ");
stringBuilder.append(newCounterStress.getMean());
bitmapFont.draw(batch, stringBuilder, 0, 200 - 100);
float oldMeanStress = oldCounterStress.getMean();
float newMeanStress = newCounterStress.getMean();
float meanedStressAverage = newMeanStress/oldMeanStress;
stringBuilder.setLength(0);
stringBuilder.append("Stress: New VBO time as a percentage of Old Time: ");
stringBuilder.append(meanedStressAverage);
bitmapFont.draw(batch, stringBuilder, 0, 200 - 120);
batch.end();
}
@Override
public void dispose () {
oldVBOWithVAOMesh.dispose();
newVBOWithVAOMesh.dispose();
texture.dispose();
shader.dispose();
}
private static class OldVertexBufferObjectWithVAO implements VertexData {
final static IntBuffer tmpHandle = BufferUtils.newIntBuffer(1);
final VertexAttributes attributes;
final FloatBuffer buffer;
final ByteBuffer byteBuffer;
int bufferHandle;
final boolean isStatic;
final int usage;
boolean isDirty = false;
boolean isBound = false;
boolean vaoDirty = true;
int vaoHandle = -1;
public OldVertexBufferObjectWithVAO (boolean isStatic, int numVertices, VertexAttribute... attributes) {
this(isStatic, numVertices, new VertexAttributes(attributes));
}
public OldVertexBufferObjectWithVAO (boolean isStatic, int numVertices, VertexAttributes attributes) {
this.isStatic = isStatic;
this.attributes = attributes;
byteBuffer = BufferUtils.newUnsafeByteBuffer(this.attributes.vertexSize * numVertices);
buffer = byteBuffer.asFloatBuffer();
buffer.flip();
byteBuffer.flip();
bufferHandle = Gdx.gl20.glGenBuffer();
usage = isStatic ? GL20.GL_STATIC_DRAW : GL20.GL_DYNAMIC_DRAW;
}
@Override
public VertexAttributes getAttributes() {
return attributes;
}
@Override
public int getNumVertices() {
return buffer.limit() * 4 / attributes.vertexSize;
}
@Override
public int getNumMaxVertices() {
return byteBuffer.capacity() / attributes.vertexSize;
}
@Override
public FloatBuffer getBuffer() {
isDirty = true;
return buffer;
}
private void bufferChanged() {
if (isBound) {
Gdx.gl20.glBufferData(GL20.GL_ARRAY_BUFFER, byteBuffer.limit(), byteBuffer, usage);
isDirty = false;
}
}
@Override
public void setVertices(float[] vertices, int offset, int count) {
isDirty = true;
BufferUtils.copy(vertices, byteBuffer, count, offset);
buffer.position(0);
buffer.limit(count);
bufferChanged();
}
@Override
public void updateVertices(int targetOffset, float[] vertices, int sourceOffset, int count) {
isDirty = true;
final int pos = byteBuffer.position();
byteBuffer.position(targetOffset * 4);
BufferUtils.copy(vertices, sourceOffset, count, byteBuffer);
byteBuffer.position(pos);
buffer.position(0);
bufferChanged();
}
@Override
public void bind(ShaderProgram shader) {
bind(shader, null);
}
@Override
public void bind(ShaderProgram shader, int[] locations) {
GL30 gl = Gdx.gl30;
if (vaoDirty || !gl.glIsVertexArray(vaoHandle)) {
//initialize the VAO with our vertex attributes and buffer:
tmpHandle.clear();
gl.glGenVertexArrays(1, tmpHandle);
vaoHandle = tmpHandle.get(0);
gl.glBindVertexArray(vaoHandle);
vaoDirty = false;
} else {
//else simply bind the VAO.
gl.glBindVertexArray(vaoHandle);
}
bindAttributes(shader, locations);
//if our data has changed upload it:
bindData(gl);
isBound = true;
}
private void bindAttributes(ShaderProgram shader, int[] locations) {
final GL20 gl = Gdx.gl20;
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, bufferHandle);
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);
}
} else {
for (int i = 0; i < numAttributes; i++) {
final VertexAttribute attribute = attributes.get(i);
final int location = locations[i];
if (location < 0) continue;
shader.enableVertexAttribute(location);
shader.setVertexAttribute(location, attribute.numComponents, attribute.type, attribute.normalized, attributes.vertexSize,
attribute.offset);
}
}
}
private void bindData(GL20 gl) {
if (isDirty) {
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, bufferHandle);
byteBuffer.limit(buffer.limit() * 4);
gl.glBufferData(GL20.GL_ARRAY_BUFFER, byteBuffer.limit(), byteBuffer, usage);
isDirty = false;
}
}
@Override
public void unbind(final ShaderProgram shader) {
unbind(shader, null);
}
@Override
public void unbind(final ShaderProgram shader, final int[] locations) {
GL30 gl = Gdx.gl30;
gl.glBindVertexArray(0);
isBound = false;
}
@Override
public void invalidate() {
bufferHandle = Gdx.gl20.glGenBuffer();
isDirty = true;
vaoDirty = true;
}
@Override
public void dispose() {
GL30 gl = Gdx.gl30;
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, 0);
gl.glDeleteBuffer(bufferHandle);
bufferHandle = 0;
BufferUtils.disposeUnsafeByteBuffer(byteBuffer);
if (gl.glIsVertexArray(vaoHandle)) {
tmpHandle.clear();
tmpHandle.put(vaoHandle);
tmpHandle.flip();
gl.glDeleteVertexArrays(1, tmpHandle);
}
}
}
}