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;
18
19import android.annotation.TargetApi;
20import android.graphics.SurfaceTexture;
21import android.os.ConditionVariable;
22import android.os.Handler;
23import android.os.HandlerThread;
24import android.os.Looper;
25import android.os.Message;
26import android.util.Log;
27
28import com.android.gallery3d.common.ApiHelper;
29
30import javax.microedition.khronos.egl.EGL10;
31import javax.microedition.khronos.egl.EGLConfig;
32import javax.microedition.khronos.egl.EGLContext;
33import javax.microedition.khronos.egl.EGLDisplay;
34import javax.microedition.khronos.egl.EGLSurface;
35import javax.microedition.khronos.opengles.GL10;
36
37@TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) // uses SurfaceTexture
38public class MosaicPreviewRenderer {
39    private static final String TAG = "MosaicPreviewRenderer";
40    private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
41    private static final boolean DEBUG = false;
42
43    private int mWidth; // width of the view in UI
44    private int mHeight; // height of the view in UI
45
46    private boolean mIsLandscape = true;
47    private final float[] mTransformMatrix = new float[16];
48
49    private ConditionVariable mEglThreadBlockVar = new ConditionVariable();
50    private HandlerThread mEglThread;
51    private EGLHandler mEglHandler;
52
53    private EGLConfig mEglConfig;
54    private EGLDisplay mEglDisplay;
55    private EGLContext mEglContext;
56    private EGLSurface mEglSurface;
57    private SurfaceTexture mMosaicOutputSurfaceTexture;
58    private SurfaceTexture mInputSurfaceTexture;
59    private EGL10 mEgl;
60    private GL10 mGl;
61
62    private class EGLHandler extends Handler {
63        public static final int MSG_INIT_EGL_SYNC = 0;
64        public static final int MSG_SHOW_PREVIEW_FRAME_SYNC = 1;
65        public static final int MSG_SHOW_PREVIEW_FRAME = 2;
66        public static final int MSG_ALIGN_FRAME_SYNC = 3;
67        public static final int MSG_RELEASE = 4;
68
69        public EGLHandler(Looper looper) {
70            super(looper);
71        }
72
73        @Override
74        public void handleMessage(Message msg) {
75            switch (msg.what) {
76                case MSG_INIT_EGL_SYNC:
77                    doInitGL();
78                    mEglThreadBlockVar.open();
79                    break;
80                case MSG_SHOW_PREVIEW_FRAME_SYNC:
81                    doShowPreviewFrame();
82                    mEglThreadBlockVar.open();
83                    break;
84                case MSG_SHOW_PREVIEW_FRAME:
85                    doShowPreviewFrame();
86                    break;
87                case MSG_ALIGN_FRAME_SYNC:
88                    doAlignFrame();
89                    mEglThreadBlockVar.open();
90                    break;
91                case MSG_RELEASE:
92                    doRelease();
93                    mEglThreadBlockVar.open();
94                    break;
95            }
96        }
97
98        private void doAlignFrame() {
99            mInputSurfaceTexture.updateTexImage();
100            mInputSurfaceTexture.getTransformMatrix(mTransformMatrix);
101
102            MosaicRenderer.setWarping(true);
103            // Call preprocess to render it to low-res and high-res RGB textures.
104            MosaicRenderer.preprocess(mTransformMatrix);
105            // Now, transfer the textures from GPU to CPU memory for processing
106            MosaicRenderer.transferGPUtoCPU();
107            MosaicRenderer.updateMatrix();
108            draw();
109            mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
110        }
111
112        private void doShowPreviewFrame() {
113            mInputSurfaceTexture.updateTexImage();
114            mInputSurfaceTexture.getTransformMatrix(mTransformMatrix);
115
116            MosaicRenderer.setWarping(false);
117            // Call preprocess to render it to low-res and high-res RGB textures.
118            MosaicRenderer.preprocess(mTransformMatrix);
119            MosaicRenderer.updateMatrix();
120            draw();
121            mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
122        }
123
124        private void doInitGL() {
125            // These are copied from GLSurfaceView
126            mEgl = (EGL10) EGLContext.getEGL();
127            mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
128            if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
129                throw new RuntimeException("eglGetDisplay failed");
130            }
131            int[] version = new int[2];
132            if (!mEgl.eglInitialize(mEglDisplay, version)) {
133                throw new RuntimeException("eglInitialize failed");
134            } else {
135                Log.v(TAG, "EGL version: " + version[0] + '.' + version[1]);
136            }
137            int[] attribList = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
138            mEglConfig = chooseConfig(mEgl, mEglDisplay);
139            mEglContext = mEgl.eglCreateContext(mEglDisplay, mEglConfig, EGL10.EGL_NO_CONTEXT,
140                                                attribList);
141
142            if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) {
143                throw new RuntimeException("failed to createContext");
144            }
145            mEglSurface = mEgl.eglCreateWindowSurface(
146                    mEglDisplay, mEglConfig, mMosaicOutputSurfaceTexture, null);
147            if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
148                throw new RuntimeException("failed to createWindowSurface");
149            }
150
151            if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
152                throw new RuntimeException("failed to eglMakeCurrent");
153            }
154
155            mGl = (GL10) mEglContext.getGL();
156
157            mInputSurfaceTexture = new SurfaceTexture(MosaicRenderer.init());
158            MosaicRenderer.reset(mWidth, mHeight, mIsLandscape);
159        }
160
161        private void doRelease() {
162            mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
163            mEgl.eglDestroyContext(mEglDisplay, mEglContext);
164            mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE,
165                    EGL10.EGL_NO_CONTEXT);
166            mEgl.eglTerminate(mEglDisplay);
167            mEglSurface = null;
168            mEglContext = null;
169            mEglDisplay = null;
170            releaseSurfaceTexture(mInputSurfaceTexture);
171            mEglThread.quit();
172        }
173
174        @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
175        private void releaseSurfaceTexture(SurfaceTexture st) {
176            if (ApiHelper.HAS_RELEASE_SURFACE_TEXTURE) {
177                st.release();
178            }
179        }
180
181        // Should be called from other thread.
182        public void sendMessageSync(int msg) {
183            mEglThreadBlockVar.close();
184            sendEmptyMessage(msg);
185            mEglThreadBlockVar.block();
186        }
187
188    }
189
190    public MosaicPreviewRenderer(SurfaceTexture tex, int w, int h, boolean isLandscape) {
191        mMosaicOutputSurfaceTexture = tex;
192        mWidth = w;
193        mHeight = h;
194        mIsLandscape = isLandscape;
195
196        mEglThread = new HandlerThread("PanoramaRealtimeRenderer");
197        mEglThread.start();
198        mEglHandler = new EGLHandler(mEglThread.getLooper());
199
200        // We need to sync this because the generation of surface texture for input is
201        // done here and the client will continue with the assumption that the
202        // generation is completed.
203        mEglHandler.sendMessageSync(EGLHandler.MSG_INIT_EGL_SYNC);
204    }
205
206    public void release() {
207        mEglHandler.sendMessageSync(EGLHandler.MSG_RELEASE);
208    }
209
210    public void showPreviewFrameSync() {
211        mEglHandler.sendMessageSync(EGLHandler.MSG_SHOW_PREVIEW_FRAME_SYNC);
212    }
213
214    public void showPreviewFrame() {
215        mEglHandler.sendEmptyMessage(EGLHandler.MSG_SHOW_PREVIEW_FRAME);
216    }
217
218    public void alignFrameSync() {
219        mEglHandler.sendMessageSync(EGLHandler.MSG_ALIGN_FRAME_SYNC);
220    }
221
222    public SurfaceTexture getInputSurfaceTexture() {
223        return mInputSurfaceTexture;
224    }
225
226    private void draw() {
227        MosaicRenderer.step();
228    }
229
230    private static void checkEglError(String prompt, EGL10 egl) {
231        int error;
232        while ((error = egl.eglGetError()) != EGL10.EGL_SUCCESS) {
233            Log.e(TAG, String.format("%s: EGL error: 0x%x", prompt, error));
234        }
235    }
236
237    private static final int EGL_OPENGL_ES2_BIT = 4;
238    private static final int[] CONFIG_SPEC = new int[] {
239            EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
240            EGL10.EGL_RED_SIZE, 8,
241            EGL10.EGL_GREEN_SIZE, 8,
242            EGL10.EGL_BLUE_SIZE, 8,
243            EGL10.EGL_NONE
244    };
245
246    private static EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
247        int[] numConfig = new int[1];
248        if (!egl.eglChooseConfig(display, CONFIG_SPEC, null, 0, numConfig)) {
249            throw new IllegalArgumentException("eglChooseConfig failed");
250        }
251
252        int numConfigs = numConfig[0];
253        if (numConfigs <= 0) {
254            throw new IllegalArgumentException("No configs match configSpec");
255        }
256
257        EGLConfig[] configs = new EGLConfig[numConfigs];
258        if (!egl.eglChooseConfig(
259                display, CONFIG_SPEC, configs, numConfigs, numConfig)) {
260            throw new IllegalArgumentException("eglChooseConfig#2 failed");
261        }
262
263        return configs[0];
264    }
265}
266