Add Camera + SurfaceTexture tests into CTS.
Tests basic functionality for setting a SurfaceTexture as the camera
preview destination.
Change-Id: I42181ce2955a438a22967e849409d24d4e9ed9d1
diff --git a/tests/src/android/opengl/cts/GLSurfaceViewStubActivity.java b/tests/src/android/opengl/cts/GLSurfaceViewStubActivity.java
index 5a8f310..0644913 100644
--- a/tests/src/android/opengl/cts/GLSurfaceViewStubActivity.java
+++ b/tests/src/android/opengl/cts/GLSurfaceViewStubActivity.java
@@ -25,6 +25,7 @@
/**
* A minimal activity for testing {@link android.opengl.GLSurfaceView}.
+ * Also accepts non-blank renderers to allow its use for more complex tests.
*/
public class GLSurfaceViewStubActivity extends Activity {
@@ -45,11 +46,61 @@
private GLSurfaceView mView;
+ /** To override the blank renderer, or other settings, these
+ * static set* methods must be called before onCreate() is called.
+ * If using ActivityInstrumentationTestCase2, that means the set
+ * methods need to be called before calling getActivity in the
+ * test setUp().
+ */
+ private static GLSurfaceView.Renderer mRenderer = null;
+ public static void setRenderer(GLSurfaceView.Renderer renderer) {
+ mRenderer = renderer;
+ }
+ public static void resetRenderer() {
+ mRenderer = null;
+ }
+
+
+ private static int mRenderMode = 0;
+ private static boolean mRenderModeSet = false;
+ public static void setRenderMode(int renderMode) {
+ mRenderModeSet = true;
+ mRenderMode = renderMode;
+ }
+ public static void resetRenderMode() {
+ mRenderModeSet = false;
+ mRenderMode = 0;
+ }
+
+ private static int mGlVersion = 0;
+ private static boolean mGlVersionSet = false;
+ public static void setGlVersion(int glVersion) {
+ mGlVersionSet = true;
+ mGlVersion = glVersion;
+ }
+ public static void resetGlVersion() {
+ mGlVersionSet = false;
+ mGlVersion = 0;
+ }
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mView = new GLSurfaceView(this);
- mView.setRenderer(new Renderer());
+ // Only set this if explicitly asked for
+ if (mGlVersionSet) {
+ mView.setEGLContextClientVersion(mGlVersion);
+ }
+ // Use no-op renderer by default
+ if (mRenderer == null) {
+ mView.setRenderer(new Renderer());
+ } else {
+ mView.setRenderer(mRenderer);
+ }
+ // Only set this if explicitly asked for
+ if (mRenderModeSet) {
+ mView.setRenderMode(mRenderMode);
+ }
setContentView(mView);
}
diff --git a/tests/tests/hardware/src/android/hardware/cts/CameraGLTest.java b/tests/tests/hardware/src/android/hardware/cts/CameraGLTest.java
new file mode 100644
index 0000000..6f6494f
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/CameraGLTest.java
@@ -0,0 +1,751 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * 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 android.hardware.cts;
+
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera;
+import android.hardware.Camera.Parameters;
+import android.hardware.Camera.Size;
+
+import android.opengl.GLES20;
+import android.opengl.GLSurfaceView;
+import android.opengl.Matrix;
+import android.opengl.cts.GLSurfaceViewStubActivity;
+
+import android.os.ConditionVariable;
+import android.os.Environment;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.MoreAsserts;
+import android.test.UiThreadTest;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+
+import dalvik.annotation.TestLevel;
+import dalvik.annotation.TestTargetClass;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * This test case must run with hardware. It can't be tested in emulator
+ */
+@LargeTest
+@TestTargetClass(Camera.class)
+public class CameraGLTest extends ActivityInstrumentationTestCase2<GLSurfaceViewStubActivity> {
+ private static final String TAG = "CameraGLTest";
+ private static final String PACKAGE = "com.android.cts.stub";
+ private static final boolean LOGV = false;
+ private static final int EGL_OPENGL_ES2_BIT = 0x0004;
+
+ private boolean mSurfaceTextureCallbackResult = false;
+
+ private static final int WAIT_FOR_COMMAND_TO_COMPLETE = 1500; // Milliseconds.
+ private static final int WAIT_FOR_FOCUS_TO_COMPLETE = 3000;
+ private static final int WAIT_FOR_SNAPSHOT_TO_COMPLETE = 5000;
+
+ private SurfaceTextureCallback mSurfaceTextureCallback = new SurfaceTextureCallback();
+ private SurfaceTextureBurstCallback mSurfaceTextureBurstCallback = new SurfaceTextureBurstCallback();
+ private PreviewCallback mPreviewCallback = new PreviewCallback();
+
+ private Looper mLooper = null;
+ private final ConditionVariable mSurfaceTextureDone = new ConditionVariable();
+ private final ConditionVariable mPreviewDone = new ConditionVariable();
+
+ Camera mCamera;
+ SurfaceTexture mSurfaceTexture;
+ Renderer mRenderer;
+ GLSurfaceView mGLView;
+
+ public CameraGLTest() {
+ super(PACKAGE, GLSurfaceViewStubActivity.class);
+ if (LOGV) Log.v(TAG, "CameraGLTest Constructor");
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ // Set up renderer instance
+ mRenderer = this.new Renderer();
+ GLSurfaceViewStubActivity.setRenderer(mRenderer);
+ GLSurfaceViewStubActivity.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
+ GLSurfaceViewStubActivity.setGlVersion(2);
+ // Start CameraStubActivity.
+ GLSurfaceViewStubActivity stubActivity = getActivity();
+ // Store a link to the view so we can redraw it when needed
+ mGLView = stubActivity.getView();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (mCamera != null) {
+ mCamera.release();
+ mCamera = null;
+ }
+ // Clean up static values in stub so it can be reused
+ GLSurfaceViewStubActivity.resetRenderMode();
+ GLSurfaceViewStubActivity.resetRenderer();
+ GLSurfaceViewStubActivity.resetGlVersion();
+
+ super.tearDown();
+ }
+
+ /**
+ * Initializes the message looper so that the Camera object can
+ * receive the callback messages.
+ */
+ private void initializeMessageLooper(final int cameraId) {
+ final ConditionVariable startDone = new ConditionVariable();
+ new Thread() {
+ @Override
+ public void run() {
+ Log.v(TAG, "Start camera/surfacetexture thread");
+ // Set up a looper to be used by camera.
+ Looper.prepare();
+ // Save the looper so that we can terminate this thread
+ // after we are done with it.
+ mLooper = Looper.myLooper();
+ // These must be instantiated outside the UI thread, since the
+ // UI thread will be doing a lot of waiting, stopping callbacks.
+ mCamera = Camera.open(cameraId);
+ mSurfaceTexture = new SurfaceTexture(mRenderer.getTextureID());
+ Log.v(TAG, "Camera " + cameraId + " is opened.");
+ startDone.open();
+ Looper.loop(); // Blocks forever until Looper.quit() is called.
+ Log.v(TAG, "Stop camera/surfacetexture thread");
+ }
+ }.start();
+
+ Log.v(TAG, "start waiting for looper");
+ if (!startDone.block(WAIT_FOR_COMMAND_TO_COMPLETE)) {
+ Log.v(TAG, "initializeMessageLooper: start timeout");
+ fail("initializeMessageLooper: start timeout");
+ }
+ }
+
+ /**
+ * Terminates the message looper thread.
+ */
+ private void terminateMessageLooper() throws Exception {
+ mCamera.release();
+ mLooper.quit();
+ // Looper.quit() is asynchronous. The looper may still has some
+ // preview callbacks in the queue after quit is called. The preview
+ // callback still uses the camera object (setHasPreviewCallback).
+ // After camera is released, RuntimeException will be thrown from
+ // the method. So we need to join the looper thread here.
+ mLooper.getThread().join();
+ mCamera = null;
+ mSurfaceTexture = null;
+ }
+
+ /** The camera preview callback. Stops capture after the first callback */
+ private final class PreviewCallback
+ implements android.hardware.Camera.PreviewCallback {
+ public void onPreviewFrame(byte [] data, Camera camera) {
+ assertNotNull(data);
+ Size size = camera.getParameters().getPreviewSize();
+ assertEquals(size.width * size.height * 3 / 2, data.length);
+ mCamera.stopPreview();
+ if (LOGV) Log.v(TAG, "notify the preview callback");
+ mPreviewDone.open();
+ if (LOGV) Log.v(TAG, "Preview callback stop");
+ }
+ }
+
+ /** A simple SurfaceTexture listener callback, meant to be used together with the camera preview
+ * callback */
+ private final class SurfaceTextureCallback
+ implements android.graphics.SurfaceTexture.OnFrameAvailableListener {
+ public void onFrameAvailable(SurfaceTexture surfaceTexture) {
+ if (LOGV) Log.v(TAG, "SurfaceTextureCallback");
+ mSurfaceTextureDone.open();
+ // Assumes preview is stopped elsewhere
+ }
+ }
+
+ /** A burst SurfaceTexture listener callback, used for multiple-frame capture */
+ private final class SurfaceTextureBurstCallback
+ implements android.graphics.SurfaceTexture.OnFrameAvailableListener {
+
+ public void setBurstCount(int burstCount) {
+ mBurstCount = burstCount;
+ }
+
+ public void onFrameAvailable(SurfaceTexture surfaceTexture) {
+ if (LOGV) Log.v(TAG, "SurfaceTextureBurstCallback");
+ mBurstCount = mBurstCount-1;
+ if (!mSurfaceTextureCallbackResult) {
+ if (mBurstCount <= 0) {
+ if (LOGV) Log.v(TAG, "SurfaceTextureBurstCallback: Stopping preview");
+ mCamera.stopPreview();
+ if (LOGV) Log.v(TAG, "SurfaceTextureBurstCallback: Preview stopped");
+ mSurfaceTextureCallbackResult = true;
+ }
+ mSurfaceTextureDone.open();
+ }
+ }
+
+ private int mBurstCount = 0;
+ }
+
+ /** Waits until surface texture callbacks have fired */
+ private boolean waitForSurfaceTextureDone() {
+ if (LOGV) Log.v(TAG, "Wait for surface texture callback");
+ if (!mSurfaceTextureDone.block(WAIT_FOR_COMMAND_TO_COMPLETE)) {
+ // timeout could be expected or unexpected. The caller will decide.
+ Log.v(TAG, "waitForSurfaceTextureDone: timeout");
+ return false;
+ }
+ mSurfaceTextureDone.close();
+ return true;
+ }
+
+ /** Waits until the camera preview callback has fired */
+ private boolean waitForPreviewDone() {
+ if (LOGV) Log.v(TAG, "Wait for preview callback");
+ if (!mPreviewDone.block(WAIT_FOR_COMMAND_TO_COMPLETE)) {
+ // timeout could be expected or unexpected. The caller will decide.
+ Log.v(TAG, "waitForPreviewDone: timeout");
+ return false;
+ }
+ mPreviewDone.close();
+ return true;
+ }
+
+ /** @return OpenGL ES major version 1 or 2 or some negative number for error */
+ private static int getDetectedVersion() {
+ /*
+ * Get all the device configurations and check if any of the attributes specify the
+ * the EGL_OPENGL_ES2_BIT to determine whether the device supports 2.0.
+ */
+ EGL10 egl = (EGL10) EGLContext.getEGL();
+ EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+ int[] numConfigs = new int[1];
+
+ if (egl.eglInitialize(display, null)) {
+ try {
+ if (egl.eglGetConfigs(display, null, 0, numConfigs)) {
+ EGLConfig[] configs = new EGLConfig[numConfigs[0]];
+ if (egl.eglGetConfigs(display, configs, numConfigs[0], numConfigs)) {
+ int[] value = new int[1];
+ for (int i = 0; i < numConfigs[0]; i++) {
+ if (egl.eglGetConfigAttrib(display, configs[i],
+ EGL10.EGL_RENDERABLE_TYPE, value)) {
+ if ((value[0] & EGL_OPENGL_ES2_BIT) == EGL_OPENGL_ES2_BIT) {
+ return 2;
+ }
+ } else {
+ Log.w(TAG, "Getting config attribute with "
+ + "EGL10#eglGetConfigAttrib failed "
+ + "(" + i + "/" + numConfigs[0] + "): "
+ + egl.eglGetError());
+ }
+ }
+ return 1;
+ } else {
+ Log.e(TAG, "Getting configs with EGL10#eglGetConfigs failed: "
+ + egl.eglGetError());
+ return -1;
+ }
+ } else {
+ Log.e(TAG, "Getting number of configs with EGL10#eglGetConfigs failed: "
+ + egl.eglGetError());
+ return -2;
+ }
+ } finally {
+ egl.eglTerminate(display);
+ }
+ } else {
+ Log.e(TAG, "Couldn't initialize EGL.");
+ return -3;
+ }
+ }
+
+ /** Generic per-camera test interface */
+ private interface RunPerCamera {
+ void run(int cameraId) throws Exception;
+ }
+
+ /** Generic camera test runner, to minimize boilerplace duplication */
+ private void runForAllCameras(RunPerCamera test) throws Exception {
+ /* Currently OpenGL ES 2.0 is supported for this test, so just skip it
+ if only 1.0 is available. */
+ int glVersion = getDetectedVersion();
+ assertTrue(glVersion > 0);
+ if (glVersion != 2) {
+ Log.w(TAG, "Skipping test because OpenGL ES 2 is not supported");
+ return;
+ }
+
+ /* Make sure the screen stays on while testing - otherwise the OpenGL context may disappear */
+ PowerManager pm = (PowerManager) getActivity().getSystemService(Context.POWER_SERVICE);
+ PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "CameraGLTest");
+ wl.acquire();
+ try {
+ /* Run the requested test per camera */
+ int nCameras = Camera.getNumberOfCameras();
+ for (int id = 0; id < nCameras; id++) {
+ Log.v(TAG, "Camera id=" + id);
+ test.run(id);
+ }
+ } finally {
+ wl.release();
+ }
+ }
+
+ /** Test Camera.setPreviewTexture in conjunction with the standard Camera preview callback */
+ @UiThreadTest
+ public void testSetPreviewTexturePreviewCallback() throws Exception {
+ runForAllCameras(testSetPreviewTexturePreviewCallbackByCamera);
+ }
+
+ private RunPerCamera testSetPreviewTexturePreviewCallbackByCamera = new RunPerCamera() {
+ public void run(int cameraId) throws Exception {
+ boolean noTimeout;
+ // Check the order: startPreview->setPreviewTexture, with a PreviewCallback as well
+ mPreviewDone.close();
+ initializeMessageLooper(cameraId);
+ mCamera.setPreviewCallback(mPreviewCallback);
+ mCamera.startPreview();
+ mCamera.setPreviewTexture(mSurfaceTexture);
+ noTimeout = waitForPreviewDone();
+ assertTrue(noTimeout);
+ terminateMessageLooper();
+
+ // Check the order: setPreviewTexture->startPreview.
+ initializeMessageLooper(cameraId);
+ mCamera.setPreviewCallback(mPreviewCallback);
+ mCamera.setPreviewTexture(mSurfaceTexture);
+ mCamera.startPreview();
+ noTimeout = waitForPreviewDone();
+ assertTrue(noTimeout);
+
+ // Check the order: setting preview display to null->startPreview->
+ // setPreviewTexture.
+ mCamera.setPreviewCallback(mPreviewCallback);
+ mCamera.setPreviewTexture(null);
+ mCamera.startPreview();
+ mCamera.setPreviewTexture(mSurfaceTexture);
+ noTimeout = waitForPreviewDone();
+ assertTrue(noTimeout);
+ terminateMessageLooper();
+ }
+ };
+
+ /** Test Camera.setPreviewTexture in conjunction with both the standard Camera preview callback,
+ and the SurfaceTexture onFrameAvailable callback */
+ @UiThreadTest
+ public void testSetPreviewTextureBothCallbacks() throws Exception {
+ runForAllCameras(testSetPreviewTextureBothCallbacksByCamera);
+ }
+
+ private RunPerCamera testSetPreviewTextureBothCallbacksByCamera = new RunPerCamera() {
+ public void run(int cameraId) throws Exception {
+ boolean noTimeout;
+ // Check SurfaceTexture callback together with preview callback
+ // Check the order: setPreviewTexture->startPreview
+ mSurfaceTextureDone.close();
+ initializeMessageLooper(cameraId);
+ mRenderer.setCameraSizing(mCamera.getParameters().getPreviewSize());
+ mCamera.setPreviewCallback(mPreviewCallback);
+ mSurfaceTexture.setOnFrameAvailableListener(mSurfaceTextureCallback);
+ mCamera.setPreviewTexture(mSurfaceTexture);
+ mCamera.startPreview();
+
+ noTimeout = waitForSurfaceTextureDone();
+ assertTrue(noTimeout);
+ noTimeout = waitForPreviewDone();
+ assertTrue(noTimeout);
+
+ mGLView.requestRender();
+ terminateMessageLooper();
+
+ // Check the order: startPreview->setPreviewTexture
+ mSurfaceTextureDone.close();
+ initializeMessageLooper(cameraId);
+ mRenderer.setCameraSizing(mCamera.getParameters().getPreviewSize());
+ mCamera.setPreviewCallback(mPreviewCallback);
+ mSurfaceTexture.setOnFrameAvailableListener(mSurfaceTextureCallback);
+ mCamera.startPreview();
+ mCamera.setPreviewTexture(mSurfaceTexture);
+
+ noTimeout = waitForSurfaceTextureDone();
+ assertTrue(noTimeout);
+ noTimeout = waitForPreviewDone();
+ assertTrue(noTimeout);
+
+ mGLView.requestRender();
+
+ // Check the order: setting preview to null->startPreview->setPreviewTexture
+ mCamera.setPreviewCallback(mPreviewCallback);
+ mCamera.setPreviewTexture(null);
+ mSurfaceTexture.setOnFrameAvailableListener(mSurfaceTextureCallback);
+ mCamera.startPreview();
+ mCamera.setPreviewTexture(mSurfaceTexture);
+ noTimeout = waitForPreviewDone();
+ assertTrue(noTimeout);
+ terminateMessageLooper();
+ }
+ };
+
+ /** Test Camera.setPreviewTexture in conjunction with just the SurfaceTexture onFrameAvailable callback */
+ @UiThreadTest
+ public void testSetPreviewTextureTextureCallback() throws Exception {
+ runForAllCameras(testSetPreviewTextureTextureCallbackByCamera);
+ }
+
+ private RunPerCamera testSetPreviewTextureTextureCallbackByCamera = new RunPerCamera() {
+ public void run(int cameraId) throws Exception {
+ boolean noTimeout;
+ // Check that SurfaceTexture callbacks work with no standard
+ // preview callback
+ mSurfaceTextureCallbackResult = false;
+ mSurfaceTextureDone.close();
+ initializeMessageLooper(cameraId);
+ mSurfaceTextureBurstCallback.setBurstCount(1);
+ mSurfaceTexture.setOnFrameAvailableListener(mSurfaceTextureBurstCallback);
+ mCamera.setPreviewTexture(mSurfaceTexture);
+ mRenderer.setCameraSizing(mCamera.getParameters().getPreviewSize());
+ mCamera.startPreview();
+
+ noTimeout = waitForSurfaceTextureDone();
+ mGLView.requestRender();
+ assertTrue(noTimeout);
+
+ terminateMessageLooper();
+ assertTrue(mSurfaceTextureCallbackResult);
+
+ // Check that SurfaceTexture callbacks also work with
+ // startPreview->setPreviewTexture
+ mSurfaceTextureCallbackResult = false;
+ mSurfaceTextureDone.close();
+ initializeMessageLooper(cameraId);
+ mSurfaceTextureBurstCallback.setBurstCount(1);
+ mSurfaceTexture.setOnFrameAvailableListener(mSurfaceTextureBurstCallback);
+ mRenderer.setCameraSizing(mCamera.getParameters().getPreviewSize());
+ mCamera.startPreview();
+ mCamera.setPreviewTexture(mSurfaceTexture);
+
+ noTimeout = waitForSurfaceTextureDone();
+ assertTrue(noTimeout);
+
+ terminateMessageLooper();
+ assertTrue(mSurfaceTextureCallbackResult);
+
+ // Check that SurfaceTexture callbacks also work with
+ // null->startPreview->setPreviewTexture
+ mSurfaceTextureCallbackResult = false;
+ mSurfaceTextureDone.close();
+ initializeMessageLooper(cameraId);
+ mSurfaceTextureBurstCallback.setBurstCount(1);
+ mSurfaceTexture.setOnFrameAvailableListener(mSurfaceTextureBurstCallback);
+ mRenderer.setCameraSizing(mCamera.getParameters().getPreviewSize());
+ mCamera.setPreviewTexture(null);
+ mCamera.startPreview();
+ mCamera.setPreviewTexture(mSurfaceTexture);
+
+ noTimeout = waitForSurfaceTextureDone();
+ assertTrue(noTimeout);
+
+ terminateMessageLooper();
+ assertTrue(mSurfaceTextureCallbackResult);
+ }
+ };
+
+ /** Basic OpenGL ES 2.0 renderer to draw SurfaceTexture-sourced frames to the screen */
+ private class Renderer implements GLSurfaceView.Renderer {
+ public Renderer() {
+ mTriangleVertices =
+ ByteBuffer.allocateDirect(mTriangleVerticesData.length * FLOAT_SIZE_BYTES).
+ order(ByteOrder.nativeOrder()).asFloatBuffer();
+ mTriangleVertices.put(mTriangleVerticesData).position(0);
+
+ Matrix.setIdentityM(mSTMatrix, 0);
+ Matrix.setIdentityM(mMMatrix, 0);
+
+ mTextureID = 0;
+ }
+
+ public void setCameraSizing(Camera.Size previewSize) {
+ mCameraRatio = (float)previewSize.width/previewSize.height;
+ }
+
+ public boolean waitForDrawDone() {
+ if (!mDrawDone.block(WAIT_FOR_COMMAND_TO_COMPLETE) ) {
+ // timeout could be expected or unexpected. The caller will decide.
+ Log.v(TAG, "waitForSurfaceTextureDone: timeout");
+ return false;
+ }
+ mDrawDone.close();
+ return true;
+ }
+
+ private final ConditionVariable mDrawDone = new ConditionVariable();
+
+ public void onDrawFrame(GL10 glUnused) {
+ if (LOGV) Log.v(TAG, "onDrawFrame()");
+ if (CameraGLTest.this.mSurfaceTexture != null) {
+ CameraGLTest.this.mSurfaceTexture.updateTexImage();
+ CameraGLTest.this.mSurfaceTexture.getTransformMatrix(mSTMatrix);
+ mDrawDone.open();
+ }
+
+ // Ignore the passed-in GL10 interface, and use the GLES20
+ // class's static methods instead.
+ GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
+ GLES20.glUseProgram(mProgram);
+ checkGlError("glUseProgram");
+
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+ GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureID);
+
+ mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
+ GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false,
+ TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
+ checkGlError("glVertexAttribPointer maPosition");
+ GLES20.glEnableVertexAttribArray(maPositionHandle);
+ checkGlError("glEnableVertexAttribArray maPositionHandle");
+
+ mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
+ GLES20.glVertexAttribPointer(maTextureHandle, 3, GLES20.GL_FLOAT, false,
+ TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
+ checkGlError("glVertexAttribPointer maTextureHandle");
+ GLES20.glEnableVertexAttribArray(maTextureHandle);
+ checkGlError("glEnableVertexAttribArray maTextureHandle");
+
+ Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, mMMatrix, 0);
+ Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0);
+
+ GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0);
+ GLES20.glUniformMatrix4fv(muSTMatrixHandle, 1, false, mSTMatrix, 0);
+ GLES20.glUniform1f(muCRatioHandle, mCameraRatio);
+
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+ checkGlError("glDrawArrays");
+ }
+
+ public void onSurfaceChanged(GL10 glUnused, int width, int height) {
+ if (LOGV) Log.v(TAG, "onSurfaceChanged()");
+ // Ignore the passed-in GL10 interface, and use the GLES20
+ // class's static methods instead.
+ GLES20.glViewport(0, 0, width, height);
+ mRatio = (float) width / height;
+ Matrix.frustumM(mProjMatrix, 0, -mRatio, mRatio, -1, 1, 3, 7);
+ }
+
+ public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
+ if (LOGV) Log.v(TAG, "onSurfaceCreated()");
+ // Ignore the passed-in GL10 interface, and use the GLES20
+ // class's static methods instead.
+
+ /* Set up shaders and handles to their variables */
+ mProgram = createProgram(mVertexShader, mFragmentShader);
+ if (mProgram == 0) {
+ return;
+ }
+ maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
+ checkGlError("glGetAttribLocation aPosition");
+ if (maPositionHandle == -1) {
+ throw new RuntimeException("Could not get attrib location for aPosition");
+ }
+ maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
+ checkGlError("glGetAttribLocation aTextureCoord");
+ if (maTextureHandle == -1) {
+ throw new RuntimeException("Could not get attrib location for aTextureCoord");
+ }
+
+ muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
+ checkGlError("glGetUniformLocation uMVPMatrix");
+ if (muMVPMatrixHandle == -1) {
+ throw new RuntimeException("Could not get attrib location for uMVPMatrix");
+ }
+
+ muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix");
+ checkGlError("glGetUniformLocation uSTMatrix");
+ if (muMVPMatrixHandle == -1) {
+ throw new RuntimeException("Could not get attrib location for uSTMatrix");
+ }
+
+ muCRatioHandle = GLES20.glGetUniformLocation(mProgram, "uCRatio");
+ checkGlError("glGetUniformLocation uCRatio");
+ if (muMVPMatrixHandle == -1) {
+ throw new RuntimeException("Could not get attrib location for uCRatio");
+ }
+
+ /*
+ * Create our texture. This has to be done each time the
+ * surface is created.
+ */
+
+ int[] textures = new int[1];
+ GLES20.glGenTextures(1, textures, 0);
+
+ mTextureID = textures[0];
+ GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureID);
+ checkGlError("glBindTexture mTextureID");
+
+ // Can't do mipmapping with camera source
+ GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
+ GLES20.GL_NEAREST);
+ GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
+ GLES20.GL_LINEAR);
+ // Clamp to edge is the only option
+ GLES20.glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S,
+ GLES20.GL_CLAMP_TO_EDGE);
+ GLES20.glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T,
+ GLES20.GL_CLAMP_TO_EDGE);
+ checkGlError("glTexParameteri mTextureID");
+
+ Matrix.setLookAtM(mVMatrix, 0, 0, 0, 4f, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
+ }
+
+ public int getTextureID() {
+ return mTextureID;
+ }
+
+ private int loadShader(int shaderType, String source) {
+ int shader = GLES20.glCreateShader(shaderType);
+ if (shader != 0) {
+ GLES20.glShaderSource(shader, source);
+ GLES20.glCompileShader(shader);
+ int[] compiled = new int[1];
+ GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
+ if (compiled[0] == 0) {
+ Log.e(TAG, "Could not compile shader " + shaderType + ":");
+ Log.e(TAG, GLES20.glGetShaderInfoLog(shader));
+ GLES20.glDeleteShader(shader);
+ shader = 0;
+ }
+ }
+ return shader;
+ }
+
+ private int createProgram(String vertexSource, String fragmentSource) {
+ int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
+ if (vertexShader == 0) {
+ return 0;
+ }
+ int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
+ if (pixelShader == 0) {
+ return 0;
+ }
+
+ int program = GLES20.glCreateProgram();
+ if (program != 0) {
+ GLES20.glAttachShader(program, vertexShader);
+ checkGlError("glAttachShader");
+ GLES20.glAttachShader(program, pixelShader);
+ checkGlError("glAttachShader");
+ GLES20.glLinkProgram(program);
+ int[] linkStatus = new int[1];
+ GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
+ if (linkStatus[0] != GLES20.GL_TRUE) {
+ Log.e(TAG, "Could not link program: ");
+ Log.e(TAG, GLES20.glGetProgramInfoLog(program));
+ GLES20.glDeleteProgram(program);
+ program = 0;
+ }
+ }
+ return program;
+ }
+
+ private void checkGlError(String op) {
+ int error;
+ while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
+ Log.e(TAG, op + ": glError " + error);
+ throw new RuntimeException(op + ": glError " + error);
+ }
+ }
+
+ private static final int FLOAT_SIZE_BYTES = 4;
+ private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
+ private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
+ private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
+ private final float[] mTriangleVerticesData = {
+ // X, Y, Z, U, V
+ -1.0f, -1.0f, 0, 0.f, 0.f,
+ 1.0f, -1.0f, 0, 1.f, 0.f,
+ -1.0f, 1.0f, 0, 0.f, 1.f,
+ 1.0f, 1.0f, 0, 1.f, 1.f,
+ };
+
+ private FloatBuffer mTriangleVertices;
+
+ private final String mVertexShader =
+ "uniform mat4 uMVPMatrix;\n" +
+ "uniform mat4 uSTMatrix;\n" +
+ "uniform float uCRatio;\n" +
+ "attribute vec4 aPosition;\n" +
+ "attribute vec4 aTextureCoord;\n" +
+ "varying vec2 vTextureCoord;\n" +
+ "void main() {\n" +
+ " gl_Position = vec4(uCRatio,1,1,1) * uMVPMatrix * aPosition;\n" +
+ " vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
+ "}\n";
+
+ private final String mFragmentShader =
+ "#extension GL_OES_EGL_image_external : require\n" +
+ "precision mediump float;\n" +
+ "varying vec2 vTextureCoord;\n" +
+ "uniform samplerExternalOES sTexture;\n" +
+ "void main() {\n" +
+ " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
+ "}\n";
+
+ private float[] mMVPMatrix = new float[16];
+ private float[] mProjMatrix = new float[16];
+ private float[] mMMatrix = new float[16];
+ private float[] mVMatrix = new float[16];
+ private float[] mSTMatrix = new float[16];
+
+ private int mProgram;
+ private int mTextureID;
+ private int muMVPMatrixHandle;
+ private int muSTMatrixHandle;
+ private int muCRatioHandle;
+ private int maPositionHandle;
+ private int maTextureHandle;
+
+ private float mRatio = 1.0f;
+ private float mCameraRatio = 1.0f;
+
+ private Context mContext;
+ private static final String TAG = "CameraGLTest.Renderer";
+
+ // Magic key
+ private static final int GL_TEXTURE_EXTERNAL_OES = 0x8D65;
+ }
+
+}