Home | History | Annotate | Download | only in views
      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 
     41     public BlockingGLTextureView(Context context) {
     42         super(context);
     43         setSurfaceTextureListener(this);
     44     }
     45 
     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 
     53     public void render() {
     54         mRenderThread.render();
     55     }
     56 
     57     public void destroy() {
     58         if (mRenderThread != null) {
     59             mRenderThread.finish();
     60             mRenderThread = null;
     61         }
     62     }
     63 
     64     @Override
     65     public void onSurfaceTextureAvailable(SurfaceTexture surface, int width,
     66             int height) {
     67         mRenderThread.setSurface(surface);
     68         mRenderThread.setSize(width, height);
     69     }
     70 
     71     @Override
     72     public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width,
     73             int height) {
     74         mRenderThread.setSize(width, height);
     75     }
     76 
     77     @Override
     78     public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
     79         if (mRenderThread != null) {
     80             mRenderThread.setSurface(null);
     81         }
     82         return false;
     83     }
     84 
     85     @Override
     86     public void onSurfaceTextureUpdated(SurfaceTexture surface) {
     87     }
     88 
     89     @Override
     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 
    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 
    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 
    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          */
    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          */
    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          */
    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          */
    253         public int swap() {
    254             if (! mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
    255                 return mEgl.eglGetError();
    256             }
    257             return EGL10.EGL_SUCCESS;
    258         }
    259 
    260         public void destroySurface() {
    261             destroySurfaceImp();
    262         }
    263 
    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 
    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 
    285         private void throwEglException(String function) {
    286             throwEglException(function, mEgl.eglGetError());
    287         }
    288 
    289         public static void throwEglException(String function, int error) {
    290             String message = formatEglError(function, error);
    291             throw new RuntimeException(message);
    292         }
    293 
    294         public static void logEglErrorAsWarning(String tag, String function, int error) {
    295             Log.w(tag, formatEglError(function, error));
    296         }
    297 
    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 
    322         public RenderThread(Renderer renderer) {
    323             super("RenderThread");
    324             mRenderer = renderer;
    325             start();
    326         }
    327 
    328         private void checkRenderer() {
    329             if (mRenderer == null) {
    330                 throw new IllegalArgumentException("Renderer is null!");
    331             }
    332         }
    333 
    334         private void checkSurface() {
    335             if (mSurface == null) {
    336                 throw new IllegalArgumentException("surface is null!");
    337             }
    338         }
    339 
    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 
    350         public void setSize(int width, int height) {
    351             checkRenderer();
    352             checkSurface();
    353             mWidth = width;
    354             mHeight = height;
    355             exec(RESIZE_SURFACE);
    356         }
    357 
    358         public void render() {
    359             checkRenderer();
    360             if (mSurface != null) {
    361                 exec(RENDER);
    362                 mSurface.updateTexImage();
    363             }
    364         }
    365 
    366         public void finish() {
    367             mSurface = null;
    368             exec(FINISH);
    369             try {
    370                 join();
    371             } catch (InterruptedException e) {}
    372         }
    373 
    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 
    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
    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