Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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 | } |