1/*
2 * Copyright (C) 2011 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 */
16
17package com.android.camera.panorama;
18
19import android.app.Activity;
20import android.content.Context;
21import android.content.pm.ActivityInfo;
22import android.graphics.PixelFormat;
23import android.opengl.GLSurfaceView;
24import android.os.ConditionVariable;
25import android.util.AttributeSet;
26import android.util.Log;
27
28import javax.microedition.khronos.egl.EGL10;
29import javax.microedition.khronos.egl.EGLConfig;
30import javax.microedition.khronos.egl.EGLContext;
31import javax.microedition.khronos.egl.EGLDisplay;
32
33public class MosaicRendererSurfaceView extends GLSurfaceView {
34    private static final String TAG = "MosaicRendererSurfaceView";
35    private static final boolean DEBUG = false;
36    private MosaicRendererSurfaceViewRenderer mRenderer;
37    private ConditionVariable mPreviewFrameReadyForProcessing;
38    private boolean mIsLandscapeOrientation = true;
39
40    public MosaicRendererSurfaceView(Context context) {
41        super(context);
42        initialize(context, false, 0, 0);
43    }
44
45    public MosaicRendererSurfaceView(Context context, AttributeSet attrs) {
46        super(context, attrs);
47        initialize(context, false, 0, 0);
48    }
49
50    public MosaicRendererSurfaceView(Context context, boolean translucent,
51            int depth, int stencil) {
52        super(context);
53        initialize(context, translucent, depth, stencil);
54    }
55
56    private void initialize(Context context, boolean translucent, int depth, int stencil) {
57        getDisplayOrientation(context);
58        init(translucent, depth, stencil);
59        setZOrderMediaOverlay(true);
60    }
61
62    private void getDisplayOrientation(Context context) {
63        Activity activity = (PanoramaActivity) context;
64        mIsLandscapeOrientation = (activity.getRequestedOrientation()
65                == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE );
66    }
67
68    private void init(boolean translucent, int depth, int stencil) {
69
70        /* By default, GLSurfaceView() creates a RGB_565 opaque surface.
71         * If we want a translucent one, we should change the surface's
72         * format here, using PixelFormat.TRANSLUCENT for GL Surfaces
73         * is interpreted as any 32-bit surface with alpha by SurfaceFlinger.
74         */
75        if (translucent) {
76            this.getHolder().setFormat(PixelFormat.TRANSLUCENT);
77        }
78
79        /* Setup the context factory for 2.0 rendering.
80         * See ContextFactory class definition below
81         */
82        setEGLContextFactory(new ContextFactory());
83
84        /* We need to choose an EGLConfig that matches the format of
85         * our surface exactly. This is going to be done in our
86         * custom config chooser. See ConfigChooser class definition
87         * below.
88         */
89        setEGLConfigChooser(
90            translucent ? new ConfigChooser(8, 8, 8, 8, depth, stencil) :
91            new ConfigChooser(5, 6, 5, 0, depth, stencil));
92
93        /* Set the renderer responsible for frame rendering */
94        mRenderer = new MosaicRendererSurfaceViewRenderer(mIsLandscapeOrientation);
95        setRenderer(mRenderer);
96        setRenderMode(RENDERMODE_WHEN_DIRTY);
97        mPreviewFrameReadyForProcessing = new ConditionVariable();
98    }
99
100    private static class ContextFactory implements GLSurfaceView.EGLContextFactory {
101        private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
102        public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) {
103            Log.w(TAG, "creating OpenGL ES 2.0 context");
104            checkEglError("Before eglCreateContext", egl);
105            int[] attribList = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
106            EGLContext context = egl.eglCreateContext(
107                display, eglConfig, EGL10.EGL_NO_CONTEXT, attribList);
108            checkEglError("After eglCreateContext", egl);
109            return context;
110        }
111
112        public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) {
113            egl.eglDestroyContext(display, context);
114        }
115    }
116
117    private static void checkEglError(String prompt, EGL10 egl) {
118        int error;
119        while ((error = egl.eglGetError()) != EGL10.EGL_SUCCESS) {
120            Log.e(TAG, String.format("%s: EGL error: 0x%x", prompt, error));
121        }
122    }
123
124    private static class ConfigChooser implements GLSurfaceView.EGLConfigChooser {
125
126        public ConfigChooser(int r, int g, int b, int a, int depth, int stencil) {
127            mRedSize = r;
128            mGreenSize = g;
129            mBlueSize = b;
130            mAlphaSize = a;
131            mDepthSize = depth;
132            mStencilSize = stencil;
133        }
134
135        /* This EGL config specification is used to specify 2.0 rendering.
136         * We use a minimum size of 4 bits for red/green/blue, but will
137         * perform actual matching in chooseConfig() below.
138         */
139        private static final int EGL_OPENGL_ES2_BIT = 4;
140        private static final int[] CONFIG_ATTRIBUTES =
141        {
142            EGL10.EGL_RED_SIZE, 4,
143            EGL10.EGL_GREEN_SIZE, 4,
144            EGL10.EGL_BLUE_SIZE, 4,
145            EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
146            EGL10.EGL_NONE
147        };
148
149        public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
150
151            /* Get the number of minimally matching EGL configurations
152             */
153            int[] numConfig = new int[1];
154            egl.eglChooseConfig(display, CONFIG_ATTRIBUTES, null, 0, numConfig);
155
156            int numConfigs = numConfig[0];
157
158            if (numConfigs <= 0) {
159                throw new IllegalArgumentException("No configs match configSpec");
160            }
161
162            /* Allocate then read the array of minimally matching EGL configs
163             */
164            EGLConfig[] configs = new EGLConfig[numConfigs];
165            egl.eglChooseConfig(display, CONFIG_ATTRIBUTES, configs, numConfigs, numConfig);
166
167            if (DEBUG) {
168                 printConfigs(egl, display, configs);
169            }
170            /* Now return the "best" one
171             */
172            return chooseConfig(egl, display, configs);
173        }
174
175        public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,
176                EGLConfig[] configs) {
177            for (EGLConfig config : configs) {
178                int d = findConfigAttrib(egl, display, config,
179                        EGL10.EGL_DEPTH_SIZE, 0);
180                int s = findConfigAttrib(egl, display, config,
181                        EGL10.EGL_STENCIL_SIZE, 0);
182
183                // We need at least mDepthSize and mStencilSize bits
184                if (d < mDepthSize || s < mStencilSize)
185                    continue;
186
187                // We want an *exact* match for red/green/blue/alpha
188                int r = findConfigAttrib(egl, display, config,
189                        EGL10.EGL_RED_SIZE, 0);
190                int g = findConfigAttrib(egl, display, config,
191                            EGL10.EGL_GREEN_SIZE, 0);
192                int b = findConfigAttrib(egl, display, config,
193                            EGL10.EGL_BLUE_SIZE, 0);
194                int a = findConfigAttrib(egl, display, config,
195                        EGL10.EGL_ALPHA_SIZE, 0);
196
197                if (r == mRedSize && g == mGreenSize && b == mBlueSize && a == mAlphaSize)
198                    return config;
199            }
200            return null;
201        }
202
203        private int findConfigAttrib(EGL10 egl, EGLDisplay display,
204                EGLConfig config, int attribute, int defaultValue) {
205
206            if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
207                return mValue[0];
208            }
209            return defaultValue;
210        }
211
212        private void printConfigs(EGL10 egl, EGLDisplay display,
213            EGLConfig[] configs) {
214            int numConfigs = configs.length;
215            Log.w(TAG, String.format("%d configurations", numConfigs));
216            for (int i = 0; i < numConfigs; i++) {
217                Log.w(TAG, String.format("Configuration %d:\n", i));
218                printConfig(egl, display, configs[i]);
219            }
220        }
221
222        private void printConfig(EGL10 egl, EGLDisplay display,
223                EGLConfig config) {
224            int[] attributes = {
225                    EGL10.EGL_BUFFER_SIZE,
226                    EGL10.EGL_ALPHA_SIZE,
227                    EGL10.EGL_BLUE_SIZE,
228                    EGL10.EGL_GREEN_SIZE,
229                    EGL10.EGL_RED_SIZE,
230                    EGL10.EGL_DEPTH_SIZE,
231                    EGL10.EGL_STENCIL_SIZE,
232                    EGL10.EGL_CONFIG_CAVEAT,
233                    EGL10.EGL_CONFIG_ID,
234                    EGL10.EGL_LEVEL,
235                    EGL10.EGL_MAX_PBUFFER_HEIGHT,
236                    EGL10.EGL_MAX_PBUFFER_PIXELS,
237                    EGL10.EGL_MAX_PBUFFER_WIDTH,
238                    EGL10.EGL_NATIVE_RENDERABLE,
239                    EGL10.EGL_NATIVE_VISUAL_ID,
240                    EGL10.EGL_NATIVE_VISUAL_TYPE,
241                    0x3030, // EGL10.EGL_PRESERVED_RESOURCES,
242                    EGL10.EGL_SAMPLES,
243                    EGL10.EGL_SAMPLE_BUFFERS,
244                    EGL10.EGL_SURFACE_TYPE,
245                    EGL10.EGL_TRANSPARENT_TYPE,
246                    EGL10.EGL_TRANSPARENT_RED_VALUE,
247                    EGL10.EGL_TRANSPARENT_GREEN_VALUE,
248                    EGL10.EGL_TRANSPARENT_BLUE_VALUE,
249                    0x3039, // EGL10.EGL_BIND_TO_TEXTURE_RGB,
250                    0x303A, // EGL10.EGL_BIND_TO_TEXTURE_RGBA,
251                    0x303B, // EGL10.EGL_MIN_SWAP_INTERVAL,
252                    0x303C, // EGL10.EGL_MAX_SWAP_INTERVAL,
253                    EGL10.EGL_LUMINANCE_SIZE,
254                    EGL10.EGL_ALPHA_MASK_SIZE,
255                    EGL10.EGL_COLOR_BUFFER_TYPE,
256                    EGL10.EGL_RENDERABLE_TYPE,
257                    0x3042 // EGL10.EGL_CONFORMANT
258            };
259            String[] names = {
260                    "EGL_BUFFER_SIZE",
261                    "EGL_ALPHA_SIZE",
262                    "EGL_BLUE_SIZE",
263                    "EGL_GREEN_SIZE",
264                    "EGL_RED_SIZE",
265                    "EGL_DEPTH_SIZE",
266                    "EGL_STENCIL_SIZE",
267                    "EGL_CONFIG_CAVEAT",
268                    "EGL_CONFIG_ID",
269                    "EGL_LEVEL",
270                    "EGL_MAX_PBUFFER_HEIGHT",
271                    "EGL_MAX_PBUFFER_PIXELS",
272                    "EGL_MAX_PBUFFER_WIDTH",
273                    "EGL_NATIVE_RENDERABLE",
274                    "EGL_NATIVE_VISUAL_ID",
275                    "EGL_NATIVE_VISUAL_TYPE",
276                    "EGL_PRESERVED_RESOURCES",
277                    "EGL_SAMPLES",
278                    "EGL_SAMPLE_BUFFERS",
279                    "EGL_SURFACE_TYPE",
280                    "EGL_TRANSPARENT_TYPE",
281                    "EGL_TRANSPARENT_RED_VALUE",
282                    "EGL_TRANSPARENT_GREEN_VALUE",
283                    "EGL_TRANSPARENT_BLUE_VALUE",
284                    "EGL_BIND_TO_TEXTURE_RGB",
285                    "EGL_BIND_TO_TEXTURE_RGBA",
286                    "EGL_MIN_SWAP_INTERVAL",
287                    "EGL_MAX_SWAP_INTERVAL",
288                    "EGL_LUMINANCE_SIZE",
289                    "EGL_ALPHA_MASK_SIZE",
290                    "EGL_COLOR_BUFFER_TYPE",
291                    "EGL_RENDERABLE_TYPE",
292                    "EGL_CONFORMANT"
293            };
294            int[] value = new int[1];
295            for (int i = 0; i < attributes.length; i++) {
296                int attribute = attributes[i];
297                String name = names[i];
298                if (egl.eglGetConfigAttrib(display, config, attribute, value)) {
299                    Log.w(TAG, String.format("  %s: %d\n", name, value[0]));
300                } else {
301                    // Log.w(TAG, String.format("  %s: failed\n", name));
302                    while (egl.eglGetError() != EGL10.EGL_SUCCESS);
303                }
304            }
305        }
306
307        // Subclasses can adjust these values:
308        protected int mRedSize;
309        protected int mGreenSize;
310        protected int mBlueSize;
311        protected int mAlphaSize;
312        protected int mDepthSize;
313        protected int mStencilSize;
314        private int[] mValue = new int[1];
315    }
316
317    public void lockPreviewReadyFlag() {
318        mPreviewFrameReadyForProcessing.close();
319    }
320
321    private void unlockPreviewReadyFlag() {
322        mPreviewFrameReadyForProcessing.open();
323    }
324
325    public void waitUntilPreviewReady() {
326        mPreviewFrameReadyForProcessing.block();
327    }
328
329    public void setReady() {
330        queueEvent(new Runnable() {
331
332            @Override
333            public void run() {
334                mRenderer.setReady();
335            }
336        });
337    }
338
339    public void preprocess(final float[] transformMatrix) {
340        queueEvent(new Runnable() {
341
342            @Override
343            public void run() {
344                mRenderer.preprocess(transformMatrix);
345            }
346        });
347    }
348
349    public void transferGPUtoCPU() {
350        queueEvent(new Runnable() {
351
352            @Override
353            public void run() {
354                mRenderer.transferGPUtoCPU();
355                unlockPreviewReadyFlag();
356            }
357        });
358    }
359
360    public void setWarping(final boolean flag) {
361        queueEvent(new Runnable() {
362
363            @Override
364            public void run() {
365                mRenderer.setWarping(flag);
366            }
367        });
368    }
369
370    public MosaicRendererSurfaceViewRenderer getRenderer() {
371        return mRenderer;
372    }
373
374}
375