I’m drawing about 30 – 80 textured planes (squares) – backgrounds, player, enemies, bullets, etc.
All planes move and scale, some +rotate and some have animated textures.
I think, it can’t be too hard for CPU or GPU, but on slower/older devices it has relative low performance – for example on Galaxy ACE.
Please, Can you look my code, what am I doing wrong or dirty? Or what can be optimalized?
Thank you.
This is my onSurfaceCreated, onSurfaceChanged and onDrawFrame:
public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
..
// load and prepare all textures
..
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
GLES20.glEnable(GLES20.GL_CULL_FACE);
GLES20.glDisable(GLES20.GL_DEPTH_TEST);
GLES20.glEnable(GLES20.GL_TEXTURE_2D);
GLES20.glDisable(GL10.GL_DITHER);
GLES20.glDisable(GL10.GL_LIGHTING);
final float eyeX = 0.0f; final float eyeY = 0.0f; final float eyeZ = 1.5f;
final float lookX = 0.0f; final float lookY = 0.0f; final float lookZ = -3.0f;
final float upX = 0.0f; final float upY = 1.0f; final float upZ = 0.0f;
Matrix.setLookAtM(mViewMatrix, 0, eyeX, eyeY, eyeZ, lookX, lookY, lookZ, upX, upY, upZ);
mProgramHandle = GLES20.glCreateProgram();
final String vertexShader =
"uniform mat4 u_MVPMatrix; \n"
+ "attribute vec4 a_Position; \n"
+ "attribute vec2 a_TexCoordinate; \n"
+ "attribute float a_AlphaValue; \n"
+ "varying vec2 v_TexCoordinate; \n"
+ "varying float v_AlphaValue; \n"
+ "void main() \n"
+ "{ \n"
+ " v_TexCoordinate = a_TexCoordinate; \n"
+ " v_AlphaValue = a_AlphaValue; \n"
+ " gl_Position = u_MVPMatrix * a_Position; \n"
+ "} \n";
final String fragmentShader =
"precision lowp float; \n"
+ "uniform sampler2D u_Texture; \n"
+ "varying vec2 v_TexCoordinate; \n"
+ "varying float v_AlphaValue; \n"
+ "void main() \n"
+ "{ \n"
+ " gl_FragColor = texture2D(u_Texture, v_TexCoordinate); \n"
+ " gl_FragColor.a *= v_AlphaValue; \n"
+ "} \n";
final int vertexShaderHandle = ShaderHelper.compileShader(GLES20.GL_VERTEX_SHADER, vertexShader);
final int fragmentShaderHandle = ShaderHelper.compileShader(GLES20.GL_FRAGMENT_SHADER, fragmentShader);
mProgramHandle = ShaderHelper.createAndLinkProgram(vertexShaderHandle, fragmentShaderHandle, new String[] {"a_Position", "a_TexCoordinate", "a_AlphaValue"});
GLES20.glUseProgram(mProgramHandle);
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgramHandle, "u_MVPMatrix");
mMVMatrixHandle = GLES20.glGetUniformLocation(mProgramHandle, "u_MVMatrix");
mPositionHandle = GLES20.glGetAttribLocation(mProgramHandle, "a_Position");
}
public void onSurfaceChanged(GL10 glUnused, int width, int height) {
GLES20.glViewport(0, 0, width, height);
float ratio = (float) width / height;
Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 1, 1000);
}
public void onDrawFrame(GL10 glUnused) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
updateGame();
// all planes are saved in Vector<Mesh> children
int size = children.size();
for (int i = 0; i < size; i++)
{
children.get(i).Draw(renderer);
}
}
And Draw(Renderer) in ‘plane’ object:
public void Draw(Renderer renderer)
{
if (!Visible) return; // no visible object, no need draw
mTextureCoordinateHandle = GLES20.glGetAttribLocation(renderer.mProgramHandle, "a_TexCoordinate");
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mBrickDataHandle);
mAlphaHandle = GLES20.glGetAttribLocation(renderer.mProgramHandle, "a_AlphaValue");
GLES20.glVertexAttrib1f(mAlphaHandle, alpha);
// texture animation
renderer.mPlaneTextureCoords[(byte)indexAnim].position(0);
GLES20.glVertexAttribPointer(mTextureCoordinateHandle, mTextureCoordinateDataSize, GLES20.GL_FLOAT, false, 0, renderer.mPlaneTextureCoords[(byte)indexAnim]);
indexAnim++;
renderer.mPlanePositions.position(0);
GLES20.glVertexAttribPointer(Renderer.mPositionHandle, renderer.mPositionDataSize, GLES20.GL_FLOAT, false, 0, renderer.mPlanePositions);
GLES20.glEnableVertexAttribArray(Renderer.mPositionHandle);
if (angleZ == 0) // no rotating plane -> no need setRotateM
{
Matrix.setIdentityM(renderer.mModelMatrix, 0);
Matrix.translateM(renderer.mModelMatrix, 0, x, y, z);
Matrix.scaleM(renderer.mModelMatrix, 0, scaleX, scaleY, 1);
}
else // rotating plane
{
float[] mt = new float[16];
Matrix.setIdentityM(mt, 0);
Matrix.translateM(mt, 0, x, y, z);
Matrix.scaleM(mt, 0, scaleX, scaleY, 1);
Matrix.setRotateM(mRotZMatrix, 0, angleZ, 0, 0, 1);
Matrix.multiplyMM(renderer.mModelMatrix, 0, mt, 0, mRotZMatrix, 0);
}
Matrix.multiplyMM(renderer.mMVPMatrix, 0, renderer.mViewMatrix, 0, renderer.mModelMatrix, 0);
GLES20.glUniformMatrix4fv(renderer.mMVMatrixHandle, 1, false, renderer.mMVPMatrix, 0);
//Matrix.multiplyMM(renderer.mMVPMatrix, 0, renderer.mProjectionMatrix, 0, renderer.mMVPMatrix, 0);
Matrix.multiplyMM(renderer.mTemporaryMatrix, 0, renderer.mProjectionMatrix, 0, renderer.mMVPMatrix, 0); // little bit faster (?)
System.arraycopy(renderer.mTemporaryMatrix, 0, renderer.mMVPMatrix, 0, 16);
GLES20.glUniformMatrix4fv(renderer.mMVPMatrixHandle, 1, false, renderer.mMVPMatrix, 0); // pass in the combined matrix
GLES20.glEnableVertexAttribArray(mTextureCoordinateHandle);
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 6);
}
If you’re running on a newer version of the Android SDK (e.g. v16) you can use the profiler to find out where the problem areas are:
http://developer.android.com/tools/debugging/debugging-tracing.html
But at first glance there are a couple of things I can see:
Another responder highlighted that you are calling
glGetAttribLocationinside your draw call, you don’t need to do this every time you draw. Once the “program” is set these references can be obtained and they won’t change as long as that program doesn’t change (i.e you don’t change shaders). Likewise with the calls toglEnableVertexAttribArrayConsider using a texture atlas. Each call to
glBindTexturemay result in a state change in VRAM which is a slow operation. A texture atlas is simply a single image which contains all (or many) or your textures (e.g. sprites). When you specify the texture coordinates you simply specify a value between 0 and 1 representing the location of your single sprite within the atlas. If you want to use this approach you will have to “bin pack” your atlas so you’re not wasting space. There are a few tools out there to do this (eg TexturePacker). This means fewer (possibly only one) calls toglBindTextureYou’re passing your MVP matrix twice with
glUniformMatrix4fvwhich is unnecessary. Changing the matrix that is sent to the shader after it’s been sent will have no effect (i.e. the initial set operation will be overwritten) as the matrix is only used when the shader executes in the call toglDrawArraysI have recently discovered that
setRotateMis pretty damn slow. There are alternatives around the place which I need to find also!Your last
System.arrayCopyis unnecessary. You can just chain the matrix multiplication without needing the temp. matrix. Model * View = MV, then MV * Projection = MVPIf you haven’t already you should also check out this tutorial:
http://www.learnopengles.com/android-lesson-one-getting-started/
And if you DO decide to go down the route of a 3rd party game engine, IMO libgdx is by FAR the best:
http://libgdx.badlogicgames.com/
It’s free, open source and has a strong focus on performance. Having said that I too decided to build my own because I didn’t need the breadth of features offered by libgdx but I have to say that every line of code I write makes me stop and question this decision 😉
Good luck.