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