Hello android developers,
I am developing a simple game for Android in Eclipse using OpenGLES 1.0. I am using Samsung Galaxy S2 Android(2.3) as a device for development.
And I have a question about dual core and making frame rate constant.
So I have managed creating GLSurfaceView and override onDrawFrame() function where I call LogicUpdate(deltatime) function and Render() function.
Yes, all in single thread for now.
The problem I am getting is with dual core. If I disable dual core by going to Setting->Power saving and check System power saving I realize that rendering is automatically locked at 30 FPS. But if I enable dual core by unchecking System power saving I see that rendering is locked at 60 FPS but, phone gets hot and it drains battery really fast.
So the idea is keep my game run at 30 FPS to save some battery.
So to do this I use the code bellow.
Before I do logic update I call this peace of code, remember all this is done in onDrawFrame().
if( CONST_FPS > 0 && StartTime > 0 )
{
/////////////////////////////////////////////////////////////////
// Get frame time
////////////////////////////////////////////////////////////////
long endTime = System.currentTimeMillis();
long time = endTime - StartTime;
//
long wantedtime = 1000 / CONST_FPS;
//
long wait = 0;
if( time < wantedtime )
{
wait = wantedtime - time;
//
Thread.sleep(wait);
}
else
{
//Time to big game will slow down
}
}
Where CONST_FPS = 30
And then
StartTime = System.currentTimeMillis(); //
UpdateLogic(1.0 / CONST_FPS);
Render();
Gameplay at 30 FPS is very smooth mainly because it does not need to lock FPS.
BUT, when trying to lock 60FPS to 30 FPS I get stuttering. I did some research and found out that Thread.Sleep() is not precise. Is this true? What else can I do to make gameplay more smooth when locking 60FPS to 30FPS.
Thanks for the answer ...
You should use elapsed time to scale all your movement, so that the movement stays smooth at varying FPS rates. You can get elapsed time like this:
long currentTime = System.currentTimeMillis();
float elapsed = (System.currentTimeMillis() - lastFrameTime) * .001f;//convert ms to seconds
lastFrameTime = currentTime;
Then express your velocities in units per second, and update position like this:
sprite.x += (sprite.xspeed * elapsed);
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
GLES20.glClearColor(0.1f, 0.1f, 0.1f, 1);
GLES20.glEnable(GLES20.GL_DEPTH_TEST);
GLES20.glDepthFunc(GLES20.GL_LEQUAL);
GLES20Renderer.programLight = GLES20.glCreateProgram();
int vertexShaderLight = GLES20Renderer.loadShader(GLES20.GL_VERTEX_SHADER, GLES20Renderer.vertexShaderCodeLight);
int fragmentShaderLight = GLES20Renderer.loadShader(GLES20.GL_FRAGMENT_SHADER, GLES20Renderer.fragmentShaderCodeLight);
GLES20.glAttachShader(GLES20Renderer.programLight, vertexShaderLight);
GLES20.glAttachShader(GLES20Renderer.programLight, fragmentShaderLight);
GLES20.glLinkProgram(GLES20Renderer.programLight);
System.gc();
}
public void onSurfaceChanged(GL10 gl, int width, int height) {
gl.glViewport(0, 0, width, height);
float ratio = (float) width / height;
Matrix.setLookAtM(GLES20Renderer.ViewMatrix, 0, 0, 0, 5f, 0, 0, 0, 0, 1, 0);
Matrix.frustumM(GLES20Renderer.ProjectionMatrix, 0, -ratio, ratio, -1, 1, 2, 8);
Matrix.setIdentityM(LightModelMatrix, 0);
Matrix.translateM(LightModelMatrix, 0, -1.0f, 0.0f, 3f);
Matrix.multiplyMV(LightPosInWorldSpace, 0, LightModelMatrix, 0, LightPosInModelSpace, 0);
Matrix.multiplyMV(LightPosInEyeSpace, 0, ViewMatrix, 0, LightPosInWorldSpace, 0);
System.gc();
}
public void onDrawFrame(GL10 gl) {
long deltaTime,startTime,endTime;
startTime = SystemClock.uptimeMillis() % 1000;
gl.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
synchronized (this) {
updateModel(GLES20Renderer._xAngle, GLES20Renderer._yAngle, GLES20Renderer._zAngle);
}
renderModel(gl);
endTime = SystemClock.uptimeMillis() % 1000;
deltaTime = endTime - startTime;
if (deltaTime < 30) {
try {
Thread.sleep(30 - deltaTime);
} catch (InterruptedException e) {
//e.printStackTrace();
}
}
System.gc();
}
ATTENTION:
- Never use sleep in the UI thread; it is very very very bad
- Use statics: although this is bad OOP practice, it is useful here
- Use vertexbuffer objects
- Initialize in onsurfacechanged and fetch locations (glget__location) here too
- It is not your fault always because Froyo garbage collector is bad and then on Gingerbread the touch drivers fire too many events (Google this) so gradually switch to other input methods for your app/game that does not use touch like Android sensors
- Upgrade to Gingerbread