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                    break;
94            }
95        }
96
97        private void doAlignFrame() {
98            mInputSurfaceTexture.updateTexImage();
99            mInputSurfaceTexture.getTransformMatrix(mTransformMatrix);
100
101            MosaicRenderer.setWarping(true);
102            // Call preprocess to render it to low-res and high-res RGB textures.
103            MosaicRenderer.preprocess(mTransformMatrix);
104            // Now, transfer the textures from GPU to CPU memory for processing
105            MosaicRenderer.transferGPUtoCPU();
106            MosaicRenderer.updateMatrix();
107            draw();
108            mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
109        }
110
111        private void doShowPreviewFrame() {
112            mInputSurfaceTexture.updateTexImage();
113            mInputSurfaceTexture.getTransformMatrix(mTransformMatrix);
114
115            MosaicRenderer.setWarping(false);
116            // Call preprocess to render it to low-res and high-res RGB textures.
117            MosaicRenderer.preprocess(mTransformMatrix);
118            MosaicRenderer.updateMatrix();
119            draw();
120            mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
121        }
122
123        private void doInitGL() {
124            // These are copied from GLSurfaceView
125            mEgl = (EGL10) EGLContext.getEGL();
126            mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
127            if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
128                throw new RuntimeException("eglGetDisplay failed");
129            }
130            int[] version = new int[2];
131            if (!mEgl.eglInitialize(mEglDisplay, version)) {
132                throw new RuntimeException("eglInitialize failed");
133            } else {
134                Log.v(TAG, "EGL version: " + version[0] + '.' + version[1]);
135            }
136            int[] attribList = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
137            mEglConfig = chooseConfig(mEgl, mEglDisplay);
138            mEglContext = mEgl.eglCreateContext(mEglDisplay, mEglConfig, EGL10.EGL_NO_CONTEXT,
139                                                attribList);
140
141            if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) {
142                throw new RuntimeException("failed to createContext");
143            }
144            mEglSurface = mEgl.eglCreateWindowSurface(
145                    mEglDisplay, mEglConfig, mMosaicOutputSurfaceTexture, null);
146            if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
147                throw new RuntimeException("failed to createWindowSurface");
148            }
149
150            if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
151                throw new RuntimeException("failed to eglMakeCurrent");
152            }
153
154            mGl = (GL10) mEglContext.getGL();
155
156            mInputSurfaceTexture = new SurfaceTexture(MosaicRenderer.init());
157            MosaicRenderer.reset(mWidth, mHeight, mIsLandscape);
158        }
159
160        private void doRelease() {
161            mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
162            mEgl.eglDestroyContext(mEglDisplay, mEglContext);
163            mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE,
164                    EGL10.EGL_NO_CONTEXT);
165            mEgl.eglTerminate(mEglDisplay);
166            mEglSurface = null;
167            mEglContext = null;
168            mEglDisplay = null;
169            releaseSurfaceTexture(mInputSurfaceTexture);
170            mEglThread.quit();
171        }
172
173        @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
174        private void releaseSurfaceTexture(SurfaceTexture st) {
175            if (ApiHelper.HAS_RELEASE_SURFACE_TEXTURE) {
176                st.release();
177            }
178        }
179
180        // Should be called from other thread.
181        public void sendMessageSync(int msg) {
182            mEglThreadBlockVar.close();
183            sendEmptyMessage(msg);
184            mEglThreadBlockVar.block();
185        }
186
187    }
188
189    public MosaicPreviewRenderer(SurfaceTexture tex, int w, int h, boolean isLandscape) {
190        mMosaicOutputSurfaceTexture = tex;
191        mWidth = w;
192        mHeight = h;
193        mIsLandscape = isLandscape;
194
195        mEglThread = new HandlerThread("PanoramaRealtimeRenderer");
196        mEglThread.start();
197        mEglHandler = new EGLHandler(mEglThread.getLooper());
198
199        // We need to sync this because the generation of surface texture for input is
200        // done here and the client will continue with the assumption that the
201        // generation is completed.
202        mEglHandler.sendMessageSync(EGLHandler.MSG_INIT_EGL_SYNC);
203    }
204
205    public void release() {
206        mEglHandler.sendEmptyMessage(EGLHandler.MSG_RELEASE);
207    }
208
209    public void showPreviewFrameSync() {
210        mEglHandler.sendMessageSync(EGLHandler.MSG_SHOW_PREVIEW_FRAME_SYNC);
211    }
212
213    public void showPreviewFrame() {
214        mEglHandler.sendEmptyMessage(EGLHandler.MSG_SHOW_PREVIEW_FRAME);
215    }
216
217    public void alignFrameSync() {
218        mEglHandler.sendMessageSync(EGLHandler.MSG_ALIGN_FRAME_SYNC);
219    }
220
221    public SurfaceTexture getInputSurfaceTexture() {
222        return mInputSurfaceTexture;
223    }
224
225    private void draw() {
226        MosaicRenderer.step();
227    }
228
229    private static void checkEglError(String prompt, EGL10 egl) {
230        int error;
231        while ((error = egl.eglGetError()) != EGL10.EGL_SUCCESS) {
232            Log.e(TAG, String.format("%s: EGL error: 0x%x", prompt, error));
233        }
234    }
235
236    private static final int EGL_OPENGL_ES2_BIT = 4;
237    private static final int[] CONFIG_SPEC = new int[] {
238            EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
239            EGL10.EGL_RED_SIZE, 8,
240            EGL10.EGL_GREEN_SIZE, 8,
241            EGL10.EGL_BLUE_SIZE, 8,
242            EGL10.EGL_NONE
243    };
244
245    private static EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
246        int[] numConfig = new int[1];
247        if (!egl.eglChooseConfig(display, CONFIG_SPEC, null, 0, numConfig)) {
248            throw new IllegalArgumentException("eglChooseConfig failed");
249        }
250
251        int numConfigs = numConfig[0];
252        if (numConfigs <= 0) {
253            throw new IllegalArgumentException("No configs match configSpec");
254        }
255
256        EGLConfig[] configs = new EGLConfig[numConfigs];
257        if (!egl.eglChooseConfig(
258                display, CONFIG_SPEC, configs, numConfigs, numConfig)) {
259            throw new IllegalArgumentException("eglChooseConfig#2 failed");
260        }
261
262        return configs[0];
263    }
264}
265