blob: a05a8ec00e797c5c6b0c2d8fb0ace7920daa7a10 [file] [log] [blame]
Ruben Brunkfeb50af2014-05-09 19:58:49 -07001/*
Ruben Brunk28c49c92014-06-16 18:43:59 -07002 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
Ruben Brunkfeb50af2014-05-09 19:58:49 -070016package android.hardware.camera2.legacy;
17
18import android.graphics.ImageFormat;
Ruben Brunka9bc3552014-08-14 15:57:28 -070019import android.graphics.RectF;
Ruben Brunkfeb50af2014-05-09 19:58:49 -070020import android.graphics.SurfaceTexture;
Ruben Brunkb68dd5c2014-09-04 21:23:42 -070021import android.hardware.camera2.CameraCharacteristics;
Eino-Ville Talvalae1f57d62014-05-29 17:17:07 -070022import android.os.Environment;
Ruben Brunkfeb50af2014-05-09 19:58:49 -070023import android.opengl.EGL14;
24import android.opengl.EGLConfig;
25import android.opengl.EGLContext;
26import android.opengl.EGLDisplay;
27import android.opengl.EGLSurface;
28import android.opengl.GLES11Ext;
29import android.opengl.GLES20;
30import android.opengl.Matrix;
Eino-Ville Talvalae1f57d62014-05-29 17:17:07 -070031import android.text.format.Time;
Ruben Brunkfeb50af2014-05-09 19:58:49 -070032import android.util.Log;
Ruben Brunk91838de2014-07-16 17:24:17 -070033import android.util.Pair;
Ruben Brunkef14da32014-06-24 16:06:54 -070034import android.util.Size;
Ruben Brunkfeb50af2014-05-09 19:58:49 -070035import android.view.Surface;
Eino-Ville Talvalae1f57d62014-05-29 17:17:07 -070036import android.os.SystemProperties;
Ruben Brunkfeb50af2014-05-09 19:58:49 -070037
Eino-Ville Talvalae1f57d62014-05-29 17:17:07 -070038import java.io.File;
Ruben Brunkfeb50af2014-05-09 19:58:49 -070039import java.nio.ByteBuffer;
40import java.nio.ByteOrder;
41import java.nio.FloatBuffer;
42import java.util.ArrayList;
43import java.util.Collection;
44import java.util.List;
45
46/**
47 * A renderer class that manages the GL state, and can draw a frame into a set of output
48 * {@link Surface}s.
49 */
50public class SurfaceTextureRenderer {
51 private static final String TAG = SurfaceTextureRenderer.class.getSimpleName();
Eino-Ville Talvalaa78791f2015-06-01 12:39:54 -070052 private static final boolean DEBUG = false;
Ruben Brunkfeb50af2014-05-09 19:58:49 -070053 private static final int EGL_RECORDABLE_ANDROID = 0x3142; // from EGL/eglext.h
54 private static final int GL_MATRIX_SIZE = 16;
55 private static final int VERTEX_POS_SIZE = 3;
56 private static final int VERTEX_UV_SIZE = 2;
57 private static final int EGL_COLOR_BITLENGTH = 8;
58 private static final int GLES_VERSION = 2;
59 private static final int PBUFFER_PIXEL_BYTES = 4;
60
Ruben Brunk433e7152014-12-10 19:12:41 -080061 private static final int FLIP_TYPE_NONE = 0;
62 private static final int FLIP_TYPE_HORIZONTAL = 1;
63 private static final int FLIP_TYPE_VERTICAL = 2;
64 private static final int FLIP_TYPE_BOTH = FLIP_TYPE_HORIZONTAL | FLIP_TYPE_VERTICAL;
65
Ruben Brunkfeb50af2014-05-09 19:58:49 -070066 private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;
67 private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;
68 private EGLConfig mConfigs;
69
70 private class EGLSurfaceHolder {
71 Surface surface;
72 EGLSurface eglSurface;
73 int width;
74 int height;
75 }
76
77 private List<EGLSurfaceHolder> mSurfaces = new ArrayList<EGLSurfaceHolder>();
78 private List<EGLSurfaceHolder> mConversionSurfaces = new ArrayList<EGLSurfaceHolder>();
79
80 private ByteBuffer mPBufferPixels;
81
82 // Hold this to avoid GC
83 private volatile SurfaceTexture mSurfaceTexture;
84
85 private static final int FLOAT_SIZE_BYTES = 4;
86 private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
87 private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
88 private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
Ruben Brunkb68dd5c2014-09-04 21:23:42 -070089
Ruben Brunk433e7152014-12-10 19:12:41 -080090 // Sampling is mirrored across the horizontal axis
91 private static final float[] sHorizontalFlipTriangleVertices = {
Ruben Brunkb68dd5c2014-09-04 21:23:42 -070092 // X, Y, Z, U, V
93 -1.0f, -1.0f, 0, 1.f, 0.f,
94 1.0f, -1.0f, 0, 0.f, 0.f,
95 -1.0f, 1.0f, 0, 1.f, 1.f,
96 1.0f, 1.0f, 0, 0.f, 1.f,
97 };
98
Ruben Brunk433e7152014-12-10 19:12:41 -080099 // Sampling is mirrored across the vertical axis
100 private static final float[] sVerticalFlipTriangleVertices = {
101 // X, Y, Z, U, V
102 -1.0f, -1.0f, 0, 0.f, 1.f,
103 1.0f, -1.0f, 0, 1.f, 1.f,
104 -1.0f, 1.0f, 0, 0.f, 0.f,
105 1.0f, 1.0f, 0, 1.f, 0.f,
106 };
107
108 // Sampling is mirrored across the both axes
109 private static final float[] sBothFlipTriangleVertices = {
110 // X, Y, Z, U, V
111 -1.0f, -1.0f, 0, 1.f, 1.f,
112 1.0f, -1.0f, 0, 0.f, 1.f,
113 -1.0f, 1.0f, 0, 1.f, 0.f,
114 1.0f, 1.0f, 0, 0.f, 0.f,
115 };
116
Ruben Brunkb68dd5c2014-09-04 21:23:42 -0700117 // Sampling is 1:1 for a straight copy for the back camera
Ruben Brunk433e7152014-12-10 19:12:41 -0800118 private static final float[] sRegularTriangleVertices = {
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700119 // X, Y, Z, U, V
120 -1.0f, -1.0f, 0, 0.f, 0.f,
121 1.0f, -1.0f, 0, 1.f, 0.f,
122 -1.0f, 1.0f, 0, 0.f, 1.f,
123 1.0f, 1.0f, 0, 1.f, 1.f,
124 };
125
Ruben Brunk433e7152014-12-10 19:12:41 -0800126 private FloatBuffer mRegularTriangleVertices;
127 private FloatBuffer mHorizontalFlipTriangleVertices;
128 private FloatBuffer mVerticalFlipTriangleVertices;
129 private FloatBuffer mBothFlipTriangleVertices;
130 private final int mFacing;
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700131
132 /**
133 * As used in this file, this vertex shader maps a unit square to the view, and
134 * tells the fragment shader to interpolate over it. Each surface pixel position
135 * is mapped to a 2D homogeneous texture coordinate of the form (s, t, 0, 1) with
136 * s and t in the inclusive range [0, 1], and the matrix from
137 * {@link SurfaceTexture#getTransformMatrix(float[])} is used to map this
138 * coordinate to a texture location.
139 */
140 private static final String VERTEX_SHADER =
141 "uniform mat4 uMVPMatrix;\n" +
Ruben Brunk28c49c92014-06-16 18:43:59 -0700142 "uniform mat4 uSTMatrix;\n" +
143 "attribute vec4 aPosition;\n" +
144 "attribute vec4 aTextureCoord;\n" +
145 "varying vec2 vTextureCoord;\n" +
146 "void main() {\n" +
147 " gl_Position = uMVPMatrix * aPosition;\n" +
148 " vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
149 "}\n";
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700150
151 /**
152 * This fragment shader simply draws the color in the 2D texture at
153 * the location from the {@code VERTEX_SHADER}.
154 */
155 private static final String FRAGMENT_SHADER =
156 "#extension GL_OES_EGL_image_external : require\n" +
Ruben Brunk28c49c92014-06-16 18:43:59 -0700157 "precision mediump float;\n" +
158 "varying vec2 vTextureCoord;\n" +
159 "uniform samplerExternalOES sTexture;\n" +
160 "void main() {\n" +
161 " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
162 "}\n";
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700163
164 private float[] mMVPMatrix = new float[GL_MATRIX_SIZE];
165 private float[] mSTMatrix = new float[GL_MATRIX_SIZE];
166
167 private int mProgram;
168 private int mTextureID = 0;
169 private int muMVPMatrixHandle;
170 private int muSTMatrixHandle;
171 private int maPositionHandle;
172 private int maTextureHandle;
173
Eino-Ville Talvalae1f57d62014-05-29 17:17:07 -0700174 private PerfMeasurement mPerfMeasurer = null;
175 private static final String LEGACY_PERF_PROPERTY = "persist.camera.legacy_perf";
176
Ruben Brunkb68dd5c2014-09-04 21:23:42 -0700177 public SurfaceTextureRenderer(int facing) {
Ruben Brunk433e7152014-12-10 19:12:41 -0800178 mFacing = facing;
179
180 mRegularTriangleVertices = ByteBuffer.allocateDirect(sRegularTriangleVertices.length *
181 FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
182 mRegularTriangleVertices.put(sRegularTriangleVertices).position(0);
183
184 mHorizontalFlipTriangleVertices = ByteBuffer.allocateDirect(
185 sHorizontalFlipTriangleVertices.length * FLOAT_SIZE_BYTES).
186 order(ByteOrder.nativeOrder()).asFloatBuffer();
187 mHorizontalFlipTriangleVertices.put(sHorizontalFlipTriangleVertices).position(0);
188
189 mVerticalFlipTriangleVertices = ByteBuffer.allocateDirect(
190 sVerticalFlipTriangleVertices.length * FLOAT_SIZE_BYTES).
191 order(ByteOrder.nativeOrder()).asFloatBuffer();
192 mVerticalFlipTriangleVertices.put(sVerticalFlipTriangleVertices).position(0);
193
194 mBothFlipTriangleVertices = ByteBuffer.allocateDirect(
195 sBothFlipTriangleVertices.length * FLOAT_SIZE_BYTES).
196 order(ByteOrder.nativeOrder()).asFloatBuffer();
197 mBothFlipTriangleVertices.put(sBothFlipTriangleVertices).position(0);
198
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700199 Matrix.setIdentityM(mSTMatrix, 0);
200 }
201
202 private int loadShader(int shaderType, String source) {
203 int shader = GLES20.glCreateShader(shaderType);
204 checkGlError("glCreateShader type=" + shaderType);
205 GLES20.glShaderSource(shader, source);
206 GLES20.glCompileShader(shader);
207 int[] compiled = new int[1];
208 GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
209 if (compiled[0] == 0) {
210 Log.e(TAG, "Could not compile shader " + shaderType + ":");
211 Log.e(TAG, " " + GLES20.glGetShaderInfoLog(shader));
212 GLES20.glDeleteShader(shader);
213 // TODO: handle this more gracefully
214 throw new IllegalStateException("Could not compile shader " + shaderType);
215 }
216 return shader;
217 }
218
219 private int createProgram(String vertexSource, String fragmentSource) {
220 int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
221 if (vertexShader == 0) {
222 return 0;
223 }
224 int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
225 if (pixelShader == 0) {
226 return 0;
227 }
228
229 int program = GLES20.glCreateProgram();
230 checkGlError("glCreateProgram");
231 if (program == 0) {
232 Log.e(TAG, "Could not create program");
233 }
234 GLES20.glAttachShader(program, vertexShader);
235 checkGlError("glAttachShader");
236 GLES20.glAttachShader(program, pixelShader);
237 checkGlError("glAttachShader");
238 GLES20.glLinkProgram(program);
239 int[] linkStatus = new int[1];
240 GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
241 if (linkStatus[0] != GLES20.GL_TRUE) {
242 Log.e(TAG, "Could not link program: ");
243 Log.e(TAG, GLES20.glGetProgramInfoLog(program));
244 GLES20.glDeleteProgram(program);
245 // TODO: handle this more gracefully
246 throw new IllegalStateException("Could not link program");
247 }
248 return program;
249 }
250
Yin-Chia Yeh09166132016-10-19 13:53:33 -0700251 private void drawFrame(SurfaceTexture st, int width, int height, int flipType)
252 throws LegacyExceptionUtils.BufferQueueAbandonedException {
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700253 checkGlError("onDrawFrame start");
254 st.getTransformMatrix(mSTMatrix);
255
Ruben Brunka9bc3552014-08-14 15:57:28 -0700256 Matrix.setIdentityM(mMVPMatrix, /*smOffset*/0);
257
258 // Find intermediate buffer dimensions
Ruben Brunk28c49c92014-06-16 18:43:59 -0700259 Size dimens;
260 try {
261 dimens = LegacyCameraDevice.getTextureSize(st);
262 } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
263 // Should never hit this.
264 throw new IllegalStateException("Surface abandoned, skipping drawFrame...", e);
265 }
Ruben Brunk28c49c92014-06-16 18:43:59 -0700266 float texWidth = dimens.getWidth();
267 float texHeight = dimens.getHeight();
268
269 if (texWidth <= 0 || texHeight <= 0) {
270 throw new IllegalStateException("Illegal intermediate texture with dimension of 0");
271 }
272
Ruben Brunk259f3d92015-02-12 12:16:44 -0800273 // Letterbox or pillar-box output dimensions into intermediate dimensions.
Ruben Brunka9bc3552014-08-14 15:57:28 -0700274 RectF intermediate = new RectF(/*left*/0, /*top*/0, /*right*/texWidth, /*bottom*/texHeight);
275 RectF output = new RectF(/*left*/0, /*top*/0, /*right*/width, /*bottom*/height);
276 android.graphics.Matrix boxingXform = new android.graphics.Matrix();
277 boxingXform.setRectToRect(output, intermediate, android.graphics.Matrix.ScaleToFit.CENTER);
278 boxingXform.mapRect(output);
279
Ruben Brunk259f3d92015-02-12 12:16:44 -0800280 // Find scaling factor from pillar-boxed/letter-boxed output dimensions to intermediate
Ruben Brunka9bc3552014-08-14 15:57:28 -0700281 // buffer dimensions.
282 float scaleX = intermediate.width() / output.width();
283 float scaleY = intermediate.height() / output.height();
284
Ruben Brunk259f3d92015-02-12 12:16:44 -0800285 // Intermediate texture is implicitly scaled to 'fill' the output dimensions in clip space
286 // coordinates in the shader. To avoid stretching, we need to scale the larger dimension
287 // of the intermediate buffer so that the output buffer is actually letter-boxed
288 // or pillar-boxed into the intermediate buffer after clipping.
289 Matrix.scaleM(mMVPMatrix, /*offset*/0, /*x*/scaleX, /*y*/scaleY, /*z*/1);
Ruben Brunk28c49c92014-06-16 18:43:59 -0700290
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700291 if (DEBUG) {
Ruben Brunka9bc3552014-08-14 15:57:28 -0700292 Log.d(TAG, "Scaling factors (S_x = " + scaleX + ",S_y = " + scaleY + ") used for " +
293 width + "x" + height + " surface, intermediate buffer size is " + texWidth +
294 "x" + texHeight);
Ruben Brunk28c49c92014-06-16 18:43:59 -0700295 }
296
Ruben Brunka9bc3552014-08-14 15:57:28 -0700297 // Set viewport to be output buffer dimensions
298 GLES20.glViewport(0, 0, width, height);
Ruben Brunk28c49c92014-06-16 18:43:59 -0700299
300 if (DEBUG) {
301 GLES20.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700302 GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
303 }
304
305 GLES20.glUseProgram(mProgram);
306 checkGlError("glUseProgram");
307
308 GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
309 GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);
310
Ruben Brunk433e7152014-12-10 19:12:41 -0800311 FloatBuffer triangleVertices;
312 switch(flipType) {
313 case FLIP_TYPE_HORIZONTAL:
314 triangleVertices = mHorizontalFlipTriangleVertices;
315 break;
316 case FLIP_TYPE_VERTICAL:
317 triangleVertices = mVerticalFlipTriangleVertices;
318 break;
319 case FLIP_TYPE_BOTH:
320 triangleVertices = mBothFlipTriangleVertices;
321 break;
322 default:
323 triangleVertices = mRegularTriangleVertices;
324 break;
325 }
326
327 triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700328 GLES20.glVertexAttribPointer(maPositionHandle, VERTEX_POS_SIZE, GLES20.GL_FLOAT,
Ruben Brunk433e7152014-12-10 19:12:41 -0800329 /*normalized*/ false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700330 checkGlError("glVertexAttribPointer maPosition");
331 GLES20.glEnableVertexAttribArray(maPositionHandle);
332 checkGlError("glEnableVertexAttribArray maPositionHandle");
333
Ruben Brunk433e7152014-12-10 19:12:41 -0800334 triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700335 GLES20.glVertexAttribPointer(maTextureHandle, VERTEX_UV_SIZE, GLES20.GL_FLOAT,
Ruben Brunk433e7152014-12-10 19:12:41 -0800336 /*normalized*/ false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700337 checkGlError("glVertexAttribPointer maTextureHandle");
338 GLES20.glEnableVertexAttribArray(maTextureHandle);
339 checkGlError("glEnableVertexAttribArray maTextureHandle");
340
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700341 GLES20.glUniformMatrix4fv(muMVPMatrixHandle, /*count*/ 1, /*transpose*/ false, mMVPMatrix,
342 /*offset*/ 0);
343 GLES20.glUniformMatrix4fv(muSTMatrixHandle, /*count*/ 1, /*transpose*/ false, mSTMatrix,
344 /*offset*/ 0);
345
346 GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /*offset*/ 0, /*count*/ 4);
Yin-Chia Yeh09166132016-10-19 13:53:33 -0700347 checkGlDrawError("glDrawArrays");
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700348 }
349
350 /**
351 * Initializes GL state. Call this after the EGL surface has been created and made current.
352 */
353 private void initializeGLState() {
354 mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
355 if (mProgram == 0) {
356 throw new IllegalStateException("failed creating program");
357 }
358 maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
359 checkGlError("glGetAttribLocation aPosition");
360 if (maPositionHandle == -1) {
361 throw new IllegalStateException("Could not get attrib location for aPosition");
362 }
363 maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
364 checkGlError("glGetAttribLocation aTextureCoord");
365 if (maTextureHandle == -1) {
366 throw new IllegalStateException("Could not get attrib location for aTextureCoord");
367 }
368
369 muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
370 checkGlError("glGetUniformLocation uMVPMatrix");
371 if (muMVPMatrixHandle == -1) {
372 throw new IllegalStateException("Could not get attrib location for uMVPMatrix");
373 }
374
375 muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix");
376 checkGlError("glGetUniformLocation uSTMatrix");
377 if (muSTMatrixHandle == -1) {
378 throw new IllegalStateException("Could not get attrib location for uSTMatrix");
379 }
380
381 int[] textures = new int[1];
382 GLES20.glGenTextures(/*n*/ 1, textures, /*offset*/ 0);
383
384 mTextureID = textures[0];
385 GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);
386 checkGlError("glBindTexture mTextureID");
387
388 GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
389 GLES20.GL_NEAREST);
390 GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
391 GLES20.GL_LINEAR);
392 GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S,
393 GLES20.GL_CLAMP_TO_EDGE);
394 GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T,
395 GLES20.GL_CLAMP_TO_EDGE);
396 checkGlError("glTexParameter");
397 }
398
399 private int getTextureId() {
400 return mTextureID;
401 }
402
403 private void clearState() {
404 mSurfaces.clear();
Chien-Yu Chen29c36302016-03-02 15:34:00 -0800405 for (EGLSurfaceHolder holder : mConversionSurfaces) {
406 try {
407 LegacyCameraDevice.disconnectSurface(holder.surface);
408 } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
409 Log.w(TAG, "Surface abandoned, skipping...", e);
410 }
411 }
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700412 mConversionSurfaces.clear();
413 mPBufferPixels = null;
Ruben Brunk3fe9eba2014-07-24 13:33:47 -0700414 if (mSurfaceTexture != null) {
415 mSurfaceTexture.release();
416 }
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700417 mSurfaceTexture = null;
418 }
419
420 private void configureEGLContext() {
421 mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
422 if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
423 throw new IllegalStateException("No EGL14 display");
424 }
425 int[] version = new int[2];
426 if (!EGL14.eglInitialize(mEGLDisplay, version, /*offset*/ 0, version, /*offset*/ 1)) {
427 throw new IllegalStateException("Cannot initialize EGL14");
428 }
429
430 int[] attribList = {
431 EGL14.EGL_RED_SIZE, EGL_COLOR_BITLENGTH,
432 EGL14.EGL_GREEN_SIZE, EGL_COLOR_BITLENGTH,
433 EGL14.EGL_BLUE_SIZE, EGL_COLOR_BITLENGTH,
434 EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
435 EGL_RECORDABLE_ANDROID, 1,
436 EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT | EGL14.EGL_WINDOW_BIT,
437 EGL14.EGL_NONE
438 };
439 EGLConfig[] configs = new EGLConfig[1];
440 int[] numConfigs = new int[1];
441 EGL14.eglChooseConfig(mEGLDisplay, attribList, /*offset*/ 0, configs, /*offset*/ 0,
442 configs.length, numConfigs, /*offset*/ 0);
443 checkEglError("eglCreateContext RGB888+recordable ES2");
444 mConfigs = configs[0];
445 int[] attrib_list = {
446 EGL14.EGL_CONTEXT_CLIENT_VERSION, GLES_VERSION,
447 EGL14.EGL_NONE
448 };
449 mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT,
450 attrib_list, /*offset*/ 0);
451 checkEglError("eglCreateContext");
452 if(mEGLContext == EGL14.EGL_NO_CONTEXT) {
453 throw new IllegalStateException("No EGLContext could be made");
454 }
455 }
456
457 private void configureEGLOutputSurfaces(Collection<EGLSurfaceHolder> surfaces) {
458 if (surfaces == null || surfaces.size() == 0) {
459 throw new IllegalStateException("No Surfaces were provided to draw to");
460 }
461 int[] surfaceAttribs = {
462 EGL14.EGL_NONE
463 };
464 for (EGLSurfaceHolder holder : surfaces) {
Ruben Brunkf4a637d2014-11-20 18:01:36 -0800465 holder.eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mConfigs,
466 holder.surface, surfaceAttribs, /*offset*/ 0);
467 checkEglError("eglCreateWindowSurface");
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700468 }
469 }
470
471 private void configureEGLPbufferSurfaces(Collection<EGLSurfaceHolder> surfaces) {
472 if (surfaces == null || surfaces.size() == 0) {
473 throw new IllegalStateException("No Surfaces were provided to draw to");
474 }
475
476 int maxLength = 0;
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700477 for (EGLSurfaceHolder holder : surfaces) {
Ruben Brunkf4a637d2014-11-20 18:01:36 -0800478 int length = holder.width * holder.height;
479 // Find max surface size, ensure PBuffer can hold this many pixels
480 maxLength = (length > maxLength) ? length : maxLength;
481 int[] surfaceAttribs = {
482 EGL14.EGL_WIDTH, holder.width,
483 EGL14.EGL_HEIGHT, holder.height,
484 EGL14.EGL_NONE
485 };
486 holder.eglSurface =
487 EGL14.eglCreatePbufferSurface(mEGLDisplay, mConfigs, surfaceAttribs, 0);
488 checkEglError("eglCreatePbufferSurface");
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700489 }
490 mPBufferPixels = ByteBuffer.allocateDirect(maxLength * PBUFFER_PIXEL_BYTES)
491 .order(ByteOrder.nativeOrder());
492 }
493
494 private void releaseEGLContext() {
495 if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
496 EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
497 EGL14.EGL_NO_CONTEXT);
Eino-Ville Talvalae1f57d62014-05-29 17:17:07 -0700498 dumpGlTiming();
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700499 if (mSurfaces != null) {
500 for (EGLSurfaceHolder holder : mSurfaces) {
501 if (holder.eglSurface != null) {
502 EGL14.eglDestroySurface(mEGLDisplay, holder.eglSurface);
503 }
504 }
505 }
506 if (mConversionSurfaces != null) {
507 for (EGLSurfaceHolder holder : mConversionSurfaces) {
508 if (holder.eglSurface != null) {
509 EGL14.eglDestroySurface(mEGLDisplay, holder.eglSurface);
510 }
511 }
512 }
513 EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
514 EGL14.eglReleaseThread();
515 EGL14.eglTerminate(mEGLDisplay);
516 }
517
518 mConfigs = null;
519 mEGLDisplay = EGL14.EGL_NO_DISPLAY;
520 mEGLContext = EGL14.EGL_NO_CONTEXT;
521 clearState();
522 }
523
524 private void makeCurrent(EGLSurface surface) {
525 EGL14.eglMakeCurrent(mEGLDisplay, surface, surface, mEGLContext);
526 checkEglError("makeCurrent");
527 }
528
Chien-Yu Chen2da496f2016-04-14 13:33:00 -0700529 private boolean swapBuffers(EGLSurface surface)
530 throws LegacyExceptionUtils.BufferQueueAbandonedException {
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700531 boolean result = EGL14.eglSwapBuffers(mEGLDisplay, surface);
Chien-Yu Chen2da496f2016-04-14 13:33:00 -0700532 int error = EGL14.eglGetError();
533 if (error == EGL14.EGL_BAD_SURFACE) {
534 throw new LegacyExceptionUtils.BufferQueueAbandonedException();
535 } else if (error != EGL14.EGL_SUCCESS) {
536 throw new IllegalStateException("swapBuffers: EGL error: 0x" +
537 Integer.toHexString(error));
538 }
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700539 return result;
540 }
541
542 private void checkEglError(String msg) {
543 int error;
544 if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
545 throw new IllegalStateException(msg + ": EGL error: 0x" + Integer.toHexString(error));
546 }
547 }
548
549 private void checkGlError(String msg) {
550 int error;
551 while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
Yin-Chia Yeh09166132016-10-19 13:53:33 -0700552 throw new IllegalStateException(
553 msg + ": GLES20 error: 0x" + Integer.toHexString(error));
554 }
555 }
556
557 private void checkGlDrawError(String msg)
558 throws LegacyExceptionUtils.BufferQueueAbandonedException {
559 int error;
560 boolean surfaceAbandoned = false;
561 boolean glError = false;
562 while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
563 if (error == GLES20.GL_OUT_OF_MEMORY) {
564 surfaceAbandoned = true;
565 } else {
566 glError = true;
567 }
568 }
569 if (glError) {
570 throw new IllegalStateException(
571 msg + ": GLES20 error: 0x" + Integer.toHexString(error));
572 }
573 if (surfaceAbandoned) {
574 throw new LegacyExceptionUtils.BufferQueueAbandonedException();
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700575 }
576 }
577
578 /**
Eino-Ville Talvalae1f57d62014-05-29 17:17:07 -0700579 * Save a measurement dump to disk, in
580 * {@code /sdcard/CameraLegacy/durations_<time>_<width1>x<height1>_...txt}
581 */
582 private void dumpGlTiming() {
583 if (mPerfMeasurer == null) return;
584
585 File legacyStorageDir = new File(Environment.getExternalStorageDirectory(), "CameraLegacy");
586 if (!legacyStorageDir.exists()){
587 if (!legacyStorageDir.mkdirs()){
588 Log.e(TAG, "Failed to create directory for data dump");
589 return;
590 }
591 }
592
593 StringBuilder path = new StringBuilder(legacyStorageDir.getPath());
594 path.append(File.separator);
595 path.append("durations_");
596
597 Time now = new Time();
598 now.setToNow();
599 path.append(now.format2445());
600 path.append("_S");
601 for (EGLSurfaceHolder surface : mSurfaces) {
602 path.append(String.format("_%d_%d", surface.width, surface.height));
603 }
604 path.append("_C");
605 for (EGLSurfaceHolder surface : mConversionSurfaces) {
606 path.append(String.format("_%d_%d", surface.width, surface.height));
607 }
608 path.append(".txt");
609 mPerfMeasurer.dumpPerformanceData(path.toString());
610 }
611
612 private void setupGlTiming() {
613 if (PerfMeasurement.isGlTimingSupported()) {
614 Log.d(TAG, "Enabling GL performance measurement");
615 mPerfMeasurer = new PerfMeasurement();
616 } else {
617 Log.d(TAG, "GL performance measurement not supported on this device");
618 mPerfMeasurer = null;
619 }
620 }
621
622 private void beginGlTiming() {
623 if (mPerfMeasurer == null) return;
624 mPerfMeasurer.startTimer();
625 }
626
627 private void addGlTimestamp(long timestamp) {
628 if (mPerfMeasurer == null) return;
629 mPerfMeasurer.addTimestamp(timestamp);
630 }
631
632 private void endGlTiming() {
633 if (mPerfMeasurer == null) return;
634 mPerfMeasurer.stopTimer();
635 }
636
637 /**
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700638 * Return the surface texture to draw to - this is the texture use to when producing output
639 * surface buffers.
640 *
641 * @return a {@link SurfaceTexture}.
642 */
643 public SurfaceTexture getSurfaceTexture() {
644 return mSurfaceTexture;
645 }
646
647 /**
648 * Set a collection of output {@link Surface}s that can be drawn to.
649 *
650 * @param surfaces a {@link Collection} of surfaces.
651 */
Ruben Brunkf4a637d2014-11-20 18:01:36 -0800652 public void configureSurfaces(Collection<Pair<Surface, Size>> surfaces) {
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700653 releaseEGLContext();
654
Ruben Brunkd85e1a62014-06-11 10:35:45 -0700655 if (surfaces == null || surfaces.size() == 0) {
656 Log.w(TAG, "No output surfaces configured for GL drawing.");
657 return;
658 }
659
Ruben Brunkf4a637d2014-11-20 18:01:36 -0800660 for (Pair<Surface, Size> p : surfaces) {
661 Surface s = p.first;
662 Size surfaceSize = p.second;
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700663 // If pixel conversions aren't handled by egl, use a pbuffer
Ruben Brunkef14da32014-06-24 16:06:54 -0700664 try {
Ruben Brunkf4a637d2014-11-20 18:01:36 -0800665 EGLSurfaceHolder holder = new EGLSurfaceHolder();
666 holder.surface = s;
667 holder.width = surfaceSize.getWidth();
668 holder.height = surfaceSize.getHeight();
Ruben Brunkef14da32014-06-24 16:06:54 -0700669 if (LegacyCameraDevice.needsConversion(s)) {
Ruben Brunkef14da32014-06-24 16:06:54 -0700670 mConversionSurfaces.add(holder);
Chien-Yu Chen29c36302016-03-02 15:34:00 -0800671 // LegacyCameraDevice is the producer of surfaces if it's not handled by EGL,
672 // so LegacyCameraDevice needs to connect to the surfaces.
673 LegacyCameraDevice.connectSurface(s);
Ruben Brunkef14da32014-06-24 16:06:54 -0700674 } else {
Ruben Brunkef14da32014-06-24 16:06:54 -0700675 mSurfaces.add(holder);
676 }
677 } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
678 Log.w(TAG, "Surface abandoned, skipping configuration... ", e);
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700679 }
680 }
681
682 // Set up egl display
683 configureEGLContext();
684
685 // Set up regular egl surfaces if needed
686 if (mSurfaces.size() > 0) {
687 configureEGLOutputSurfaces(mSurfaces);
688 }
689
690 // Set up pbuffer surface if needed
691 if (mConversionSurfaces.size() > 0) {
692 configureEGLPbufferSurfaces(mConversionSurfaces);
693 }
694 makeCurrent((mSurfaces.size() > 0) ? mSurfaces.get(0).eglSurface :
695 mConversionSurfaces.get(0).eglSurface);
696 initializeGLState();
697 mSurfaceTexture = new SurfaceTexture(getTextureId());
Eino-Ville Talvalae1f57d62014-05-29 17:17:07 -0700698
699 // Set up performance tracking if enabled
700 if (SystemProperties.getBoolean(LEGACY_PERF_PROPERTY, false)) {
701 setupGlTiming();
702 }
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700703 }
704
705 /**
706 * Draw the current buffer in the {@link SurfaceTexture} returned from
Ruben Brunk91838de2014-07-16 17:24:17 -0700707 * {@link #getSurfaceTexture()} into the set of target {@link Surface}s
708 * in the next request from the given {@link CaptureCollector}, or drop
709 * the frame if none is available.
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700710 *
711 * <p>
Ruben Brunk91838de2014-07-16 17:24:17 -0700712 * Any {@link Surface}s targeted must be a subset of the {@link Surface}s
713 * set in the last {@link #configureSurfaces(java.util.Collection)} call.
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700714 * </p>
715 *
Ruben Brunk91838de2014-07-16 17:24:17 -0700716 * @param targetCollector the surfaces to draw to.
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700717 */
Ruben Brunk91838de2014-07-16 17:24:17 -0700718 public void drawIntoSurfaces(CaptureCollector targetCollector) {
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700719 if ((mSurfaces == null || mSurfaces.size() == 0)
720 && (mConversionSurfaces == null || mConversionSurfaces.size() == 0)) {
721 return;
722 }
Ruben Brunk3c8fa3b2014-06-30 16:45:05 -0700723
Ruben Brunk91838de2014-07-16 17:24:17 -0700724 boolean doTiming = targetCollector.hasPendingPreviewCaptures();
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700725 checkGlError("before updateTexImage");
Eino-Ville Talvalae1f57d62014-05-29 17:17:07 -0700726
Ruben Brunk91838de2014-07-16 17:24:17 -0700727 if (doTiming) {
728 beginGlTiming();
Eino-Ville Talvalae1f57d62014-05-29 17:17:07 -0700729 }
730
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700731 mSurfaceTexture.updateTexImage();
Eino-Ville Talvalae1f57d62014-05-29 17:17:07 -0700732
733 long timestamp = mSurfaceTexture.getTimestamp();
Ruben Brunk91838de2014-07-16 17:24:17 -0700734
735 Pair<RequestHolder, Long> captureHolder = targetCollector.previewCaptured(timestamp);
736
737 // No preview request queued, drop frame.
738 if (captureHolder == null) {
Ruben Brunke663cb772014-09-16 13:18:31 -0700739 if (DEBUG) {
740 Log.d(TAG, "Dropping preview frame.");
741 }
Ruben Brunk91838de2014-07-16 17:24:17 -0700742 if (doTiming) {
743 endGlTiming();
744 }
745 return;
746 }
747
748 RequestHolder request = captureHolder.first;
749
750 Collection<Surface> targetSurfaces = request.getHolderTargets();
751 if (doTiming) {
752 addGlTimestamp(timestamp);
753 }
Eino-Ville Talvalae1f57d62014-05-29 17:17:07 -0700754
Chien-Yu Chen2da496f2016-04-14 13:33:00 -0700755 List<Long> targetSurfaceIds = new ArrayList();
756 try {
757 targetSurfaceIds = LegacyCameraDevice.getSurfaceIds(targetSurfaces);
758 } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
759 Log.w(TAG, "Surface abandoned, dropping frame. ", e);
760 request.setOutputAbandoned();
761 }
762
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700763 for (EGLSurfaceHolder holder : mSurfaces) {
Ruben Brunk3c8fa3b2014-06-30 16:45:05 -0700764 if (LegacyCameraDevice.containsSurfaceId(holder.surface, targetSurfaceIds)) {
Ruben Brunkf4a637d2014-11-20 18:01:36 -0800765 try{
Ruben Brunka9bc3552014-08-14 15:57:28 -0700766 LegacyCameraDevice.setSurfaceDimens(holder.surface, holder.width,
767 holder.height);
Ruben Brunkf4a637d2014-11-20 18:01:36 -0800768 makeCurrent(holder.eglSurface);
769
Ruben Brunk91838de2014-07-16 17:24:17 -0700770 LegacyCameraDevice.setNextTimestamp(holder.surface, captureHolder.second);
Ruben Brunk433e7152014-12-10 19:12:41 -0800771 drawFrame(mSurfaceTexture, holder.width, holder.height,
772 (mFacing == CameraCharacteristics.LENS_FACING_FRONT) ?
773 FLIP_TYPE_HORIZONTAL : FLIP_TYPE_NONE);
Ruben Brunk91838de2014-07-16 17:24:17 -0700774 swapBuffers(holder.eglSurface);
775 } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
776 Log.w(TAG, "Surface abandoned, dropping frame. ", e);
Chien-Yu Chen2da496f2016-04-14 13:33:00 -0700777 request.setOutputAbandoned();
Ruben Brunk91838de2014-07-16 17:24:17 -0700778 }
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700779 }
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700780 }
781 for (EGLSurfaceHolder holder : mConversionSurfaces) {
Ruben Brunk3c8fa3b2014-06-30 16:45:05 -0700782 if (LegacyCameraDevice.containsSurfaceId(holder.surface, targetSurfaceIds)) {
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700783 makeCurrent(holder.eglSurface);
Ruben Brunk433e7152014-12-10 19:12:41 -0800784 // glReadPixels reads from the bottom of the buffer, so add an extra vertical flip
Yin-Chia Yeh09166132016-10-19 13:53:33 -0700785 try {
786 drawFrame(mSurfaceTexture, holder.width, holder.height,
787 (mFacing == CameraCharacteristics.LENS_FACING_FRONT) ?
788 FLIP_TYPE_BOTH : FLIP_TYPE_VERTICAL);
789 } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
790 // Should never hit this.
791 throw new IllegalStateException("Surface abandoned, skipping drawFrame...", e);
792 }
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700793 mPBufferPixels.clear();
Ruben Brunk28c49c92014-06-16 18:43:59 -0700794 GLES20.glReadPixels(/*x*/ 0, /*y*/ 0, holder.width, holder.height,
795 GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mPBufferPixels);
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700796 checkGlError("glReadPixels");
Ruben Brunk28c49c92014-06-16 18:43:59 -0700797
Ruben Brunkef14da32014-06-24 16:06:54 -0700798 try {
799 int format = LegacyCameraDevice.detectSurfaceType(holder.surface);
Ruben Brunkf4a637d2014-11-20 18:01:36 -0800800 LegacyCameraDevice.setSurfaceDimens(holder.surface, holder.width,
801 holder.height);
Ruben Brunk91838de2014-07-16 17:24:17 -0700802 LegacyCameraDevice.setNextTimestamp(holder.surface, captureHolder.second);
Ruben Brunkef14da32014-06-24 16:06:54 -0700803 LegacyCameraDevice.produceFrame(holder.surface, mPBufferPixels.array(),
804 holder.width, holder.height, format);
Ruben Brunkef14da32014-06-24 16:06:54 -0700805 } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
806 Log.w(TAG, "Surface abandoned, dropping frame. ", e);
Chien-Yu Chen2da496f2016-04-14 13:33:00 -0700807 request.setOutputAbandoned();
Ruben Brunkef14da32014-06-24 16:06:54 -0700808 }
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700809 }
810 }
Ruben Brunk91838de2014-07-16 17:24:17 -0700811 targetCollector.previewProduced();
Eino-Ville Talvalae1f57d62014-05-29 17:17:07 -0700812
Ruben Brunk91838de2014-07-16 17:24:17 -0700813 if (doTiming) {
814 endGlTiming();
815 }
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700816 }
817
818 /**
819 * Clean up the current GL context.
820 */
821 public void cleanupEGLContext() {
822 releaseEGLContext();
823 }
824
825 /**
826 * Drop all current GL operations on the floor.
827 */
828 public void flush() {
829 // TODO: implement flush
830 Log.e(TAG, "Flush not yet implemented.");
831 }
832}