Jan 25, 2012

Flickering problems due to double buffer of SurfaceView

If you try the code in last post Link SurfaceView and Background Thread work together, you can note that the screen seem to flicker between two bitmap! It's due to double buffer of Android's SurfaceView: When you draw on Buffer A, Buffer B is being displayed; then you draw on Buffer B, Buffer A is being displayed. Such that you draw random points on Buffer A and B alternatively, not a single bitmap. This feature can solve some problem of display performance - but not in our case.

In order to solve the problem, you can:
- Draw each pixel on each buffer (It's not practical in our case), OR
- Draw on a single bitmap, then draw the bitmap on canvas.

Here is how to solve the double buffer problem using the second approach:

Modify the SurfaceView - MyGameSurfaceView.java

In surfaceCreated(), create a bitmap(myCanvasBitmap) accroading to the dimension of the SurfaceView. (Please note that not in MyGameSurfaceView_OnResume() - because the SurfaceView may be not yet ready when MyGameSurfaceView_OnResume() is called.) Create a new canvas(myCanvas). Then specify the bitmap(myCanvasBitmap) for the canvas(myCanvas) to draw into, by calling myCanvas.setBitmap(myCanvasBitmap) - Everything draw on myCanvas will draw on myCanvasBitmap.

In onDraw(), we draw on myCanvas, NOT canvas from the method argument. So everything will be draw on myCanvasBitmap. Then, draw the bitmap on canvas (from the method argument) using identity matrix, by calling canvas.drawBitmap(myCanvasBitmap, identityMatrix, null).

Such that, all we draw are draw on a single bitmap; to make both buffer consistance.

MyGameSurfaceView.java
package com.MyGame;

import java.util.Random;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class MyGameSurfaceView extends SurfaceView implements SurfaceHolder.Callback{

SurfaceHolder surfaceHolder;

MyGameThread myGameThread = null;

int myCanvas_w, myCanvas_h;
Bitmap myCanvasBitmap = null;
Canvas myCanvas = null;
Matrix identityMatrix;

private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
Random random;

public MyGameSurfaceView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}

public MyGameSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}

public MyGameSurfaceView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// TODO Auto-generated method stub

}

@Override
public void surfaceCreated(SurfaceHolder holder) {

myCanvas_w = getWidth();
myCanvas_h = getHeight();
myCanvasBitmap = Bitmap.createBitmap(myCanvas_w, myCanvas_h, Bitmap.Config.ARGB_8888);
myCanvas = new Canvas();
myCanvas.setBitmap(myCanvasBitmap);

identityMatrix = new Matrix();
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub

}

public void MyGameSurfaceView_OnResume(){

random = new Random();
surfaceHolder = getHolder();
getHolder().addCallback(this);

//Create and start background Thread
myGameThread = new MyGameThread(this, 200);
myGameThread.setRunning(true);
myGameThread.start();

}

public void MyGameSurfaceView_OnPause(){
//Kill the background Thread
boolean retry = true;
myGameThread.setRunning(false);

while(retry){
try {
myGameThread.join();
retry = false;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

@Override
protected void onDraw(Canvas canvas) {

paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);

//int w = myCanvas.getWidth();
//int h = myCanvas.getHeight();
int x = random.nextInt(myCanvas_w-1);
int y = random.nextInt(myCanvas_h-1);
int r = random.nextInt(255);
int g = random.nextInt(255);
int b = random.nextInt(255);

paint.setColor(0xff000000 + (r << 16) + (g << 8) + b);
myCanvas.drawPoint(x, y, paint);

canvas.drawBitmap(myCanvasBitmap, identityMatrix, null);

}

public void updateStates(){
//Dummy method() to handle the States
}

public void updateSurfaceView(){
//The function run in background thread, not ui thread.

Canvas canvas = null;

try{
canvas = surfaceHolder.lockCanvas();

synchronized (surfaceHolder) {
updateStates();
onDraw(canvas);
}
}finally{
if(canvas != null){
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}

}



next:
- Create transparent foreground SurfaceView

11 comments:

  1. Hello,

    thanks for the post.
    I am trying to make this work on my application; but for some reasons it does not seem to work.

    I have just a question, why didn't you use canvas.drawColor(Color.White)?
    Without that, my canvas becomes black.

    And what about scale, rotation and translation, do we apply those to the canvas directly or to myCanvas?

    ReplyDelete
  2. Hello,

    As I remember: because I draw on a bitmap(myCanvasBitmap) actually, not on a canvas.

    ReplyDelete
  3. Hello,

    so what should I do?
    I mean, how should I apply the rotation, scale and translation?

    I have tried several combinations and the result was not the original version. I also need to be able to move the canvas.

    Thanks in advance.

    PS. The comment system is really bad :(

    N.

    ReplyDelete
  4. So, scaling the canvas will NOT scale the bitmap itself? What about the translation and rotation?

    I find your solution useful only when u dont touch the canvas, otherwise it gets too messed-up. Isn't it?

    ReplyDelete
  5. hello Nesh108,

    I tried to apply scale, translate and rotate on canvas, it affect the bitmap also. Please read Gestures detection and canvas scale, translate and rotate.

    ReplyDelete
  6. Is it possible to get an hold of u by email?

    ReplyDelete
  7. Hello,

    I cant get it to work...

    The main reason, I assume, is that I am working quite a lot with rotation and translation and this gets broken when you add a bitmap in between.

    Any suggestion?

    ReplyDelete
  8. When running this code I get a Null Pointer exception from the onDraw() that is being called from updateSurfaceView(). I am also getting an illegal argument exception from the random.nextInt found in the onDraw() method. Any idea as to why this is happening? My emulator is Android 2.2 with API level 8.

    ReplyDelete
  9. @john Imboden

    I am facing the exactly same issue.please help

    ReplyDelete
  10. @Gagabdeep & John - this is happening because the surfaceview or the canvas is not ready... another Heisenbug :)) ... it is fixable.

    ReplyDelete

Infolinks In Text Ads