Question

So, I'm developing a game and I'm using Canvas with SurfaceHolder to update the screen every time an object is supposed to move. That much is working fine so far. Now, the problem happens when I want to stop drawing to the Canvas and just leave it as it is based on the last drawing commands.

So one way that I tried was to simply return from the function that I call when drawing when the end condition is met. However, when I do this, the canvas starts rapidly alternating between the commands sent right when the condition was met and the commands sent one iteration before. I have no idea how or why this is happening since the drawing function is not executing any of its draw commands after the condition is met. Can anyone explain how the canvas can keep refreshing itself when it doesn't get any draw commands?

The code in the thread for locking and unlocking is pretty simple:

    public void run() {
        Canvas c = null;
        try {
            c = sh.lockCanvas(null);
            synchronized(sh) {
                drawCan(c);
            }
        }
        finally {
            if(c!=null) {
                sh.unlockCanvasAndPost(c);
            }
        }
    }

and the drawCan function is structured like this:

public void drawCan(Canvas c) {
    /* Check if user's health is greater than 0. Don't draw anything if it is less */
    if(userHealth<=0) {
        return;
    }
    /* Drawing commands - drawRect(), drawBitmap(), etc are run here */
}

Now normally, this runs fine. But when the userHealth condition is met, the Canvas constantly alternates between the last commands sent and the commands right before that. I know that the draw functions are not being called because I used Log.d() in that area of the code and no messages appeared on LogCat after the condition was met. Can someone explain why this is happening and what the solution would be?

Was it helpful?

Solution

The Canvas is double- or triple- buffered and not erased between frames. When you call lock/unlock, you're switching between previously-rendered buffers.

If you move your if(userHealth<=0) test into run() and use it to avoid calling lock/unlock, you should get the desired effect.

For a much longer explanation about what's going on, see this post.

Update: I realized today that I'd omitted a detail (from the answer and ensuing comments). It doesn't change the answer but it may be useful to know.

The lockCanvas() method takes an optional "dirty" rect that allows you to do partial updates. If you use this, and Surface is able to keep track of the "front" buffer you just rendered, the system will copy the non-dirty contents of the front buffer to the back buffer as part of locking the Surface (see copyBlt() in Surface.cpp).

The system doesn't guarantee that this will work, which is why the "dirty" rect is an in-out parameter. If the front buffer isn't available to copy from, the lock method will just expand the dirty rect to cover the entire screen. In either case, your app is responsible for updating every pixel in the "dirty" rect; if you don't, you get the effects you observed.

This does mean that the Surface is explicitly trying to be double-buffered when used with a Canvas, which would explain why you're seeing two frames alternating rather than three even though SurfaceView is generally triple-buffered. (Which is the thing that has been nagging at me since I wrote this up.) It's also possible to be double-buffered if you're just not generating frames fast enough to require triple-buffering.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top