Java – Drawing to Canvas is too slow, how to achieve a constant 30 fps or higher? (Android)

Drawing to Canvas is too slow, how to achieve a constant 30 fps or higher? (Android)… here is a solution to the problem.

Drawing to Canvas is too slow, how to achieve a constant 30 fps or higher? (Android)

I’m trying to create a 2d game for Android and I’m currently working on a constant fps of about 30 fps (I think 30 is enough for a mobile game). I’m using SurfaceView and a very simple game loop :

 while (isOk==true)
    {
        if (!holder.getSurface().isValid()) continue;

c = holder.lockCanvas();

update();
        draw(c);
        holder.unlockCanvasAndPost(c);

t2= System.currentTimeMillis();
        all++;
        if ( mspf /* miliseconds per frame, and it's set to 1000/30 */ -(t2-t1)  > 0 )
        {
            try {
                t.sleep(mspf-(t2-t1));
            } catch (InterruptedException e) {
                 TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        else
                Log.w("Super",String.valueOf(mspf-(t2-t1))+ " o " + String.valueOf(++total)+ "out of " +String.valueOf(all));

t1=Math.max( t1+mspf, t2);
    }

It’s a game loop. As you may have noticed, I use Log.w to see how many frames I still have some time left to get into Thread.sleep. The result was not very good. I have about 10-25% of frames over 1000/30ms to run :

09-07 00:00:39.069: W/Super(28328): -1 o 4395out of 16353
09-07 00:00:39.109: W/Super(28328): -1 o 4396out of 16354
09-07 00:00:39.209: W/Super(28328): 0 o 4397out of 16357
09-07 00:00:40.269: W/Super(28328): -4 o 4398out of 16389
09-07 00:00:40.299: W/Super(28328): -3 o 4399out of 16390
09-07 00:00:40.339: W/Super(28328): -4 o 4400out of 16391
09-07 00:00:40.379: W/Super(28328): -4 o 4401out of 16392
09-07 00:00:40.409: W/Super(28328): -5 o 4402out of 16393
09-07 00:00:40.459: W/Super(28328): -8 o 4403out of 16394
09-07 00:00:40.499: W/Super(28328): -7 o 4404out of 16395
09-07 00:00:40.529: W/Super(28328): -3 o 4405out of 16396
09-07 00:00:40.569: W/Super(28328): -5 o 4406out of 16397
09-07 00:00:40.609: W/Super(28328): -5 o 4407out of 16398
09-07 00:00:40.639: W/Super(28328): -3 o 4408out of 16399
09-07 00:00:40.679: W/Super(28328): -3 o 4409out of 16400
09-07 00:00:40.719: W/Super(28328): -4 o 4410out of 16401

In drawing, I don’t have that many bitmaps to draw. (About 15 bitmaps, including shaders, transparency masks for some effects, and scaling). All bitmaps are loaded into the create method from the resource (PNG files only).

public void draw(Canvas c)
{

c.drawBitmap(background,0,0,null);
    c.drawBitmap(m_image.get(m_cn),400 , 100, test);

c.drawBitmap(start_menu,10 ,10 , test);
    c.drawBitmap(inventory_menu, 200 , 10, test);
    c.drawBitmap(shop_menu,400  , 10, test);
    c.drawBitmap(options_menu, 10 , 300, test);
    c.drawBitmap(facebook_menu,200 , 300, test);

light.drawColor(Color.TRANSPARENT, Mode.CLEAR);

light_matrix.setScale(scale_factor, scale_factor);
    light_matrix.postTranslate(light_x-mask_aux.getWidth()/2*scale_factor, light_y-mask_aux.getHeight()/2*scale_factor);
    light.drawBitmap(mask_aux, light_matrix, test);

copy_the_screen.drawBitmap(buffer.get(b_activ),0,0,test);
    sh =  new BitmapShader(screen_copy,Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
    pnt.setShader(sh);

c.drawARGB(200, 0, 0,0);
    c.drawBitmap(mask,0,0 ,pnt);

}

The update method is not complicated, so there are a few things that need to be done in both functions to get a better FPS. Please help me, please forgive me for my poor English.

Good. The update function looks like this:

     public void update()
{
    m_time++;
    if (m_time==2)
    {
        m_time=0;
        m_cn++;
        if (m_cn==4)m_cn=0; 
    }

if (touched==true)
    {
        light_x=t_x;
        light_y=t_y;
        scale_factor=t_y/150;
    }
}

It only modifies the bitmap I selected from the sprite. I don’t have a large bitmap that contains all the positions of the character, but 4 different bitmaps. The second “if” refers to a mask image that creates a ball of light exactly where I touch. I use that light_matrix to scale the ball of light proportional to the Y coordinate (click event). That’s why I use shaders to create lighting effects. Here, take a look at OnTouchEvent:

   public boolean onTouchEvent(MotionEvent event) {
     TODO Auto-generated method stub

I put this sleep method here because I noticed it was the lagging the game if i let it run 
     without a sleep.
    try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
         TODO Auto-generated catch block
        e.printStackTrace();
    }
      after sleep , i only check for events.

}

Solution

First, creating a new object every frame is a bad technique, so take the following line:

sh =  new BitmapShader(screen_copy,Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

And moving it to the constructor (or where you initialize the game), I’m not sure if you need to change BitmapShader every frame because screen_copy has changed, but if there is a way to avoid making a new frame every frame is best.

private BitmapShader sh;

@Override
public void surfaceCreated(SurfaceHolder holder)
{
    //...

sh =  new BitmapShader(screen_copy,Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
}

The rest of the draw method looks good.

onTouchEvent looks weird, and I’m not sure why you’d use Thread.sleep(int) there, but I suspect it’s a big deal.

Now I take a closer look at your main loop, it looks a bit weird, especially this line

t1=Math.max( t1+mspf, t2);

I guess that’s why it all happened, let me rewrite your main loop, you can try, and then tell me the result

while (isOk) /*you can skip the "==true" part*/
{
    if (!holder.getSurface().isValid()) continue;

Start Frame
    t1 = System.currentTimeMillis();

Canvas and drawing
    c = holder.lockCanvas();

update();
    draw(c);
    holder.unlockCanvasAndPost(c);

End Frame
    t2= System.currentTimeMillis();
    all++;
    if ( t2 -t1 > mspf )
    {
        try {t.sleep(mspf-(t2-t1)); } 
            catch (InterruptedException e) {e.printStackTrace(); }
    }
    else
        Log.w("Super",String.valueOf(mspf-(t2-t1))+ " o " + String.valueOf(++total)+ "out of " +String.valueOf(all));
}

Related Problems and Solutions