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.graphics.SurfaceTexture;
20import android.os.Handler;
21
22import com.android.camera.debug.Log;
23
24import javax.microedition.khronos.egl.EGL10;
25import javax.microedition.khronos.egl.EGLConfig;
26import javax.microedition.khronos.egl.EGLContext;
27import javax.microedition.khronos.egl.EGLDisplay;
28import javax.microedition.khronos.egl.EGLSurface;
29import javax.microedition.khronos.opengles.GL10;
30
31public class SurfaceTextureRenderer {
32
33    public interface FrameDrawer {
34        public void onDrawFrame(GL10 gl);
35    }
36
37    private static final Log.Tag TAG = new Log.Tag("SurfTexRenderer");
38    private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
39
40    private EGLConfig mEglConfig;
41    private EGLDisplay mEglDisplay;
42    private EGLContext mEglContext;
43    private EGLSurface mEglSurface;
44    private EGL10 mEgl;
45    private GL10 mGl;
46
47    private volatile boolean mDrawPending = false;
48
49    private final Handler mEglHandler;
50    private final FrameDrawer mFrameDrawer;
51
52    private final Object mRenderLock = new Object();
53    private final Runnable mRenderTask = new Runnable() {
54        @Override
55        public void run() {
56            synchronized (mRenderLock) {
57                if (mEglDisplay != null && mEglSurface != null) {
58                    mFrameDrawer.onDrawFrame(mGl);
59                    mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
60                    mDrawPending = false;
61                }
62                mRenderLock.notifyAll();
63            }
64        }
65    };
66
67    public SurfaceTextureRenderer(SurfaceTexture tex,
68            Handler handler, FrameDrawer renderer) {
69        mEglHandler = handler;
70        mFrameDrawer = renderer;
71
72        initialize(tex);
73    }
74
75    public void release() {
76        mEglHandler.post(new Runnable() {
77            @Override
78            public void run() {
79                mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
80                mEgl.eglDestroyContext(mEglDisplay, mEglContext);
81                mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE,
82                        EGL10.EGL_NO_CONTEXT);
83                mEgl.eglTerminate(mEglDisplay);
84                mEglSurface = null;
85                mEglContext = null;
86                mEglDisplay = null;
87            }
88        });
89    }
90
91    /**
92     * Posts a render request to the GL thread.
93     * @param sync      set <code>true</code> if the caller needs it to be
94     *                  a synchronous call.
95     */
96    public void draw(boolean sync) {
97        synchronized (mRenderLock) {
98            if (!mDrawPending) {
99                mEglHandler.post(mRenderTask);
100                mDrawPending = true;
101                if (sync) {
102                    try {
103                        mRenderLock.wait();
104                    } catch (InterruptedException ex) {
105                        Log.v(TAG, "RenderLock.wait() interrupted");
106                    }
107                }
108            }
109        }
110    }
111
112    private void initialize(final SurfaceTexture target) {
113        mEglHandler.post(new Runnable() {
114            @Override
115            public void run() {
116                mEgl = (EGL10) EGLContext.getEGL();
117                mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
118                if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
119                    throw new RuntimeException("eglGetDisplay failed");
120                }
121                int[] version = new int[2];
122                if (!mEgl.eglInitialize(mEglDisplay, version)) {
123                    throw new RuntimeException("eglInitialize failed");
124                } else {
125                    Log.v(TAG, "EGL version: " + version[0] + '.' + version[1]);
126                }
127                int[] attribList = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
128                mEglConfig = chooseConfig(mEgl, mEglDisplay);
129                mEglContext = mEgl.eglCreateContext(
130                        mEglDisplay, mEglConfig, EGL10.EGL_NO_CONTEXT, attribList);
131
132                if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) {
133                    throw new RuntimeException("failed to createContext");
134                }
135                mEglSurface = mEgl.eglCreateWindowSurface(
136                        mEglDisplay, mEglConfig, target, null);
137                if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
138                    throw new RuntimeException("failed to createWindowSurface");
139                }
140
141                if (!mEgl.eglMakeCurrent(
142                        mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
143                    throw new RuntimeException("failed to eglMakeCurrent");
144                }
145
146                mGl = (GL10) mEglContext.getGL();
147            }
148        });
149        waitDone();
150    }
151
152    private void waitDone() {
153        final Object lock = new Object();
154        synchronized (lock) {
155            mEglHandler.post(new Runnable() {
156                @Override
157                public void run() {
158                    synchronized (lock) {
159                        lock.notifyAll();
160                    }
161                }
162            });
163            try {
164                lock.wait();
165            } catch (InterruptedException ex) {
166                Log.v(TAG, "waitDone() interrupted");
167            }
168        }
169    }
170
171    private static void checkEglError(String prompt, EGL10 egl) {
172        int error;
173        while ((error = egl.eglGetError()) != EGL10.EGL_SUCCESS) {
174            Log.e(TAG, String.format("%s: EGL error: 0x%x", prompt, error));
175        }
176    }
177
178    private static final int EGL_OPENGL_ES2_BIT = 4;
179    private static final int[] CONFIG_SPEC = new int[] {
180            EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
181            EGL10.EGL_RED_SIZE, 8,
182            EGL10.EGL_GREEN_SIZE, 8,
183            EGL10.EGL_BLUE_SIZE, 8,
184            EGL10.EGL_ALPHA_SIZE, 0,
185            EGL10.EGL_DEPTH_SIZE, 0,
186            EGL10.EGL_STENCIL_SIZE, 0,
187            EGL10.EGL_NONE
188    };
189
190    private static EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
191        int[] numConfig = new int[1];
192        if (!egl.eglChooseConfig(display, CONFIG_SPEC, null, 0, numConfig)) {
193            throw new IllegalArgumentException("eglChooseConfig failed");
194        }
195
196        int numConfigs = numConfig[0];
197        if (numConfigs <= 0) {
198            throw new IllegalArgumentException("No configs match configSpec");
199        }
200
201        EGLConfig[] configs = new EGLConfig[numConfigs];
202        if (!egl.eglChooseConfig(
203                display, CONFIG_SPEC, configs, numConfigs, numConfig)) {
204            throw new IllegalArgumentException("eglChooseConfig#2 failed");
205        }
206
207        return configs[0];
208    }
209}
210