mobile/android/base/gfx/GLController.java

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
     2  * This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 package org.mozilla.gecko.gfx;
     8 import org.mozilla.gecko.GeckoAppShell;
     9 import org.mozilla.gecko.GeckoEvent;
    10 import org.mozilla.gecko.GeckoThread;
    11 import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
    12 import org.mozilla.gecko.util.ThreadUtils;
    14 import android.util.Log;
    16 import javax.microedition.khronos.egl.EGL10;
    17 import javax.microedition.khronos.egl.EGLConfig;
    18 import javax.microedition.khronos.egl.EGLContext;
    19 import javax.microedition.khronos.egl.EGLDisplay;
    20 import javax.microedition.khronos.egl.EGLSurface;
    22 /**
    23  * EGLPreloadingThread is purely a preloading optimization, not something
    24  * we rely on for anything else than performance. We will be initializing
    25  * EGL in GLController::initEGL() when we need it, but having EGL initialization
    26  * already previously done by EGLPreloadingThread::run() will make it much
    27  * faster for GLController to do again.
    28  *
    29  * For example, here are some timings recorded on two devices:
    30  *
    31  * Device                 | EGLPreloadingThread::run() | GLController::initEGL()
    32  * -----------------------+----------------------------+------------------------
    33  * Nexus S (Android 2.3)  | ~ 80 ms                    | < 0.1 ms
    34  * Nexus 10 (Android 4.3) | ~ 35 ms                    | < 0.1 ms
    35  */
    36 class EGLPreloadingThread extends Thread
    37 {
    38     private static final String LOGTAG = "EGLPreloadingThread";
    39     private EGL10 mEGL;
    40     private EGLDisplay mEGLDisplay;
    42     public EGLPreloadingThread()
    43     {
    44     }
    46     @Override
    47     public void run()
    48     {
    49         mEGL = (EGL10)EGLContext.getEGL();
    50         mEGLDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
    51         if (mEGLDisplay == EGL10.EGL_NO_DISPLAY) {
    52             Log.w(LOGTAG, "Can't get EGL display!");
    53             return;
    54         }
    56         int[] returnedVersion = new int[2];
    57         if (!mEGL.eglInitialize(mEGLDisplay, returnedVersion)) {
    58             Log.w(LOGTAG, "eglInitialize failed");
    59             return;
    60         }
    61     }
    62 }
    64 /**
    65  * This class is a singleton that tracks EGL and compositor things over
    66  * the lifetime of Fennec running.
    67  * We only ever create one C++ compositor over Fennec's lifetime, but
    68  * most of the Java-side objects (e.g. LayerView, GeckoLayerClient,
    69  * LayerRenderer) can all get destroyed and re-created if the GeckoApp
    70  * activity is destroyed. This GLController is never destroyed, so that
    71  * the mCompositorCreated field and other state variables are always
    72  * accurate.
    73  */
    74 public class GLController {
    75     private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
    76     private static final String LOGTAG = "GeckoGLController";
    78     private static GLController sInstance;
    80     private LayerView mView;
    81     private boolean mServerSurfaceValid;
    82     private int mWidth, mHeight;
    84     /* This is written by the compositor thread (while the UI thread
    85      * is blocked on it) and read by the UI thread. */
    86     private volatile boolean mCompositorCreated;
    88     private EGL10 mEGL;
    89     private EGLDisplay mEGLDisplay;
    90     private EGLConfig mEGLConfig;
    91     private EGLPreloadingThread mEGLPreloadingThread;
    92     private EGLSurface mEGLSurfaceForCompositor;
    94     private static final int LOCAL_EGL_OPENGL_ES2_BIT = 4;
    96     private static final int[] CONFIG_SPEC_16BPP = {
    97         EGL10.EGL_RED_SIZE, 5,
    98         EGL10.EGL_GREEN_SIZE, 6,
    99         EGL10.EGL_BLUE_SIZE, 5,
   100         EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT,
   101         EGL10.EGL_RENDERABLE_TYPE, LOCAL_EGL_OPENGL_ES2_BIT,
   102         EGL10.EGL_NONE
   103     };
   105     private static final int[] CONFIG_SPEC_24BPP = {
   106         EGL10.EGL_RED_SIZE, 8,
   107         EGL10.EGL_GREEN_SIZE, 8,
   108         EGL10.EGL_BLUE_SIZE, 8,
   109         EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT,
   110         EGL10.EGL_RENDERABLE_TYPE, LOCAL_EGL_OPENGL_ES2_BIT,
   111         EGL10.EGL_NONE
   112     };
   114     private GLController() {
   115         mEGLPreloadingThread = new EGLPreloadingThread();
   116         mEGLPreloadingThread.start();
   117     }
   119     static GLController getInstance(LayerView view) {
   120         if (sInstance == null) {
   121             sInstance = new GLController();
   122         }
   123         sInstance.mView = view;
   124         return sInstance;
   125     }
   127     synchronized void serverSurfaceDestroyed() {
   128         ThreadUtils.assertOnUiThread();
   130         mServerSurfaceValid = false;
   132         if (mEGLSurfaceForCompositor != null) {
   133           mEGL.eglDestroySurface(mEGLDisplay, mEGLSurfaceForCompositor);
   134           mEGLSurfaceForCompositor = null;
   135         }
   137         // We need to coordinate with Gecko when pausing composition, to ensure
   138         // that Gecko never executes a draw event while the compositor is paused.
   139         // This is sent synchronously to make sure that we don't attempt to use
   140         // any outstanding Surfaces after we call this (such as from a
   141         // serverSurfaceDestroyed notification), and to make sure that any in-flight
   142         // Gecko draw events have been processed.  When this returns, composition is
   143         // definitely paused -- it'll synchronize with the Gecko event loop, which
   144         // in turn will synchronize with the compositor thread.
   145         if (mCompositorCreated) {
   146             GeckoAppShell.sendEventToGeckoSync(GeckoEvent.createCompositorPauseEvent());
   147         }
   148     }
   150     synchronized void serverSurfaceChanged(int newWidth, int newHeight) {
   151         ThreadUtils.assertOnUiThread();
   153         mWidth = newWidth;
   154         mHeight = newHeight;
   155         mServerSurfaceValid = true;
   157         // we defer to a runnable the task of updating the compositor, because this is going to
   158         // call back into createEGLSurfaceForCompositor, which will try to create an EGLSurface
   159         // against mView, which we suspect might fail if called too early. By posting this to
   160         // mView, we hope to ensure that it is deferred until mView is actually "ready" for some
   161         // sense of "ready".
   162         mView.post(new Runnable() {
   163             @Override
   164             public void run() {
   165                 updateCompositor();
   166             }
   167         });
   168     }
   170     void updateCompositor() {
   171         ThreadUtils.assertOnUiThread();
   173         if (mCompositorCreated) {
   174             // If the compositor has already been created, just resume it instead. We don't need
   175             // to block here because if the surface is destroyed before the compositor grabs it,
   176             // we can handle that gracefully (i.e. the compositor will remain paused).
   177             resumeCompositor(mWidth, mHeight);
   178             return;
   179         }
   181         if (!AttemptPreallocateEGLSurfaceForCompositor()) {
   182             return;
   183         }
   185         // Only try to create the compositor if we have a valid surface and gecko is up. When these
   186         // two conditions are satisfied, we can be relatively sure that the compositor creation will
   187         // happen without needing to block anyhwere. Do it with a sync gecko event so that the
   188         // android doesn't have a chance to destroy our surface in between.
   189         if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
   190             GeckoAppShell.sendEventToGeckoSync(GeckoEvent.createCompositorCreateEvent(mWidth, mHeight));
   191         }
   192     }
   194     void compositorCreated() {
   195         // This is invoked on the compositor thread, while the java UI thread
   196         // is blocked on the gecko sync event in updateCompositor() above
   197         mCompositorCreated = true;
   198     }
   200     public boolean isServerSurfaceValid() {
   201         return mServerSurfaceValid;
   202     }
   204     public boolean isCompositorCreated() {
   205         return mCompositorCreated;
   206     }
   208     private void initEGL() {
   209         if (mEGL != null) {
   210             return;
   211         }
   213         // This join() should not be necessary, but makes this code a bit easier to think about.
   214         // The EGLPreloadingThread should long be done by now, and even if it's not,
   215         // it shouldn't be a problem to be initalizing EGL from two different threads.
   216         // Still, having this join() here means that we don't have to wonder about what
   217         // kind of caveats might exist with EGL initialization reentrancy on various drivers.
   218         try {
   219             mEGLPreloadingThread.join();
   220         } catch (InterruptedException e) {
   221             Log.w(LOGTAG, "EGLPreloadingThread interrupted", e);
   222         }
   224         mEGL = (EGL10)EGLContext.getEGL();
   226         mEGLDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
   227         if (mEGLDisplay == EGL10.EGL_NO_DISPLAY) {
   228             Log.w(LOGTAG, "Can't get EGL display!");
   229             return;
   230         }
   232         // while calling eglInitialize here should not be necessary as it was already called
   233         // by the EGLPreloadingThread, it really doesn't cost much to call it again here,
   234         // and makes this code easier to think about: EGLPreloadingThread is only a
   235         // preloading optimization, not something we rely on for anything else.
   236         //
   237         // Also note that while calling eglInitialize isn't necessary on Android 4.x
   238         // (at least Android's HardwareRenderer does it for us already), it is necessary
   239         // on Android 2.x.
   240         int[] returnedVersion = new int[2];
   241         if (!mEGL.eglInitialize(mEGLDisplay, returnedVersion)) {
   242             Log.w(LOGTAG, "eglInitialize failed");
   243             return;
   244         }
   246         mEGLConfig = chooseConfig();
   247     }
   249     private EGLConfig chooseConfig() {
   250         int[] desiredConfig;
   251         int rSize, gSize, bSize;
   252         int[] numConfigs = new int[1];
   254         switch (GeckoAppShell.getScreenDepth()) {
   255         case 24:
   256             desiredConfig = CONFIG_SPEC_24BPP;
   257             rSize = gSize = bSize = 8;
   258             break;
   259         case 16:
   260         default:
   261             desiredConfig = CONFIG_SPEC_16BPP;
   262             rSize = 5; gSize = 6; bSize = 5;
   263             break;
   264         }
   266         if (!mEGL.eglChooseConfig(mEGLDisplay, desiredConfig, null, 0, numConfigs) ||
   267                 numConfigs[0] <= 0) {
   268             throw new GLControllerException("No available EGL configurations " +
   269                                             getEGLError());
   270         }
   272         EGLConfig[] configs = new EGLConfig[numConfigs[0]];
   273         if (!mEGL.eglChooseConfig(mEGLDisplay, desiredConfig, configs, numConfigs[0], numConfigs)) {
   274             throw new GLControllerException("No EGL configuration for that specification " +
   275                                             getEGLError());
   276         }
   278         // Select the first configuration that matches the screen depth.
   279         int[] red = new int[1], green = new int[1], blue = new int[1];
   280         for (EGLConfig config : configs) {
   281             mEGL.eglGetConfigAttrib(mEGLDisplay, config, EGL10.EGL_RED_SIZE, red);
   282             mEGL.eglGetConfigAttrib(mEGLDisplay, config, EGL10.EGL_GREEN_SIZE, green);
   283             mEGL.eglGetConfigAttrib(mEGLDisplay, config, EGL10.EGL_BLUE_SIZE, blue);
   284             if (red[0] == rSize && green[0] == gSize && blue[0] == bSize) {
   285                 return config;
   286             }
   287         }
   289         throw new GLControllerException("No suitable EGL configuration found");
   290     }
   292     private synchronized boolean AttemptPreallocateEGLSurfaceForCompositor() {
   293         if (mEGLSurfaceForCompositor == null) {
   294             initEGL();
   295             try {
   296                 mEGLSurfaceForCompositor = mEGL.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, mView.getNativeWindow(), null);
   297                 // In failure cases, eglCreateWindowSurface should return EGL_NO_SURFACE.
   298                 // We currently normalize this to null, and compare to null in all our checks.
   299                 if (mEGLSurfaceForCompositor == EGL10.EGL_NO_SURFACE) {
   300                     mEGLSurfaceForCompositor = null;
   301                 }
   302             } catch (Exception e) {
   303                 Log.e(LOGTAG, "eglCreateWindowSurface threw", e);
   304             }
   305         }
   306         if (mEGLSurfaceForCompositor == null) {
   307             Log.w(LOGTAG, "eglCreateWindowSurface returned no surface!");
   308         }
   309         return mEGLSurfaceForCompositor != null;
   310     }
   312     @WrapElementForJNI(allowMultithread = true, stubName = "CreateEGLSurfaceForCompositorWrapper")
   313     private synchronized EGLSurface createEGLSurfaceForCompositor() {
   314         AttemptPreallocateEGLSurfaceForCompositor();
   315         EGLSurface result = mEGLSurfaceForCompositor;
   316         mEGLSurfaceForCompositor = null;
   317         return result;
   318     }
   320     private String getEGLError() {
   321         return "Error " + (mEGL == null ? "(no mEGL)" : mEGL.eglGetError());
   322     }
   324     void resumeCompositor(int width, int height) {
   325         // Asking Gecko to resume the compositor takes too long (see
   326         // https://bugzilla.mozilla.org/show_bug.cgi?id=735230#c23), so we
   327         // resume the compositor directly. We still need to inform Gecko about
   328         // the compositor resuming, so that Gecko knows that it can now draw.
   329         // It is important to not notify Gecko until after the compositor has
   330         // been resumed, otherwise Gecko may send updates that get dropped.
   331         if (mCompositorCreated) {
   332             GeckoAppShell.scheduleResumeComposition(width, height);
   333             GeckoAppShell.sendEventToGecko(GeckoEvent.createCompositorResumeEvent());
   334         }
   335     }
   337     public static class GLControllerException extends RuntimeException {
   338         public static final long serialVersionUID = 1L;
   340         GLControllerException(String e) {
   341             super(e);
   342         }
   343     }
   344 }

mercurial