0

I am experimenting with SurfaceView. My requirement is to simply render a node (simple drawable) first. Then, render more nodes at a later point in time.

The snippets of my thread's run method & my doDraw method are below. I am just trying to render 2 different drawables in subsequent passes while retaining both. The problem is it wipes away whatever gets written in 1st pass (see comment in code). How to retain the previously drawn object?

    public void run() {
        Canvas canvas;
        while (_running) {
            canvas = null;
            try {
                canvas = _surfaceHolder.lockCanvas(null);
                synchronized (_surfaceHolder) {
                    doDraw(canvas, isUpdate);
                }
            } finally {
                if (canvas != null) {
                    _surfaceHolder.unlockCanvasAndPost(canvas);
                }
            }
        }
    }


    public void doDraw(Canvas canvas, boolean update){
        if(update){
            //goes here 2nd pass & wipes away things done in 1st pass
            //I want to retain whatever was drawn in 1st pass
            Bitmap thumb = BitmapFactory.decodeResource(getResources(),R.drawable.icon);
            //canvas.drawColor(Color.RED);
            canvas.drawBitmap(thumb, 0, 0, null);
        } else{
            //goes here 1st pass
            Bitmap thumb = BitmapFactory.decodeResource(getResources(), R.drawable.thumb);
            //canvas.drawColor(Color.BLACK);
            canvas.drawBitmap(thumb, 300, 300, null);
            this.isUpdate = true;
        }
    }

UPDATE 1: Still does not seem to work. I changed the run code to this passing a non:

     public void run() {
        Canvas canvas;
        while (_running) {
            canvas = null;
            try {
                Rect dty = null;
                if(isUpdate == true){
//--> In 2nd pass, I was hoping that only these co-ordinates will be updated
                    dty = new Rect(0,0,100,100);  
                    canvas = _surfaceHolder.lockCanvas(dty);
                }else{
                    canvas = _surfaceHolder.lockCanvas(null);
                }
                synchronized (_surfaceHolder) {
                    doDraw(canvas, isUpdate);
                }
            } finally {
                if (canvas != null) {
                    _surfaceHolder.unlockCanvasAndPost(canvas);
                }
            }
        }
    }

Later I tried passing 0,0,1,1 to dirty rectangle. Could not get it to work yet...

Ravi
  • 3,719
  • 6
  • 28
  • 40

2 Answers2

0

I found this interesting note in the Android documentation:

The content of the Surface is never preserved between unlockCanvas() and lockCanvas(), for this reason, every pixel within the Surface area must be written. The only exception to this rule is when a dirty rectangle is specified, in which case, non-dirty pixels will be preserved.

So to do what you are trying to do, it looks like you need to provide a non-null dirty rectangle in your lockCanvas call. Also, this will only work as long as none of your node pixels intersect.

Dan Harms
  • 4,725
  • 2
  • 18
  • 28
  • You still have the original `lockCanvas` outside your if/else. – Dan Harms May 13 '14 at 04:47
  • Oops.. copy paste typo. The real code did not have that. Updated in question as well. – Ravi May 13 '14 at 04:49
  • Hmm I'm not sure what else you need. Based on the documentation it appears you are doing it correctly. – Dan Harms May 13 '14 at 05:03
  • You may be better off just storing all the objects that need to be drawn and redrawing each pass. – Dan Harms May 13 '14 at 13:27
  • Yes I ended up redrawing the entire the canvas. The nodes had to be elastic et al & it became a bit messy to experiment with dirty rectangles. – Ravi May 14 '14 at 14:20
0

The SurfaceView is double- or triple-buffered. The previous contents are "preserved" in the sense that the system doesn't go out of its way to clear older buffers, but you can't rely on that behavior.

If you specify a dirty rect, the framework will render whatever you ask, then copy the non-dirty region from the previous buffer on top of the new buffer.

The system is allowed to expand the dirty rectangle -- the Rect you pass to lockCanvas() may be updated. You're required to redraw every pixel inside it.

For a (somewhat eye-searing) example of this in action, see "Simple Canvas in TextureView" in Grafika.

For more details on how the system works, see this article.

fadden
  • 51,356
  • 5
  • 116
  • 166
  • Thanks for the links. I ended up redrawing the entire the canvas. – Ravi May 14 '14 at 14:18
  • Is there no way to turn off this buffering and just use a single render texture? – jjxtra Jun 25 '15 at 20:24
  • @PsychoDad: You can draw on an off-screen Bitmap or FBO and blit that to the surface. Double buffering of the surface itself is necessary to avoid tearing. – fadden Jun 25 '15 at 21:03
  • @fadden I've tried that, performance is still terrible. I am drawing lines and paths to a bitmap. I've tried then calling Invalidate(rect) and using TextureView to then blit the portion of the bitmap that changed, but it's very slow. – jjxtra Jun 25 '15 at 21:05
  • @PsychoDad: you should open a new question, describing in detail what you're trying to do and what you've tried so far. – fadden Jun 26 '15 at 04:59
  • @fadden http://stackoverflow.com/questions/31057293/android-textureview-drawing-painting-performance/31060371#31060371 – jjxtra Jun 26 '15 at 14:28