1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/gfx/GLController.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,344 @@ 1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +package org.mozilla.gecko.gfx; 1.10 + 1.11 +import org.mozilla.gecko.GeckoAppShell; 1.12 +import org.mozilla.gecko.GeckoEvent; 1.13 +import org.mozilla.gecko.GeckoThread; 1.14 +import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI; 1.15 +import org.mozilla.gecko.util.ThreadUtils; 1.16 + 1.17 +import android.util.Log; 1.18 + 1.19 +import javax.microedition.khronos.egl.EGL10; 1.20 +import javax.microedition.khronos.egl.EGLConfig; 1.21 +import javax.microedition.khronos.egl.EGLContext; 1.22 +import javax.microedition.khronos.egl.EGLDisplay; 1.23 +import javax.microedition.khronos.egl.EGLSurface; 1.24 + 1.25 +/** 1.26 + * EGLPreloadingThread is purely a preloading optimization, not something 1.27 + * we rely on for anything else than performance. We will be initializing 1.28 + * EGL in GLController::initEGL() when we need it, but having EGL initialization 1.29 + * already previously done by EGLPreloadingThread::run() will make it much 1.30 + * faster for GLController to do again. 1.31 + * 1.32 + * For example, here are some timings recorded on two devices: 1.33 + * 1.34 + * Device | EGLPreloadingThread::run() | GLController::initEGL() 1.35 + * -----------------------+----------------------------+------------------------ 1.36 + * Nexus S (Android 2.3) | ~ 80 ms | < 0.1 ms 1.37 + * Nexus 10 (Android 4.3) | ~ 35 ms | < 0.1 ms 1.38 + */ 1.39 +class EGLPreloadingThread extends Thread 1.40 +{ 1.41 + private static final String LOGTAG = "EGLPreloadingThread"; 1.42 + private EGL10 mEGL; 1.43 + private EGLDisplay mEGLDisplay; 1.44 + 1.45 + public EGLPreloadingThread() 1.46 + { 1.47 + } 1.48 + 1.49 + @Override 1.50 + public void run() 1.51 + { 1.52 + mEGL = (EGL10)EGLContext.getEGL(); 1.53 + mEGLDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); 1.54 + if (mEGLDisplay == EGL10.EGL_NO_DISPLAY) { 1.55 + Log.w(LOGTAG, "Can't get EGL display!"); 1.56 + return; 1.57 + } 1.58 + 1.59 + int[] returnedVersion = new int[2]; 1.60 + if (!mEGL.eglInitialize(mEGLDisplay, returnedVersion)) { 1.61 + Log.w(LOGTAG, "eglInitialize failed"); 1.62 + return; 1.63 + } 1.64 + } 1.65 +} 1.66 + 1.67 +/** 1.68 + * This class is a singleton that tracks EGL and compositor things over 1.69 + * the lifetime of Fennec running. 1.70 + * We only ever create one C++ compositor over Fennec's lifetime, but 1.71 + * most of the Java-side objects (e.g. LayerView, GeckoLayerClient, 1.72 + * LayerRenderer) can all get destroyed and re-created if the GeckoApp 1.73 + * activity is destroyed. This GLController is never destroyed, so that 1.74 + * the mCompositorCreated field and other state variables are always 1.75 + * accurate. 1.76 + */ 1.77 +public class GLController { 1.78 + private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; 1.79 + private static final String LOGTAG = "GeckoGLController"; 1.80 + 1.81 + private static GLController sInstance; 1.82 + 1.83 + private LayerView mView; 1.84 + private boolean mServerSurfaceValid; 1.85 + private int mWidth, mHeight; 1.86 + 1.87 + /* This is written by the compositor thread (while the UI thread 1.88 + * is blocked on it) and read by the UI thread. */ 1.89 + private volatile boolean mCompositorCreated; 1.90 + 1.91 + private EGL10 mEGL; 1.92 + private EGLDisplay mEGLDisplay; 1.93 + private EGLConfig mEGLConfig; 1.94 + private EGLPreloadingThread mEGLPreloadingThread; 1.95 + private EGLSurface mEGLSurfaceForCompositor; 1.96 + 1.97 + private static final int LOCAL_EGL_OPENGL_ES2_BIT = 4; 1.98 + 1.99 + private static final int[] CONFIG_SPEC_16BPP = { 1.100 + EGL10.EGL_RED_SIZE, 5, 1.101 + EGL10.EGL_GREEN_SIZE, 6, 1.102 + EGL10.EGL_BLUE_SIZE, 5, 1.103 + EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT, 1.104 + EGL10.EGL_RENDERABLE_TYPE, LOCAL_EGL_OPENGL_ES2_BIT, 1.105 + EGL10.EGL_NONE 1.106 + }; 1.107 + 1.108 + private static final int[] CONFIG_SPEC_24BPP = { 1.109 + EGL10.EGL_RED_SIZE, 8, 1.110 + EGL10.EGL_GREEN_SIZE, 8, 1.111 + EGL10.EGL_BLUE_SIZE, 8, 1.112 + EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT, 1.113 + EGL10.EGL_RENDERABLE_TYPE, LOCAL_EGL_OPENGL_ES2_BIT, 1.114 + EGL10.EGL_NONE 1.115 + }; 1.116 + 1.117 + private GLController() { 1.118 + mEGLPreloadingThread = new EGLPreloadingThread(); 1.119 + mEGLPreloadingThread.start(); 1.120 + } 1.121 + 1.122 + static GLController getInstance(LayerView view) { 1.123 + if (sInstance == null) { 1.124 + sInstance = new GLController(); 1.125 + } 1.126 + sInstance.mView = view; 1.127 + return sInstance; 1.128 + } 1.129 + 1.130 + synchronized void serverSurfaceDestroyed() { 1.131 + ThreadUtils.assertOnUiThread(); 1.132 + 1.133 + mServerSurfaceValid = false; 1.134 + 1.135 + if (mEGLSurfaceForCompositor != null) { 1.136 + mEGL.eglDestroySurface(mEGLDisplay, mEGLSurfaceForCompositor); 1.137 + mEGLSurfaceForCompositor = null; 1.138 + } 1.139 + 1.140 + // We need to coordinate with Gecko when pausing composition, to ensure 1.141 + // that Gecko never executes a draw event while the compositor is paused. 1.142 + // This is sent synchronously to make sure that we don't attempt to use 1.143 + // any outstanding Surfaces after we call this (such as from a 1.144 + // serverSurfaceDestroyed notification), and to make sure that any in-flight 1.145 + // Gecko draw events have been processed. When this returns, composition is 1.146 + // definitely paused -- it'll synchronize with the Gecko event loop, which 1.147 + // in turn will synchronize with the compositor thread. 1.148 + if (mCompositorCreated) { 1.149 + GeckoAppShell.sendEventToGeckoSync(GeckoEvent.createCompositorPauseEvent()); 1.150 + } 1.151 + } 1.152 + 1.153 + synchronized void serverSurfaceChanged(int newWidth, int newHeight) { 1.154 + ThreadUtils.assertOnUiThread(); 1.155 + 1.156 + mWidth = newWidth; 1.157 + mHeight = newHeight; 1.158 + mServerSurfaceValid = true; 1.159 + 1.160 + // we defer to a runnable the task of updating the compositor, because this is going to 1.161 + // call back into createEGLSurfaceForCompositor, which will try to create an EGLSurface 1.162 + // against mView, which we suspect might fail if called too early. By posting this to 1.163 + // mView, we hope to ensure that it is deferred until mView is actually "ready" for some 1.164 + // sense of "ready". 1.165 + mView.post(new Runnable() { 1.166 + @Override 1.167 + public void run() { 1.168 + updateCompositor(); 1.169 + } 1.170 + }); 1.171 + } 1.172 + 1.173 + void updateCompositor() { 1.174 + ThreadUtils.assertOnUiThread(); 1.175 + 1.176 + if (mCompositorCreated) { 1.177 + // If the compositor has already been created, just resume it instead. We don't need 1.178 + // to block here because if the surface is destroyed before the compositor grabs it, 1.179 + // we can handle that gracefully (i.e. the compositor will remain paused). 1.180 + resumeCompositor(mWidth, mHeight); 1.181 + return; 1.182 + } 1.183 + 1.184 + if (!AttemptPreallocateEGLSurfaceForCompositor()) { 1.185 + return; 1.186 + } 1.187 + 1.188 + // Only try to create the compositor if we have a valid surface and gecko is up. When these 1.189 + // two conditions are satisfied, we can be relatively sure that the compositor creation will 1.190 + // happen without needing to block anyhwere. Do it with a sync gecko event so that the 1.191 + // android doesn't have a chance to destroy our surface in between. 1.192 + if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) { 1.193 + GeckoAppShell.sendEventToGeckoSync(GeckoEvent.createCompositorCreateEvent(mWidth, mHeight)); 1.194 + } 1.195 + } 1.196 + 1.197 + void compositorCreated() { 1.198 + // This is invoked on the compositor thread, while the java UI thread 1.199 + // is blocked on the gecko sync event in updateCompositor() above 1.200 + mCompositorCreated = true; 1.201 + } 1.202 + 1.203 + public boolean isServerSurfaceValid() { 1.204 + return mServerSurfaceValid; 1.205 + } 1.206 + 1.207 + public boolean isCompositorCreated() { 1.208 + return mCompositorCreated; 1.209 + } 1.210 + 1.211 + private void initEGL() { 1.212 + if (mEGL != null) { 1.213 + return; 1.214 + } 1.215 + 1.216 + // This join() should not be necessary, but makes this code a bit easier to think about. 1.217 + // The EGLPreloadingThread should long be done by now, and even if it's not, 1.218 + // it shouldn't be a problem to be initalizing EGL from two different threads. 1.219 + // Still, having this join() here means that we don't have to wonder about what 1.220 + // kind of caveats might exist with EGL initialization reentrancy on various drivers. 1.221 + try { 1.222 + mEGLPreloadingThread.join(); 1.223 + } catch (InterruptedException e) { 1.224 + Log.w(LOGTAG, "EGLPreloadingThread interrupted", e); 1.225 + } 1.226 + 1.227 + mEGL = (EGL10)EGLContext.getEGL(); 1.228 + 1.229 + mEGLDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); 1.230 + if (mEGLDisplay == EGL10.EGL_NO_DISPLAY) { 1.231 + Log.w(LOGTAG, "Can't get EGL display!"); 1.232 + return; 1.233 + } 1.234 + 1.235 + // while calling eglInitialize here should not be necessary as it was already called 1.236 + // by the EGLPreloadingThread, it really doesn't cost much to call it again here, 1.237 + // and makes this code easier to think about: EGLPreloadingThread is only a 1.238 + // preloading optimization, not something we rely on for anything else. 1.239 + // 1.240 + // Also note that while calling eglInitialize isn't necessary on Android 4.x 1.241 + // (at least Android's HardwareRenderer does it for us already), it is necessary 1.242 + // on Android 2.x. 1.243 + int[] returnedVersion = new int[2]; 1.244 + if (!mEGL.eglInitialize(mEGLDisplay, returnedVersion)) { 1.245 + Log.w(LOGTAG, "eglInitialize failed"); 1.246 + return; 1.247 + } 1.248 + 1.249 + mEGLConfig = chooseConfig(); 1.250 + } 1.251 + 1.252 + private EGLConfig chooseConfig() { 1.253 + int[] desiredConfig; 1.254 + int rSize, gSize, bSize; 1.255 + int[] numConfigs = new int[1]; 1.256 + 1.257 + switch (GeckoAppShell.getScreenDepth()) { 1.258 + case 24: 1.259 + desiredConfig = CONFIG_SPEC_24BPP; 1.260 + rSize = gSize = bSize = 8; 1.261 + break; 1.262 + case 16: 1.263 + default: 1.264 + desiredConfig = CONFIG_SPEC_16BPP; 1.265 + rSize = 5; gSize = 6; bSize = 5; 1.266 + break; 1.267 + } 1.268 + 1.269 + if (!mEGL.eglChooseConfig(mEGLDisplay, desiredConfig, null, 0, numConfigs) || 1.270 + numConfigs[0] <= 0) { 1.271 + throw new GLControllerException("No available EGL configurations " + 1.272 + getEGLError()); 1.273 + } 1.274 + 1.275 + EGLConfig[] configs = new EGLConfig[numConfigs[0]]; 1.276 + if (!mEGL.eglChooseConfig(mEGLDisplay, desiredConfig, configs, numConfigs[0], numConfigs)) { 1.277 + throw new GLControllerException("No EGL configuration for that specification " + 1.278 + getEGLError()); 1.279 + } 1.280 + 1.281 + // Select the first configuration that matches the screen depth. 1.282 + int[] red = new int[1], green = new int[1], blue = new int[1]; 1.283 + for (EGLConfig config : configs) { 1.284 + mEGL.eglGetConfigAttrib(mEGLDisplay, config, EGL10.EGL_RED_SIZE, red); 1.285 + mEGL.eglGetConfigAttrib(mEGLDisplay, config, EGL10.EGL_GREEN_SIZE, green); 1.286 + mEGL.eglGetConfigAttrib(mEGLDisplay, config, EGL10.EGL_BLUE_SIZE, blue); 1.287 + if (red[0] == rSize && green[0] == gSize && blue[0] == bSize) { 1.288 + return config; 1.289 + } 1.290 + } 1.291 + 1.292 + throw new GLControllerException("No suitable EGL configuration found"); 1.293 + } 1.294 + 1.295 + private synchronized boolean AttemptPreallocateEGLSurfaceForCompositor() { 1.296 + if (mEGLSurfaceForCompositor == null) { 1.297 + initEGL(); 1.298 + try { 1.299 + mEGLSurfaceForCompositor = mEGL.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, mView.getNativeWindow(), null); 1.300 + // In failure cases, eglCreateWindowSurface should return EGL_NO_SURFACE. 1.301 + // We currently normalize this to null, and compare to null in all our checks. 1.302 + if (mEGLSurfaceForCompositor == EGL10.EGL_NO_SURFACE) { 1.303 + mEGLSurfaceForCompositor = null; 1.304 + } 1.305 + } catch (Exception e) { 1.306 + Log.e(LOGTAG, "eglCreateWindowSurface threw", e); 1.307 + } 1.308 + } 1.309 + if (mEGLSurfaceForCompositor == null) { 1.310 + Log.w(LOGTAG, "eglCreateWindowSurface returned no surface!"); 1.311 + } 1.312 + return mEGLSurfaceForCompositor != null; 1.313 + } 1.314 + 1.315 + @WrapElementForJNI(allowMultithread = true, stubName = "CreateEGLSurfaceForCompositorWrapper") 1.316 + private synchronized EGLSurface createEGLSurfaceForCompositor() { 1.317 + AttemptPreallocateEGLSurfaceForCompositor(); 1.318 + EGLSurface result = mEGLSurfaceForCompositor; 1.319 + mEGLSurfaceForCompositor = null; 1.320 + return result; 1.321 + } 1.322 + 1.323 + private String getEGLError() { 1.324 + return "Error " + (mEGL == null ? "(no mEGL)" : mEGL.eglGetError()); 1.325 + } 1.326 + 1.327 + void resumeCompositor(int width, int height) { 1.328 + // Asking Gecko to resume the compositor takes too long (see 1.329 + // https://bugzilla.mozilla.org/show_bug.cgi?id=735230#c23), so we 1.330 + // resume the compositor directly. We still need to inform Gecko about 1.331 + // the compositor resuming, so that Gecko knows that it can now draw. 1.332 + // It is important to not notify Gecko until after the compositor has 1.333 + // been resumed, otherwise Gecko may send updates that get dropped. 1.334 + if (mCompositorCreated) { 1.335 + GeckoAppShell.scheduleResumeComposition(width, height); 1.336 + GeckoAppShell.sendEventToGecko(GeckoEvent.createCompositorResumeEvent()); 1.337 + } 1.338 + } 1.339 + 1.340 + public static class GLControllerException extends RuntimeException { 1.341 + public static final long serialVersionUID = 1L; 1.342 + 1.343 + GLControllerException(String e) { 1.344 + super(e); 1.345 + } 1.346 + } 1.347 +}