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 83906 32d494dce3427db139995626c5d4aa6e94ced784
parent 83905 4a363485a656177d6cf1e4ea320943293a195c2c
child 83907 a1f1e4d685c0008b2cb6a4038a9004ef0ab6b598
push id4632
push userchrislord.net@gmail.com
push dateFri, 06 Jan 2012 11:23:43 +0000
treeherdermozilla-inbound@a1f1e4d685c0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspcwalton
bugs709120
milestone12.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 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);
     }