diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/PreviewTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraPreviewTest.java
similarity index 98%
rename from cameraview/src/androidTest/java/com/otaliastudios/cameraview/PreviewTest.java
rename to cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraPreviewTest.java
index eb1fbf88..3d35c998 100644
--- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/PreviewTest.java
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraPreviewTest.java
@@ -17,7 +17,7 @@ import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
-public abstract class PreviewTest extends BaseTest {
+public abstract class CameraPreviewTest extends BaseTest {
protected abstract CameraPreview createPreview(Context context, ViewGroup parent, CameraPreview.SurfaceCallback callback);
diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java
index 08ee413e..d1839109 100644
--- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java
@@ -68,6 +68,30 @@ public class CameraViewTest extends BaseTest {
hasPermissions = false;
}
+ //region testLifecycle
+
+ @Test
+ public void testStart() {
+ cameraView.start();
+ verify(mockPreview, times(1)).onResume();
+ // Can't verify controller, depends on permissions.
+ // See to-do at the end.
+ }
+
+ @Test
+ public void testStop() {
+ cameraView.stop();
+ verify(mockPreview, times(1)).onPause();
+ verify(mockController, times(1)).stop();
+ }
+
+ @Test
+ public void testDestroy() {
+ cameraView.destroy();
+ verify(mockPreview, times(1)).onDestroy();
+ verify(mockController, times(1)).destroy();
+ }
+
//region testDefaults
@Test
diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/GLCameraPreviewTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/GLCameraPreviewTest.java
new file mode 100644
index 00000000..e766f856
--- /dev/null
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/GLCameraPreviewTest.java
@@ -0,0 +1,19 @@
+package com.otaliastudios.cameraview;
+
+
+import android.content.Context;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.ViewGroup;
+
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class GLCameraPreviewTest extends CameraPreviewTest {
+
+ @Override
+ protected CameraPreview createPreview(Context context, ViewGroup parent, CameraPreview.SurfaceCallback callback) {
+ return new GLCameraPreview(context, parent, callback);
+ }
+}
diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/SurfaceCameraPreviewTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/SurfaceCameraPreviewTest.java
index d3d0cdb6..14a7e849 100644
--- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/SurfaceCameraPreviewTest.java
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/SurfaceCameraPreviewTest.java
@@ -10,7 +10,7 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
@SmallTest
-public class SurfaceCameraPreviewTest extends PreviewTest {
+public class SurfaceCameraPreviewTest extends CameraPreviewTest {
@Override
protected CameraPreview createPreview(Context context, ViewGroup parent, CameraPreview.SurfaceCallback callback) {
diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/TextureCameraPreviewTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/TextureCameraPreviewTest.java
index 8a1f056e..994aacce 100644
--- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/TextureCameraPreviewTest.java
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/TextureCameraPreviewTest.java
@@ -10,7 +10,7 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
@SmallTest
-public class TextureCameraPreviewTest extends PreviewTest {
+public class TextureCameraPreviewTest extends CameraPreviewTest {
@Override
protected CameraPreview createPreview(Context context, ViewGroup parent, CameraPreview.SurfaceCallback callback) {
diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
index 656ae697..7f0fddd1 100644
--- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
+++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
@@ -70,6 +70,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
TapGestureLayout mTapGestureLayout;
ScrollGestureLayout mScrollGestureLayout;
private boolean mKeepScreenOn;
+ private boolean mExperimental;
// Threading
private Handler mUiHandler;
@@ -94,6 +95,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
// Self managed
boolean playSounds = a.getBoolean(R.styleable.CameraView_cameraPlaySounds, DEFAULT_PLAY_SOUNDS);
+ mExperimental = a.getBoolean(R.styleable.CameraView_cameraExperimental, false);
// Camera controller params
Facing facing = Facing.fromValue(a.getInteger(R.styleable.CameraView_cameraFacing, Facing.DEFAULT.value()));
@@ -229,6 +231,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
protected CameraPreview instantiatePreview(Context context, ViewGroup container) {
// TextureView is not supported without hardware acceleration.
LOG.w("preview:", "isHardwareAccelerated:", isHardwareAccelerated());
+ if (mExperimental) return new GLCameraPreview(context, container, null);
return isHardwareAccelerated() ?
new TextureCameraPreview(context, container, null) :
new SurfaceCameraPreview(context, container, null);
@@ -580,6 +583,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
public void start() {
if (!isEnabled()) return;
+ if (mCameraPreview != null) mCameraPreview.onResume();
if (checkPermissions(getMode(), getAudio())) {
// Update display orientation for current CameraController
@@ -650,6 +654,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
public void stop() {
mCameraController.stop();
+ if (mCameraPreview != null) mCameraPreview.onPause();
}
@@ -662,6 +667,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
clearCameraListeners();
clearFrameProcessors();
mCameraController.destroy();
+ if (mCameraPreview != null) mCameraPreview.onDestroy();
}
//endregion
diff --git a/cameraview/src/main/res/layout/cameraview_gl_view.xml b/cameraview/src/main/res/layout/cameraview_gl_view.xml
new file mode 100644
index 00000000..0525ee6e
--- /dev/null
+++ b/cameraview/src/main/res/layout/cameraview_gl_view.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cameraview/src/main/res/values/attrs.xml b/cameraview/src/main/res/values/attrs.xml
index 5c217374..d5c0e5b7 100644
--- a/cameraview/src/main/res/values/attrs.xml
+++ b/cameraview/src/main/res/values/attrs.xml
@@ -118,5 +118,7 @@
+
+
\ No newline at end of file
diff --git a/cameraview/src/main/views/com/otaliastudios/cameraview/CameraPreview.java b/cameraview/src/main/views/com/otaliastudios/cameraview/CameraPreview.java
index b7210575..2fcaf630 100644
--- a/cameraview/src/main/views/com/otaliastudios/cameraview/CameraPreview.java
+++ b/cameraview/src/main/views/com/otaliastudios/cameraview/CameraPreview.java
@@ -102,6 +102,12 @@ abstract class CameraPreview {
mSurfaceHeight = 0;
}
+ void onResume() {}
+
+ void onPause() {}
+
+ void onDestroy() {}
+
final boolean isReady() {
return mSurfaceWidth > 0 && mSurfaceHeight > 0;
}
diff --git a/cameraview/src/main/views/com/otaliastudios/cameraview/GLCameraPreview.java b/cameraview/src/main/views/com/otaliastudios/cameraview/GLCameraPreview.java
new file mode 100644
index 00000000..30782dab
--- /dev/null
+++ b/cameraview/src/main/views/com/otaliastudios/cameraview/GLCameraPreview.java
@@ -0,0 +1,134 @@
+package com.otaliastudios.cameraview;
+
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.opengl.GLSurfaceView;
+import android.support.annotation.NonNull;
+import android.view.LayoutInflater;
+import android.view.SurfaceHolder;
+import android.view.View;
+import android.view.ViewGroup;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+class GLCameraPreview extends CameraPreview implements GLSurfaceView.Renderer {
+
+ private boolean mDispatched;
+ private final float[] mTransformMatrix = new float[16];
+ private int mTextureId = -1;
+ private SurfaceTexture mSurfaceTexture;
+ private GLViewport mViewport;
+
+ GLCameraPreview(Context context, ViewGroup parent, SurfaceCallback callback) {
+ super(context, parent, callback);
+ }
+
+ @NonNull
+ @Override
+ protected GLSurfaceView onCreateView(Context context, ViewGroup parent) {
+ View root = LayoutInflater.from(context).inflate(R.layout.cameraview_gl_view, parent, false);
+ parent.addView(root, 0);
+ GLSurfaceView glView = root.findViewById(R.id.gl_surface_view);
+ glView.setEGLContextClientVersion(2);
+ glView.setRenderer(this);
+ glView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
+ glView.getHolder().addCallback(new SurfaceHolder.Callback() {
+ public void surfaceCreated(SurfaceHolder holder) {}
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ onSurfaceDestroyed();
+ }
+ });
+ return glView;
+ }
+
+ @Override
+ void onResume() {
+ super.onResume();
+ getView().onResume();
+ }
+
+ @Override
+ void onPause() {
+ super.onPause();
+ getView().onPause();
+ }
+
+ @Override
+ void onDestroy() {
+ super.onDestroy();
+ if (mSurfaceTexture != null) {
+ mSurfaceTexture.release();
+ mSurfaceTexture = null;
+ }
+ if (mViewport != null) {
+ mViewport.release();
+ mViewport = null;
+ }
+ }
+
+ @Override
+ public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+ mViewport = new GLViewport();
+ mTextureId = mViewport.createTexture();
+ mSurfaceTexture = new SurfaceTexture(mTextureId);
+ mSurfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
+ @Override
+ public void onFrameAvailable(SurfaceTexture surfaceTexture) {
+ // requestRender is thread-safe.
+ getView().requestRender();
+ }
+ });
+ }
+
+ @Override
+ public void onSurfaceChanged(GL10 gl, int width, int height) {
+ if (!mDispatched) {
+ onSurfaceAvailable(width, height);
+ mDispatched = true;
+ } else {
+ onSurfaceSizeChanged(width, height);
+ }
+ }
+
+
+ @Override
+ public void onDrawFrame(GL10 gl) {
+ // Latch the latest frame. If there isn't anything new,
+ // we'll just re-use whatever was there before.
+ mSurfaceTexture.updateTexImage();
+ if (mDesiredWidth <= 0 || mDesiredHeight <= 0) {
+ // Skip drawing. Camera was not opened.
+ return;
+ }
+
+ // Draw the video frame.
+ mSurfaceTexture.getTransformMatrix(mTransformMatrix);
+ mViewport.drawFrame(mTextureId, mTransformMatrix);
+ }
+
+ @Override
+ Class getOutputClass() {
+ return SurfaceTexture.class;
+ }
+
+ @Override
+ SurfaceTexture getOutput() {
+ return mSurfaceTexture;
+ }
+
+
+ @Override
+ boolean supportsCropping() {
+ return true;
+ }
+
+ @Override
+ protected void crop() {
+ mCropTask.start();
+ mCropTask.end(null);
+ }
+}
diff --git a/cameraview/src/main/views/com/otaliastudios/cameraview/GLElement.java b/cameraview/src/main/views/com/otaliastudios/cameraview/GLElement.java
new file mode 100644
index 00000000..4c05cd29
--- /dev/null
+++ b/cameraview/src/main/views/com/otaliastudios/cameraview/GLElement.java
@@ -0,0 +1,92 @@
+package com.otaliastudios.cameraview;
+
+
+import android.opengl.GLES11Ext;
+import android.opengl.GLES20;
+import android.opengl.Matrix;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+class GLElement {
+
+ private final static CameraLogger LOG = CameraLogger.create(GLElement.class.getSimpleName());
+
+ // Identity matrix for general use.
+ protected static final float[] IDENTITY_MATRIX = new float[16];
+ static {
+ Matrix.setIdentityM(IDENTITY_MATRIX, 0);
+ }
+
+ // Check for GLES errors.
+ protected static void check(String opName) {
+ int error = GLES20.glGetError();
+ if (error != GLES20.GL_NO_ERROR) {
+ LOG.e("Error during", opName, "glError 0x", Integer.toHexString(error));
+ throw new RuntimeException(CameraLogger.lastMessage);
+ }
+ }
+
+ // Check for valid location.
+ protected static void checkLocation(int location, String label) {
+ if (location < 0) {
+ LOG.e("Unable to locate", label, "in program");
+ throw new RuntimeException(CameraLogger.lastMessage);
+ }
+ }
+
+ // Compiles the given shader, returns a handle.
+ protected static int loadShader(int shaderType, String source) {
+ int shader = GLES20.glCreateShader(shaderType);
+ check("glCreateShader type=" + shaderType);
+ 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("Could not compile shader", shaderType, ":", GLES20.glGetShaderInfoLog(shader));
+ GLES20.glDeleteShader(shader);
+ shader = 0;
+ }
+ return shader;
+ }
+
+ // Creates a program with given vertex shader and pixel shader.
+ protected static 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();
+ check("glCreateProgram");
+ if (program == 0) {
+ LOG.e("Could not create program");
+ }
+ GLES20.glAttachShader(program, vertexShader);
+ check("glAttachShader");
+ GLES20.glAttachShader(program, pixelShader);
+ check("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("Could not link program:", GLES20.glGetProgramInfoLog(program));
+ GLES20.glDeleteProgram(program);
+ program = 0;
+ }
+ return program;
+ }
+
+ // Allocates a direct float buffer, and populates it with the float array data.
+ protected static FloatBuffer floatBuffer(float[] coords) {
+ // Allocate a direct ByteBuffer, using 4 bytes per float, and copy coords into it.
+ ByteBuffer bb = ByteBuffer.allocateDirect(coords.length * 4);
+ bb.order(ByteOrder.nativeOrder());
+ FloatBuffer fb = bb.asFloatBuffer();
+ fb.put(coords);
+ fb.position(0);
+ return fb;
+ }
+}
diff --git a/cameraview/src/main/views/com/otaliastudios/cameraview/GLViewport.java b/cameraview/src/main/views/com/otaliastudios/cameraview/GLViewport.java
new file mode 100644
index 00000000..a8ced7a6
--- /dev/null
+++ b/cameraview/src/main/views/com/otaliastudios/cameraview/GLViewport.java
@@ -0,0 +1,179 @@
+package com.otaliastudios.cameraview;
+
+
+import android.opengl.GLES11Ext;
+import android.opengl.GLES20;
+import android.opengl.Matrix;
+import android.util.Log;
+
+import java.nio.FloatBuffer;
+
+class GLViewport extends GLElement {
+
+ private final static CameraLogger LOG = CameraLogger.create(GLViewport.class.getSimpleName());
+
+ // Simple vertex shader.
+ private static final String SIMPLE_VERTEX_SHADER =
+ "uniform mat4 uMVPMatrix;\n" +
+ "uniform mat4 uTexMatrix;\n" +
+ "attribute vec4 aPosition;\n" +
+ "attribute vec4 aTextureCoord;\n" +
+ "varying vec2 vTextureCoord;\n" +
+ "void main() {\n" +
+ " gl_Position = uMVPMatrix * aPosition;\n" +
+ " vTextureCoord = (uTexMatrix * aTextureCoord).xy;\n" +
+ "}\n";
+
+ // Simple fragment shader for use with external 2D textures
+ // (e.g. what we get from SurfaceTexture).
+ private static final String SIMPLE_FRAGMENT_SHADER =
+ "#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";
+
+ // Stuff from Drawable2d.FULL_RECTANGLE
+ // A full square, extending from -1 to +1 in both dimensions.
+ // When the model/view/projection matrix is identity, this will exactly cover the viewport.
+ private static final float FULL_RECTANGLE_COORDS[] = {
+ -1.0f, -1.0f, // 0 bottom left
+ 1.0f, -1.0f, // 1 bottom right
+ -1.0f, 1.0f, // 2 top left
+ 1.0f, 1.0f, // 3 top right
+ };
+
+ // Stuff from Drawable2d.FULL_RECTANGLE
+ // A full square, extending from -1 to +1 in both dimensions.
+ private static final float FULL_RECTANGLE_TEX_COORDS[] = {
+ 0.0f, 0.0f, // 0 bottom left
+ 1.0f, 0.0f, // 1 bottom right
+ 0.0f, 1.0f, // 2 top left
+ 1.0f, 1.0f // 3 top right
+ };
+
+ // Stuff from Drawable2d.FULL_RECTANGLE
+ private FloatBuffer mVertexArray = floatBuffer(FULL_RECTANGLE_COORDS);
+ private FloatBuffer mTexCoordArray = floatBuffer(FULL_RECTANGLE_TEX_COORDS);
+ private int mVertexCount = FULL_RECTANGLE_COORDS.length / 2;
+ private final int mCoordsPerVertex = 2;
+ private final int mVertexStride = 8;
+ private final int mTexCoordStride = 8;
+
+ // Stuff from Texture2dProgram
+ private int mProgramHandle;
+ private int mTextureTarget;
+ private int muMVPMatrixLoc;
+ private int muTexMatrixLoc;
+ private int maPositionLoc;
+ private int maTextureCoordLoc;
+
+ // private int muKernelLoc; // Used for filtering
+ // private int muTexOffsetLoc; // Used for filtering
+ // private int muColorAdjustLoc; // Used for filtering
+
+ GLViewport() {
+ mTextureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES;
+ mProgramHandle = createProgram(SIMPLE_VERTEX_SHADER, SIMPLE_FRAGMENT_SHADER);
+ maPositionLoc = GLES20.glGetAttribLocation(mProgramHandle, "aPosition");
+ checkLocation(maPositionLoc, "aPosition");
+ maTextureCoordLoc = GLES20.glGetAttribLocation(mProgramHandle, "aTextureCoord");
+ checkLocation(maTextureCoordLoc, "aTextureCoord");
+ muMVPMatrixLoc = GLES20.glGetUniformLocation(mProgramHandle, "uMVPMatrix");
+ checkLocation(muMVPMatrixLoc, "uMVPMatrix");
+ muTexMatrixLoc = GLES20.glGetUniformLocation(mProgramHandle, "uTexMatrix");
+ checkLocation(muTexMatrixLoc, "uTexMatrix");
+
+ // Stuff from Drawable2d.FULL_RECTANGLE
+
+ }
+
+
+ void release() {
+ GLES20.glDeleteProgram(mProgramHandle);
+ mProgramHandle = -1;
+ }
+
+ int createTexture() {
+ int[] textures = new int[1];
+ GLES20.glGenTextures(1, textures, 0);
+ check("glGenTextures");
+
+ int texId = textures[0];
+ GLES20.glBindTexture(mTextureTarget, texId);
+ check("glBindTexture " + texId);
+
+ GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
+ GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
+ GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
+ GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
+ GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
+ GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
+ GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
+ GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
+ check("glTexParameter");
+
+ return texId;
+ }
+
+ void drawFrame(int textureId, float[] texMatrix) {
+ drawFrame(textureId, texMatrix,
+ IDENTITY_MATRIX, mVertexArray, 0,
+ mVertexCount, mCoordsPerVertex,
+ mVertexStride, mTexCoordArray,
+ mTexCoordStride);
+ }
+
+ private void drawFrame(int textureId, float[] texMatrix,
+ float[] mvpMatrix, FloatBuffer vertexBuffer, int firstVertex,
+ int vertexCount, int coordsPerVertex, int vertexStride,
+ FloatBuffer texBuffer, int texStride) {
+ check("draw start");
+
+ // Select the program.
+ GLES20.glUseProgram(mProgramHandle);
+ check("glUseProgram");
+
+ // Set the texture.
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+ GLES20.glBindTexture(mTextureTarget, textureId);
+
+ // Copy the model / view / projection matrix over.
+ GLES20.glUniformMatrix4fv(muMVPMatrixLoc, 1, false, mvpMatrix, 0);
+ check("glUniformMatrix4fv");
+
+ // Copy the texture transformation matrix over.
+ GLES20.glUniformMatrix4fv(muTexMatrixLoc, 1, false, texMatrix, 0);
+ check("glUniformMatrix4fv");
+
+ // Enable the "aPosition" vertex attribute.
+ GLES20.glEnableVertexAttribArray(maPositionLoc);
+ check("glEnableVertexAttribArray");
+
+ // Connect vertexBuffer to "aPosition".
+ GLES20.glVertexAttribPointer(maPositionLoc, coordsPerVertex,
+ GLES20.GL_FLOAT, false, vertexStride, vertexBuffer);
+ check("glVertexAttribPointer");
+
+ // Enable the "aTextureCoord" vertex attribute.
+ GLES20.glEnableVertexAttribArray(maTextureCoordLoc);
+ check("glEnableVertexAttribArray");
+
+ // Connect texBuffer to "aTextureCoord".
+ GLES20.glVertexAttribPointer(maTextureCoordLoc, 2,
+ GLES20.GL_FLOAT, false, texStride, texBuffer);
+ check("glVertexAttribPointer");
+
+ // Draw the rect.
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, firstVertex, vertexCount);
+ check("glDrawArrays");
+
+ // Done -- disable vertex array, texture, and program.
+ GLES20.glDisableVertexAttribArray(maPositionLoc);
+ GLES20.glDisableVertexAttribArray(maTextureCoordLoc);
+ GLES20.glBindTexture(mTextureTarget, 0);
+ GLES20.glUseProgram(0);
+ }
+}
diff --git a/demo/src/main/res/layout/activity_camera.xml b/demo/src/main/res/layout/activity_camera.xml
index 5398cbcf..a159747d 100644
--- a/demo/src/main/res/layout/activity_camera.xml
+++ b/demo/src/main/res/layout/activity_camera.xml
@@ -11,10 +11,11 @@