1 /******************************************************************************* 2 * Copyright 2011 See AUTHORS file. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 ******************************************************************************/ 16 17 package com.badlogic.gdx.tests; 18 19 import com.badlogic.gdx.Gdx; 20 import com.badlogic.gdx.graphics.GL20; 21 import com.badlogic.gdx.graphics.GL30; 22 import com.badlogic.gdx.graphics.Mesh; 23 import com.badlogic.gdx.graphics.Texture; 24 import com.badlogic.gdx.graphics.VertexAttribute; 25 import com.badlogic.gdx.graphics.VertexAttributes; 26 import com.badlogic.gdx.graphics.g2d.BitmapFont; 27 import com.badlogic.gdx.graphics.g2d.SpriteBatch; 28 import com.badlogic.gdx.graphics.glutils.IndexBufferObjectSubData; 29 import com.badlogic.gdx.graphics.glutils.ShaderProgram; 30 import com.badlogic.gdx.graphics.glutils.VertexBufferObjectWithVAO; 31 import com.badlogic.gdx.graphics.glutils.VertexData; 32 import com.badlogic.gdx.math.MathUtils; 33 import com.badlogic.gdx.math.Matrix4; 34 import com.badlogic.gdx.math.WindowedMean; 35 import com.badlogic.gdx.tests.utils.GdxTest; 36 import com.badlogic.gdx.utils.BufferUtils; 37 import com.badlogic.gdx.utils.GdxRuntimeException; 38 import com.badlogic.gdx.utils.StringBuilder; 39 40 import java.nio.ByteBuffer; 41 import java.nio.FloatBuffer; 42 import java.nio.IntBuffer; 43 44 public class VBOWithVAOPerformanceTest extends GdxTest { 45 46 ShaderProgram shader; 47 Texture texture; 48 Matrix4 matrix = new Matrix4(); 49 50 Mesh oldVBOWithVAOMesh; 51 Mesh newVBOWithVAOMesh; 52 53 SpriteBatch batch; 54 BitmapFont bitmapFont; 55 StringBuilder stringBuilder; 56 57 WindowedMean newCounter = new WindowedMean(100); 58 WindowedMean oldCounter = new WindowedMean(100); 59 60 WindowedMean newCounterStress = new WindowedMean(100); 61 WindowedMean oldCounterStress = new WindowedMean(100); 62 63 @Override create()64 public void create () { 65 if (Gdx.gl30 == null) { 66 throw new GdxRuntimeException("GLES 3.0 profile required for this test"); 67 } 68 String vertexShader = "attribute vec4 a_position; \n" + "attribute vec4 a_color;\n" + "attribute vec2 a_texCoord0;\n" 69 + "uniform mat4 u_worldView;\n" + "varying vec4 v_color;" + "varying vec2 v_texCoords;" 70 + "void main() \n" + "{ \n" + " v_color = a_color; \n" 71 + " v_texCoords = a_texCoord0; \n" + " gl_Position = u_worldView * a_position; \n" 72 + "} \n"; 73 String fragmentShader = "#ifdef GL_ES\n" + "precision mediump float;\n" + "#endif\n" + "varying vec4 v_color;\n" 74 + "varying vec2 v_texCoords;\n" + "uniform sampler2D u_texture;\n" + "void main() \n" 75 + "{ \n" + " gl_FragColor = v_color * texture2D(u_texture, v_texCoords);\n" 76 + "}"; 77 78 shader = new ShaderProgram(vertexShader, fragmentShader); 79 if (shader.isCompiled() == false) { 80 Gdx.app.log("ShaderTest", shader.getLog()); 81 Gdx.app.exit(); 82 } 83 84 int numSprites = 1000; 85 int maxIndices = numSprites * 6; 86 int maxVertices = numSprites * 6; 87 88 VertexAttribute[] vertexAttributes = new VertexAttribute[] {VertexAttribute.Position(), VertexAttribute.ColorUnpacked(), VertexAttribute.TexCoords(0)}; 89 90 VertexBufferObjectWithVAO newVBOWithVAO = new VertexBufferObjectWithVAO(false, maxVertices, vertexAttributes); 91 OldVertexBufferObjectWithVAO oldVBOWithVAO = new OldVertexBufferObjectWithVAO(false, maxVertices, vertexAttributes); 92 93 IndexBufferObjectSubData newIndices = new IndexBufferObjectSubData(false, maxIndices); 94 IndexBufferObjectSubData oldIndices = new IndexBufferObjectSubData(false, maxIndices); 95 96 newVBOWithVAOMesh = new Mesh(newVBOWithVAO, newIndices, false) {}; 97 oldVBOWithVAOMesh = new Mesh(oldVBOWithVAO, oldIndices, false) {}; 98 99 float[] vertexArray = new float[maxVertices * 9]; 100 int index = 0; 101 int stride = 9 * 6; 102 for (int i = 0; i < numSprites; i++) { 103 addRandomSprite(vertexArray, index); 104 index += stride; 105 } 106 short[] indexArray = new short[maxIndices]; 107 for (short i = 0; i < maxIndices; i++) { 108 indexArray[i] = i; 109 } 110 111 newVBOWithVAOMesh.setVertices(vertexArray); 112 newVBOWithVAOMesh.setIndices(indexArray); 113 114 oldVBOWithVAOMesh.setVertices(vertexArray); 115 oldVBOWithVAOMesh.setIndices(indexArray); 116 117 texture = new Texture(Gdx.files.internal("data/badlogic.jpg")); 118 119 batch = new SpriteBatch(); 120 bitmapFont = new BitmapFont(); 121 stringBuilder = new StringBuilder(); 122 } 123 addRandomSprite(float[] vertArray, int currentIndex)124 private void addRandomSprite (float[] vertArray, int currentIndex) { 125 float width = MathUtils.random(0.05f, 0.2f); 126 float height = MathUtils.random(0.05f, 0.2f); 127 float x = MathUtils.random(-1f, 1f); 128 float y = MathUtils.random(-1f, 1f); 129 float r = MathUtils.random(); 130 float g = MathUtils.random(); 131 float b = MathUtils.random(); 132 float a = MathUtils.random(); 133 134 vertArray[currentIndex++] = x; 135 vertArray[currentIndex++] = y; 136 vertArray[currentIndex++] = 0; 137 vertArray[currentIndex++] = r; 138 vertArray[currentIndex++] = g; 139 vertArray[currentIndex++] = b; 140 vertArray[currentIndex++] = a; 141 vertArray[currentIndex++] = 0; 142 vertArray[currentIndex++] = 1; 143 144 vertArray[currentIndex++] = x + width; 145 vertArray[currentIndex++] = y; 146 vertArray[currentIndex++] = 0; 147 vertArray[currentIndex++] = r; 148 vertArray[currentIndex++] = g; 149 vertArray[currentIndex++] = b; 150 vertArray[currentIndex++] = a; 151 vertArray[currentIndex++] = 1; 152 vertArray[currentIndex++] = 1; 153 154 vertArray[currentIndex++] = x + width; 155 vertArray[currentIndex++] = y + height; 156 vertArray[currentIndex++] = 0; 157 vertArray[currentIndex++] = r; 158 vertArray[currentIndex++] = g; 159 vertArray[currentIndex++] = b; 160 vertArray[currentIndex++] = a; 161 vertArray[currentIndex++] = 1; 162 vertArray[currentIndex++] = 0; 163 164 vertArray[currentIndex++] = x + width; 165 vertArray[currentIndex++] = y + height; 166 vertArray[currentIndex++] = 0; 167 vertArray[currentIndex++] = r; 168 vertArray[currentIndex++] = g; 169 vertArray[currentIndex++] = b; 170 vertArray[currentIndex++] = a; 171 vertArray[currentIndex++] = 1; 172 vertArray[currentIndex++] = 0; 173 174 vertArray[currentIndex++] = x; 175 vertArray[currentIndex++] = y + height; 176 vertArray[currentIndex++] = 0; 177 vertArray[currentIndex++] = r; 178 vertArray[currentIndex++] = g; 179 vertArray[currentIndex++] = b; 180 vertArray[currentIndex++] = a; 181 vertArray[currentIndex++] = 0; 182 vertArray[currentIndex++] = 0; 183 184 vertArray[currentIndex++] = x; 185 vertArray[currentIndex++] = y; 186 vertArray[currentIndex++] = 0; 187 vertArray[currentIndex++] = r; 188 vertArray[currentIndex++] = g; 189 vertArray[currentIndex++] = b; 190 vertArray[currentIndex++] = a; 191 vertArray[currentIndex++] = 0; 192 vertArray[currentIndex++] = 1; 193 } 194 195 @Override render()196 public void render () { 197 Gdx.gl20.glViewport(0, 0, Gdx.graphics.getBackBufferWidth(), Gdx.graphics.getBackBufferHeight()); 198 Gdx.gl20.glClearColor(0.2f, 0.2f, 0.2f, 1); 199 Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT); 200 Gdx.gl20.glEnable(GL20.GL_TEXTURE_2D); 201 Gdx.gl20.glEnable(GL20.GL_BLEND); 202 Gdx.gl20.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); 203 204 205 texture.bind(); 206 shader.begin(); 207 shader.setUniformMatrix("u_worldView", matrix); 208 shader.setUniformi("u_texture", 0); 209 210 long beforeOld = System.nanoTime(); 211 oldVBOWithVAOMesh.render(shader, GL20.GL_TRIANGLES); 212 Gdx.gl.glFlush(); 213 oldCounter.addValue((System.nanoTime() - beforeOld)); 214 shader.end(); 215 216 Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT); 217 218 texture.bind(); 219 shader.begin(); 220 shader.setUniformMatrix("u_worldView", matrix); 221 shader.setUniformi("u_texture", 0); 222 223 long beforeNew = System.nanoTime(); 224 newVBOWithVAOMesh.render(shader, GL20.GL_TRIANGLES); 225 Gdx.gl.glFlush(); 226 newCounter.addValue((System.nanoTime() - beforeNew)); 227 shader.end(); 228 229 230 Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT); 231 232 texture.bind(); 233 shader.begin(); 234 shader.setUniformMatrix("u_worldView", matrix); 235 shader.setUniformi("u_texture", 0); 236 237 long beforeOldStress = System.nanoTime(); 238 for (int i = 0; i < 100; i++) 239 oldVBOWithVAOMesh.render(shader, GL20.GL_TRIANGLES); 240 Gdx.gl.glFlush(); 241 oldCounterStress.addValue((System.nanoTime() - beforeOldStress)); 242 shader.end(); 243 244 245 Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT); 246 247 texture.bind(); 248 shader.begin(); 249 shader.setUniformMatrix("u_worldView", matrix); 250 shader.setUniformi("u_texture", 0); 251 252 long beforeNewStress = System.nanoTime(); 253 for (int i = 0; i < 100; i++) 254 newVBOWithVAOMesh.render(shader, GL20.GL_TRIANGLES); 255 Gdx.gl.glFlush(); 256 newCounterStress.addValue((System.nanoTime() - beforeNewStress)); 257 shader.end(); 258 259 260 261 batch.begin(); 262 stringBuilder.setLength(0); 263 stringBuilder.append("O Mean Time: "); 264 stringBuilder.append(oldCounter.getMean()); 265 bitmapFont.draw(batch, stringBuilder, 0, 200); 266 stringBuilder.setLength(0); 267 stringBuilder.append("N Mean Time: "); 268 stringBuilder.append(newCounter.getMean()); 269 bitmapFont.draw(batch, stringBuilder, 0, 200 - 20); 270 271 float oldMean = oldCounter.getMean(); 272 float newMean = newCounter.getMean(); 273 274 float meanedAverage = newMean/oldMean; 275 stringBuilder.setLength(0); 276 stringBuilder.append("New VBO time as a percentage of Old Time: "); 277 stringBuilder.append(meanedAverage); 278 bitmapFont.draw(batch, stringBuilder, 0, 200 - 40); 279 280 stringBuilder.setLength(0); 281 stringBuilder.append("Stress: O Mean Time: "); 282 stringBuilder.append(oldCounterStress.getMean()); 283 bitmapFont.draw(batch, stringBuilder, 0, 200 - 80); 284 stringBuilder.setLength(0); 285 stringBuilder.append("Stress: N Mean Time: "); 286 stringBuilder.append(newCounterStress.getMean()); 287 bitmapFont.draw(batch, stringBuilder, 0, 200 - 100); 288 289 float oldMeanStress = oldCounterStress.getMean(); 290 float newMeanStress = newCounterStress.getMean(); 291 292 float meanedStressAverage = newMeanStress/oldMeanStress; 293 stringBuilder.setLength(0); 294 stringBuilder.append("Stress: New VBO time as a percentage of Old Time: "); 295 stringBuilder.append(meanedStressAverage); 296 bitmapFont.draw(batch, stringBuilder, 0, 200 - 120); 297 298 299 batch.end(); 300 } 301 302 @Override dispose()303 public void dispose () { 304 oldVBOWithVAOMesh.dispose(); 305 newVBOWithVAOMesh.dispose(); 306 texture.dispose(); 307 shader.dispose(); 308 } 309 310 private static class OldVertexBufferObjectWithVAO implements VertexData { 311 final static IntBuffer tmpHandle = BufferUtils.newIntBuffer(1); 312 313 final VertexAttributes attributes; 314 final FloatBuffer buffer; 315 final ByteBuffer byteBuffer; 316 int bufferHandle; 317 final boolean isStatic; 318 final int usage; 319 boolean isDirty = false; 320 boolean isBound = false; 321 boolean vaoDirty = true; 322 int vaoHandle = -1; 323 OldVertexBufferObjectWithVAO(boolean isStatic, int numVertices, VertexAttribute... attributes)324 public OldVertexBufferObjectWithVAO (boolean isStatic, int numVertices, VertexAttribute... attributes) { 325 this(isStatic, numVertices, new VertexAttributes(attributes)); 326 } 327 OldVertexBufferObjectWithVAO(boolean isStatic, int numVertices, VertexAttributes attributes)328 public OldVertexBufferObjectWithVAO (boolean isStatic, int numVertices, VertexAttributes attributes) { 329 this.isStatic = isStatic; 330 this.attributes = attributes; 331 332 byteBuffer = BufferUtils.newUnsafeByteBuffer(this.attributes.vertexSize * numVertices); 333 buffer = byteBuffer.asFloatBuffer(); 334 buffer.flip(); 335 byteBuffer.flip(); 336 bufferHandle = Gdx.gl20.glGenBuffer(); 337 usage = isStatic ? GL20.GL_STATIC_DRAW : GL20.GL_DYNAMIC_DRAW; 338 } 339 340 @Override getAttributes()341 public VertexAttributes getAttributes() { 342 return attributes; 343 } 344 345 @Override getNumVertices()346 public int getNumVertices() { 347 return buffer.limit() * 4 / attributes.vertexSize; 348 } 349 350 @Override getNumMaxVertices()351 public int getNumMaxVertices() { 352 return byteBuffer.capacity() / attributes.vertexSize; 353 } 354 355 @Override getBuffer()356 public FloatBuffer getBuffer() { 357 isDirty = true; 358 return buffer; 359 } 360 bufferChanged()361 private void bufferChanged() { 362 if (isBound) { 363 Gdx.gl20.glBufferData(GL20.GL_ARRAY_BUFFER, byteBuffer.limit(), byteBuffer, usage); 364 isDirty = false; 365 } 366 } 367 368 @Override setVertices(float[] vertices, int offset, int count)369 public void setVertices(float[] vertices, int offset, int count) { 370 isDirty = true; 371 BufferUtils.copy(vertices, byteBuffer, count, offset); 372 buffer.position(0); 373 buffer.limit(count); 374 bufferChanged(); 375 } 376 377 @Override updateVertices(int targetOffset, float[] vertices, int sourceOffset, int count)378 public void updateVertices(int targetOffset, float[] vertices, int sourceOffset, int count) { 379 isDirty = true; 380 final int pos = byteBuffer.position(); 381 byteBuffer.position(targetOffset * 4); 382 BufferUtils.copy(vertices, sourceOffset, count, byteBuffer); 383 byteBuffer.position(pos); 384 buffer.position(0); 385 bufferChanged(); 386 } 387 388 @Override bind(ShaderProgram shader)389 public void bind(ShaderProgram shader) { 390 bind(shader, null); 391 } 392 393 @Override bind(ShaderProgram shader, int[] locations)394 public void bind(ShaderProgram shader, int[] locations) { 395 GL30 gl = Gdx.gl30; 396 if (vaoDirty || !gl.glIsVertexArray(vaoHandle)) { 397 //initialize the VAO with our vertex attributes and buffer: 398 tmpHandle.clear(); 399 gl.glGenVertexArrays(1, tmpHandle); 400 vaoHandle = tmpHandle.get(0); 401 gl.glBindVertexArray(vaoHandle); 402 vaoDirty = false; 403 404 } else { 405 //else simply bind the VAO. 406 gl.glBindVertexArray(vaoHandle); 407 } 408 409 bindAttributes(shader, locations); 410 411 //if our data has changed upload it: 412 bindData(gl); 413 414 isBound = true; 415 } 416 bindAttributes(ShaderProgram shader, int[] locations)417 private void bindAttributes(ShaderProgram shader, int[] locations) { 418 final GL20 gl = Gdx.gl20; 419 gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, bufferHandle); 420 final int numAttributes = attributes.size(); 421 if (locations == null) { 422 for (int i = 0; i < numAttributes; i++) { 423 final VertexAttribute attribute = attributes.get(i); 424 final int location = shader.getAttributeLocation(attribute.alias); 425 if (location < 0) continue; 426 shader.enableVertexAttribute(location); 427 428 shader.setVertexAttribute(location, attribute.numComponents, attribute.type, attribute.normalized, attributes.vertexSize, 429 attribute.offset); 430 } 431 432 } else { 433 for (int i = 0; i < numAttributes; i++) { 434 final VertexAttribute attribute = attributes.get(i); 435 final int location = locations[i]; 436 if (location < 0) continue; 437 shader.enableVertexAttribute(location); 438 439 shader.setVertexAttribute(location, attribute.numComponents, attribute.type, attribute.normalized, attributes.vertexSize, 440 attribute.offset); 441 } 442 } 443 } 444 bindData(GL20 gl)445 private void bindData(GL20 gl) { 446 if (isDirty) { 447 gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, bufferHandle); 448 byteBuffer.limit(buffer.limit() * 4); 449 gl.glBufferData(GL20.GL_ARRAY_BUFFER, byteBuffer.limit(), byteBuffer, usage); 450 isDirty = false; 451 } 452 } 453 454 @Override unbind(final ShaderProgram shader)455 public void unbind(final ShaderProgram shader) { 456 unbind(shader, null); 457 } 458 459 @Override unbind(final ShaderProgram shader, final int[] locations)460 public void unbind(final ShaderProgram shader, final int[] locations) { 461 GL30 gl = Gdx.gl30; 462 gl.glBindVertexArray(0); 463 isBound = false; 464 } 465 466 @Override invalidate()467 public void invalidate() { 468 bufferHandle = Gdx.gl20.glGenBuffer(); 469 isDirty = true; 470 vaoDirty = true; 471 } 472 473 @Override dispose()474 public void dispose() { 475 GL30 gl = Gdx.gl30; 476 477 gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, 0); 478 gl.glDeleteBuffer(bufferHandle); 479 bufferHandle = 0; 480 BufferUtils.disposeUnsafeByteBuffer(byteBuffer); 481 482 if (gl.glIsVertexArray(vaoHandle)) { 483 tmpHandle.clear(); 484 tmpHandle.put(vaoHandle); 485 tmpHandle.flip(); 486 gl.glDeleteVertexArrays(1, tmpHandle); 487 } 488 } 489 } 490 } 491