Bug 709120 - Use MultiTileLayer in Android native fennec. r=pcwalton
authorChris Lord <chrislord.net@gmail.com>
Fri, 06 Jan 2012 11:22:38 +0000
changeset 85152 32d494dce3427db139995626c5d4aa6e94ced784
parent 85151 4a363485a656177d6cf1e4ea320943293a195c2c
child 85153 a1f1e4d685c0008b2cb6a4038a9004ef0ab6b598
push id805
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 18:17:35 +0000
treeherdermozilla-aurora@6fb3bf232436 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspcwalton
bugs709120
milestone12.0a1
Bug 709120 - Use MultiTileLayer in Android native fennec. r=pcwalton This uses MultiTileLayer instead of SingleTileLayer for devices that don't have gralloc support. This has the advantage of reduced memory usage due to less wastage from power-of-two textures, and can be a performance boost due to slow sub-image updates on some devices.
mobile/android/base/GeckoEvent.java
mobile/android/base/gfx/GeckoSoftwareLayerClient.java
--- a/mobile/android/base/GeckoEvent.java
+++ b/mobile/android/base/GeckoEvent.java
@@ -32,16 +32,17 @@
  * 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 ***** */
 
 package org.mozilla.gecko;
 
+import org.mozilla.gecko.gfx.IntSize;
 import org.mozilla.gecko.gfx.ViewportMetrics;
 import android.os.*;
 import android.app.*;
 import android.view.*;
 import android.content.*;
 import android.graphics.*;
 import android.widget.*;
 import android.hardware.*;
@@ -73,16 +74,17 @@ public class GeckoEvent {
     public static final int ACTIVITY_SHUTDOWN = 11;
     public static final int LOAD_URI = 12;
     public static final int SURFACE_CREATED = 13;
     public static final int SURFACE_DESTROYED = 14;
     public static final int GECKO_EVENT_SYNC = 15;
     public static final int ACTIVITY_START = 17;
     public static final int BROADCAST = 19;
     public static final int VIEWPORT = 20;
+    public static final int TILE_SIZE = 21;
 
     public static final int IME_COMPOSITION_END = 0;
     public static final int IME_COMPOSITION_BEGIN = 1;
     public static final int IME_SET_TEXT = 2;
     public static final int IME_GET_TEXT = 3;
     public static final int IME_DELETE_TEXT = 4;
     public static final int IME_SET_SELECTION = 5;
     public static final int IME_GET_SELECTION = 6;
@@ -223,16 +225,26 @@ public class GeckoEvent {
         }
 
         mType = etype;
 
         mP0 = new Point(w, h);
         mP1 = new Point(screenw, screenh);
     }
 
+    public GeckoEvent(int etype, IntSize size) {
+        if (etype != TILE_SIZE) {
+            mType = INVALID;
+            return;
+        }
+
+        mType = etype;
+        mP0 = new Point(size.width, size.height);
+    }
+
     public GeckoEvent(String subject, String data) {
         mType = BROADCAST;
         mCharacters = subject;
         mCharactersExtra = data;
     }
 
     public GeckoEvent(ViewportMetrics viewport) {
         mType = VIEWPORT;
--- a/mobile/android/base/gfx/GeckoSoftwareLayerClient.java
+++ b/mobile/android/base/gfx/GeckoSoftwareLayerClient.java
@@ -38,26 +38,27 @@
 
 package org.mozilla.gecko.gfx;
 
 import org.mozilla.gecko.gfx.CairoImage;
 import org.mozilla.gecko.gfx.IntSize;
 import org.mozilla.gecko.gfx.LayerClient;
 import org.mozilla.gecko.gfx.LayerController;
 import org.mozilla.gecko.gfx.LayerRenderer;
+import org.mozilla.gecko.gfx.MultiTileLayer;
 import org.mozilla.gecko.gfx.PointUtils;
-import org.mozilla.gecko.gfx.SingleTileLayer;
 import org.mozilla.gecko.gfx.WidgetTileLayer;
 import org.mozilla.gecko.FloatUtils;
 import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.GeckoEventListener;
 import android.content.Context;
 import android.graphics.Bitmap;
+import android.graphics.Canvas;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -79,16 +80,18 @@ public class GeckoSoftwareLayerClient ex
     private ByteBuffer mBuffer;
     private Layer mTileLayer;
 
     /* The viewport rect that Gecko is currently displaying. */
     private ViewportMetrics mGeckoViewport;
 
     private CairoImage mCairoImage;
 
+    private static final IntSize TILE_SIZE = new IntSize(256, 256);
+
     private static final long MIN_VIEWPORT_CHANGE_DELAY = 350L;
     private long mLastViewportChangeTime;
     private boolean mPendingViewportAdjust;
     private boolean mViewportSizeChanged;
 
     // mUpdateViewportOnEndDraw is used to indicate that we received a
     // viewport update notification while drawing. therefore, when the
     // draw finishes, we need to update the entire viewport rather than
@@ -107,17 +110,17 @@ public class GeckoSoftwareLayerClient ex
             @Override
             public ByteBuffer getBuffer() { return mBuffer; }
             @Override
             public IntSize getSize() { return mBufferSize; }
             @Override
             public int getFormat() { return mFormat; }
         };
 
-        mTileLayer = new SingleTileLayer(mCairoImage);
+        mTileLayer = new MultiTileLayer(mCairoImage, TILE_SIZE);
     }
 
 
     protected void finalize() throws Throwable {
         try {
             if (mBuffer != null)
                 GeckoAppShell.freeDirectBuffer(mBuffer);
             mBuffer = null;
@@ -138,16 +141,24 @@ public class GeckoSoftwareLayerClient ex
         layerController.setRoot(mTileLayer);
         if (mGeckoViewport != null) {
             layerController.setViewportMetrics(mGeckoViewport);
         }
 
         GeckoAppShell.registerGeckoEventListener("Viewport:UpdateAndDraw", this);
         GeckoAppShell.registerGeckoEventListener("Viewport:UpdateLater", this);
 
+        // This needs to happen before a call to sendResizeEventIfNecessary
+        // happens, but only needs to be called once. As that is only called by
+        // the layer controller or this, here is a safe place to do so.
+        if (mTileLayer instanceof MultiTileLayer) {
+            GeckoEvent event = new GeckoEvent(GeckoEvent.TILE_SIZE, TILE_SIZE);
+            GeckoAppShell.sendEventToGecko(event);
+        }
+
         sendResizeEventIfNecessary();
     }
 
     public void beginDrawing(int width, int height) {
         beginTransaction(mTileLayer);
 
         if (mBufferSize.width != width || mBufferSize.height != height) {
             mBufferSize = new IntSize(width, height);
@@ -208,42 +219,74 @@ public class GeckoSoftwareLayerClient ex
      */
     public void endDrawing(int x, int y, int width, int height, String metadata) {
         synchronized (getLayerController()) {
             try {
                 updateViewport(metadata, !mUpdateViewportOnEndDraw);
                 mUpdateViewportOnEndDraw = false;
                 Rect rect = new Rect(x, y, x + width, y + height);
 
-                if (mTileLayer instanceof SingleTileLayer)
-                    ((SingleTileLayer)mTileLayer).invalidate(rect);
+                if (mTileLayer instanceof MultiTileLayer)
+                    ((MultiTileLayer)mTileLayer).invalidate(rect);
             } finally {
                 endTransaction(mTileLayer);
             }
         }
     }
 
     public ViewportMetrics getGeckoViewportMetrics() {
         // Return a copy, as we modify this inside the Gecko thread
         if (mGeckoViewport != null)
             return new ViewportMetrics(mGeckoViewport);
         return null;
     }
 
+    public void copyPixelsFromMultiTileLayer(Bitmap target) {
+        Canvas c = new Canvas(target);
+        ByteBuffer tileBuffer = mBuffer.slice();
+        int bpp = CairoUtils.bitsPerPixelForCairoFormat(mFormat) / 8;
+
+        for (int y = 0; y < mBufferSize.height; y += TILE_SIZE.height) {
+            for (int x = 0; x < mBufferSize.width; x += TILE_SIZE.width) {
+                // Calculate tile size
+                IntSize tileSize = new IntSize(Math.min(mBufferSize.width - x, TILE_SIZE.width),
+                                               Math.min(mBufferSize.height - y, TILE_SIZE.height));
+
+                // Create a Bitmap from this tile
+                Bitmap tile = Bitmap.createBitmap(tileSize.width, tileSize.height,
+                                                  CairoUtils.cairoFormatTobitmapConfig(mFormat));
+                tile.copyPixelsFromBuffer(tileBuffer.asIntBuffer());
+
+                // Copy the tile to the master Bitmap and recycle it
+                c.drawBitmap(tile, x, y, null);
+                tile.recycle();
+
+                // Progress the buffer to the next tile
+                tileBuffer.position(tileSize.getArea() * bpp);
+                tileBuffer = tileBuffer.slice();
+            }
+        }
+    }
+
     public Bitmap getBitmap() {
         // Begin a tile transaction, otherwise the buffer can be destroyed while
         // we're reading from it.
         beginTransaction(mTileLayer);
         try {
             if (mBuffer == null || mBufferSize.width <= 0 || mBufferSize.height <= 0)
                 return null;
             try {
                 Bitmap b = Bitmap.createBitmap(mBufferSize.width, mBufferSize.height,
                                                CairoUtils.cairoFormatTobitmapConfig(mFormat));
-                b.copyPixelsFromBuffer(mBuffer.asIntBuffer());
+
+                if (mTileLayer instanceof MultiTileLayer)
+                    copyPixelsFromMultiTileLayer(b);
+                else
+                    b.copyPixelsFromBuffer(mBuffer.asIntBuffer());
+
                 return b;
             } catch (OutOfMemoryError oom) {
                 Log.w(LOGTAG, "Unable to create bitmap", oom);
                 return null;
             }
         } finally {
             endTransaction(mTileLayer);
         }
@@ -275,25 +318,35 @@ public class GeckoSoftwareLayerClient ex
         GeckoApp.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics);
 
         if (metrics.widthPixels == mScreenSize.width &&
                 metrics.heightPixels == mScreenSize.height) {
             return;
         }
 
         mScreenSize = new IntSize(metrics.widthPixels, metrics.heightPixels);
-        int maxSize = getLayerController().getView().getMaxTextureSize();
+        IntSize bufferSize;
+
+        // Round up depending on layer implementation to remove texture wastage
+        if (mTileLayer instanceof MultiTileLayer) {
+            // Round to the next multiple of the tile size, respecting maximum texture size
+            bufferSize = new IntSize(((mScreenSize.width + LayerController.MIN_BUFFER.width - 1) / TILE_SIZE.width + 1) * TILE_SIZE.width,
+                                     ((mScreenSize.height + LayerController.MIN_BUFFER.height - 1) / TILE_SIZE.height + 1) * TILE_SIZE.height);
 
-        // XXX Introduce tiling to solve this?
-        if (mScreenSize.width > maxSize || mScreenSize.height > maxSize)
-            throw new RuntimeException("Screen size of " + mScreenSize + " larger than maximum texture size of " + maxSize);
+        } else {
+            int maxSize = getLayerController().getView().getMaxTextureSize();
 
-        // Round to next power of two until we use NPOT texture support
-        IntSize bufferSize = new IntSize(Math.min(maxSize, IntSize.nextPowerOfTwo(mScreenSize.width + LayerController.MIN_BUFFER.width)),
-                                         Math.min(maxSize, IntSize.nextPowerOfTwo(mScreenSize.height + LayerController.MIN_BUFFER.height)));
+            // XXX Integrate gralloc/tiling work to circumvent this
+            if (mScreenSize.width > maxSize || mScreenSize.height > maxSize)
+                throw new RuntimeException("Screen size of " + mScreenSize + " larger than maximum texture size of " + maxSize);
+
+            // Round to next power of two until we have NPOT texture support
+            bufferSize = new IntSize(Math.min(maxSize, IntSize.nextPowerOfTwo(mScreenSize.width + LayerController.MIN_BUFFER.width)),
+                                     Math.min(maxSize, IntSize.nextPowerOfTwo(mScreenSize.height + LayerController.MIN_BUFFER.height)));
+        }
 
         Log.i(LOGTAG, "Screen-size changed to " + mScreenSize);
         GeckoEvent event = new GeckoEvent(GeckoEvent.SIZE_CHANGED,
                                           bufferSize.width, bufferSize.height,
                                           metrics.widthPixels, metrics.heightPixels);
         GeckoAppShell.sendEventToGecko(event);
     }