Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Canvas.drawBitmap() is intermittently slowed, causing white flashes

I am working on a live wallpaper with a scrolling background. I have two bitmap objects which I alternate between in order to keep the previously drawn pixels for the next frame. I draw a new line at the top of the canvas, then call drawBitmap to copy the rest of the pixels onto the canvas.

I am using a Runnable object to do the heavy lifting. It does all copying and calculations required and then locks the canvas, enters a synchronous block on the holder, and makes a single call to Canvas.drawBitmap(bitmap,rect,rect,paint). Occasionally there will be a white flash on the screen, which seems to correlate with high CPU activity. In using traceview, I found that the drawBitmap operation, specifically Canvas.native_drawBitmap(), is taking much longer than normal. Typically it completes in 2-4msec, but when I see a white flash, it can take anywhere from 10 to 100 msec.

private void draw() {
    SurfaceHolder holder = getSurfaceHolder();

    Canvas canvas = null;
    prepareFrame();
    try {
        canvas = holder.lockCanvas();
        synchronized (holder) {
            if (canvas != null) {
                drawFrame(canvas);
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (canvas != null)
            holder.unlockCanvasAndPost(canvas);
    }
    afterDrawFrame();
    handler.removeCallbacks(drawRunner);
    if (visible) {
        handler.post(drawRunner);
    }
}

The draw() function is called in the run() of the Runnable.

private void prepareFrame() {
    num++;
    if (num%2 == 0) {
        mainBmp = mainBmp1;
        mainCan.setBitmap(mainBmp1);
        mainCan.drawBitmap(mainBmp2, source, destination, null);
    } else {
        mainBmp = mainBmp2;
        mainCan.setBitmap(mainBmp2);
        mainCan.drawBitmap(mainBmp1, source, destination, null);
    }
}

The prepareFrame() function is how I keep hold of the previous pixels I've drawn. The Rect called source is one row short of full screen sized at the bottom, where as destination is one row short at the top. The drawBitmap() calls in prepareFrame() are never longer than 2-4msec.

private void drawFrame(Canvas can) {
    can.drawBitmap(mainBmp, source, destination,null);
}

This single operation is done on the canvas while holding the lock.

private void afterDrawFrame() {
    ca.calcNextRow();
    mainBmp.setPixels(ca.getRow(), 0, canWidth, 0, 0, canWidth, 1);
}

Then the next new row of pixels is drawn onto one of my bitmaps in memory.

I have tried using the various signatures of drawBitmap() but only found them slower on average and still resulting in the anomalous white flashes.

My overall speed is great. Without the intermittent flashes, it works really well. Does anyone have suggestions on how to eliminate the flashes?

like image 924
rlong Avatar asked Jun 19 '12 01:06

rlong


1 Answers

It's kind of hard to know exactly what's going on here because you're not including the definition or use of some central variables like "mainCan" or "ca". A more complete source reference would be great.

But...

What's probably happening is that since drawFrame(canvas) is synchronized on holder, but

handler.post(drawRunner);

is not, there will be occurences where you are trying to draw mainBmp to the system canvas at the same time as you are writing to it in prepareFrame().

The best solution to this problem would probably be some kind of double buffering, where you do something like

1) Write to a temporary bitmap
2) Change the ref of that bitmap to the double buffer i.e. mainBmp = tempBitmap;

The main objective is to never do long writes to the variables you are using for system canvas rendering, just change the object reference.

Hope this helps.

like image 114
Erik Zivkovic Avatar answered Sep 23 '22 12:09

Erik Zivkovic