SurfaceTextureRenderer.java revision a9bc3559109836efe7479a3279713bd58810b153
1/*
2 * 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 */
16package android.hardware.camera2.legacy;
17
18import android.graphics.ImageFormat;
19import android.graphics.RectF;
20import android.graphics.SurfaceTexture;
21import android.os.Environment;
22import android.opengl.EGL14;
23import android.opengl.EGLConfig;
24import android.opengl.EGLContext;
25import android.opengl.EGLDisplay;
26import android.opengl.EGLSurface;
27import android.opengl.GLES11Ext;
28import android.opengl.GLES20;
29import android.opengl.Matrix;
30import android.text.format.Time;
31import android.util.Log;
32import android.util.Pair;
33import android.util.Size;
34import android.view.Surface;
35import android.os.SystemProperties;
36
37import java.io.File;
38import java.nio.ByteBuffer;
39import java.nio.ByteOrder;
40import java.nio.FloatBuffer;
41import java.util.ArrayList;
42import java.util.Collection;
43import java.util.List;
44
45/**
46 * A renderer class that manages the GL state, and can draw a frame into a set of output
47 * {@link Surface}s.
48 */
49public class SurfaceTextureRenderer {
50    private static final String TAG = SurfaceTextureRenderer.class.getSimpleName();
51    private static final boolean DEBUG = Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.DEBUG);
52    private static final int EGL_RECORDABLE_ANDROID = 0x3142; // from EGL/eglext.h
53    private static final int GL_MATRIX_SIZE = 16;
54    private static final int VERTEX_POS_SIZE = 3;
55    private static final int VERTEX_UV_SIZE = 2;
56    private static final int EGL_COLOR_BITLENGTH = 8;
57    private static final int GLES_VERSION = 2;
58    private static final int PBUFFER_PIXEL_BYTES = 4;
59
60    private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;
61    private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;
62    private EGLConfig mConfigs;
63
64    private class EGLSurfaceHolder {
65        Surface surface;
66        EGLSurface eglSurface;
67        int width;
68        int height;
69    }
70
71    private List<EGLSurfaceHolder> mSurfaces = new ArrayList<EGLSurfaceHolder>();
72    private List<EGLSurfaceHolder> mConversionSurfaces = new ArrayList<EGLSurfaceHolder>();
73
74    private ByteBuffer mPBufferPixels;
75
76    // Hold this to avoid GC
77    private volatile SurfaceTexture mSurfaceTexture;
78
79    private static final int FLOAT_SIZE_BYTES = 4;
80    private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
81    private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
82    private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
83    private final float[] mTriangleVerticesData = {
84            // X, Y, Z, U, V
85            -1.0f, -1.0f, 0, 0.f, 0.f,
86            1.0f, -1.0f, 0, 1.f, 0.f,
87            -1.0f,  1.0f, 0, 0.f, 1.f,
88            1.0f,  1.0f, 0, 1.f, 1.f,
89    };
90
91    private FloatBuffer mTriangleVertices;
92
93    /**
94     * As used in this file, this vertex shader maps a unit square to the view, and
95     * tells the fragment shader to interpolate over it.  Each surface pixel position
96     * is mapped to a 2D homogeneous texture coordinate of the form (s, t, 0, 1) with
97     * s and t in the inclusive range [0, 1], and the matrix from
98     * {@link SurfaceTexture#getTransformMatrix(float[])} is used to map this
99     * coordinate to a texture location.
100     */
101    private static final String VERTEX_SHADER =
102            "uniform mat4 uMVPMatrix;\n" +
103            "uniform mat4 uSTMatrix;\n" +
104            "attribute vec4 aPosition;\n" +
105            "attribute vec4 aTextureCoord;\n" +
106            "varying vec2 vTextureCoord;\n" +
107            "void main() {\n" +
108            "  gl_Position = uMVPMatrix * aPosition;\n" +
109            "  vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
110            "}\n";
111
112    /**
113     * This fragment shader simply draws the color in the 2D texture at
114     * the location from the {@code VERTEX_SHADER}.
115     */
116    private static final String FRAGMENT_SHADER =
117            "#extension GL_OES_EGL_image_external : require\n" +
118            "precision mediump float;\n" +
119            "varying vec2 vTextureCoord;\n" +
120            "uniform samplerExternalOES sTexture;\n" +
121            "void main() {\n" +
122            "  gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
123            "}\n";
124
125    private float[] mMVPMatrix = new float[GL_MATRIX_SIZE];
126    private float[] mSTMatrix = new float[GL_MATRIX_SIZE];
127
128    private int mProgram;
129    private int mTextureID = 0;
130    private int muMVPMatrixHandle;
131    private int muSTMatrixHandle;
132    private int maPositionHandle;
133    private int maTextureHandle;
134
135    private PerfMeasurement mPerfMeasurer = null;
136    private static final String LEGACY_PERF_PROPERTY = "persist.camera.legacy_perf";
137
138    public SurfaceTextureRenderer() {
139        mTriangleVertices = ByteBuffer.allocateDirect(mTriangleVerticesData.length *
140                FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
141        mTriangleVertices.put(mTriangleVerticesData).position(0);
142        Matrix.setIdentityM(mSTMatrix, 0);
143    }
144
145    private int loadShader(int shaderType, String source) {
146        int shader = GLES20.glCreateShader(shaderType);
147        checkGlError("glCreateShader type=" + shaderType);
148        GLES20.glShaderSource(shader, source);
149        GLES20.glCompileShader(shader);
150        int[] compiled = new int[1];
151        GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
152        if (compiled[0] == 0) {
153            Log.e(TAG, "Could not compile shader " + shaderType + ":");
154            Log.e(TAG, " " + GLES20.glGetShaderInfoLog(shader));
155            GLES20.glDeleteShader(shader);
156            // TODO: handle this more gracefully
157            throw new IllegalStateException("Could not compile shader " + shaderType);
158        }
159        return shader;
160    }
161
162    private int createProgram(String vertexSource, String fragmentSource) {
163        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
164        if (vertexShader == 0) {
165            return 0;
166        }
167        int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
168        if (pixelShader == 0) {
169            return 0;
170        }
171
172        int program = GLES20.glCreateProgram();
173        checkGlError("glCreateProgram");
174        if (program == 0) {
175            Log.e(TAG, "Could not create program");
176        }
177        GLES20.glAttachShader(program, vertexShader);
178        checkGlError("glAttachShader");
179        GLES20.glAttachShader(program, pixelShader);
180        checkGlError("glAttachShader");
181        GLES20.glLinkProgram(program);
182        int[] linkStatus = new int[1];
183        GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
184        if (linkStatus[0] != GLES20.GL_TRUE) {
185            Log.e(TAG, "Could not link program: ");
186            Log.e(TAG, GLES20.glGetProgramInfoLog(program));
187            GLES20.glDeleteProgram(program);
188            // TODO: handle this more gracefully
189            throw new IllegalStateException("Could not link program");
190        }
191        return program;
192    }
193
194    private void drawFrame(SurfaceTexture st, int width, int height) {
195        checkGlError("onDrawFrame start");
196        st.getTransformMatrix(mSTMatrix);
197
198        Matrix.setIdentityM(mMVPMatrix, /*smOffset*/0);
199
200        // Find intermediate buffer dimensions
201        Size dimens;
202        try {
203            dimens = LegacyCameraDevice.getTextureSize(st);
204        } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
205            // Should never hit this.
206            throw new IllegalStateException("Surface abandoned, skipping drawFrame...", e);
207        }
208        float texWidth = dimens.getWidth();
209        float texHeight = dimens.getHeight();
210
211        if (texWidth <= 0 || texHeight <= 0) {
212            throw new IllegalStateException("Illegal intermediate texture with dimension of 0");
213        }
214
215        // Letterbox or pillerbox output dimensions into intermediate dimensions.
216        RectF intermediate = new RectF(/*left*/0, /*top*/0, /*right*/texWidth, /*bottom*/texHeight);
217        RectF output = new RectF(/*left*/0, /*top*/0, /*right*/width, /*bottom*/height);
218        android.graphics.Matrix boxingXform = new android.graphics.Matrix();
219        boxingXform.setRectToRect(output, intermediate, android.graphics.Matrix.ScaleToFit.CENTER);
220        boxingXform.mapRect(output);
221
222        // Find scaling factor from pillerboxed/letterboxed output dimensions to intermediate
223        // buffer dimensions.
224        float scaleX = intermediate.width() / output.width();
225        float scaleY = intermediate.height() / output.height();
226
227        // Scale opposite dimension in clip coordinates so output is letterboxed/pillerboxed into
228        // the intermediate dimensions (rather than vice-versa).
229        Matrix.scaleM(mMVPMatrix, /*offset*/0, /*x*/scaleY, /*y*/scaleX, /*z*/1);
230
231        if (DEBUG) {
232            Log.d(TAG, "Scaling factors (S_x = " + scaleX + ",S_y = " + scaleY + ") used for " +
233                    width + "x" + height + " surface, intermediate buffer size is " + texWidth +
234                    "x" + texHeight);
235        }
236
237        // Set viewport to be output buffer dimensions
238        GLES20.glViewport(0, 0, width, height);
239
240        if (DEBUG) {
241            GLES20.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
242            GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
243        }
244
245        GLES20.glUseProgram(mProgram);
246        checkGlError("glUseProgram");
247
248        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
249        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);
250
251        mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
252        GLES20.glVertexAttribPointer(maPositionHandle, VERTEX_POS_SIZE, GLES20.GL_FLOAT,
253                /*normalized*/ false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
254        checkGlError("glVertexAttribPointer maPosition");
255        GLES20.glEnableVertexAttribArray(maPositionHandle);
256        checkGlError("glEnableVertexAttribArray maPositionHandle");
257
258        mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
259        GLES20.glVertexAttribPointer(maTextureHandle, VERTEX_UV_SIZE, GLES20.GL_FLOAT,
260                /*normalized*/ false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
261        checkGlError("glVertexAttribPointer maTextureHandle");
262        GLES20.glEnableVertexAttribArray(maTextureHandle);
263        checkGlError("glEnableVertexAttribArray maTextureHandle");
264
265        GLES20.glUniformMatrix4fv(muMVPMatrixHandle, /*count*/ 1, /*transpose*/ false, mMVPMatrix,
266                /*offset*/ 0);
267        GLES20.glUniformMatrix4fv(muSTMatrixHandle, /*count*/ 1, /*transpose*/ false, mSTMatrix,
268                /*offset*/ 0);
269
270        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /*offset*/ 0, /*count*/ 4);
271        checkGlError("glDrawArrays");
272    }
273
274    /**
275     * Initializes GL state.  Call this after the EGL surface has been created and made current.
276     */
277    private void initializeGLState() {
278        mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
279        if (mProgram == 0) {
280            throw new IllegalStateException("failed creating program");
281        }
282        maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
283        checkGlError("glGetAttribLocation aPosition");
284        if (maPositionHandle == -1) {
285            throw new IllegalStateException("Could not get attrib location for aPosition");
286        }
287        maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
288        checkGlError("glGetAttribLocation aTextureCoord");
289        if (maTextureHandle == -1) {
290            throw new IllegalStateException("Could not get attrib location for aTextureCoord");
291        }
292
293        muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
294        checkGlError("glGetUniformLocation uMVPMatrix");
295        if (muMVPMatrixHandle == -1) {
296            throw new IllegalStateException("Could not get attrib location for uMVPMatrix");
297        }
298
299        muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix");
300        checkGlError("glGetUniformLocation uSTMatrix");
301        if (muSTMatrixHandle == -1) {
302            throw new IllegalStateException("Could not get attrib location for uSTMatrix");
303        }
304
305        int[] textures = new int[1];
306        GLES20.glGenTextures(/*n*/ 1, textures, /*offset*/ 0);
307
308        mTextureID = textures[0];
309        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);
310        checkGlError("glBindTexture mTextureID");
311
312        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
313                GLES20.GL_NEAREST);
314        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
315                GLES20.GL_LINEAR);
316        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S,
317                GLES20.GL_CLAMP_TO_EDGE);
318        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T,
319                GLES20.GL_CLAMP_TO_EDGE);
320        checkGlError("glTexParameter");
321    }
322
323    private int getTextureId() {
324        return mTextureID;
325    }
326
327    private void clearState() {
328        mSurfaces.clear();
329        mConversionSurfaces.clear();
330        mPBufferPixels = null;
331        if (mSurfaceTexture != null) {
332            mSurfaceTexture.release();
333        }
334        mSurfaceTexture = null;
335    }
336
337    private void configureEGLContext() {
338        mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
339        if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
340            throw new IllegalStateException("No EGL14 display");
341        }
342        int[] version = new int[2];
343        if (!EGL14.eglInitialize(mEGLDisplay, version, /*offset*/ 0, version, /*offset*/ 1)) {
344            throw new IllegalStateException("Cannot initialize EGL14");
345        }
346
347        int[] attribList = {
348                EGL14.EGL_RED_SIZE, EGL_COLOR_BITLENGTH,
349                EGL14.EGL_GREEN_SIZE, EGL_COLOR_BITLENGTH,
350                EGL14.EGL_BLUE_SIZE, EGL_COLOR_BITLENGTH,
351                EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
352                EGL_RECORDABLE_ANDROID, 1,
353                EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT | EGL14.EGL_WINDOW_BIT,
354                EGL14.EGL_NONE
355        };
356        EGLConfig[] configs = new EGLConfig[1];
357        int[] numConfigs = new int[1];
358        EGL14.eglChooseConfig(mEGLDisplay, attribList, /*offset*/ 0, configs, /*offset*/ 0,
359                configs.length, numConfigs, /*offset*/ 0);
360        checkEglError("eglCreateContext RGB888+recordable ES2");
361        mConfigs = configs[0];
362        int[] attrib_list = {
363                EGL14.EGL_CONTEXT_CLIENT_VERSION, GLES_VERSION,
364                EGL14.EGL_NONE
365        };
366        mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT,
367                attrib_list, /*offset*/ 0);
368        checkEglError("eglCreateContext");
369        if(mEGLContext == EGL14.EGL_NO_CONTEXT) {
370            throw new IllegalStateException("No EGLContext could be made");
371        }
372    }
373
374    private void configureEGLOutputSurfaces(Collection<EGLSurfaceHolder> surfaces) {
375        if (surfaces == null || surfaces.size() == 0) {
376            throw new IllegalStateException("No Surfaces were provided to draw to");
377        }
378        int[] surfaceAttribs = {
379                EGL14.EGL_NONE
380        };
381        for (EGLSurfaceHolder holder : surfaces) {
382            try {
383                Size size = LegacyCameraDevice.getSurfaceSize(holder.surface);
384                holder.width = size.getWidth();
385                holder.height = size.getHeight();
386                holder.eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mConfigs,
387                        holder.surface, surfaceAttribs, /*offset*/ 0);
388                checkEglError("eglCreateWindowSurface");
389            } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
390                Log.w(TAG, "Surface abandoned, skipping...", e);
391            }
392        }
393    }
394
395    private void configureEGLPbufferSurfaces(Collection<EGLSurfaceHolder> surfaces) {
396        if (surfaces == null || surfaces.size() == 0) {
397            throw new IllegalStateException("No Surfaces were provided to draw to");
398        }
399
400        int maxLength = 0;
401        for (EGLSurfaceHolder holder : surfaces) {
402            try {
403                Size size = LegacyCameraDevice.getSurfaceSize(holder.surface);
404                int length = size.getWidth() * size.getHeight();
405                // Find max surface size, ensure PBuffer can hold this many pixels
406                maxLength = (length > maxLength) ? length : maxLength;
407                int[] surfaceAttribs = {
408                        EGL14.EGL_WIDTH, size.getWidth(),
409                        EGL14.EGL_HEIGHT, size.getHeight(),
410                        EGL14.EGL_NONE
411                };
412                holder.width = size.getWidth();
413                holder.height = size.getHeight();
414                holder.eglSurface =
415                        EGL14.eglCreatePbufferSurface(mEGLDisplay, mConfigs, surfaceAttribs, 0);
416                checkEglError("eglCreatePbufferSurface");
417            } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
418                Log.w(TAG, "Surface abandoned, skipping...", e);
419            }
420        }
421        mPBufferPixels = ByteBuffer.allocateDirect(maxLength * PBUFFER_PIXEL_BYTES)
422                .order(ByteOrder.nativeOrder());
423    }
424
425    private void releaseEGLContext() {
426        if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
427            EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
428                    EGL14.EGL_NO_CONTEXT);
429            dumpGlTiming();
430            if (mSurfaces != null) {
431                for (EGLSurfaceHolder holder : mSurfaces) {
432                    if (holder.eglSurface != null) {
433                        EGL14.eglDestroySurface(mEGLDisplay, holder.eglSurface);
434                    }
435                }
436            }
437            if (mConversionSurfaces != null) {
438                for (EGLSurfaceHolder holder : mConversionSurfaces) {
439                    if (holder.eglSurface != null) {
440                        EGL14.eglDestroySurface(mEGLDisplay, holder.eglSurface);
441                    }
442                }
443            }
444            EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
445            EGL14.eglReleaseThread();
446            EGL14.eglTerminate(mEGLDisplay);
447        }
448
449        mConfigs = null;
450        mEGLDisplay = EGL14.EGL_NO_DISPLAY;
451        mEGLContext = EGL14.EGL_NO_CONTEXT;
452        clearState();
453    }
454
455    private void makeCurrent(EGLSurface surface) {
456        EGL14.eglMakeCurrent(mEGLDisplay, surface, surface, mEGLContext);
457        checkEglError("makeCurrent");
458    }
459
460    private boolean swapBuffers(EGLSurface surface) {
461        boolean result = EGL14.eglSwapBuffers(mEGLDisplay, surface);
462        checkEglError("swapBuffers");
463        return result;
464    }
465
466    private void checkEglError(String msg) {
467        int error;
468        if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
469            throw new IllegalStateException(msg + ": EGL error: 0x" + Integer.toHexString(error));
470        }
471    }
472
473    private void checkGlError(String msg) {
474        int error;
475        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
476            throw new IllegalStateException(msg + ": GLES20 error: 0x" + Integer.toHexString(error));
477        }
478    }
479
480    /**
481     * Save a measurement dump to disk, in
482     * {@code /sdcard/CameraLegacy/durations_<time>_<width1>x<height1>_...txt}
483     */
484    private void dumpGlTiming() {
485        if (mPerfMeasurer == null) return;
486
487        File legacyStorageDir = new File(Environment.getExternalStorageDirectory(), "CameraLegacy");
488        if (!legacyStorageDir.exists()){
489            if (!legacyStorageDir.mkdirs()){
490                Log.e(TAG, "Failed to create directory for data dump");
491                return;
492            }
493        }
494
495        StringBuilder path = new StringBuilder(legacyStorageDir.getPath());
496        path.append(File.separator);
497        path.append("durations_");
498
499        Time now = new Time();
500        now.setToNow();
501        path.append(now.format2445());
502        path.append("_S");
503        for (EGLSurfaceHolder surface : mSurfaces) {
504            path.append(String.format("_%d_%d", surface.width, surface.height));
505        }
506        path.append("_C");
507        for (EGLSurfaceHolder surface : mConversionSurfaces) {
508            path.append(String.format("_%d_%d", surface.width, surface.height));
509        }
510        path.append(".txt");
511        mPerfMeasurer.dumpPerformanceData(path.toString());
512    }
513
514    private void setupGlTiming() {
515        if (PerfMeasurement.isGlTimingSupported()) {
516            Log.d(TAG, "Enabling GL performance measurement");
517            mPerfMeasurer = new PerfMeasurement();
518        } else {
519            Log.d(TAG, "GL performance measurement not supported on this device");
520            mPerfMeasurer = null;
521        }
522    }
523
524    private void beginGlTiming() {
525        if (mPerfMeasurer == null) return;
526        mPerfMeasurer.startTimer();
527    }
528
529    private void addGlTimestamp(long timestamp) {
530        if (mPerfMeasurer == null) return;
531        mPerfMeasurer.addTimestamp(timestamp);
532    }
533
534    private void endGlTiming() {
535        if (mPerfMeasurer == null) return;
536        mPerfMeasurer.stopTimer();
537    }
538
539    /**
540     * Return the surface texture to draw to - this is the texture use to when producing output
541     * surface buffers.
542     *
543     * @return a {@link SurfaceTexture}.
544     */
545    public SurfaceTexture getSurfaceTexture() {
546        return mSurfaceTexture;
547    }
548
549    /**
550     * Set a collection of output {@link Surface}s that can be drawn to.
551     *
552     * @param surfaces a {@link Collection} of surfaces.
553     */
554    public void configureSurfaces(Collection<Surface> surfaces) {
555        releaseEGLContext();
556
557        if (surfaces == null || surfaces.size() == 0) {
558            Log.w(TAG, "No output surfaces configured for GL drawing.");
559            return;
560        }
561
562        for (Surface s : surfaces) {
563            // If pixel conversions aren't handled by egl, use a pbuffer
564            try {
565                if (LegacyCameraDevice.needsConversion(s)) {
566                    LegacyCameraDevice.setSurfaceFormat(s, ImageFormat.YV12);
567                    EGLSurfaceHolder holder = new EGLSurfaceHolder();
568                    holder.surface = s;
569                    mConversionSurfaces.add(holder);
570                } else {
571                    EGLSurfaceHolder holder = new EGLSurfaceHolder();
572                    holder.surface = s;
573                    mSurfaces.add(holder);
574                }
575            } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
576                Log.w(TAG, "Surface abandoned, skipping configuration... ", e);
577            }
578        }
579
580        // Set up egl display
581        configureEGLContext();
582
583        // Set up regular egl surfaces if needed
584        if (mSurfaces.size() > 0) {
585            configureEGLOutputSurfaces(mSurfaces);
586        }
587
588        // Set up pbuffer surface if needed
589        if (mConversionSurfaces.size() > 0) {
590            configureEGLPbufferSurfaces(mConversionSurfaces);
591        }
592        makeCurrent((mSurfaces.size() > 0) ? mSurfaces.get(0).eglSurface :
593                mConversionSurfaces.get(0).eglSurface);
594        initializeGLState();
595        mSurfaceTexture = new SurfaceTexture(getTextureId());
596
597        // Set up performance tracking if enabled
598        if (SystemProperties.getBoolean(LEGACY_PERF_PROPERTY, false)) {
599            setupGlTiming();
600        }
601    }
602
603    /**
604     * Draw the current buffer in the {@link SurfaceTexture} returned from
605     * {@link #getSurfaceTexture()} into the set of target {@link Surface}s
606     * in the next request from the given {@link CaptureCollector}, or drop
607     * the frame if none is available.
608     *
609     * <p>
610     * Any {@link Surface}s targeted must be a subset of the {@link Surface}s
611     * set in the last {@link #configureSurfaces(java.util.Collection)} call.
612     * </p>
613     *
614     * @param targetCollector the surfaces to draw to.
615     */
616    public void drawIntoSurfaces(CaptureCollector targetCollector) {
617        if ((mSurfaces == null || mSurfaces.size() == 0)
618                && (mConversionSurfaces == null || mConversionSurfaces.size() == 0)) {
619            return;
620        }
621
622        boolean doTiming = targetCollector.hasPendingPreviewCaptures();
623        checkGlError("before updateTexImage");
624
625        if (doTiming) {
626            beginGlTiming();
627        }
628
629        mSurfaceTexture.updateTexImage();
630
631        long timestamp = mSurfaceTexture.getTimestamp();
632
633        Pair<RequestHolder, Long> captureHolder = targetCollector.previewCaptured(timestamp);
634
635        // No preview request queued, drop frame.
636        if (captureHolder == null) {
637            Log.w(TAG, "Dropping preview frame.");
638            if (doTiming) {
639                endGlTiming();
640            }
641            return;
642        }
643
644        RequestHolder request = captureHolder.first;
645
646        Collection<Surface> targetSurfaces = request.getHolderTargets();
647        if (doTiming) {
648            addGlTimestamp(timestamp);
649        }
650
651        List<Long> targetSurfaceIds = LegacyCameraDevice.getSurfaceIds(targetSurfaces);
652        for (EGLSurfaceHolder holder : mSurfaces) {
653            if (LegacyCameraDevice.containsSurfaceId(holder.surface, targetSurfaceIds)) {
654                makeCurrent(holder.eglSurface);
655                try {
656                    LegacyCameraDevice.setSurfaceDimens(holder.surface, holder.width,
657                            holder.height);
658                    LegacyCameraDevice.setNextTimestamp(holder.surface, captureHolder.second);
659                    drawFrame(mSurfaceTexture, holder.width, holder.height);
660                    swapBuffers(holder.eglSurface);
661                } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
662                    Log.w(TAG, "Surface abandoned, dropping frame. ", e);
663                }
664            }
665        }
666        for (EGLSurfaceHolder holder : mConversionSurfaces) {
667            if (LegacyCameraDevice.containsSurfaceId(holder.surface, targetSurfaceIds)) {
668                makeCurrent(holder.eglSurface);
669                drawFrame(mSurfaceTexture, holder.width, holder.height);
670                mPBufferPixels.clear();
671                GLES20.glReadPixels(/*x*/ 0, /*y*/ 0, holder.width, holder.height,
672                        GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mPBufferPixels);
673                checkGlError("glReadPixels");
674
675                try {
676                    int format = LegacyCameraDevice.detectSurfaceType(holder.surface);
677                    LegacyCameraDevice.setNextTimestamp(holder.surface, captureHolder.second);
678                    LegacyCameraDevice.produceFrame(holder.surface, mPBufferPixels.array(),
679                            holder.width, holder.height, format);
680                    swapBuffers(holder.eglSurface);
681                } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
682                    Log.w(TAG, "Surface abandoned, dropping frame. ", e);
683                }
684            }
685        }
686        targetCollector.previewProduced();
687
688        if (doTiming) {
689            endGlTiming();
690        }
691    }
692
693    /**
694     * Clean up the current GL context.
695     */
696    public void cleanupEGLContext() {
697        releaseEGLContext();
698    }
699
700    /**
701     * Drop all current GL operations on the floor.
702     */
703    public void flush() {
704        // TODO: implement flush
705        Log.e(TAG, "Flush not yet implemented.");
706    }
707}
708