/* * Copyright 2014 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.grafika; import android.os.Bundle; import android.util.Log; import android.view.Surface; import android.view.TextureView; import android.app.Activity; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.SurfaceTexture; /** * A demonstration of using Canvas to draw on a TextureView. Based on TextureViewGLActivity. *

* Currently renders frames as fast as possible, without waiting for the consumer. *

* As part of experimenting with the framework, this allows the renderer thread to continue * to run as the TextureView is being destroyed (we stop the thread in onDestroy() rather * than onPause()). Normally the renderer would be stopped when the application pauses. */ public class TextureViewCanvasActivity extends Activity { private static final String TAG = MainActivity.TAG; private TextureView mTextureView; private Renderer mRenderer; @Override protected void onCreate(Bundle savedInstanceState) { Log.d(TAG, "onCreate"); super.onCreate(savedInstanceState); // Start up the Renderer thread. It'll sleep until the TextureView is ready. mRenderer = new Renderer(); mRenderer.start(); setContentView(R.layout.activity_texture_view_canvas); mTextureView = (TextureView) findViewById(R.id.canvasTextureView); mTextureView.setSurfaceTextureListener(mRenderer); } @Override protected void onResume() { super.onResume(); } @Override protected void onDestroy() { Log.d(TAG, "onDestroy"); super.onDestroy(); // Don't do this -- halt the thread in onPause() and wait for it to finish. mRenderer.halt(); } /** * Handles Canvas rendering and SurfaceTexture callbacks. *

* We don't create a Looper, so the SurfaceTexture-by-way-of-TextureView callbacks * happen on the UI thread. */ private static class Renderer extends Thread implements TextureView.SurfaceTextureListener { private Object mLock = new Object(); // guards mSurfaceTexture, mDone private SurfaceTexture mSurfaceTexture; private boolean mDone; private int mWidth; // from SurfaceTexture private int mHeight; public Renderer() { super("TextureViewCanvas Renderer"); } @Override public void run() { while (true) { SurfaceTexture surfaceTexture = null; // Latch the SurfaceTexture when it becomes available. We have to wait for // the TextureView to create it. synchronized (mLock) { while (!mDone && (surfaceTexture = mSurfaceTexture) == null) { try { mLock.wait(); } catch (InterruptedException ie) { throw new RuntimeException(ie); // not expected } } if (mDone) { break; } } Log.d(TAG, "Got surfaceTexture=" + surfaceTexture); // Render frames until we're told to stop or the SurfaceTexture is destroyed. doAnimation(); } Log.d(TAG, "Renderer thread exiting"); } /** * Draws updates as fast as the system will allow. *

* In 4.4, with the synchronous buffer queue queue, the frame rate will be limited. * In previous (and future) releases, with the async queue, many of the frames we * render may be dropped. *

* The correct thing to do here is use Choreographer to schedule frame updates off * of vsync, but that's not nearly as much fun. */ private void doAnimation() { final int BLOCK_WIDTH = 80; final int BLOCK_SPEED = 2; int clearColor = 0; int xpos = -BLOCK_WIDTH / 2; int xdir = BLOCK_SPEED; // Create a Surface for the SurfaceTexture. Surface surface = null; synchronized (mLock) { SurfaceTexture surfaceTexture = mSurfaceTexture; if (surfaceTexture == null) { Log.d(TAG, "ST null on entry"); return; } surface = new Surface(surfaceTexture); } Paint paint = new Paint(); paint.setColor(Color.RED); paint.setStyle(Paint.Style.FILL); boolean partial = false; while (true) { Rect dirty = null; if (partial) { // Set a dirty rect to confirm that the feature is working. It's // possible for lockCanvas() to expand the dirty rect if for some // reason the system doesn't have access to the previous buffer. dirty = new Rect(0, mHeight * 3 / 8, mWidth, mHeight * 5 / 8); } Canvas canvas = surface.lockCanvas(dirty); if (canvas == null) { Log.d(TAG, "lockCanvas() failed"); break; } try { // just curious if (canvas.getWidth() != mWidth || canvas.getHeight() != mHeight) { Log.d(TAG, "WEIRD: width/height mismatch"); } // Draw the entire window. If the dirty rect is set we should actually // just be drawing into the area covered by it -- the system lets us draw // whatever we want, then overwrites the areas outside the dirty rect with // the previous contents. So we've got a lot of overdraw here. canvas.drawRGB(clearColor, clearColor, clearColor); canvas.drawRect(xpos, mHeight / 4, xpos + BLOCK_WIDTH, mHeight * 3 / 4, paint); } finally { // Publish the frame. If we overrun the consumer, frames will be dropped, // so on a sufficiently fast device the animation will run at faster than // the display refresh rate. // // If the SurfaceTexture has been destroyed, this will throw an exception. try { surface.unlockCanvasAndPost(canvas); } catch (IllegalArgumentException iae) { Log.d(TAG, "unlockCanvasAndPost failed: " + iae.getMessage()); break; } } // Advance state clearColor += 4; if (clearColor > 255) { clearColor = 0; partial = !partial; } xpos += xdir; if (xpos <= -BLOCK_WIDTH / 2 || xpos >= mWidth - BLOCK_WIDTH / 2) { Log.d(TAG, "change direction"); xdir = -xdir; } } surface.release(); } /** * Tells the thread to stop running. */ public void halt() { synchronized (mLock) { mDone = true; mLock.notify(); } } @Override // will be called on UI thread public void onSurfaceTextureAvailable(SurfaceTexture st, int width, int height) { Log.d(TAG, "onSurfaceTextureAvailable(" + width + "x" + height + ")"); mWidth = width; mHeight = height; synchronized (mLock) { mSurfaceTexture = st; mLock.notify(); } } @Override // will be called on UI thread public void onSurfaceTextureSizeChanged(SurfaceTexture st, int width, int height) { Log.d(TAG, "onSurfaceTextureSizeChanged(" + width + "x" + height + ")"); mWidth = width; mHeight = height; } @Override // will be called on UI thread public boolean onSurfaceTextureDestroyed(SurfaceTexture st) { Log.d(TAG, "onSurfaceTextureDestroyed"); synchronized (mLock) { mSurfaceTexture = null; } return true; } @Override // will be called on UI thread public void onSurfaceTextureUpdated(SurfaceTexture st) { //Log.d(TAG, "onSurfaceTextureUpdated"); } } }