Bug 803299 - Enable 32-bit colour on Android. r=kats
☠☠ backed out by 281dc9793a73 ☠ ☠
authorChris Lord <chrislord.net@gmail.com>
Wed, 29 May 2013 15:25:40 +0100
changeset 133277 a9485787fdb1bb5de9c7c80b4ff7939bb8380861
parent 133276 4655d7317a03014a6df464e5e19509c1f51ab008
child 133278 5269f0483d1e6905c6269d26aeeca6863543a4ab
push id28683
push userchrislord.net@gmail.com
push dateWed, 29 May 2013 14:34:19 +0000
treeherdermozilla-inbound@946467115924 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats
bugs803299
milestone24.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 803299 - Enable 32-bit colour on Android. r=kats
mobile/android/base/GeckoAppShell.java
mobile/android/base/Tab.java
mobile/android/base/ThumbnailHelper.java
mobile/android/base/gfx/GLController.java
mobile/android/base/widget/TopSitesView.java
widget/android/AndroidBridge.cpp
widget/android/AndroidBridge.h
widget/android/nsScreenManagerAndroid.cpp
widget/android/nsWindow.cpp
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -33,16 +33,17 @@ import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.Signature;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.ImageFormat;
 import android.graphics.Paint;
+import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.SurfaceTexture;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.hardware.Sensor;
 import android.hardware.SensorManager;
 import android.hardware.SensorEventListener;
@@ -130,16 +131,17 @@ public class GeckoAppShell
     static public final int WPL_STATE_IS_NETWORK = 0x00040000;
 
     public static final String SHORTCUT_TYPE_WEBAPP = "webapp";
     public static final String SHORTCUT_TYPE_BOOKMARK = "bookmark";
 
     static private final boolean LOGGING = false;
 
     static private int sDensityDpi = 0;
+    static private int sScreenDepth = 0;
 
     private static final EventDispatcher sEventDispatcher = new EventDispatcher();
 
     /* Default colors. */
     private static final float[] DEFAULT_LAUNCHER_ICON_HSV = { 32.0f, 1.0f, 1.0f };
 
     /* Is the value in sVibrationEndTime valid? */
     private static boolean sVibrationMaybePlaying = false;
@@ -1381,16 +1383,37 @@ public class GeckoAppShell
     public static int getDpi() {
         if (sDensityDpi == 0) {
             sDensityDpi = getContext().getResources().getDisplayMetrics().densityDpi;
         }
 
         return sDensityDpi;
     }
 
+    /**
+     * Returns the colour depth of the default screen. This will either be
+     * 24 or 16.
+     */
+    public static int getScreenDepth() {
+        if (sScreenDepth == 0 && getGeckoInterface() != null) {
+            switch (getGeckoInterface().getActivity().getWindowManager().getDefaultDisplay().getPixelFormat()) {
+            case PixelFormat.RGBA_8888 :
+            case PixelFormat.RGBX_8888 :
+            case PixelFormat.RGB_888 :
+                sScreenDepth = 24;
+                break;
+            default:
+                sScreenDepth = 16;
+                break;
+            }
+        }
+
+        return sScreenDepth;
+    }
+
     public static void setFullScreen(boolean fullscreen) {
         if (getGeckoInterface() != null)
             getGeckoInterface().setFullScreen(fullscreen);
     }
 
     public static String showFilePickerForExtensions(String aExtensions) {
         if (getGeckoInterface() != null)
             return sActivityHelper.showFilePicker(getGeckoInterface().getActivity(), getMimeTypeFromExtensions(aExtensions));
--- a/mobile/android/base/Tab.java
+++ b/mobile/android/base/Tab.java
@@ -177,17 +177,19 @@ public class Tab {
             boolean sizeChange = mThumbnailBitmap.getWidth() != width
                               || mThumbnailBitmap.getHeight() != height;
             if (honeycomb || sizeChange) {
                 mThumbnailBitmap = null;
             }
         }
 
         if (mThumbnailBitmap == null) {
-            mThumbnailBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
+            Bitmap.Config config = (GeckoAppShell.getScreenDepth() == 24) ?
+                Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
+            mThumbnailBitmap = Bitmap.createBitmap(width, height, config);
         }
 
         return mThumbnailBitmap;
     }
 
     public void updateThumbnail(final Bitmap b) {
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
--- a/mobile/android/base/ThumbnailHelper.java
+++ b/mobile/android/base/ThumbnailHelper.java
@@ -100,17 +100,18 @@ public final class ThumbnailHelper {
 
     private void updateThumbnailSize() {
         // Apply any pending width updates
         mWidth = mPendingWidth.get();
 
         mWidth &= ~0x1; // Ensure the width is always an even number (bug 776906)
         mHeight = Math.round(mWidth * THUMBNAIL_ASPECT_RATIO);
 
-        int capacity = mWidth * mHeight * 2; // Multiply by 2 for 16bpp
+        int pixelSize = (GeckoAppShell.getScreenDepth() == 24) ? 4 : 2;
+        int capacity = mWidth * mHeight * pixelSize;
         if (mBuffer == null || mBuffer.capacity() != capacity) {
             if (mBuffer != null) {
                 mBuffer = DirectBufferAllocator.free(mBuffer);
             }
             try {
                 mBuffer = DirectBufferAllocator.allocate(capacity);
             } catch (IllegalArgumentException iae) {
                 Log.w(LOGTAG, iae.toString());
@@ -177,17 +178,33 @@ public final class ThumbnailHelper {
         if (shouldUpdateThumbnail(tab)) {
             processThumbnailData(tab, data);
         }
     }
 
     private void processThumbnailData(Tab tab, ByteBuffer data) {
         Bitmap b = tab.getThumbnailBitmap(mWidth, mHeight);
         data.position(0);
-        b.copyPixelsFromBuffer(data);
+        if (b.getConfig() == Bitmap.Config.RGB_565) {
+            b.copyPixelsFromBuffer(data);
+        } else {
+            // Unfortunately, Gecko's 32-bit format is BGRA and Android's is
+            // ARGB, so we need to manually swizzle.
+            for (int y = 0; y < mHeight; y++) {
+                for (int x = 0; x < mWidth; x++) {
+                    int index = (y * mWidth + x) * 4;
+                    int bgra = data.getInt(index);
+                    int argb = ((bgra << 24) & 0xFF000000)
+                             | ((bgra << 8) & 0x00FF0000)
+                             | ((bgra >> 8) & 0x0000FF00)
+                             | ((bgra >> 24) & 0x000000FF);
+                    b.setPixel(x, y, argb);
+                }
+            }
+        }
         setTabThumbnail(tab, b, null);
     }
 
     private void setTabThumbnail(Tab tab, Bitmap bitmap, byte[] compressed) {
         if (bitmap == null) {
             if (compressed == null) {
                 Log.w(LOGTAG, "setTabThumbnail: one of bitmap or compressed must be non-null!");
                 return;
--- a/mobile/android/base/gfx/GLController.java
+++ b/mobile/android/base/gfx/GLController.java
@@ -44,25 +44,34 @@ public class GLController {
 
     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 = {
+    private static final int[] CONFIG_SPEC_16BPP = {
         EGL10.EGL_RED_SIZE, 5,
         EGL10.EGL_GREEN_SIZE, 6,
         EGL10.EGL_BLUE_SIZE, 5,
         EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT,
         EGL10.EGL_RENDERABLE_TYPE, LOCAL_EGL_OPENGL_ES2_BIT,
         EGL10.EGL_NONE
     };
 
+    private static final int[] CONFIG_SPEC_24BPP = {
+        EGL10.EGL_RED_SIZE, 8,
+        EGL10.EGL_GREEN_SIZE, 8,
+        EGL10.EGL_BLUE_SIZE, 8,
+        EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT,
+        EGL10.EGL_RENDERABLE_TYPE, LOCAL_EGL_OPENGL_ES2_BIT,
+        EGL10.EGL_NONE
+    };
+
     private GLController() {
         // 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 compositor (it takes about 100 ms
         // to obtain).
         GfxInfoThread.startThread();
     }
 
@@ -197,36 +206,51 @@ public class GLController {
         if (mEGLDisplay == EGL10.EGL_NO_DISPLAY) {
             throw new GLControllerException("eglGetDisplay() failed");
         }
 
         mEGLConfig = chooseConfig();
     }
 
     private EGLConfig chooseConfig() {
+        int[] desiredConfig;
+        int rSize, gSize, bSize;
         int[] numConfigs = new int[1];
-        if (!mEGL.eglChooseConfig(mEGLDisplay, CONFIG_SPEC, null, 0, numConfigs) ||
+
+        switch (GeckoAppShell.getScreenDepth()) {
+        case 24:
+            desiredConfig = CONFIG_SPEC_24BPP;
+            rSize = gSize = bSize = 8;
+            break;
+        case 16:
+        default:
+            desiredConfig = CONFIG_SPEC_16BPP;
+            rSize = 5; gSize = 6; bSize = 5;
+            break;
+        }
+
+        if (!mEGL.eglChooseConfig(mEGLDisplay, desiredConfig, null, 0, numConfigs) ||
                 numConfigs[0] <= 0) {
             throw new GLControllerException("No available EGL configurations " +
                                             getEGLError());
         }
 
         EGLConfig[] configs = new EGLConfig[numConfigs[0]];
-        if (!mEGL.eglChooseConfig(mEGLDisplay, CONFIG_SPEC, configs, numConfigs[0], numConfigs)) {
+        if (!mEGL.eglChooseConfig(mEGLDisplay, desiredConfig, configs, numConfigs[0], numConfigs)) {
             throw new GLControllerException("No EGL configuration for that specification " +
                                             getEGLError());
         }
 
-        // Select the first 565 RGB configuration.
+        // Select the first configuration that matches the screen depth.
         int[] red = new int[1], green = new int[1], blue = new int[1];
         for (EGLConfig config : configs) {
             mEGL.eglGetConfigAttrib(mEGLDisplay, config, EGL10.EGL_RED_SIZE, red);
             mEGL.eglGetConfigAttrib(mEGLDisplay, config, EGL10.EGL_GREEN_SIZE, green);
             mEGL.eglGetConfigAttrib(mEGLDisplay, config, EGL10.EGL_BLUE_SIZE, blue);
-            if (red[0] == 5 && green[0] == 6 && blue[0] == 5) {
+            if (red[0] == rSize && green[0] == gSize && blue[0] == bSize) {
                 return config;
             }
         }
 
         throw new GLControllerException("No suitable EGL configuration found");
     }
 
     /* This function is invoked by JNI on the compositor thread */
--- a/mobile/android/base/widget/TopSitesView.java
+++ b/mobile/android/base/widget/TopSitesView.java
@@ -311,17 +311,22 @@ public class TopSitesView extends GridVi
                 return thumbnails;
 
             do {
                 final String url = c.getString(c.getColumnIndexOrThrow(Thumbnails.URL));
                 final byte[] b = c.getBlob(c.getColumnIndexOrThrow(Thumbnails.DATA));
                 if (b == null)
                     continue;
 
-                Bitmap thumbnail = BitmapUtils.decodeByteArray(b);
+                Bitmap thumbnail = null;
+                try {
+                    thumbnail = BitmapUtils.decodeByteArray(b);
+                } catch (IllegalArgumentException e) {
+                    Log.e(LOGTAG, "Error decoding thumbnail", e);
+                }
                 if (thumbnail == null)
                     continue;
 
                 thumbnails.put(url, thumbnail);
             } while (c.moveToNext());
         } finally {
             if (c != null)
                 c.close();
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -126,16 +126,17 @@ AndroidBridge::Init(JNIEnv *jEnv,
     jShowFilePickerForExtensions = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "showFilePickerForExtensions", "(Ljava/lang/String;)Ljava/lang/String;");
     jShowFilePickerForMimeType = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "showFilePickerForMimeType", "(Ljava/lang/String;)Ljava/lang/String;");
     jShowFilePickerAsync = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "showFilePickerAsync", "(Ljava/lang/String;J)V");
     jUnlockProfile = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "unlockProfile", "()Z");
     jKillAnyZombies = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "killAnyZombies", "()V");
     jAlertsProgressListener_OnProgress = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "alertsProgressListener_OnProgress", "(Ljava/lang/String;JJLjava/lang/String;)V");
     jCloseNotification = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "closeNotification", "(Ljava/lang/String;)V");
     jGetDpi = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getDpi", "()I");
+    jGetScreenDepth = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getScreenDepth", "()I");
     jSetFullScreen = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "setFullScreen", "(Z)V");
     jShowInputMethodPicker = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "showInputMethodPicker", "()V");
     jNotifyDefaultPrevented = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "notifyDefaultPrevented", "(Z)V");
     jHideProgressDialog = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "hideProgressDialog", "()V");
     jPerformHapticFeedback = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "performHapticFeedback", "(Z)V");
     jVibrate1 = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "vibrate", "(J)V");
     jVibrateA = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "vibrate", "([JI)V");
     jCancelVibrate = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "cancelVibrate", "()V");
@@ -767,16 +768,39 @@ AndroidBridge::GetDPI()
     if (jniFrame.CheckForException()) {
         sDPI = 0;
         return DEFAULT_DPI;
     }
 
     return sDPI;
 }
 
+int
+AndroidBridge::GetScreenDepth()
+{
+    static int sDepth = 0;
+    if (sDepth)
+        return sDepth;
+
+    ALOG_BRIDGE("AndroidBridge::GetScreenDepth");
+    const int DEFAULT_DEPTH = 16;
+    JNIEnv *env = GetJNIEnv();
+    if (!env)
+        return DEFAULT_DEPTH;
+    AutoLocalJNIFrame jniFrame(env);
+
+    sDepth = (int)env->CallStaticIntMethod(mGeckoAppShellClass, jGetScreenDepth);
+    if (jniFrame.CheckForException()) {
+        sDepth = 0;
+        return DEFAULT_DEPTH;
+    }
+
+    return sDepth;
+}
+
 void
 AndroidBridge::ShowFilePickerForExtensions(nsAString& aFilePath, const nsAString& aExtensions)
 {
     ALOG_BRIDGE("AndroidBridge::ShowFilePickerForExtensions");
 
     JNIEnv *env = GetJNIEnv();
     if (!env)
         return;
@@ -2649,23 +2673,27 @@ nsresult AndroidBridge::CaptureThumbnail
     nsCOMPtr<nsIPresShell> presShell = presContext->PresShell();
     uint32_t renderDocFlags = (nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING |
                                nsIPresShell::RENDER_DOCUMENT_RELATIVE);
     nsRect r(nsPresContext::CSSPixelsToAppUnits(srcX / scale),
              nsPresContext::CSSPixelsToAppUnits(srcY / scale),
              nsPresContext::CSSPixelsToAppUnits(srcW / scale),
              nsPresContext::CSSPixelsToAppUnits(srcH / scale));
 
-    uint32_t stride = bufW * 2 /* 16 bpp */;
+    bool is24bit = (GetScreenDepth() == 24);
+    uint32_t stride = bufW * (is24bit ? 4 : 2);
 
     void* data = env->GetDirectBufferAddress(buffer);
     if (!data)
         return NS_ERROR_FAILURE;
 
-    nsRefPtr<gfxImageSurface> surf = new gfxImageSurface(static_cast<unsigned char*>(data), nsIntSize(bufW, bufH), stride, gfxASurface::ImageFormatRGB16_565);
+    nsRefPtr<gfxImageSurface> surf =
+        new gfxImageSurface(static_cast<unsigned char*>(data), nsIntSize(bufW, bufH), stride,
+                            is24bit ? gfxASurface::ImageFormatRGB24 :
+                                      gfxASurface::ImageFormatRGB16_565);
     if (surf->CairoStatus() != 0) {
         ALOG_BRIDGE("Error creating gfxImageSurface");
         return NS_ERROR_FAILURE;
     }
     nsRefPtr<gfxContext> context = new gfxContext(surf);
     gfxPoint pt(0, 0);
     context->Translate(pt);
     context->Scale(scale * bufW / srcW, scale * bufH / srcH);
--- a/widget/android/AndroidBridge.h
+++ b/widget/android/AndroidBridge.h
@@ -249,16 +249,17 @@ public:
     void AlertsProgressListener_OnProgress(const nsAString& aAlertName,
                                            int64_t aProgress,
                                            int64_t aProgressMax,
                                            const nsAString& aAlertText);
 
     void CloseNotification(const nsAString& aAlertName);
 
     int GetDPI();
+    int GetScreenDepth();
 
     void ShowFilePickerForExtensions(nsAString& aFilePath, const nsAString& aExtensions);
     void ShowFilePickerForMimeType(nsAString& aFilePath, const nsAString& aMimeType);
     void ShowFilePickerAsync(const nsAString& aMimeType, nsFilePickerCallback* callback);
 
     void PerformHapticFeedback(bool aIsLongPress);
 
     void Vibrate(const nsTArray<uint32_t>& aPattern);
@@ -477,16 +478,17 @@ protected:
     jmethodID jShowFilePickerForExtensions;
     jmethodID jShowFilePickerForMimeType;
     jmethodID jShowFilePickerAsync;
     jmethodID jUnlockProfile;
     jmethodID jKillAnyZombies;
     jmethodID jAlertsProgressListener_OnProgress;
     jmethodID jCloseNotification;
     jmethodID jGetDpi;
+    jmethodID jGetScreenDepth;
     jmethodID jSetFullScreen;
     jmethodID jShowInputMethodPicker;
     jmethodID jNotifyDefaultPrevented;
     jmethodID jHideProgressDialog;
     jmethodID jPerformHapticFeedback;
     jmethodID jVibrate1;
     jmethodID jVibrateA;
     jmethodID jCancelVibrate;
--- a/widget/android/nsScreenManagerAndroid.cpp
+++ b/widget/android/nsScreenManagerAndroid.cpp
@@ -40,19 +40,17 @@ nsScreenAndroid::GetAvailRect(int32_t *o
     return GetRect(outLeft, outTop, outWidth, outHeight);
 }
 
 
 
 NS_IMETHODIMP
 nsScreenAndroid::GetPixelDepth(int32_t *aPixelDepth)
 {
-    // XXX do we need to lie here about 16bpp?  Or
-    // should we actually check and return the right thing?
-    *aPixelDepth = 16;
+    *aPixelDepth = AndroidBridge::Bridge()->GetScreenDepth();
     return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsScreenAndroid::GetColorDepth(int32_t *aColorDepth)
 {
     return GetPixelDepth(aColorDepth);
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -1061,21 +1061,27 @@ nsWindow::OnDraw(AndroidGeckoEvent *ae)
         return;
     AutoLocalJNIFrame jniFrame;
 
     // We're paused, or we haven't been given a window-size yet, so do nothing
     if (sCompositorPaused || gAndroidBounds.width <= 0 || gAndroidBounds.height <= 0) {
         return;
     }
 
+    int bytesPerPixel = 2;
+    gfxASurface::gfxImageFormat format = gfxASurface::ImageFormatRGB16_565;
+    if (AndroidBridge::Bridge()->GetScreenDepth() == 24) {
+        bytesPerPixel = 4;
+        format = gfxASurface::ImageFormatRGB24;
+    }
+
     layers::renderTraceEventStart("Get surface", "424545");
-    static unsigned char bits2[32 * 32 * 2];
+    static unsigned char bits2[32 * 32 * 4];
     nsRefPtr<gfxImageSurface> targetSurface =
-        new gfxImageSurface(bits2, gfxIntSize(32, 32), 32 * 2,
-                            gfxASurface::ImageFormatRGB16_565);
+        new gfxImageSurface(bits2, gfxIntSize(32, 32), 32 * bytesPerPixel, format);
     layers::renderTraceEventEnd("Get surface", "424545");
 
     layers::renderTraceEventStart("Widget draw to", "434646");
     if (targetSurface->CairoStatus()) {
         ALOG("### Failed to create a valid surface from the bitmap");
     } else {
         DrawTo(targetSurface, ae->Rect());
     }