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