Bug 661843 - GeckoSurfaceView may double memory requirement for painting. r=dougt
authorChris Lord <chrislord.net@gmail.com>
Thu, 16 Jun 2011 02:03:00 -0700
changeset 71182 8c5e954a80b8e5c6e4794907320650f5ed3df60c
parent 71181 37eb61062444afb2913daeb1b0fb27500cb10d82
child 71183 9a4d38add60a723f81e53c5d330fac11a0ab32b5
push id6
push usergsharp@mozilla.com
push dateFri, 17 Jun 2011 14:49:53 +0000
treeherderfx-team@9ac190a247ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdougt
bugs661843
milestone7.0a1
Bug 661843 - GeckoSurfaceView may double memory requirement for painting. r=dougt
embedding/android/GeckoSurfaceView.java
widget/src/android/AndroidBridge.cpp
widget/src/android/AndroidBridge.h
widget/src/android/AndroidJavaWrappers.cpp
widget/src/android/AndroidJavaWrappers.h
widget/src/android/nsWindow.cpp
--- a/embedding/android/GeckoSurfaceView.java
+++ b/embedding/android/GeckoSurfaceView.java
@@ -127,34 +127,69 @@ class GeckoSurfaceView
         c.drawText(GeckoSurfaceView.mSplashStatusMsg, width/2, y + h + 16, p);
         holder.unlockCanvasAndPost(c);
     }
 
     /*
      * Called on main thread
      */
 
+    public void draw(SurfaceHolder holder, ByteBuffer buffer) {
+        if (buffer == null || buffer.capacity() != (mWidth * mHeight * 2))
+            return;
+
+        synchronized (mSoftwareBuffer) {
+            if (buffer != mSoftwareBuffer || mSoftwareBufferCopy == null)
+                return;
+
+            Canvas c = holder.lockCanvas();
+            if (c == null)
+                return;
+            mSoftwareBufferCopy.copyPixelsFromBuffer(buffer);
+            c.drawBitmap(mSoftwareBufferCopy, 0, 0, null);
+            holder.unlockCanvasAndPost(c);
+        }
+    }
+
+    public void draw(SurfaceHolder holder, Bitmap bitmap) {
+        if (bitmap == null ||
+            bitmap.getWidth() != mWidth || bitmap.getHeight() != mHeight)
+            return;
+
+        synchronized (mSoftwareBitmap) {
+            if (bitmap != mSoftwareBitmap)
+                return;
+
+            Canvas c = holder.lockCanvas();
+            if (c == null)
+                return;
+            c.drawBitmap(bitmap, 0, 0, null);
+            holder.unlockCanvasAndPost(c);
+        }
+    }
+
     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
         if (mShowingSplashScreen)
             drawSplashScreen(holder, width, height);
         mSurfaceLock.lock();
 
         try {
             if (mInDrawing) {
                 Log.w("GeckoAppJava", "surfaceChanged while mInDrawing is true!");
             }
 
-            if (width == 0 || height == 0)
+            if (width == 0 || height == 0) {
+                mSoftwareBitmap = null;
                 mSoftwareBuffer = null;
-            else if (mSoftwareBuffer == null ||
-                     mSoftwareBuffer.capacity() < (width * height * 2) ||
-                     mWidth != width || mHeight != height)
-                mSoftwareBuffer = ByteBuffer.allocateDirect(width * height * 2);
-            boolean doSyncDraw = mDrawMode == DRAW_2D &&
-                mSoftwareBuffer != null &&
+                mSoftwareBufferCopy = null;
+            }
+
+            boolean doSyncDraw =
+                mDrawMode == DRAW_2D &&
+                (mSoftwareBitmap != null || mSoftwareBuffer != null) &&
                 GeckoApp.checkLaunchState(GeckoApp.LaunchState.GeckoRunning);
             mSyncDraw = doSyncDraw;
 
             mFormat = format;
             mWidth = width;
             mHeight = height;
             mSurfaceValid = true;
 
@@ -162,63 +197,88 @@ class GeckoSurfaceView
 
             DisplayMetrics metrics = new DisplayMetrics();
             GeckoApp.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics);
 
             GeckoEvent e = new GeckoEvent(GeckoEvent.SIZE_CHANGED, width, height,
                                           metrics.widthPixels, metrics.heightPixels);
             GeckoAppShell.sendEventToGecko(e);
 
-            if (mSoftwareBuffer != null)
+            if (mSoftwareBitmap != null || mSoftwareBuffer != null)
                 GeckoAppShell.scheduleRedraw();
 
             if (!doSyncDraw) {
                 if (mDrawMode == DRAW_GLES_2 || mShowingSplashScreen)
                     return;
                 Canvas c = holder.lockCanvas();
                 c.drawARGB(255, 255, 255, 255);
                 holder.unlockCanvasAndPost(c);
                 return;
             }
         } finally {
             mSurfaceLock.unlock();
         }
 
-        ByteBuffer bb = null;
+        Object syncDrawObject = null;
         try {
-            bb = mSyncBuf.take();
+            Object syncObject = mSyncDraws.take();
         } catch (InterruptedException ie) {
-            Log.e("GeckoAppJava", "Threw exception while getting sync buf: ", ie);
+            Log.e("GeckoAppJava", "Threw exception while getting sync draw bitmap/buffer: ", ie);
         }
-        if (bb != null && bb.capacity() == (width * height * 2)) {
-            mSoftwareBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.RGB_565);
-            mSoftwareBitmap.copyPixelsFromBuffer(bb);
-            Canvas c = holder.lockCanvas();
-            c.drawBitmap(mSoftwareBitmap, 0, 0, null);
-            holder.unlockCanvasAndPost(c);
+        if (syncDrawObject != null) {
+            if (syncDrawObject instanceof Bitmap)
+                draw(holder, (Bitmap)syncDrawObject);
+            else
+                draw(holder, (ByteBuffer)syncDrawObject);
         }
     }
 
     public void surfaceCreated(SurfaceHolder holder) {
         Log.i("GeckoAppJava", "surface created");
         GeckoEvent e = new GeckoEvent(GeckoEvent.SURFACE_CREATED);
         GeckoAppShell.sendEventToGecko(e);
         if (mShowingSplashScreen)
             drawSplashScreen();
     }
 
     public void surfaceDestroyed(SurfaceHolder holder) {
         Log.i("GeckoAppJava", "surface destroyed");
         mSurfaceValid = false;
         mSoftwareBuffer = null;
+        mSoftwareBufferCopy = null;
+        mSoftwareBitmap = null;
         GeckoEvent e = new GeckoEvent(GeckoEvent.SURFACE_DESTROYED);
         GeckoAppShell.sendEventToGecko(e);
     }
 
+    public Bitmap getSoftwareDrawBitmap() {
+        if (mSoftwareBitmap == null ||
+            mSoftwareBitmap.getHeight() != mHeight ||
+            mSoftwareBitmap.getWidth() != mWidth) {
+            mSoftwareBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.RGB_565);
+        }
+
+        mDrawMode = DRAW_2D;
+        return mSoftwareBitmap;
+    }
+
     public ByteBuffer getSoftwareDrawBuffer() {
+        // We store pixels in 565 format, so two bytes per pixel (explaining
+        // the * 2 in the following check/allocation)
+        if (mSoftwareBuffer == null ||
+            mSoftwareBuffer.capacity() != (mWidth * mHeight * 2)) {
+            mSoftwareBuffer = ByteBuffer.allocateDirect(mWidth * mHeight * 2);
+        }
+
+        if (mSoftwareBufferCopy == null ||
+            mSoftwareBufferCopy.getHeight() != mHeight ||
+            mSoftwareBufferCopy.getWidth() != mWidth) {
+            mSoftwareBufferCopy = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.RGB_565);
+        }
+
         mDrawMode = DRAW_2D;
         return mSoftwareBuffer;
     }
 
     /*
      * Called on Gecko thread
      */
 
@@ -285,59 +345,58 @@ class GeckoSurfaceView
      * unless the canvas is locked.
      * Draws originate from a different thread so the surface could change
      * at any moment while we try to draw until we lock the canvas.
      *
      * Also, never try to lock the canvas while holding the surface lock
      * unless you're in SurfaceChanged, in which case the canvas was already
      * locked. Surface lock -> Canvas lock will lead to AB-BA deadlocks.
      */
-    public void draw2D(ByteBuffer buffer, int stride) {
-        // mSurfaceLock ensures that we get mSyncDraw/mSoftwareBuffer/etc.
+    public void draw2D(Bitmap bitmap, int width, int height) {
+        // mSurfaceLock ensures that we get mSyncDraw/mSoftwareBitmap/etc.
         // set correctly before determining whether we should do a sync draw
         mSurfaceLock.lock();
         try {
             if (mSyncDraw) {
-                if (buffer != mSoftwareBuffer || stride != (mWidth * 2))
+                if (bitmap != mSoftwareBitmap || width != mWidth || height != mHeight)
                     return;
                 mSyncDraw = false;
                 try {
-                    mSyncBuf.put(buffer);
+                    mSyncDraws.put(bitmap);
                 } catch (InterruptedException ie) {
-                    Log.e("GeckoAppJava", "Threw exception while getting sync buf: ", ie);
+                    Log.e("GeckoAppJava", "Threw exception while getting sync draws queue: ", ie);
                 }
                 return;
             }
         } finally {
             mSurfaceLock.unlock();
         }
 
-        if (buffer != mSoftwareBuffer || stride != (mWidth * 2))
-            return;
-        Canvas c = getHolder().lockCanvas();
-        if (c == null)
-            return;
-        if (buffer != mSoftwareBuffer || stride != (mWidth * 2)) {
-            /* We're screwed. Fill it with white and hope it isn't too noticable
-             * This could potentially happen if this function is called
-             * right before mSurfaceLock is locked in SurfaceChanged.
-             * However, I've never actually seen this code get hit.
-             */
-            c.drawARGB(255, 255, 255, 255);
-            getHolder().unlockCanvasAndPost(c);
-            return;
+        draw(getHolder(), bitmap);
+    }
+
+    public void draw2D(ByteBuffer buffer, int stride) {
+        mSurfaceLock.lock();
+        try {
+            if (mSyncDraw) {
+                if (buffer != mSoftwareBuffer || stride != (mWidth * 2))
+                    return;
+                mSyncDraw = false;
+                try {
+                    mSyncDraws.put(buffer);
+                } catch (InterruptedException ie) {
+                    Log.e("GeckoAppJava", "Threw exception while getting sync bitmaps queue: ", ie);
+                }
+                return;
+            }
+        } finally {
+            mSurfaceLock.unlock();
         }
-        if (mSoftwareBitmap == null ||
-            mSoftwareBitmap.getHeight() != mHeight ||
-            mSoftwareBitmap.getWidth() != mWidth) {
-            mSoftwareBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.RGB_565);
-        }
-        mSoftwareBitmap.copyPixelsFromBuffer(mSoftwareBuffer);
-        c.drawBitmap(mSoftwareBitmap, 0, 0, null);
-        getHolder().unlockCanvasAndPost(c);
+
+        draw(getHolder(), buffer);
     }
 
     @Override
     public boolean onCheckIsTextEditor () {
         return false;
     }
 
     @Override
@@ -626,17 +685,18 @@ class GeckoSurfaceView
     Editable mEditable;
     Editable.Factory mEditableFactory;
     int mIMEState;
     String mIMETypeHint;
     String mIMEActionHint;
     boolean mIMELandscapeFS;
 
     // Software rendering
+    Bitmap mSoftwareBitmap;
     ByteBuffer mSoftwareBuffer;
-    Bitmap mSoftwareBitmap;
+    Bitmap mSoftwareBufferCopy;
 
     Geocoder mGeocoder;
     Address  mLastGeoAddress;
 
-    final SynchronousQueue<ByteBuffer> mSyncBuf = new SynchronousQueue<ByteBuffer>();
+    final SynchronousQueue<Object> mSyncDraws = new SynchronousQueue<Object>();
 }
 
--- a/widget/src/android/AndroidBridge.cpp
+++ b/widget/src/android/AndroidBridge.cpp
@@ -31,16 +31,17 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include <android/log.h>
+#include <dlfcn.h>
 
 #include "nsXULAppAPI.h"
 #include <pthread.h>
 #include <prthread.h>
 #include "nsXPCOMStrings.h"
 
 #include "AndroidBridge.h"
 #include "nsAppShell.h"
@@ -99,16 +100,18 @@ PRBool
 AndroidBridge::Init(JNIEnv *jEnv,
                     jclass jGeckoAppShellClass)
 {
     ALOG_BRIDGE("AndroidBridge::Init");
     jEnv->GetJavaVM(&mJavaVM);
 
     mJNIEnv = nsnull;
     mThread = nsnull;
+    mOpenedBitmapLibrary = false;
+    mHasNativeBitmapAccess = false;
 
     mGeckoAppShellClass = (jclass) jEnv->NewGlobalRef(jGeckoAppShellClass);
 
     jNotifyIME = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "notifyIME", "(II)V");
     jNotifyIMEEnabled = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "notifyIMEEnabled", "(ILjava/lang/String;Ljava/lang/String;Z)V");
     jNotifyIMEChange = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "notifyIMEChange", "(Ljava/lang/String;III)V");
     jAcknowledgeEventSync = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "acknowledgeEventSync", "()V");
 
@@ -853,8 +856,89 @@ AndroidBridge::ScanMedia(const nsAString
 
     nsString mimeType2;
     CopyUTF8toUTF16(aMimeType, mimeType2);
     jstring jstrMimeTypes = mJNIEnv->NewString(nsPromiseFlatString(mimeType2).get(), mimeType2.Length());
 
     mJNIEnv->CallStaticVoidMethod(mGeckoAppShellClass, jScanMedia, jstrFile, jstrMimeTypes);
 }
 
+bool
+AndroidBridge::HasNativeBitmapAccess()
+{
+    if (!mOpenedBitmapLibrary) {
+        // Try to dlopen libjnigraphics.so for direct bitmap access on
+        // Android 2.2+ (API level 8)
+        mOpenedBitmapLibrary = true;
+
+        void *handle = dlopen("/system/lib/libjnigraphics.so", RTLD_LAZY | RTLD_LOCAL);
+        if (handle == nsnull)
+            return false;
+
+        AndroidBitmap_getInfo = (int (*)(JNIEnv *, jobject, void *))dlsym(handle, "AndroidBitmap_getInfo");
+        if (AndroidBitmap_getInfo == nsnull)
+            return false;
+
+        AndroidBitmap_lockPixels = (int (*)(JNIEnv *, jobject, void **))dlsym(handle, "AndroidBitmap_lockPixels");
+        if (AndroidBitmap_lockPixels == nsnull)
+            return false;
+
+        AndroidBitmap_unlockPixels = (int (*)(JNIEnv *, jobject))dlsym(handle, "AndroidBitmap_unlockPixels");
+        if (AndroidBitmap_unlockPixels == nsnull)
+            return false;
+
+        ALOG_BRIDGE("Successfully opened libjnigraphics.so");
+        mHasNativeBitmapAccess = true;
+    }
+
+    return mHasNativeBitmapAccess;
+}
+
+bool
+AndroidBridge::ValidateBitmap(jobject bitmap, int width, int height)
+{
+    // This structure is defined in Android API level 8's <android/bitmap.h>
+    // Because we can't depend on this, we get the function pointers via dlsym
+    // and define this struct ourselves.
+    struct BitmapInfo {
+        uint32_t width;
+        uint32_t height;
+        uint32_t stride;
+        uint32_t format;
+        uint32_t flags;
+    };
+
+    int err;
+    struct BitmapInfo info = { 0, };
+
+    if ((err = AndroidBitmap_getInfo(JNI(), bitmap, &info)) != 0) {
+        ALOG_BRIDGE("AndroidBitmap_getInfo failed! (error %d)", err);
+        return false;
+    }
+
+    if (info.width != width || info.height != height)
+        return false;
+
+    return true;
+}
+
+void *
+AndroidBridge::LockBitmap(jobject bitmap)
+{
+    int err;
+    void *buf;
+
+    if ((err = AndroidBitmap_lockPixels(JNI(), bitmap, &buf)) != 0) {
+        ALOG_BRIDGE("AndroidBitmap_lockPixels failed! (error %d)", err);
+        buf = nsnull;
+    }
+
+    return buf;
+}
+
+void
+AndroidBridge::UnlockBitmap(jobject bitmap)
+{
+    int err;
+    if ((err = AndroidBitmap_unlockPixels(JNI(), bitmap)) != 0)
+        ALOG_BRIDGE("AndroidBitmap_unlockPixels failed! (error %d)", err);
+}
+
--- a/widget/src/android/AndroidBridge.h
+++ b/widget/src/android/AndroidBridge.h
@@ -240,16 +240,25 @@ public:
     bool GetStaticStringField(const char *classID, const char *field, nsAString &result);
 
     bool GetStaticIntField(const char *className, const char *fieldName, PRInt32* aInt);
 
     void SetKeepScreenOn(bool on);
 
     void ScanMedia(const nsAString& aFile, const nsACString& aMimeType);
 
+    // These next four functions are for native Bitmap access in Android 2.2+
+    bool HasNativeBitmapAccess();
+
+    bool ValidateBitmap(jobject bitmap, int width, int height);
+
+    void *LockBitmap(jobject bitmap);
+
+    void UnlockBitmap(jobject bitmap);
+
 protected:
     static AndroidBridge *sBridge;
 
     // the global JavaVM
     JavaVM *mJavaVM;
 
     // the JNIEnv for the main thread
     JNIEnv *mJNIEnv;
@@ -261,16 +270,19 @@ protected:
     // the GeckoAppShell java class
     jclass mGeckoAppShellClass;
 
     AndroidBridge() { }
     PRBool Init(JNIEnv *jEnv, jclass jGeckoApp);
 
     void EnsureJNIThread();
 
+    bool mOpenedBitmapLibrary;
+    bool mHasNativeBitmapAccess;
+
     // other things
     jmethodID jNotifyIME;
     jmethodID jNotifyIMEEnabled;
     jmethodID jNotifyIMEChange;
     jmethodID jAcknowledgeEventSync;
     jmethodID jEnableAccelerometer;
     jmethodID jEnableLocation;
     jmethodID jReturnIMEQueryResult;
@@ -304,16 +316,21 @@ protected:
 
     // stuff we need for CallEglCreateWindowSurface
     jclass jEGLSurfaceImplClass;
     jclass jEGLContextImplClass;
     jclass jEGLConfigImplClass;
     jclass jEGLDisplayImplClass;
     jclass jEGLContextClass;
     jclass jEGL10Class;
+
+    // calls we've dlopened from libjnigraphics.so
+    int (* AndroidBitmap_getInfo)(JNIEnv *env, jobject bitmap, void *info);
+    int (* AndroidBitmap_lockPixels)(JNIEnv *env, jobject bitmap, void **buffer);
+    int (* AndroidBitmap_unlockPixels)(JNIEnv *env, jobject bitmap);
 };
 
 }
 
 extern "C" JNIEnv * GetJNIForThread();
 extern PRBool mozilla_AndroidBridge_SetMainThread(void *);
 extern jclass GetGeckoAppShellClass();
 
--- a/widget/src/android/AndroidJavaWrappers.cpp
+++ b/widget/src/android/AndroidJavaWrappers.cpp
@@ -97,17 +97,19 @@ jmethodID AndroidAddress::jGetPremisesMe
 jmethodID AndroidAddress::jGetSubAdminAreaMethod;
 jmethodID AndroidAddress::jGetSubLocalityMethod;
 jmethodID AndroidAddress::jGetSubThoroughfareMethod;
 jmethodID AndroidAddress::jGetThoroughfareMethod;
 
 jclass AndroidGeckoSurfaceView::jGeckoSurfaceViewClass = 0;
 jmethodID AndroidGeckoSurfaceView::jBeginDrawingMethod = 0;
 jmethodID AndroidGeckoSurfaceView::jEndDrawingMethod = 0;
-jmethodID AndroidGeckoSurfaceView::jDraw2DMethod = 0;
+jmethodID AndroidGeckoSurfaceView::jDraw2DBitmapMethod = 0;
+jmethodID AndroidGeckoSurfaceView::jDraw2DBufferMethod = 0;
+jmethodID AndroidGeckoSurfaceView::jGetSoftwareDrawBitmapMethod = 0;
 jmethodID AndroidGeckoSurfaceView::jGetSoftwareDrawBufferMethod = 0;
 jmethodID AndroidGeckoSurfaceView::jGetHolderMethod = 0;
 
 #define JNI()  (AndroidBridge::JNI())
 
 #define initInit() jclass jClass
 
 // note that this also sets jClass
@@ -165,19 +167,21 @@ AndroidGeckoEvent::InitGeckoEventClass(J
 void
 AndroidGeckoSurfaceView::InitGeckoSurfaceViewClass(JNIEnv *jEnv)
 {
     initInit();
 
     jGeckoSurfaceViewClass = getClassGlobalRef("org/mozilla/gecko/GeckoSurfaceView");
 
     jBeginDrawingMethod = getMethod("beginDrawing", "()I");
+    jGetSoftwareDrawBitmapMethod = getMethod("getSoftwareDrawBitmap", "()Landroid/graphics/Bitmap;");
     jGetSoftwareDrawBufferMethod = getMethod("getSoftwareDrawBuffer", "()Ljava/nio/ByteBuffer;");
     jEndDrawingMethod = getMethod("endDrawing", "()V");
-    jDraw2DMethod = getMethod("draw2D", "(Ljava/nio/ByteBuffer;I)V");
+    jDraw2DBitmapMethod = getMethod("draw2D", "(Landroid/graphics/Bitmap;II)V");
+    jDraw2DBufferMethod = getMethod("draw2D", "(Ljava/nio/ByteBuffer;I)V");
     jGetHolderMethod = getMethod("getHolder", "()Landroid/view/SurfaceHolder;");
 }
 
 void
 AndroidLocation::InitLocationClass(JNIEnv *jEnv)
 {
     initInit();
 
@@ -459,19 +463,31 @@ AndroidGeckoSurfaceView::BeginDrawing()
 
 void
 AndroidGeckoSurfaceView::EndDrawing()
 {
     JNI()->CallVoidMethod(wrapped_obj, jEndDrawingMethod);
 }
 
 void
+AndroidGeckoSurfaceView::Draw2D(jobject bitmap, int width, int height)
+{
+    JNI()->CallVoidMethod(wrapped_obj, jDraw2DBitmapMethod, bitmap, width, height);
+}
+
+void
 AndroidGeckoSurfaceView::Draw2D(jobject buffer, int stride)
 {
-    JNI()->CallVoidMethod(wrapped_obj, jDraw2DMethod, buffer, stride);
+    JNI()->CallVoidMethod(wrapped_obj, jDraw2DBufferMethod, buffer, stride);
+}
+
+jobject
+AndroidGeckoSurfaceView::GetSoftwareDrawBitmap()
+{
+    return JNI()->CallObjectMethod(wrapped_obj, jGetSoftwareDrawBitmapMethod);
 }
 
 jobject
 AndroidGeckoSurfaceView::GetSoftwareDrawBuffer()
 {
     return JNI()->CallObjectMethod(wrapped_obj, jGetSoftwareDrawBufferMethod);
 }
 
--- a/widget/src/android/AndroidJavaWrappers.h
+++ b/widget/src/android/AndroidJavaWrappers.h
@@ -162,29 +162,33 @@ public:
     void Init(jobject jobj);
 
     enum {
         DRAW_ERROR = 0,
         DRAW_GLES_2 = 1
     };
 
     int BeginDrawing();
+    jobject GetSoftwareDrawBitmap();
     jobject GetSoftwareDrawBuffer();
     void EndDrawing();
+    void Draw2D(jobject bitmap, int width, int height);
     void Draw2D(jobject buffer, int stride);
 
     // must have a JNI local frame when calling this,
     // and you'd better know what you're doing
     jobject GetSurfaceHolder();
 
 protected:
     static jclass jGeckoSurfaceViewClass;
     static jmethodID jBeginDrawingMethod;
     static jmethodID jEndDrawingMethod;
-    static jmethodID jDraw2DMethod;
+    static jmethodID jDraw2DBitmapMethod;
+    static jmethodID jDraw2DBufferMethod;
+    static jmethodID jGetSoftwareDrawBitmapMethod;
     static jmethodID jGetSoftwareDrawBufferMethod;
     static jmethodID jGetHolderMethod;
 };
 
 class AndroidKeyEvent
 {
 public:
     enum {
--- a/widget/src/android/nsWindow.cpp
+++ b/widget/src/android/nsWindow.cpp
@@ -982,37 +982,64 @@ nsWindow::OnDraw(AndroidGeckoEvent *ae)
 
     AndroidGeckoSurfaceView& sview(AndroidBridge::Bridge()->SurfaceView());
 
     NS_ASSERTION(!sview.isNull(), "SurfaceView is null!");
 
     AndroidBridge::Bridge()->HideProgressDialogOnce();
 
     if (GetLayerManager(nsnull)->GetBackendType() == LayerManager::LAYERS_BASIC) {
-        jobject bytebuf = sview.GetSoftwareDrawBuffer();
-        if (!bytebuf) {
-            ALOG("no buffer to draw into - skipping draw");
-            return;
-        }
+        if (AndroidBridge::Bridge()->HasNativeBitmapAccess()) {
+            jobject bitmap = sview.GetSoftwareDrawBitmap();
+            if (!bitmap) {
+                ALOG("no bitmap to draw into - skipping draw");
+                return;
+            }
+
+            if (!AndroidBridge::Bridge()->ValidateBitmap(bitmap, mBounds.width, mBounds.height))
+                return;
+
+            void *buf = AndroidBridge::Bridge()->LockBitmap(bitmap);
+            if (buf == nsnull) {
+                ALOG("### Software drawing, but failed to lock bitmap.");
+                return;
+            }
+
+            nsRefPtr<gfxImageSurface> targetSurface =
+                new gfxImageSurface((unsigned char *)buf,
+                                    gfxIntSize(mBounds.width, mBounds.height),
+                                    mBounds.width * 2,
+                                    gfxASurface::ImageFormatRGB16_565);
+            DrawTo(targetSurface);
 
-        void *buf = AndroidBridge::JNI()->GetDirectBufferAddress(bytebuf);
-        int cap = AndroidBridge::JNI()->GetDirectBufferCapacity(bytebuf);
-        if (!buf || cap != (mBounds.width * mBounds.height * 2)) {
-            ALOG("### Software drawing, but unexpected buffer size %d expected %d (or no buffer %p)!", cap, mBounds.width * mBounds.height * 2, buf);
-            return;
-        }
+            AndroidBridge::Bridge()->UnlockBitmap(bitmap);
+            sview.Draw2D(bitmap, mBounds.width, mBounds.height);
+        } else {
+            jobject bytebuf = sview.GetSoftwareDrawBuffer();
+            if (!bytebuf) {
+                ALOG("no buffer to draw into - skipping draw");
+                return;
+            }
 
-        nsRefPtr<gfxImageSurface> targetSurface =
-            new gfxImageSurface((unsigned char *)buf,
-                                gfxIntSize(mBounds.width, mBounds.height),
-                                mBounds.width * 2,
-                                gfxASurface::ImageFormatRGB16_565);
+            void *buf = AndroidBridge::JNI()->GetDirectBufferAddress(bytebuf);
+            int cap = AndroidBridge::JNI()->GetDirectBufferCapacity(bytebuf);
+            if (!buf || cap != (mBounds.width * mBounds.height * 2)) {
+                ALOG("### Software drawing, but unexpected buffer size %d expected %d (or no buffer %p)!", cap, mBounds.width * mBounds.height * 2, buf);
+                return;
+            }
 
-        DrawTo(targetSurface);
-        sview.Draw2D(bytebuf, mBounds.width * 2);
+            nsRefPtr<gfxImageSurface> targetSurface =
+                new gfxImageSurface((unsigned char *)buf,
+                                    gfxIntSize(mBounds.width, mBounds.height),
+                                    mBounds.width * 2,
+                                    gfxASurface::ImageFormatRGB16_565);
+            DrawTo(targetSurface);
+
+            sview.Draw2D(bytebuf, mBounds.width * 2);
+        }
     } else {
         int drawType = sview.BeginDrawing();
 
         if (drawType == AndroidGeckoSurfaceView::DRAW_ERROR) {
             ALOG("##### BeginDrawing failed!");
             return;
         }