Bug 844275 - Drive the layer manager creation from the GLController rather than GetLayerManager. r=Cwiiis
authorKartikaya Gupta <kgupta@mozilla.com>
Thu, 28 Feb 2013 13:28:23 -0500
changeset 133767 cd5fe1593b6f9856c481c230e7849ec1c4010394
parent 133766 fc3b5a25bcae249dd1893ae1852b1f839bf054d8
child 133768 c1bcca41d5518164f3e756a9f740b8ec8b78e904
push id2452
push userlsblakk@mozilla.com
push dateMon, 13 May 2013 16:59:38 +0000
treeherdermozilla-beta@d4b152d29d8d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersCwiiis
bugs844275
milestone22.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 844275 - Drive the layer manager creation from the GLController rather than GetLayerManager. r=Cwiiis
gfx/gl/GLContextProviderEGL.cpp
mobile/android/base/gfx/GLController.java
mobile/android/base/gfx/GeckoLayerClient.java
widget/android/AndroidBridge.cpp
widget/android/AndroidBridge.h
widget/android/AndroidLayerViewWrapper.cpp
widget/android/AndroidLayerViewWrapper.h
widget/android/nsWindow.cpp
widget/android/nsWindow.h
--- a/gfx/gl/GLContextProviderEGL.cpp
+++ b/gfx/gl/GLContextProviderEGL.cpp
@@ -546,17 +546,17 @@ public:
     }
 #else
     virtual bool
     RenewSurface() {
         sEGLLibrary.fMakeCurrent(EGL_DISPLAY(), EGL_NO_SURFACE, EGL_NO_SURFACE,
                                  EGL_NO_CONTEXT);
         if (!mSurface) {
 #ifdef MOZ_ANDROID_OMTC
-            mSurface = mozilla::AndroidBridge::Bridge()->ProvideEGLSurface(false);
+            mSurface = mozilla::AndroidBridge::Bridge()->ProvideEGLSurface();
             if (!mSurface) {
                 return false;
             }
 #else
             EGLConfig config;
             CreateConfig(&config);
             mSurface = CreateSurfaceForWindow(NULL, config);
 #endif
@@ -2072,17 +2072,17 @@ GLContextProviderEGL::CreateForWindow(ns
     EGLConfig config;
     if (!CreateConfig(&config)) {
         printf_stderr("Failed to create EGL config!\n");
         return nullptr;
     }
 
 #ifdef MOZ_ANDROID_OMTC
     mozilla::AndroidBridge::Bridge()->RegisterCompositor();
-    EGLSurface surface = mozilla::AndroidBridge::Bridge()->ProvideEGLSurface(true);
+    EGLSurface surface = mozilla::AndroidBridge::Bridge()->ProvideEGLSurface();
 #else
     EGLSurface surface = CreateSurfaceForWindow(aWidget, config);
 #endif
 
     if (!surface) {
         printf_stderr("Failed to create EGLSurface!\n");
         return nullptr;
     }
--- a/mobile/android/base/gfx/GLController.java
+++ b/mobile/android/base/gfx/GLController.java
@@ -1,17 +1,21 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.gfx;
 
+import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
+import org.mozilla.gecko.GeckoThread;
+
+import android.util.Log;
 
 import javax.microedition.khronos.egl.EGL10;
 import javax.microedition.khronos.egl.EGLConfig;
 import javax.microedition.khronos.egl.EGLContext;
 import javax.microedition.khronos.egl.EGLDisplay;
 import javax.microedition.khronos.egl.EGLSurface;
 
 /**
@@ -36,16 +40,17 @@ public class GLController {
 
     /* This is written by the compositor thread (while the UI thread
      * is blocked on it) and read by the UI thread. */
     private volatile boolean mCompositorCreated;
 
     private EGL10 mEGL;
     private EGLDisplay mEGLDisplay;
     private EGLConfig mEGLConfig;
+    private EGLSurface mEGLSurface;
 
     private static final int LOCAL_EGL_OPENGL_ES2_BIT = 4;
 
     private static final int[] CONFIG_SPEC = {
         EGL10.EGL_RED_SIZE, 5,
         EGL10.EGL_GREEN_SIZE, 6,
         EGL10.EGL_BLUE_SIZE, 5,
         EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT,
@@ -59,54 +64,115 @@ public class GLController {
     static GLController getInstance(LayerView view) {
         if (sInstance == null) {
             sInstance = new GLController();
         }
         sInstance.mView = view;
         return sInstance;
     }
 
-    /* Wait until we are allowed to use EGL functions on the Surface backing
-     * this window.
-     * This function is invoked by JNI */
-    synchronized void waitForValidSurface() {
-        while (!mSurfaceValid) {
-            try {
-                wait();
-            } catch (InterruptedException e) {
-                throw new RuntimeException(e);
-            }
-        }
-    }
+    synchronized void surfaceDestroyed() {
+        GeckoApp.assertOnUiThread();
 
-    synchronized void surfaceDestroyed() {
         mSurfaceValid = false;
-        notifyAll();
+        mEGLSurface = null;
 
         // We need to coordinate with Gecko when pausing composition, to ensure
         // that Gecko never executes a draw event while the compositor is paused.
         // This is sent synchronously to make sure that we don't attempt to use
         // any outstanding Surfaces after we call this (such as from a
         // surfaceDestroyed notification), and to make sure that any in-flight
         // Gecko draw events have been processed.  When this returns, composition is
         // definitely paused -- it'll synchronize with the Gecko event loop, which
         // in turn will synchronize with the compositor thread.
         if (mCompositorCreated) {
             GeckoAppShell.sendEventToGeckoSync(GeckoEvent.createCompositorPauseEvent());
         }
     }
 
     synchronized void surfaceChanged(int newWidth, int newHeight) {
+        GeckoApp.assertOnUiThread();
+
         mWidth = newWidth;
         mHeight = newHeight;
+
+        if (mSurfaceValid) {
+            // We need to make this call even when the compositor isn't currently
+            // paused (e.g. during an orientation change), to make the compositor
+            // aware of the changed surface.
+            resumeCompositor(mWidth, mHeight);
+            return;
+        }
         mSurfaceValid = true;
-        notifyAll();
+
+        // If we get here, we supposedly have a valid surface where previously we
+        // did not. So we're going to create the window surface and hold on to it
+        // until the compositor comes asking for it. However, we can't call
+        // eglCreateWindowSurface right away because the UI thread isn't *actually*
+        // done setting up - for some reason Android will send us a surfaceChanged
+        // notification before the surface is actually ready. So, we need to do the
+        // call to eglCreateWindowSurface in a runnable posted back to the UI thread
+        // that will run once this call unwinds all the way out and Android finishes
+        // doing its thing.
+
+        mView.post(new Runnable() {
+            public void run() {
+                try {
+                    // Re-check mSurfaceValid in case the surface was destroyed between
+                    // where we set it to true above and this runnable getting run.
+                    // If mSurfaceValid is still true, try to create mEGLSurface. If
+                    // mSurfaceValid is false, leave mEGLSurface as null. So at the end
+                    // of this block mEGLSurface will be null (or EGL_NO_SURFACE) if
+                    // eglCreateWindowSurface failed or if mSurfaceValid changed to false.
+                    if (mSurfaceValid) {
+                        if (mEGL == null) {
+                            initEGL();
+                        }
+
+                        mEGLSurface = mEGL.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, mView.getNativeWindow(), null);
+                    }
+                } catch (Exception e) {
+                    Log.e(LOGTAG, "Unable to create window surface", e);
+                }
+                if (mEGLSurface == null || mEGLSurface == EGL10.EGL_NO_SURFACE) {
+                    mSurfaceValid = false;
+                    mEGLSurface = null; // normalize EGL_NO_SURFACE to null to simplify later checks
+                    Log.e(LOGTAG, "EGL window surface could not be created: " + getEGLError());
+                    return;
+                }
+                // At this point mSurfaceValid is true and mEGLSurface is a valid surface. Try
+                // to create the compositor if it hasn't been created already.
+                createCompositor();
+            }
+        });
+    }
+
+    void createCompositor() {
+        GeckoApp.assertOnUiThread();
+
+        if (mCompositorCreated) {
+            // If the compositor has already been created, just resume it instead. We don't need
+            // to block here because if the surface is destroyed before the compositor grabs it,
+            // we can handle that gracefully (i.e. the compositor will remain paused).
+            resumeCompositor(mWidth, mHeight);
+            return;
+        }
+
+        // Only try to create the compositor if we have a valid surface and gecko is up. When these
+        // two conditions are satisfied, we can be relatively sure that the compositor creation will
+        // happen without needing to block anyhwere. Do it with a sync gecko event so that the
+        // android doesn't have a chance to destroy our surface in between.
+        if (mEGLSurface != null && GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
+            GeckoAppShell.sendEventToGeckoSync(GeckoEvent.createCompositorResumeEvent());
+        }
     }
 
     void compositorCreated() {
+        // This is invoked on the compositor thread, while the java UI thread
+        // is blocked on the gecko sync event in createCompositor() above
         mCompositorCreated = true;
     }
 
     public boolean hasValidSurface() {
         return mSurfaceValid;
     }
 
     private void initEGL() {
@@ -143,40 +209,19 @@ public class GLController {
             if (red[0] == 5 && green[0] == 6 && blue[0] == 5) {
                 return config;
             }
         }
 
         throw new GLControllerException("No suitable EGL configuration found");
     }
 
-    /**
-     * Provides an EGLSurface without assuming ownership of this surface.
-     * This class does not keep a reference to the provided EGL surface; the
-     * caller assumes ownership of the surface once it is returned.
-     * This function is invoked by JNI */
+    /* This function is invoked by JNI on the compositor thread */
     private EGLSurface provideEGLSurface() {
-        synchronized (this) {
-            if (!mSurfaceValid) {
-                return null;
-            }
-        }
-
-        if (mEGL == null) {
-            initEGL();
-        }
-
-        Object window = mView.getNativeWindow();
-        EGLSurface surface = mEGL.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, window, null);
-        if (surface == null || surface == EGL10.EGL_NO_SURFACE) {
-            throw new GLControllerException("EGL window surface could not be created! " +
-                                            getEGLError());
-        }
-
-        return surface;
+        return mEGLSurface;
     }
 
     private String getEGLError() {
         return "Error " + mEGL.eglGetError();
     }
 
     void resumeCompositor(int width, int height) {
         // Asking Gecko to resume the compositor takes too long (see
--- a/mobile/android/base/gfx/GeckoLayerClient.java
+++ b/mobile/android/base/gfx/GeckoLayerClient.java
@@ -121,16 +121,27 @@ public class GeckoLayerClient implements
         mGeckoIsReady = true;
 
         mRootLayer = new VirtualLayer(new IntSize(mView.getWidth(), mView.getHeight()));
         mLayerRenderer = mView.getRenderer();
 
         sendResizeEventIfNecessary(true);
 
         DisplayPortCalculator.initPrefs();
+
+        // Gecko being ready is one of the two conditions (along with having an available
+        // surface) that cause us to create the compositor. So here, now that we know gecko
+        // is ready, call createCompositor() to see if we can actually do the creation.
+        // This needs to run on the UI thread so that the surface validity can't change on
+        // us while we're in the middle of creating the compositor.
+        mView.post(new Runnable() {
+            public void run() {
+                mView.getGLController().createCompositor();
+            }
+        });
     }
 
     public void destroy() {
         mPanZoomController.destroy();
     }
 
     /**
      * Returns true if this client is fine with performing a redraw operation or false if it
@@ -599,21 +610,16 @@ public class GeckoLayerClient implements
         // cause a visible jump.
         mView.getGLController().resumeCompositor(mWindowSize.width, mWindowSize.height);
     }
 
     /** Implementation of LayerView.Listener */
     @Override
     public void surfaceChanged(int width, int height) {
         setViewportSize(width, height);
-
-        // We need to make this call even when the compositor isn't currently
-        // paused (e.g. during an orientation change), to make the compositor
-        // aware of the changed surface.
-        mView.getGLController().resumeCompositor(width, height);
     }
 
     /** Implementation of PanZoomTarget */
     @Override
     public ImmutableViewportMetrics getViewportMetrics() {
         return mViewportMetrics;
     }
 
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -1137,21 +1137,18 @@ AndroidBridge::RegisterCompositor(JNIEnv
     jobject glController = env->CallStaticObjectMethod(jLayerView, registerCompositor);
     if (jniFrame.CheckForException())
         return;
 
     sController.Acquire(env, glController);
 }
 
 EGLSurface
-AndroidBridge::ProvideEGLSurface(bool waitUntilValid)
+AndroidBridge::ProvideEGLSurface()
 {
-    if (waitUntilValid) {
-        sController.WaitForValidSurface();
-    }
     return sController.ProvideEGLSurface();
 }
 
 bool
 AndroidBridge::GetStaticIntField(const char *className, const char *fieldName, int32_t* aInt, JNIEnv* env /* = nullptr */)
 {
     ALOG_BRIDGE("AndroidBridge::GetStaticIntField %s", fieldName);
 
--- a/widget/android/AndroidBridge.h
+++ b/widget/android/AndroidBridge.h
@@ -251,17 +251,17 @@ public:
     void GetSystemColors(AndroidSystemColors *aColors);
 
     void GetIconForExtension(const nsACString& aFileExt, uint32_t aIconSize, uint8_t * const aBuf);
 
     bool GetShowPasswordSetting();
 
     // Switch Java to composite with the Gecko Compositor thread
     void RegisterCompositor(JNIEnv* env = NULL);
-    EGLSurface ProvideEGLSurface(bool waitUntilValid);
+    EGLSurface ProvideEGLSurface();
 
     bool GetStaticStringField(const char *classID, const char *field, nsAString &result, JNIEnv* env = nullptr);
 
     bool GetStaticIntField(const char *className, const char *fieldName, int32_t* aInt, JNIEnv* env = nullptr);
 
     void SetKeepScreenOn(bool on);
 
     void ScanMedia(const nsAString& aFile, const nsACString& aMimeType);
--- a/widget/android/AndroidLayerViewWrapper.cpp
+++ b/widget/android/AndroidLayerViewWrapper.cpp
@@ -21,27 +21,25 @@ void AndroidEGLObject::Init(JNIEnv* aJEn
     jClass = reinterpret_cast<jclass>
         (aJEnv->NewGlobalRef(aJEnv->FindClass("com/google/android/gles_jni/EGLSurfaceImpl")));
     if (!jClass)
         return;
 
     jEGLSurfacePointerField = aJEnv->GetFieldID(jClass, "mEGLSurface", "I");
 }
 
-jmethodID AndroidGLController::jWaitForValidSurfaceMethod = 0;
 jmethodID AndroidGLController::jProvideEGLSurfaceMethod = 0;
 
 void
 AndroidGLController::Init(JNIEnv* aJEnv)
 {
     jclass jClass = reinterpret_cast<jclass>(aJEnv->NewGlobalRef(aJEnv->FindClass("org/mozilla/gecko/gfx/GLController")));
 
     jProvideEGLSurfaceMethod = aJEnv->GetMethodID(jClass, "provideEGLSurface",
                                                   "()Ljavax/microedition/khronos/egl/EGLSurface;");
-    jWaitForValidSurfaceMethod = aJEnv->GetMethodID(jClass, "waitForValidSurface", "()V");
 }
 
 void
 AndroidGLController::Acquire(JNIEnv* aJEnv, jobject aJObj)
 {
     mJEnv = aJEnv;
     mThread = pthread_self();
     mJObj = aJEnv->NewGlobalRef(aJObj);
@@ -57,16 +55,8 @@ AndroidGLController::ProvideEGLSurface()
 
     AutoLocalJNIFrame jniFrame(mJEnv);
     jobject jObj = mJEnv->CallObjectMethod(mJObj, jProvideEGLSurfaceMethod);
     if (jniFrame.CheckForException() || !jObj)
         return NULL;
 
     return reinterpret_cast<EGLSurface>(mJEnv->GetIntField(jObj, jEGLSurfacePointerField));
 }
-
-void
-AndroidGLController::WaitForValidSurface()
-{
-    ASSERT_THREAD();
-    AutoLocalJNIFrame jniFrame(mJEnv, 0);
-    mJEnv->CallVoidMethod(mJObj, jWaitForValidSurfaceMethod);
-}
--- a/widget/android/AndroidLayerViewWrapper.h
+++ b/widget/android/AndroidLayerViewWrapper.h
@@ -17,20 +17,18 @@ public:
 typedef void* EGLSurface;
 
 class AndroidGLController {
 public:
     static void Init(JNIEnv* aJEnv);
 
     void Acquire(JNIEnv* aJEnv, jobject aJObj);
     EGLSurface ProvideEGLSurface();
-    void WaitForValidSurface();
 
 private:
-    static jmethodID jWaitForValidSurfaceMethod;
     static jmethodID jProvideEGLSurfaceMethod;
 
     // the JNIEnv for the compositor thread
     JNIEnv* mJEnv;
     pthread_t mThread;
     jobject mJObj;
 };
 
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -683,51 +683,59 @@ nsWindow::GetLayerManager(PLayersChild*,
                           bool* aAllowRetaining)
 {
     if (aAllowRetaining) {
         *aAllowRetaining = true;
     }
     if (mLayerManager) {
         return mLayerManager;
     }
+    // for OMTC allow use of the single layer manager/compositor
+    // shared across all windows
+    if (UseOffMainThreadCompositing()) {
+        return sLayerManager;
+    }
+    return nullptr;
+}
+
+void
+nsWindow::CreateLayerManager()
+{
+    if (mLayerManager) {
+        return;
+    }
 
     nsWindow *topLevelWindow = FindTopLevel();
     if (!topLevelWindow || topLevelWindow->mWindowType == eWindowType_invisible) {
         // don't create a layer manager for an invisible top-level window
-        return nullptr;
+        return;
     }
 
     mUseLayersAcceleration = ComputeShouldAccelerate(mUseLayersAcceleration);
 
-    bool useCompositor = UseOffMainThreadCompositing();
-
-    if (useCompositor) {
+    if (UseOffMainThreadCompositing()) {
         if (sLayerManager) {
-            return sLayerManager;
+            return;
         }
         CreateCompositor();
         if (mLayerManager) {
             // for OMTC create a single layer manager and compositor that will be
             // used for all windows.
             SetCompositor(mLayerManager, mCompositorParent, mCompositorChild);
-            return mLayerManager;
+            return;
         }
 
         // If we get here, then off main thread compositing failed to initialize.
         sFailedToCreateGLContext = true;
     }
 
-    if (!mUseLayersAcceleration ||
-        sFailedToCreateGLContext)
-    {
+    if (!mUseLayersAcceleration || sFailedToCreateGLContext) {
         printf_stderr(" -- creating basic, not accelerated\n");
         mLayerManager = CreateBasicLayerManager();
     }
-
-    return mLayerManager;
 }
 
 void
 nsWindow::OnGlobalAndroidEvent(AndroidGeckoEvent *ae)
 {
     if (!AndroidBridge::Bridge())
         return;
 
@@ -881,16 +889,18 @@ nsWindow::OnGlobalAndroidEvent(AndroidGe
             // cannot make a GL context current in order to process updates.
             if (sCompositorChild) {
                 sCompositorChild->SendPause();
             }
             sCompositorPaused = true;
             break;
 
         case AndroidGeckoEvent::COMPOSITOR_RESUME:
+            win->CreateLayerManager();
+
             // When we receive this, the compositor has already been told to
             // resume. (It turns out that waiting till we reach here to tell
             // the compositor to resume takes too long, resulting in a black
             // flash.) This means it's now safe for layer updates to occur.
             // Since we might have prevented one or more draw events from
             // occurring while the compositor was paused, we need to schedule
             // a draw event now.
             if (!sCompositorPaused) {
@@ -2226,17 +2236,17 @@ nsWindow::DrawWindowOverlay(LayerManager
     mLayerRendererFrame.Dispose(env);
 }
 
 // off-main-thread compositor fields and functions
 
 nsRefPtr<mozilla::layers::LayerManager> nsWindow::sLayerManager = 0;
 nsRefPtr<mozilla::layers::CompositorParent> nsWindow::sCompositorParent = 0;
 nsRefPtr<mozilla::layers::CompositorChild> nsWindow::sCompositorChild = 0;
-bool nsWindow::sCompositorPaused = false;
+bool nsWindow::sCompositorPaused = true;
 
 void
 nsWindow::SetCompositor(mozilla::layers::LayerManager* aLayerManager,
                         mozilla::layers::CompositorParent* aCompositorParent,
                         mozilla::layers::CompositorChild* aCompositorChild)
 {
     bool sizeChangeNeeded = (aCompositorParent && !sCompositorParent && gAndroidBounds.width != 0);
 
--- a/widget/android/nsWindow.h
+++ b/widget/android/nsWindow.h
@@ -134,16 +134,17 @@ public:
     NS_IMETHOD OnIMETextChange(uint32_t aStart, uint32_t aOldEnd, uint32_t aNewEnd);
     NS_IMETHOD OnIMESelectionChange(void);
     virtual nsIMEUpdatePreference GetIMEUpdatePreference();
 
     LayerManager* GetLayerManager (PLayersChild* aShadowManager = nullptr,
                                    LayersBackend aBackendHint = mozilla::layers::LAYERS_NONE,
                                    LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT,
                                    bool* aAllowRetaining = nullptr);
+    void CreateLayerManager();
 
     NS_IMETHOD ReparentNativeWidget(nsIWidget* aNewParent);
 
     virtual bool NeedsPaint();
     virtual void DrawWindowUnderlay(LayerManager* aManager, nsIntRect aRect);
     virtual void DrawWindowOverlay(LayerManager* aManager, nsIntRect aRect);
 
     static void SetCompositor(mozilla::layers::LayerManager* aLayerManager,