The Concept of Binding Vertices

So is there anything else we can improve? Let's look at our current present() method one more time (with removed glRotatef() and glScalef()):

public void present(float deltaTime) { GL10 gl = glGraphics.getGL(); gl.glClear(GL10. GL_COLOR_BUFFER_BIT);

gl.glMatrixMode(GL10.GL_MODELVIEW); for(int i = 0; i < NUM_BOBS; i++) { gl.glLoadIdentity();

gl.glTranslatef(bobs[i].x, bobs[i].y, 0); bobModel.draw(GL10.GL_TRIANGLES, 0, 6);

fpsCounter.logFrame();

That looks pretty much optimal, doesn't it? Well, in fact it is not optimal. First, we can also move the gl.glMatrixMode() call to the resume() method. But that won't have a huge impact on performance, as we already saw. The second thing that can be optimized is a little subtler.

We use the Vertices class to store and render the model of our Bobs. Remember the Vertices.draw() method? Here it is one more time:

public void draw(int primitiveType, int offset, int numVertices) { GL10 gl = glGraphics.getGL();

gl.glEnableClientState(GL10. GL_VERTEX_ARRAY); vertices.position(0);

gl.glVertexPointer(2, GL10.GL_FLOAT, vertexSize, vertices);

if(hasColor) {

gl.glEnableClientState(GL10.GL_COLOR_ARRAY); vertices.position(2);

gl.glColorPointer(4, GL10.GL_FLOAT, vertexSize, vertices);

if(hasTexCoords) {

gl.glEnableClientState(GL10. GL_TEXTURE_COORD_ARRAY); vertices.position(hasColor?6:2);

gl.glTexCoordPointer(2, GL10.GL_FLOAT, vertexSize, vertices);

indices.position(offset);

gl.glDrawElements(primitiveType, numVertices, GL10.GL_UNSIGNED_SHORT, indices); } else {

gl.glDrawArrays(primitiveType, offset, numVertices);

if(hasTexCoords)

gl.glDisableClientState(GL10. GL_TEXTURE_COORD_ARRAY);

if(hasColor)

gl.glDisableClientState(GL10. GL_COLOR_ARRAY);

Now look at preceding the loop again. Notice something? For each Bob, we enable the same vertex attributes over and over again via glEnableClientState(). We actually only need to set those once, as each Bob uses the same model, which always uses the same vertex attributes. The next big problems are the calls to glXXXPointer() for each Bob. Since those pointers are also OpenGL ES states, we only need to set them once as well, as they will never change once set. So how can we fix that? Let's rewrite the Vertices.draw() method a little:

public void bind() {

GL10 gl = glGraphics.getGL();

gl.glEnableClientState(GL10. GL_VERTEX_ARRAY); vertices.position(0);

gl.glVertexPointer(2, GL10.GL_FLOAT, vertexSize, vertices);

if(hasColor) {

gl.glEnableClientState(GL10.GL_COLOR_ARRAY); vertices.position(2);

gl.glColorPointer(4, GL10.GL_FLOAT, vertexSize, vertices);

if(hasTexCoords) {

gl.glEnableClientState(GL10. GL_TEXTURE_COORD_ARRAY); vertices.position(hasColor?6:2);

gl.glTexCoordPointer(2, GL10.GL_FLOAT, vertexSize, vertices);

public void draw(int primitiveType, int offset, int numVertices) { GL10 gl = glGraphics.getGL();

indices.position(offset);

gl.glDrawElements(primitiveType, numVertices, GL10.GL_UNSIGNED_SHORT, indices); } else {

gl.glDrawArrays(primitiveType, offset, numVertices);

public void unbind() {

GL10 gl = glGraphics.getGL();

if(hasTexCoords)

gl.glDisableClientState(GL10. GL_TEXTURE_COORD_ARRAY);

if(hasColor)

gl.glDisableClientState(GL10. GL_COLOR_ARRAY);

Can you see what we've done here? We can treat our vertices and all those pointers just like we treat a texture. We "bind" the vertex pointers via a single call to Vertices.bind(). From this point onward every Vertices.draw() call will work with those "bound" vertices, just like the draw call will also use the currently bound texture. Once we are done rendering stuff with that Vertices instance, we call Vertices.unbind() to disable any vertex attributes that another Vertices instance might not need. Keeping our OpenGL ES state clean is a good thing. Here's how our present() method looks now (I moved the glMatrixMode(GL10.GL_M0DELVIEW) call to resume() as well):

@0verride public void present(float deltaTime) { GL10 gl = glGraphics.getGL(); gl.glClear(GL10. GL_COLOR_BUFFER_BIT);

bobModel.bind();

for(int i = 0; i < NUM_BOBS; i++) { gl.glLoadIdentity();

gl.glTranslatef(bobs[i].x, bobs[i].y, 0); bobModel.draw(GL10.GL_TRIANGLES, 0, 6);

bobModel.unbind(); fpsCounter.logFrame();

This effectively calls the glXXXPointer() and glEnableClientState() methods only once per frame. We thus save nearly 100 x 6 calls to OpenGL ES. That should have a huge impact on performance, right? Right.

Hero:

12-10 05:16:59.710 12-10 05:17:00.720 12-10 05:17:01.720 12-10 05:17:02.610 12-10 05:17:02.740 12-10 05:17:03.750

DEBUG/FPSCounter(865): fps: 51 DEBUG/FPSCounter(865): fps: 46 DEBUG/FPSCounter(865): fps: 47

DEBUG/dalvikvm(865): GC freed 21815 objects / 524272 bytes in 131ms DEBUG/FPSCounter(865): fps: 44 DEBUG/FPSCounter(865): fps: 50

Droid:

12-10 05:22:27.519: DEBUG/FPSCounter(2040): fps: 57

12-10 05:22:28.519: DEBUG/FPSCounter(2040): fps: 57

12-10 05:22:29.526: DEBUG/FPSCounter(2040): fps: 57

12-10 05:22:30.526: DEBUG/FPSCounter(2040): fps: 55

Nexus One:

12-10 05:18:31.915: DEBUG/FPSCounter(2509): fps: 56

12-10 05:18:32.935: DEBUG/FPSCounter(2509): fps: 56

12-10 05:18:33.935: DEBUG/FPSCounter(2509): fps: 55

12-10 05:18:34.965: DEBUG/FPSCounter(2509): fps: 54

All three devices are nearly on par now. The Droid performs the best, followed by the Nexus One. Our little Hero performs great as well. We are up to 50 FPS from 22 FPS in the nonoptimized case. That's an increase in performance by 100 percent. We can be proud of ourselves. Our optimized Bob test is pretty much optimal.

Our new bindable Vertices class has of course a few restrictions now:

■ We can only set the vertex and index data when the Vertices instance is not bound, as the upload of that information is performed in Vertices.bind().

■ We can't bind two Vertices instances at once. This means that we can only render with a single Vertices instance at any point in time. That's usually not a big problem, though, and given the impressive increase in performance, we will live with it.

0 0

Post a comment