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 71697 8c5e954a80b8e5c6e4794907320650f5ed3df60c
parent 71696 37eb61062444afb2913daeb1b0fb27500cb10d82
child 71698 9a4d38add60a723f81e53c5d330fac11a0ab32b5
push id159
push usereakhgari@mozilla.com
push dateTue, 16 Aug 2011 17:53:11 +0000
treeherdermozilla-beta@8786e3e49240 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdougt
bugs661843
milestone7.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 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;
         }