Bug 766251 - 1/5 - GfxInfo on Android: the Java and AndroidBridge parts - r=kats,jrmuizel
authorBenoit Jacob <bjacob@mozilla.com>
Thu, 05 Jul 2012 10:12:10 -0400
changeset 98414 13a8dd90da08a497d7aacab0f45d3ab636b45149
parent 98413 94dd16a365d93731590448197dac9a347f53374e
child 98415 d241bfe6db55bb7fd9dcfce8bc7a6c54a795b180
push id900
push usertim.taubert@gmx.de
push dateFri, 06 Jul 2012 10:59:57 +0000
treeherderfx-team@874900dd7b77 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats, jrmuizel
bugs766251
milestone16.0a1
Bug 766251 - 1/5 - GfxInfo on Android: the Java and AndroidBridge parts - r=kats,jrmuizel
embedding/android/GeckoAppShell.java
mobile/android/base/GeckoAppShell.java
mobile/android/base/GeckoThread.java
mobile/android/base/Makefile.in
mobile/android/base/gfx/GfxInfoThread.java
widget/android/AndroidBridge.cpp
widget/android/AndroidBridge.h
--- a/embedding/android/GeckoAppShell.java
+++ b/embedding/android/GeckoAppShell.java
@@ -1838,9 +1838,13 @@ public class GeckoAppShell
     static native void notifyFilePickerResult(String filePath, long id);
 
     /* Stubbed out because this is called from AndroidBridge for Native Fennec */
     public static void showFilePickerAsync(String aMimeType, long id) {
     }
 
     public static void notifyWakeLockChanged(String topic, String state) {
     }
+
+    public static String getGfxInfoData() {
+        return null;
+    }
 }
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -118,16 +118,18 @@ public class GeckoAppShell
     private static Sensor gOrientationSensor = null;
     private static Sensor gProximitySensor = null;
     private static Sensor gLightSensor = null;
 
     private static boolean mLocationHighAccuracy = false;
 
     private static Handler sGeckoHandler;
 
+    public static GfxInfoThread sGfxInfoThread = null;
+
     /* The Android-side API: API methods that Android calls */
 
     // Initialization methods
     public static native void nativeInit();
     public static native void nativeRun(String args);
 
     // helper methods
     //    public static native void setSurfaceView(GeckoSurfaceView sv);
@@ -2219,16 +2221,23 @@ public class GeckoAppShell
     // Called by AndroidBridge using JNI
     public static void notifyPaintedRect(float top, float left, float bottom, float right) {
         ScreenshotHandler.notifyPaintedRect(top, left, bottom, right);
     }
 
     public static void notifyWakeLockChanged(String topic, String state) {
         GeckoApp.mAppContext.notifyWakeLockChanged(topic, state);
     }
+
+    public static String getGfxInfoData() {
+        String data = sGfxInfoThread.getData();
+        sGfxInfoThread = null;
+        return data;
+    }
+
 }
 
 class ScreenshotHandler {
     private static Queue<PendingScreenshot> sPendingScreenshots = new LinkedList<PendingScreenshot>();
     private static RectF sCheckerboardPageRect;
     private static float sLastCheckerboardWidthRatio, sLastCheckerboardHeightRatio;
     private static RepaintRunnable sRepaintRunnable = new RepaintRunnable();
     private static int sMaxTextureSize = 0;
--- a/mobile/android/base/GeckoThread.java
+++ b/mobile/android/base/GeckoThread.java
@@ -41,16 +41,26 @@ public class GeckoThread extends Thread 
 
     public void reallyStart() {
         mStartSignal.countDown();
         if (getState() == Thread.State.NEW)
             start();
     }
 
     public void run() {
+
+        // Here we start the GfxInfo thread, which will query OpenGL
+        // system information for Gecko. This must be done early enough that the data will be
+        // ready by the time it's needed to initialize the LayerManager (it takes about 100 ms
+        // to obtain). Doing it here seems to have no negative effect on startup time. See bug 766251.
+        // Starting the GfxInfoThread here from the GeckoThread, ensures that
+        // the Gecko thread is started first, adding some determinism there.
+        GeckoAppShell.sGfxInfoThread = new GfxInfoThread();
+        GeckoAppShell.sGfxInfoThread.start();
+
         final GeckoApp app = GeckoApp.mAppContext;
 
         // At some point while loading the gecko libs our default locale gets set
         // so just save it to locale here and reset it as default after the join
         Locale locale = Locale.getDefault();
 
         String resourcePath = app.getApplication().getPackageResourcePath();
         GeckoAppShell.setupGeckoEnvironment(app);
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -102,16 +102,17 @@ FENNEC_JAVA_FILES = \
   gfx/CairoImage.java \
   gfx/CairoUtils.java \
   gfx/CheckerboardImage.java \
   gfx/DisplayPortCalculator.java \
   gfx/DisplayPortMetrics.java \
   gfx/DrawTimingQueue.java \
   gfx/FloatSize.java \
   gfx/GeckoLayerClient.java \
+  gfx/GfxInfoThread.java \
   gfx/GLController.java \
   gfx/ImmutableViewportMetrics.java \
   gfx/InputConnectionHandler.java \
   gfx/IntSize.java \
   gfx/Layer.java \
   gfx/LayerController.java \
   gfx/LayerRenderer.java \
   gfx/LayerView.java \
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/gfx/GfxInfoThread.java
@@ -0,0 +1,176 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; 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;
+
+import android.util.Log;
+import java.util.concurrent.SynchronousQueue;
+
+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;
+import android.opengl.GLES20;
+
+public class GfxInfoThread extends Thread {
+
+    private static final String LOGTAG = "GfxInfoThread";
+
+    private SynchronousQueue<String> mDataQueue;
+
+    public GfxInfoThread() {
+        mDataQueue = new SynchronousQueue<String>();
+    }
+
+    private void error(String msg) {
+        Log.e(LOGTAG, msg);
+        try {
+            mDataQueue.put("ERROR\n" + msg + "\n");
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        }
+    }
+
+    private void eglError(EGL10 egl, String msg) {
+        error(msg + " (EGL error " + Integer.toHexString(egl.eglGetError()) + ")");
+    }
+
+    public String getData() {
+        String data = mDataQueue.poll();
+        if (data != null)
+            return data;
+
+        error("We need the GfxInfo data, but it is not yet available. " +
+              "We have to wait for it, so expect abnormally long startup times. " +
+              "Please report a Mozilla bug.");
+        try {
+            data = mDataQueue.take();
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        }
+        Log.i(LOGTAG, "GfxInfo data is finally available.");
+        return data;
+    }
+
+    public void run() {
+        // initialize EGL
+        EGL10 egl = (EGL10) EGLContext.getEGL();
+        EGLDisplay eglDisplay = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+
+        if (eglDisplay == EGL10.EGL_NO_DISPLAY) {
+            eglError(egl, "eglGetDisplay failed");
+            return;
+        }
+
+        int[] returnedVersion = new int[2];
+        if (!egl.eglInitialize(eglDisplay, returnedVersion)) {
+            eglError(egl, "eglInitialize failed");
+            return;
+        }
+
+        // query number of configs
+        int[] returnedNumberOfConfigs = new int[1];
+        int EGL_OPENGL_ES2_BIT = 4;
+        int[] configAttribs = new int[] {
+            EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+            EGL10.EGL_NONE
+        };
+        if (!egl.eglChooseConfig(eglDisplay,
+                                 configAttribs,
+                                 null,
+                                 0,
+                                 returnedNumberOfConfigs))
+        {
+            eglError(egl, "eglChooseConfig failed (querying number of configs)");
+            return;
+        }
+
+        // get the first config
+        int numConfigs = returnedNumberOfConfigs[0];
+        EGLConfig[] returnedConfigs = new EGLConfig[numConfigs];
+        if (!egl.eglChooseConfig(eglDisplay,
+                                 configAttribs,
+                                 returnedConfigs,
+                                 numConfigs,
+                                 returnedNumberOfConfigs))
+        {
+            eglError(egl, "eglChooseConfig failed (listing configs)");
+            return;
+        }
+
+        EGLConfig eglConfig = returnedConfigs[0];
+
+        // create a ES 2.0 context
+        int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
+        int[] contextAttribs = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
+        EGLContext eglContext = egl.eglCreateContext(eglDisplay,
+                                                     eglConfig,
+                                                     EGL10.EGL_NO_CONTEXT,
+                                                     contextAttribs);
+        if (eglContext == EGL10.EGL_NO_CONTEXT) {
+            eglError(egl, "eglCreateContext failed");
+            return;
+        }
+
+        // create a surface, necessary to make the context current. Hopefully PBuffers
+        // are well supported enough. Are there other kinds of off-screen surfaces in
+        // Android EGL anyway?
+        int[] surfaceAttribs = new int[] {
+            EGL10.EGL_WIDTH, 16,
+            EGL10.EGL_HEIGHT, 16,
+            EGL10.EGL_NONE
+        };
+        EGLSurface eglSurface = egl.eglCreatePbufferSurface(eglDisplay,
+                                                            eglConfig,
+                                                            surfaceAttribs);
+        if (eglSurface == EGL10.EGL_NO_SURFACE) {
+            eglError(egl, "eglCreatePbufferSurface failed");
+            return;
+        }
+
+        // obtain GL strings, store them in mDataQueue
+        if (!egl.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
+            eglError(egl, "eglMakeCurrent failed");
+            return;
+        }
+
+        {
+            int error = egl.eglGetError();
+            if (error != EGL10.EGL_SUCCESS) {
+                error("EGL error " + Integer.toHexString(error));
+                return;
+            }
+        }
+
+        String data =
+            "VENDOR\n"   + GLES20.glGetString(GLES20.GL_VENDOR)   + "\n" +
+            "RENDERER\n" + GLES20.glGetString(GLES20.GL_RENDERER) + "\n" +
+            "VERSION\n"  + GLES20.glGetString(GLES20.GL_VERSION)  + "\n";
+
+        {
+            int error = GLES20.glGetError();
+            if (error != GLES20.GL_NO_ERROR) {
+                error("OpenGL error " + Integer.toHexString(error));
+                return;
+            }
+        }
+
+        // clean up after ourselves. This is especially important as some Android devices
+        // have a very low limit on the global number of GL contexts.
+        egl.eglMakeCurrent(eglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
+        egl.eglDestroySurface(eglDisplay, eglSurface);
+        egl.eglDestroyContext(eglDisplay, eglContext);
+        // intentionally do not eglTerminate: maybe this will make the next eglInitialize faster?
+
+        // finally send the data. Notice that we've already freed the EGL resources, so that they don't
+        // remain there until the data is read.
+        try {
+            mDataQueue.put(data);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        }
+    }
+}
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -180,16 +180,18 @@ AndroidBridge::Init(JNIEnv *jEnv,
 
     if (apiVersion <= 8 /* Froyo */)
         jSurfacePointerField = jEnv->GetFieldID(jSurfaceClass, "mSurface", "I");
     else /* not Froyo */
         jSurfacePointerField = jEnv->GetFieldID(jSurfaceClass, "mNativeSurface", "I");
 
     jNotifyWakeLockChanged = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "notifyWakeLockChanged", "(Ljava/lang/String;Ljava/lang/String;)V");
 
+    jGetGfxInfoData = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getGfxInfoData", "()Ljava/lang/String;");
+
 #ifdef MOZ_JAVA_COMPOSITOR
     jPumpMessageLoop = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "pumpMessageLoop", "()V");
 
     jAddPluginView = jEnv->GetStaticMethodID(jGeckoAppShellClass, "addPluginView", "(Landroid/view/View;IIIIZ)V");
     jRemovePluginView = jEnv->GetStaticMethodID(jGeckoAppShellClass, "removePluginView", "(Landroid/view/View;Z)V");
 
     jCreateSurface = jEnv->GetStaticMethodID(jGeckoAppShellClass, "createSurface", "()Landroid/view/Surface;");
     jShowSurface = jEnv->GetStaticMethodID(jGeckoAppShellClass, "showSurface", "(Landroid/view/Surface;IIIIZZ)V");
@@ -2321,16 +2323,35 @@ AndroidBridge::NotifyWakeLockChanged(con
     jstring jstrTopic = NewJavaString(&jniFrame, nsPromiseFlatString(topic).get(),
                                       topic.Length());
     jstring jstrState = NewJavaString(&jniFrame, nsPromiseFlatString(state).get(),
                                       state.Length());
 
     env->CallStaticVoidMethod(mGeckoAppShellClass, jNotifyWakeLockChanged, jstrTopic, jstrState);
 }
 
+void
+AndroidBridge::GetGfxInfoData(nsACString& aRet)
+{
+    ALOG_BRIDGE("AndroidBridge::GetGfxInfoData");
+
+    JNIEnv* env = GetJNIEnv();
+    if (!env)
+        return;
+
+    AutoLocalJNIFrame jniFrame(env);
+    jstring jstrRet = static_cast<jstring>
+        (env->CallStaticObjectMethod(mGeckoAppShellClass, jGetGfxInfoData));
+    if (jniFrame.CheckForException())
+        return;
+
+    nsJNIString jniStr(jstrRet, env);
+    CopyUTF16toUTF8(jniStr, aRet);
+}
+
 /* attribute nsIAndroidBrowserApp browserApp; */
 NS_IMETHODIMP nsAndroidBridge::GetBrowserApp(nsIAndroidBrowserApp * *aBrowserApp)
 {
     if (nsAppShell::gAppShell)
         nsAppShell::gAppShell->GetBrowserApp(aBrowserApp);
     return NS_OK;
 }
 
--- a/widget/android/AndroidBridge.h
+++ b/widget/android/AndroidBridge.h
@@ -352,16 +352,18 @@ public:
     void DisableScreenOrientationNotifications();
     void LockScreenOrientation(uint32_t aOrientation);
     void UnlockScreenOrientation();
 
     void PumpMessageLoop();
 
     void NotifyWakeLockChanged(const nsAString& topic, const nsAString& state);
 
+    void GetGfxInfoData(nsACString& aRet);
+
 protected:
     static AndroidBridge *sBridge;
 
     // the global JavaVM
     JavaVM *mJavaVM;
 
     // the JNIEnv for the main thread
     JNIEnv *mJNIEnv;
@@ -473,16 +475,19 @@ protected:
     jmethodID jGetScreenOrientation;
     jmethodID jEnableScreenOrientationNotifications;
     jmethodID jDisableScreenOrientationNotifications;
     jmethodID jLockScreenOrientation;
     jmethodID jUnlockScreenOrientation;
     jmethodID jPumpMessageLoop;
     jmethodID jNotifyWakeLockChanged;
 
+    // for GfxInfo (gfx feature detection and blacklisting)
+    jmethodID jGetGfxInfoData;
+
     // For native surface stuff
     jclass jSurfaceClass;
     jfieldID jSurfacePointerField;
 
     // stuff we need for CallEglCreateWindowSurface
     jclass jEGLSurfaceImplClass;
     jclass jEGLContextImplClass;
     jclass jEGLConfigImplClass;