mobile/android/base/gfx/GLController.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

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

mercurial