• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 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 
17 package com.android.photos.views;
18 
19 import android.annotation.SuppressLint;
20 import android.content.Context;
21 import android.graphics.SurfaceTexture;
22 import android.opengl.GLSurfaceView.Renderer;
23 import android.opengl.GLUtils;
24 import android.util.Log;
25 import android.view.TextureView;
26 import android.view.TextureView.SurfaceTextureListener;
27 
28 import javax.microedition.khronos.egl.EGL10;
29 import javax.microedition.khronos.egl.EGLConfig;
30 import javax.microedition.khronos.egl.EGLContext;
31 import javax.microedition.khronos.egl.EGLDisplay;
32 import javax.microedition.khronos.egl.EGLSurface;
33 import javax.microedition.khronos.opengles.GL10;
34 
35 
36 public class BlockingGLTextureView extends TextureView
37         implements SurfaceTextureListener {
38 
39     private RenderThread mRenderThread;
40 
BlockingGLTextureView(Context context)41     public BlockingGLTextureView(Context context) {
42         super(context);
43         setSurfaceTextureListener(this);
44     }
45 
setRenderer(Renderer renderer)46     public void setRenderer(Renderer renderer) {
47         if (mRenderThread != null) {
48             throw new IllegalArgumentException("Renderer already set");
49         }
50         mRenderThread = new RenderThread(renderer);
51     }
52 
render()53     public void render() {
54         mRenderThread.render();
55     }
56 
destroy()57     public void destroy() {
58         if (mRenderThread != null) {
59             mRenderThread.finish();
60             mRenderThread = null;
61         }
62     }
63 
64     @Override
onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height)65     public void onSurfaceTextureAvailable(SurfaceTexture surface, int width,
66             int height) {
67         mRenderThread.setSurface(surface);
68         mRenderThread.setSize(width, height);
69     }
70 
71     @Override
onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)72     public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width,
73             int height) {
74         mRenderThread.setSize(width, height);
75     }
76 
77     @Override
onSurfaceTextureDestroyed(SurfaceTexture surface)78     public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
79         if (mRenderThread != null) {
80             mRenderThread.setSurface(null);
81         }
82         return false;
83     }
84 
85     @Override
onSurfaceTextureUpdated(SurfaceTexture surface)86     public void onSurfaceTextureUpdated(SurfaceTexture surface) {
87     }
88 
89     @Override
finalize()90     protected void finalize() throws Throwable {
91         try {
92             destroy();
93         } catch (Throwable t) {}
94         super.finalize();
95     }
96 
97     /**
98      * An EGL helper class.
99      */
100 
101     private static class EglHelper {
102         private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
103         private static final int EGL_OPENGL_ES2_BIT = 4;
104 
105         EGL10 mEgl;
106         EGLDisplay mEglDisplay;
107         EGLSurface mEglSurface;
108         EGLConfig mEglConfig;
109         EGLContext mEglContext;
110 
chooseEglConfig()111         private EGLConfig chooseEglConfig() {
112             int[] configsCount = new int[1];
113             EGLConfig[] configs = new EGLConfig[1];
114             int[] configSpec = getConfig();
115             if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) {
116                 throw new IllegalArgumentException("eglChooseConfig failed " +
117                         GLUtils.getEGLErrorString(mEgl.eglGetError()));
118             } else if (configsCount[0] > 0) {
119                 return configs[0];
120             }
121             return null;
122         }
123 
getConfig()124         private static int[] getConfig() {
125             return new int[] {
126                     EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
127                     EGL10.EGL_RED_SIZE, 8,
128                     EGL10.EGL_GREEN_SIZE, 8,
129                     EGL10.EGL_BLUE_SIZE, 8,
130                     EGL10.EGL_ALPHA_SIZE, 8,
131                     EGL10.EGL_DEPTH_SIZE, 0,
132                     EGL10.EGL_STENCIL_SIZE, 0,
133                     EGL10.EGL_NONE
134             };
135         }
136 
createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig)137         EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
138             int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
139             return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
140         }
141 
142         /**
143          * Initialize EGL for a given configuration spec.
144          */
start()145         public void start() {
146             /*
147              * Get an EGL instance
148              */
149             mEgl = (EGL10) EGLContext.getEGL();
150 
151             /*
152              * Get to the default display.
153              */
154             mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
155 
156             if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
157                 throw new RuntimeException("eglGetDisplay failed");
158             }
159 
160             /*
161              * We can now initialize EGL for that display
162              */
163             int[] version = new int[2];
164             if(!mEgl.eglInitialize(mEglDisplay, version)) {
165                 throw new RuntimeException("eglInitialize failed");
166             }
167             mEglConfig = chooseEglConfig();
168 
169             /*
170             * Create an EGL context. We want to do this as rarely as we can, because an
171             * EGL context is a somewhat heavy object.
172             */
173             mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
174 
175             if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) {
176                 mEglContext = null;
177                 throwEglException("createContext");
178             }
179 
180             mEglSurface = null;
181         }
182 
183         /**
184          * Create an egl surface for the current SurfaceTexture surface. If a surface
185          * already exists, destroy it before creating the new surface.
186          *
187          * @return true if the surface was created successfully.
188          */
createSurface(SurfaceTexture surface)189         public boolean createSurface(SurfaceTexture surface) {
190             /*
191              * Check preconditions.
192              */
193             if (mEgl == null) {
194                 throw new RuntimeException("egl not initialized");
195             }
196             if (mEglDisplay == null) {
197                 throw new RuntimeException("eglDisplay not initialized");
198             }
199             if (mEglConfig == null) {
200                 throw new RuntimeException("mEglConfig not initialized");
201             }
202 
203             /*
204              *  The window size has changed, so we need to create a new
205              *  surface.
206              */
207             destroySurfaceImp();
208 
209             /*
210              * Create an EGL surface we can render into.
211              */
212             if (surface != null) {
213                 mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, surface, null);
214             } else {
215                 mEglSurface = null;
216             }
217 
218             if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
219                 int error = mEgl.eglGetError();
220                 if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
221                     Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
222                 }
223                 return false;
224             }
225 
226             /*
227              * Before we can issue GL commands, we need to make sure
228              * the context is current and bound to a surface.
229              */
230             if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
231                 /*
232                  * Could not make the context current, probably because the underlying
233                  * SurfaceView surface has been destroyed.
234                  */
235                 logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError());
236                 return false;
237             }
238 
239             return true;
240         }
241 
242         /**
243          * Create a GL object for the current EGL context.
244          */
createGL()245         public GL10 createGL() {
246             return (GL10) mEglContext.getGL();
247         }
248 
249         /**
250          * Display the current render surface.
251          * @return the EGL error code from eglSwapBuffers.
252          */
swap()253         public int swap() {
254             if (! mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
255                 return mEgl.eglGetError();
256             }
257             return EGL10.EGL_SUCCESS;
258         }
259 
destroySurface()260         public void destroySurface() {
261             destroySurfaceImp();
262         }
263 
destroySurfaceImp()264         private void destroySurfaceImp() {
265             if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {
266                 mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
267                         EGL10.EGL_NO_SURFACE,
268                         EGL10.EGL_NO_CONTEXT);
269                 mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
270                 mEglSurface = null;
271             }
272         }
273 
finish()274         public void finish() {
275             if (mEglContext != null) {
276                 mEgl.eglDestroyContext(mEglDisplay, mEglContext);
277                 mEglContext = null;
278             }
279             if (mEglDisplay != null) {
280                 mEgl.eglTerminate(mEglDisplay);
281                 mEglDisplay = null;
282             }
283         }
284 
throwEglException(String function)285         private void throwEglException(String function) {
286             throwEglException(function, mEgl.eglGetError());
287         }
288 
throwEglException(String function, int error)289         public static void throwEglException(String function, int error) {
290             String message = formatEglError(function, error);
291             throw new RuntimeException(message);
292         }
293 
logEglErrorAsWarning(String tag, String function, int error)294         public static void logEglErrorAsWarning(String tag, String function, int error) {
295             Log.w(tag, formatEglError(function, error));
296         }
297 
formatEglError(String function, int error)298         public static String formatEglError(String function, int error) {
299             return function + " failed: " + error;
300         }
301 
302     }
303 
304     private static class RenderThread extends Thread {
305         private static final int INVALID = -1;
306         private static final int RENDER = 1;
307         private static final int CHANGE_SURFACE = 2;
308         private static final int RESIZE_SURFACE = 3;
309         private static final int FINISH = 4;
310 
311         private EglHelper mEglHelper = new EglHelper();
312 
313         private Object mLock = new Object();
314         private int mExecMsgId = INVALID;
315         private SurfaceTexture mSurface;
316         private Renderer mRenderer;
317         private int mWidth, mHeight;
318 
319         private boolean mFinished = false;
320         private GL10 mGL;
321 
RenderThread(Renderer renderer)322         public RenderThread(Renderer renderer) {
323             super("RenderThread");
324             mRenderer = renderer;
325             start();
326         }
327 
checkRenderer()328         private void checkRenderer() {
329             if (mRenderer == null) {
330                 throw new IllegalArgumentException("Renderer is null!");
331             }
332         }
333 
checkSurface()334         private void checkSurface() {
335             if (mSurface == null) {
336                 throw new IllegalArgumentException("surface is null!");
337             }
338         }
339 
setSurface(SurfaceTexture surface)340         public void setSurface(SurfaceTexture surface) {
341             // If the surface is null we're being torn down, don't need a
342             // renderer then
343             if (surface != null) {
344                 checkRenderer();
345             }
346             mSurface = surface;
347             exec(CHANGE_SURFACE);
348         }
349 
setSize(int width, int height)350         public void setSize(int width, int height) {
351             checkRenderer();
352             checkSurface();
353             mWidth = width;
354             mHeight = height;
355             exec(RESIZE_SURFACE);
356         }
357 
render()358         public void render() {
359             checkRenderer();
360             if (mSurface != null) {
361                 exec(RENDER);
362                 mSurface.updateTexImage();
363             }
364         }
365 
finish()366         public void finish() {
367             mSurface = null;
368             exec(FINISH);
369             try {
370                 join();
371             } catch (InterruptedException e) {}
372         }
373 
exec(int msgid)374         private void exec(int msgid) {
375             synchronized (mLock) {
376                 if (mExecMsgId != INVALID) {
377                     throw new IllegalArgumentException("Message already set - multithreaded access?");
378                 }
379                 mExecMsgId = msgid;
380                 mLock.notify();
381                 try {
382                     mLock.wait();
383                 } catch (InterruptedException e) {}
384             }
385         }
386 
handleMessageLocked(int what)387         private void handleMessageLocked(int what) {
388             switch (what) {
389             case CHANGE_SURFACE:
390                 if (mEglHelper.createSurface(mSurface)) {
391                     mGL = mEglHelper.createGL();
392                     mRenderer.onSurfaceCreated(mGL, mEglHelper.mEglConfig);
393                 }
394                 break;
395             case RESIZE_SURFACE:
396                 mRenderer.onSurfaceChanged(mGL, mWidth, mHeight);
397                 break;
398             case RENDER:
399                 mRenderer.onDrawFrame(mGL);
400                 mEglHelper.swap();
401                 break;
402             case FINISH:
403                 mEglHelper.destroySurface();
404                 mEglHelper.finish();
405                 mFinished = true;
406                 break;
407             }
408         }
409 
410         @Override
run()411         public void run() {
412             synchronized (mLock) {
413                 mEglHelper.start();
414                 while (!mFinished) {
415                     while (mExecMsgId == INVALID) {
416                         try {
417                             mLock.wait();
418                         } catch (InterruptedException e) {}
419                     }
420                     handleMessageLocked(mExecMsgId);
421                     mExecMsgId = INVALID;
422                     mLock.notify();
423                 }
424             }
425         }
426     }
427 }
428