1/*
2 *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
3 *
4 *  Use of this source code is governed by a BSD-style license
5 *  that can be found in the LICENSE file in the root of the source
6 *  tree. An additional intellectual property rights grant can be found
7 *  in the file PATENTS.  All contributing project authors may
8 *  be found in the AUTHORS file in the root of the source tree.
9 */
10
11package org.webrtc.videoengine;
12
13import java.util.concurrent.locks.ReentrantLock;
14
15import javax.microedition.khronos.egl.EGL10;
16import javax.microedition.khronos.egl.EGLConfig;
17import javax.microedition.khronos.egl.EGLContext;
18import javax.microedition.khronos.egl.EGLDisplay;
19import javax.microedition.khronos.opengles.GL10;
20
21import android.app.ActivityManager;
22import android.content.Context;
23import android.content.pm.ConfigurationInfo;
24import android.graphics.PixelFormat;
25import android.opengl.GLSurfaceView;
26import android.util.Log;
27
28public class ViEAndroidGLES20 extends GLSurfaceView
29        implements GLSurfaceView.Renderer {
30    private static String TAG = "WEBRTC-JR";
31    private static final boolean DEBUG = false;
32    // True if onSurfaceCreated has been called.
33    private boolean surfaceCreated = false;
34    private boolean openGLCreated = false;
35    // True if NativeFunctionsRegistered has been called.
36    private boolean nativeFunctionsRegisted = false;
37    private ReentrantLock nativeFunctionLock = new ReentrantLock();
38    // Address of Native object that will do the drawing.
39    private long nativeObject = 0;
40    private int viewWidth = 0;
41    private int viewHeight = 0;
42
43    public static boolean UseOpenGL2(Object renderWindow) {
44        return ViEAndroidGLES20.class.isInstance(renderWindow);
45    }
46
47    public ViEAndroidGLES20(Context context) {
48        super(context);
49        init(false, 0, 0);
50    }
51
52    public ViEAndroidGLES20(Context context, boolean translucent,
53            int depth, int stencil) {
54        super(context);
55        init(translucent, depth, stencil);
56    }
57
58    private void init(boolean translucent, int depth, int stencil) {
59
60        // By default, GLSurfaceView() creates a RGB_565 opaque surface.
61        // If we want a translucent one, we should change the surface's
62        // format here, using PixelFormat.TRANSLUCENT for GL Surfaces
63        // is interpreted as any 32-bit surface with alpha by SurfaceFlinger.
64        if (translucent) {
65            this.getHolder().setFormat(PixelFormat.TRANSLUCENT);
66        }
67
68        // Setup the context factory for 2.0 rendering.
69        // See ContextFactory class definition below
70        setEGLContextFactory(new ContextFactory());
71
72        // We need to choose an EGLConfig that matches the format of
73        // our surface exactly. This is going to be done in our
74        // custom config chooser. See ConfigChooser class definition
75        // below.
76        setEGLConfigChooser( translucent ?
77                             new ConfigChooser(8, 8, 8, 8, depth, stencil) :
78                             new ConfigChooser(5, 6, 5, 0, depth, stencil) );
79
80        // Set the renderer responsible for frame rendering
81        this.setRenderer(this);
82        this.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
83    }
84
85    private static class ContextFactory implements GLSurfaceView.EGLContextFactory {
86        private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
87        public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) {
88            Log.w(TAG, "creating OpenGL ES 2.0 context");
89            checkEglError("Before eglCreateContext", egl);
90            int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
91            EGLContext context = egl.eglCreateContext(display, eglConfig,
92                    EGL10.EGL_NO_CONTEXT, attrib_list);
93            checkEglError("After eglCreateContext", egl);
94            return context;
95        }
96
97        public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) {
98            egl.eglDestroyContext(display, context);
99        }
100    }
101
102    private static void checkEglError(String prompt, EGL10 egl) {
103        int error;
104        while ((error = egl.eglGetError()) != EGL10.EGL_SUCCESS) {
105            Log.e(TAG, String.format("%s: EGL error: 0x%x", prompt, error));
106        }
107    }
108
109    private static class ConfigChooser implements GLSurfaceView.EGLConfigChooser {
110
111        public ConfigChooser(int r, int g, int b, int a, int depth, int stencil) {
112            mRedSize = r;
113            mGreenSize = g;
114            mBlueSize = b;
115            mAlphaSize = a;
116            mDepthSize = depth;
117            mStencilSize = stencil;
118        }
119
120        // This EGL config specification is used to specify 2.0 rendering.
121        // We use a minimum size of 4 bits for red/green/blue, but will
122        // perform actual matching in chooseConfig() below.
123        private static int EGL_OPENGL_ES2_BIT = 4;
124        private static int[] s_configAttribs2 =
125        {
126            EGL10.EGL_RED_SIZE, 4,
127            EGL10.EGL_GREEN_SIZE, 4,
128            EGL10.EGL_BLUE_SIZE, 4,
129            EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
130            EGL10.EGL_NONE
131        };
132
133        public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
134
135            // Get the number of minimally matching EGL configurations
136            int[] num_config = new int[1];
137            egl.eglChooseConfig(display, s_configAttribs2, null, 0, num_config);
138
139            int numConfigs = num_config[0];
140
141            if (numConfigs <= 0) {
142                throw new IllegalArgumentException("No configs match configSpec");
143            }
144
145            // Allocate then read the array of minimally matching EGL configs
146            EGLConfig[] configs = new EGLConfig[numConfigs];
147            egl.eglChooseConfig(display, s_configAttribs2, configs, numConfigs, num_config);
148
149            if (DEBUG) {
150                printConfigs(egl, display, configs);
151            }
152            // Now return the "best" one
153            return chooseConfig(egl, display, configs);
154        }
155
156        public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,
157                EGLConfig[] configs) {
158            for(EGLConfig config : configs) {
159                int d = findConfigAttrib(egl, display, config,
160                        EGL10.EGL_DEPTH_SIZE, 0);
161                int s = findConfigAttrib(egl, display, config,
162                        EGL10.EGL_STENCIL_SIZE, 0);
163
164                // We need at least mDepthSize and mStencilSize bits
165                if (d < mDepthSize || s < mStencilSize)
166                    continue;
167
168                // We want an *exact* match for red/green/blue/alpha
169                int r = findConfigAttrib(egl, display, config,
170                        EGL10.EGL_RED_SIZE, 0);
171                int g = findConfigAttrib(egl, display, config,
172                            EGL10.EGL_GREEN_SIZE, 0);
173                int b = findConfigAttrib(egl, display, config,
174                            EGL10.EGL_BLUE_SIZE, 0);
175                int a = findConfigAttrib(egl, display, config,
176                        EGL10.EGL_ALPHA_SIZE, 0);
177
178                if (r == mRedSize && g == mGreenSize && b == mBlueSize && a == mAlphaSize)
179                    return config;
180            }
181            return null;
182        }
183
184        private int findConfigAttrib(EGL10 egl, EGLDisplay display,
185                EGLConfig config, int attribute, int defaultValue) {
186
187            if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
188                return mValue[0];
189            }
190            return defaultValue;
191        }
192
193        private void printConfigs(EGL10 egl, EGLDisplay display,
194            EGLConfig[] configs) {
195            int numConfigs = configs.length;
196            Log.w(TAG, String.format("%d configurations", numConfigs));
197            for (int i = 0; i < numConfigs; i++) {
198                Log.w(TAG, String.format("Configuration %d:\n", i));
199                printConfig(egl, display, configs[i]);
200            }
201        }
202
203        private void printConfig(EGL10 egl, EGLDisplay display,
204                EGLConfig config) {
205            int[] attributes = {
206                    EGL10.EGL_BUFFER_SIZE,
207                    EGL10.EGL_ALPHA_SIZE,
208                    EGL10.EGL_BLUE_SIZE,
209                    EGL10.EGL_GREEN_SIZE,
210                    EGL10.EGL_RED_SIZE,
211                    EGL10.EGL_DEPTH_SIZE,
212                    EGL10.EGL_STENCIL_SIZE,
213                    EGL10.EGL_CONFIG_CAVEAT,
214                    EGL10.EGL_CONFIG_ID,
215                    EGL10.EGL_LEVEL,
216                    EGL10.EGL_MAX_PBUFFER_HEIGHT,
217                    EGL10.EGL_MAX_PBUFFER_PIXELS,
218                    EGL10.EGL_MAX_PBUFFER_WIDTH,
219                    EGL10.EGL_NATIVE_RENDERABLE,
220                    EGL10.EGL_NATIVE_VISUAL_ID,
221                    EGL10.EGL_NATIVE_VISUAL_TYPE,
222                    0x3030, // EGL10.EGL_PRESERVED_RESOURCES,
223                    EGL10.EGL_SAMPLES,
224                    EGL10.EGL_SAMPLE_BUFFERS,
225                    EGL10.EGL_SURFACE_TYPE,
226                    EGL10.EGL_TRANSPARENT_TYPE,
227                    EGL10.EGL_TRANSPARENT_RED_VALUE,
228                    EGL10.EGL_TRANSPARENT_GREEN_VALUE,
229                    EGL10.EGL_TRANSPARENT_BLUE_VALUE,
230                    0x3039, // EGL10.EGL_BIND_TO_TEXTURE_RGB,
231                    0x303A, // EGL10.EGL_BIND_TO_TEXTURE_RGBA,
232                    0x303B, // EGL10.EGL_MIN_SWAP_INTERVAL,
233                    0x303C, // EGL10.EGL_MAX_SWAP_INTERVAL,
234                    EGL10.EGL_LUMINANCE_SIZE,
235                    EGL10.EGL_ALPHA_MASK_SIZE,
236                    EGL10.EGL_COLOR_BUFFER_TYPE,
237                    EGL10.EGL_RENDERABLE_TYPE,
238                    0x3042 // EGL10.EGL_CONFORMANT
239            };
240            String[] names = {
241                    "EGL_BUFFER_SIZE",
242                    "EGL_ALPHA_SIZE",
243                    "EGL_BLUE_SIZE",
244                    "EGL_GREEN_SIZE",
245                    "EGL_RED_SIZE",
246                    "EGL_DEPTH_SIZE",
247                    "EGL_STENCIL_SIZE",
248                    "EGL_CONFIG_CAVEAT",
249                    "EGL_CONFIG_ID",
250                    "EGL_LEVEL",
251                    "EGL_MAX_PBUFFER_HEIGHT",
252                    "EGL_MAX_PBUFFER_PIXELS",
253                    "EGL_MAX_PBUFFER_WIDTH",
254                    "EGL_NATIVE_RENDERABLE",
255                    "EGL_NATIVE_VISUAL_ID",
256                    "EGL_NATIVE_VISUAL_TYPE",
257                    "EGL_PRESERVED_RESOURCES",
258                    "EGL_SAMPLES",
259                    "EGL_SAMPLE_BUFFERS",
260                    "EGL_SURFACE_TYPE",
261                    "EGL_TRANSPARENT_TYPE",
262                    "EGL_TRANSPARENT_RED_VALUE",
263                    "EGL_TRANSPARENT_GREEN_VALUE",
264                    "EGL_TRANSPARENT_BLUE_VALUE",
265                    "EGL_BIND_TO_TEXTURE_RGB",
266                    "EGL_BIND_TO_TEXTURE_RGBA",
267                    "EGL_MIN_SWAP_INTERVAL",
268                    "EGL_MAX_SWAP_INTERVAL",
269                    "EGL_LUMINANCE_SIZE",
270                    "EGL_ALPHA_MASK_SIZE",
271                    "EGL_COLOR_BUFFER_TYPE",
272                    "EGL_RENDERABLE_TYPE",
273                    "EGL_CONFORMANT"
274            };
275            int[] value = new int[1];
276            for (int i = 0; i < attributes.length; i++) {
277                int attribute = attributes[i];
278                String name = names[i];
279                if (egl.eglGetConfigAttrib(display, config, attribute, value)) {
280                    Log.w(TAG, String.format("  %s: %d\n", name, value[0]));
281                } else {
282                    // Log.w(TAG, String.format("  %s: failed\n", name));
283                    while (egl.eglGetError() != EGL10.EGL_SUCCESS);
284                }
285            }
286        }
287
288        // Subclasses can adjust these values:
289        protected int mRedSize;
290        protected int mGreenSize;
291        protected int mBlueSize;
292        protected int mAlphaSize;
293        protected int mDepthSize;
294        protected int mStencilSize;
295        private int[] mValue = new int[1];
296    }
297
298    // IsSupported
299    // Return true if this device support Open GL ES 2.0 rendering.
300    public static boolean IsSupported(Context context) {
301        ActivityManager am =
302                (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
303        ConfigurationInfo info = am.getDeviceConfigurationInfo();
304        if(info.reqGlEsVersion >= 0x20000) {
305            // Open GL ES 2.0 is supported.
306            return true;
307        }
308        return false;
309    }
310
311    public void onDrawFrame(GL10 gl) {
312        nativeFunctionLock.lock();
313        if(!nativeFunctionsRegisted || !surfaceCreated) {
314            nativeFunctionLock.unlock();
315            return;
316        }
317
318        if(!openGLCreated) {
319            if(0 != CreateOpenGLNative(nativeObject, viewWidth, viewHeight)) {
320                return; // Failed to create OpenGL
321            }
322            openGLCreated = true; // Created OpenGL successfully
323        }
324        DrawNative(nativeObject); // Draw the new frame
325        nativeFunctionLock.unlock();
326    }
327
328    public void onSurfaceChanged(GL10 gl, int width, int height) {
329        surfaceCreated = true;
330        viewWidth = width;
331        viewHeight = height;
332
333        nativeFunctionLock.lock();
334        if(nativeFunctionsRegisted) {
335            if(CreateOpenGLNative(nativeObject,width,height) == 0)
336                openGLCreated = true;
337        }
338        nativeFunctionLock.unlock();
339    }
340
341    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
342    }
343
344    public void RegisterNativeObject(long nativeObject) {
345        nativeFunctionLock.lock();
346        this.nativeObject = nativeObject;
347        nativeFunctionsRegisted = true;
348        nativeFunctionLock.unlock();
349    }
350
351    public void DeRegisterNativeObject() {
352        nativeFunctionLock.lock();
353        nativeFunctionsRegisted = false;
354        openGLCreated = false;
355        this.nativeObject = 0;
356        nativeFunctionLock.unlock();
357    }
358
359    public void ReDraw() {
360        if(surfaceCreated) {
361            // Request the renderer to redraw using the render thread context.
362            this.requestRender();
363        }
364    }
365
366    private native int CreateOpenGLNative(long nativeObject,
367            int width, int height);
368    private native void DrawNative(long nativeObject);
369
370}
371