diff --git a/mobile/android/base/gfx/GeckoSoftwareLayerClient.java b/mobile/android/base/gfx/GeckoSoftwareLayerClient.java index d319543175e6..eb82d649c5ce 100644 --- a/mobile/android/base/gfx/GeckoSoftwareLayerClient.java +++ b/mobile/android/base/gfx/GeckoSoftwareLayerClient.java @@ -80,15 +80,9 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL private ByteBuffer mBuffer; private Layer mTileLayer; - /* The viewport that Gecko is currently displaying. */ + /* The viewport rect that Gecko is currently displaying. */ private ViewportMetrics mGeckoViewport; - /* The viewport that Gecko will display when drawing is finished */ - private ViewportMetrics mNewGeckoViewport; - - /* The offset used to make sure tiles are snapped to the pixel grid */ - private Point mRenderOffset; - private CairoImage mCairoImage; private static final IntSize TILE_SIZE = new IntSize(256, 256); @@ -114,7 +108,6 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL mScreenSize = new IntSize(0, 0); mBufferSize = new IntSize(0, 0); mFormat = CairoImage.FORMAT_RGB16_565; - mRenderOffset = new Point(0, 0); mCairoImage = new CairoImage() { @Override @@ -124,6 +117,8 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL @Override public int getFormat() { return mFormat; } }; + + mTileLayer = new MultiTileLayer(mCairoImage, TILE_SIZE); } public int getWidth() { @@ -156,8 +151,6 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL GeckoAppShell.registerGeckoEventListener("Viewport:UpdateAndDraw", this); GeckoAppShell.registerGeckoEventListener("Viewport:UpdateLater", this); - GeckoAppShell.registerGeckoEventListener("Document:Shown", this); - GeckoAppShell.registerGeckoEventListener("Tab:Selected", this); // This needs to happen before a call to sendResizeEventIfNecessary // happens, but only needs to be called once. As that is only called by @@ -170,9 +163,9 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL sendResizeEventIfNecessary(); } - private boolean setHasDirectTexture(boolean hasDirectTexture) { - if (mTileLayer != null && hasDirectTexture == mHasDirectTexture) - return false; + private void setHasDirectTexture(boolean hasDirectTexture) { + if (hasDirectTexture == mHasDirectTexture) + return; mHasDirectTexture = hasDirectTexture; @@ -180,7 +173,6 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL if (mHasDirectTexture) { mTileLayer = new WidgetTileLayer(mCairoImage); tileSize = new IntSize(0, 0); - mRenderOffset.set(0, 0); } else { mTileLayer = new MultiTileLayer(mCairoImage, TILE_SIZE); tileSize = TILE_SIZE; @@ -194,62 +186,18 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL // Force a resize event to be sent because the results of this // are different depending on what tile system we're using sendResizeEventIfNecessary(true); - - return true; } - public boolean beginDrawing(int width, int height, String metadata, boolean hasDirectTexture) { - // If we've changed surface types, cancel this draw - if (setHasDirectTexture(hasDirectTexture)) - return false; - + public void beginDrawing(int width, int height) { beginTransaction(mTileLayer); - try { - JSONObject viewportObject = new JSONObject(metadata); - mNewGeckoViewport = new ViewportMetrics(viewportObject); - - // We only need to set a render offset/allocate buffer memory if - // we're using MultiTileLayer. Otherwise, just synchronise the - // buffer size and return. - if (!(mTileLayer instanceof MultiTileLayer)) { - if (mBufferSize.width != width || mBufferSize.height != height) - mBufferSize = new IntSize(width, height); - return true; - } - - // If the origin has changed, alter the rendering offset so that - // rendering is snapped to the tile grid and clear the invalid area. - boolean originChanged = true; - Point origin = PointUtils.round(mNewGeckoViewport.getDisplayportOrigin()); - - if (mGeckoViewport != null) { - Point oldOrigin = PointUtils.round(mGeckoViewport.getDisplayportOrigin()); - originChanged = !origin.equals(oldOrigin); - } - - if (originChanged) { - Point tileOrigin = new Point((origin.x / TILE_SIZE.width) * TILE_SIZE.width, - (origin.y / TILE_SIZE.height) * TILE_SIZE.height); - mRenderOffset.set(origin.x - tileOrigin.x, origin.y - tileOrigin.y); - ((MultiTileLayer)mTileLayer).invalidateBuffer(); - } - } catch (JSONException e) { - Log.e(LOGTAG, "Bad viewport description: " + metadata); - throw new RuntimeException(e); - } - if (mBufferSize.width != width || mBufferSize.height != height) { mBufferSize = new IntSize(width, height); - // We over-allocate to allow for the render offset. nsWindow - // assumes that this will happen. - IntSize realBufferSize = new IntSize(width + TILE_SIZE.width, - height + TILE_SIZE.height); - // Reallocate the buffer if necessary - int bpp = CairoUtils.bitsPerPixelForCairoFormat(mFormat) / 8; - int size = realBufferSize.getArea() * bpp; + + // * 2 because it's a 16-bit buffer (so 2 bytes per pixel). + int size = mBufferSize.getArea() * 2; if (mBuffer == null || mBuffer.capacity() != size) { // Free the old buffer if (mBuffer != null) { @@ -260,35 +208,39 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL mBuffer = GeckoAppShell.allocateDirectBuffer(size); } } - - return true; } - private void updateViewport(final boolean onlyUpdatePageSize) { - // save and restore the viewport size stored in java; never let the - // JS-side viewport dimensions override the java-side ones because - // java is the One True Source of this information, and allowing JS - // to override can lead to race conditions where this data gets clobbered. - FloatSize viewportSize = getLayerController().getViewportSize(); - mGeckoViewport = mNewGeckoViewport; - mGeckoViewport.setSize(viewportSize); + private void updateViewport(String viewportDescription, final boolean onlyUpdatePageSize) { + try { + JSONObject viewportObject = new JSONObject(viewportDescription); - LayerController controller = getLayerController(); - PointF displayportOrigin = mGeckoViewport.getDisplayportOrigin(); - Point tileOrigin = PointUtils.round(displayportOrigin); - tileOrigin.offset(-mRenderOffset.x, -mRenderOffset.y); - mTileLayer.setOrigin(tileOrigin); - mTileLayer.setResolution(mGeckoViewport.getZoomFactor()); + // save and restore the viewport size stored in java; never let the + // JS-side viewport dimensions override the java-side ones because + // java is the One True Source of this information, and allowing JS + // to override can lead to race conditions where this data gets clobbered. + FloatSize viewportSize = getLayerController().getViewportSize(); + mGeckoViewport = new ViewportMetrics(viewportObject); + mGeckoViewport.setSize(viewportSize); - if (onlyUpdatePageSize) { - // Don't adjust page size when zooming unless zoom levels are - // approximately equal. - if (FloatUtils.fuzzyEquals(controller.getZoomFactor(), - mGeckoViewport.getZoomFactor())) - controller.setPageSize(mGeckoViewport.getPageSize()); - } else { - controller.setViewportMetrics(mGeckoViewport); - controller.abortPanZoomAnimation(); + LayerController controller = getLayerController(); + PointF displayportOrigin = mGeckoViewport.getDisplayportOrigin(); + mTileLayer.setOrigin(PointUtils.round(displayportOrigin)); + mTileLayer.setResolution(mGeckoViewport.getZoomFactor()); + + if (onlyUpdatePageSize) { + // Don't adjust page size when zooming unless zoom levels are + // approximately equal. + if (FloatUtils.fuzzyEquals(controller.getZoomFactor(), + mGeckoViewport.getZoomFactor())) + controller.setPageSize(mGeckoViewport.getPageSize()); + } else { + Log.d(LOGTAG, "Received viewport update from gecko"); + controller.setViewportMetrics(mGeckoViewport); + controller.abortPanZoomAnimation(); + } + } catch (JSONException e) { + Log.e(LOGTAG, "Bad viewport description: " + viewportDescription); + throw new RuntimeException(e); } } @@ -296,17 +248,17 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL * TODO: Would be cleaner if this took an android.graphics.Rect instead, but that would require * a little more JNI magic. */ - public void endDrawing(int x, int y, int width, int height) { + public void endDrawing(int x, int y, int width, int height, String metadata, boolean hasDirectTexture) { synchronized (getLayerController()) { try { - updateViewport(!mUpdateViewportOnEndDraw); + updateViewport(metadata, !mUpdateViewportOnEndDraw); mUpdateViewportOnEndDraw = false; + Rect rect = new Rect(x, y, x + width, y + height); - if (mTileLayer instanceof MultiTileLayer) { - Rect rect = new Rect(x, y, x + width, y + height); - rect.offset(mRenderOffset.x, mRenderOffset.y); + setHasDirectTexture(hasDirectTexture); + + if (!mHasDirectTexture) ((MultiTileLayer)mTileLayer).invalidate(rect); - } } finally { endTransaction(mTileLayer); } @@ -325,28 +277,29 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL 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) { + 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(TILE_SIZE.width, TILE_SIZE.height, + 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 - mRenderOffset.x, y - mRenderOffset.y, null); + c.drawBitmap(tile, x, y, null); tile.recycle(); // Progress the buffer to the next tile - tileBuffer.position(TILE_SIZE.getArea() * bpp); + tileBuffer.position(tileSize.getArea() * bpp); tileBuffer = tileBuffer.slice(); } } } public Bitmap getBitmap() { - if (mTileLayer == null) - return null; - // Begin a tile transaction, otherwise the buffer can be destroyed while // we're reading from it. beginTransaction(mTileLayer); @@ -358,8 +311,12 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL if (mTileLayer instanceof MultiTileLayer) { b = Bitmap.createBitmap(mBufferSize.width, mBufferSize.height, - CairoUtils.cairoFormatTobitmapConfig(mFormat)); + CairoUtils.cairoFormatTobitmapConfig(mFormat)); copyPixelsFromMultiTileLayer(b); + } else if (mTileLayer instanceof SingleTileLayer) { + b = Bitmap.createBitmap(mBufferSize.width, mBufferSize.height, + CairoUtils.cairoFormatTobitmapConfig(mFormat)); + b.copyPixelsFromBuffer(mBuffer.asIntBuffer()); } else { Log.w(LOGTAG, "getBitmap() called on a layer (" + mTileLayer + ") we don't know how to get a bitmap from"); } @@ -379,10 +336,6 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL return mBuffer; } - public Point getRenderOffset() { - return mRenderOffset; - } - /** * Gecko calls this function to signal that it is done with the back buffer. After this call, * it is forbidden for Gecko to touch the buffer. @@ -417,7 +370,7 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL // Round up depending on layer implementation to remove texture wastage if (mTileLayer instanceof MultiTileLayer) { - // Round to the next multiple of the tile size + // 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); @@ -428,7 +381,7 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL 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, respecting maximum texture size + // 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))); } @@ -499,16 +452,6 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL GeckoAppShell.sendEventToGecko(new GeckoEvent(GeckoEvent.DRAW, rect)); } else if ("Viewport:UpdateLater".equals(event)) { mUpdateViewportOnEndDraw = true; - } else if (("Document:Shown".equals(event) || - "Tab:Selected".equals(event)) && - (mTileLayer instanceof MultiTileLayer)) { - beginTransaction(mTileLayer); - try { - ((MultiTileLayer)mTileLayer).invalidateTiles(); - ((MultiTileLayer)mTileLayer).invalidateBuffer(); - } finally { - endTransaction(mTileLayer); - } } } } diff --git a/mobile/android/base/gfx/Layer.java b/mobile/android/base/gfx/Layer.java index 4c3328667542..fc15f48026d8 100644 --- a/mobile/android/base/gfx/Layer.java +++ b/mobile/android/base/gfx/Layer.java @@ -63,31 +63,23 @@ public abstract class Layer { /** * Updates the layer. This returns false if there is still work to be done - * after this update. If the layer is not already in a transaction, the - * lock will be acquired and a transaction will automatically begin and - * end around the update. + * after this update. */ public final boolean update(GL10 gl, RenderContext context) { - boolean startTransaction = true; if (mTransactionLock.isHeldByCurrentThread()) { - startTransaction = false; + throw new RuntimeException("draw() called while transaction lock held by this " + + "thread?!"); } - // If we're not already in a transaction and we can't acquire the lock, - // bail out. - if (startTransaction && !mTransactionLock.tryLock()) { - return false; - } - - mInTransaction = true; - try { - return performUpdates(gl, context); - } finally { - if (startTransaction) { - mInTransaction = false; + if (mTransactionLock.tryLock()) { + try { + return performUpdates(gl, context); + } finally { mTransactionLock.unlock(); } } + + return false; } /** Subclasses override this function to draw the layer. */ @@ -117,6 +109,7 @@ public abstract class Layer { mTransactionLock.lock(); mView = aView; mInTransaction = true; + mNewResolution = mResolution; } public void beginTransaction() { diff --git a/mobile/android/base/gfx/LayerRenderer.java b/mobile/android/base/gfx/LayerRenderer.java index 6d96f04da399..fe9cdb92b0ec 100644 --- a/mobile/android/base/gfx/LayerRenderer.java +++ b/mobile/android/base/gfx/LayerRenderer.java @@ -101,30 +101,12 @@ public class LayerRenderer implements GLSurfaceView.Renderer { CairoImage backgroundImage = new BufferedCairoImage(controller.getBackgroundPattern()); mBackgroundLayer = new SingleTileLayer(true, backgroundImage); - mBackgroundLayer.beginTransaction(null); - try { - mBackgroundLayer.invalidate(); - } finally { - mBackgroundLayer.endTransaction(); - } CairoImage checkerboardImage = new BufferedCairoImage(controller.getCheckerboardPattern()); mCheckerboardLayer = new SingleTileLayer(true, checkerboardImage); - mCheckerboardLayer.beginTransaction(null); - try { - mCheckerboardLayer.invalidate(); - } finally { - mCheckerboardLayer.endTransaction(); - } CairoImage shadowImage = new BufferedCairoImage(controller.getShadowPattern()); mShadowLayer = new NinePatchTileLayer(shadowImage); - mShadowLayer.beginTransaction(null); - try { - mShadowLayer.invalidate(); - } finally { - mShadowLayer.endTransaction(); - } IntSize frameRateLayerSize = new IntSize(FRAME_RATE_METER_WIDTH, FRAME_RATE_METER_HEIGHT); mFrameRateLayer = TextLayer.create(frameRateLayerSize, "-- ms/--"); diff --git a/mobile/android/base/gfx/MultiTileLayer.java b/mobile/android/base/gfx/MultiTileLayer.java index ef4652f15672..94a605f35572 100644 --- a/mobile/android/base/gfx/MultiTileLayer.java +++ b/mobile/android/base/gfx/MultiTileLayer.java @@ -37,21 +37,15 @@ package org.mozilla.gecko.gfx; -import org.mozilla.gecko.FloatUtils; import org.mozilla.gecko.gfx.CairoImage; import org.mozilla.gecko.gfx.IntSize; import org.mozilla.gecko.gfx.SingleTileLayer; import android.graphics.Point; -import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; -import android.graphics.Region; import android.util.Log; -import java.lang.Long; import java.nio.ByteBuffer; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.ListIterator; +import java.util.ArrayList; import javax.microedition.khronos.opengles.GL10; /** @@ -63,12 +57,9 @@ public class MultiTileLayer extends Layer { private static final String LOGTAG = "GeckoMultiTileLayer"; private final CairoImage mImage; - private final IntSize mTileSize; + private IntSize mTileSize; private IntSize mBufferSize; - private Region mDirtyRegion; - private Region mValidRegion; - private final LinkedList mTiles; - private final HashMap mPositionHash; + private final ArrayList mTiles; public MultiTileLayer(CairoImage image, IntSize tileSize) { super(); @@ -76,37 +67,34 @@ public class MultiTileLayer extends Layer { mImage = image; mTileSize = tileSize; mBufferSize = new IntSize(0, 0); - mDirtyRegion = new Region(); - mValidRegion = new Region(); - mTiles = new LinkedList(); - mPositionHash = new HashMap(); + mTiles = new ArrayList(); } - /** - * Invalidates a sub-region of the layer. Data will be uploaded from the - * backing buffer over subsequent calls to update(). - * This method is only valid inside a transaction. - */ public void invalidate(Rect dirtyRect) { - if (!inTransaction()) { + if (!inTransaction()) throw new RuntimeException("invalidate() is only valid inside a transaction"); - } - mDirtyRegion.union(dirtyRect); - mValidRegion.union(dirtyRect); + int x = 0, y = 0; + IntSize size = getSize(); + for (SingleTileLayer layer : mTiles) { + Rect tileRect = new Rect(x, y, x + mTileSize.width, y + mTileSize.height); + + if (tileRect.intersect(dirtyRect)) { + tileRect.offset(-x, -y); + layer.invalidate(tileRect); + } + + x += mTileSize.width; + if (x >= size.width) { + x = 0; + y += mTileSize.height; + } + } } - /** - * Invalidates the backing buffer. Data will not be uploaded from an invalid - * backing buffer. This method is only valid inside a transaction. - */ - public void invalidateBuffer() { - if (!inTransaction()) { - throw new RuntimeException("invalidateBuffer() is only valid inside a transaction"); - } - - mDirtyRegion.setEmpty(); - mValidRegion.setEmpty(); + public void invalidate() { + for (SingleTileLayer layer : mTiles) + layer.invalidate(); } @Override @@ -114,104 +102,61 @@ public class MultiTileLayer extends Layer { return mImage.getSize(); } - /** - * Makes sure there are enough tiles to accommodate the buffer image. - */ private void validateTiles() { IntSize size = getSize(); if (size.equals(mBufferSize)) return; + // Regenerate tiles + mTiles.clear(); + int offset = 0; + final int format = mImage.getFormat(); + final ByteBuffer buffer = mImage.getBuffer().slice(); + final int bpp = CairoUtils.bitsPerPixelForCairoFormat(format) / 8; + for (int y = 0; y < size.height; y += mTileSize.height) { + for (int x = 0; x < size.width; x += mTileSize.width) { + // Create a CairoImage implementation that returns a + // tile from the parent CairoImage. It's assumed that + // the tiles are stored in series. + final IntSize layerSize = + new IntSize(Math.min(mTileSize.width, size.width - x), + Math.min(mTileSize.height, size.height - y)); + final int tileOffset = offset; + + CairoImage subImage = new CairoImage() { + @Override + public ByteBuffer getBuffer() { + // Create a ByteBuffer that shares the data of the original + // buffer, but is positioned and limited so that only the + // tile data is accessible. + buffer.position(tileOffset); + ByteBuffer tileBuffer = buffer.slice(); + tileBuffer.limit(layerSize.getArea() * bpp); + + return tileBuffer; + } + + @Override + public IntSize getSize() { + return layerSize; + } + + @Override + public int getFormat() { + return format; + } + }; + + mTiles.add(new SingleTileLayer(subImage)); + offset += layerSize.getArea() * bpp; + } + } + + // Set tile origins and resolution + refreshTileMetrics(getOrigin(), getResolution(), false); + mBufferSize = size; - - // Shrink/grow tile pool - int nTiles = (Math.round(size.width / (float)mTileSize.width) + 1) * - (Math.round(size.height / (float)mTileSize.height) + 1); - if (mTiles.size() < nTiles) { - Log.i(LOGTAG, "Tile pool growing from " + mTiles.size() + " to " + nTiles); - - for (int i = 0; i < nTiles; i++) { - mTiles.add(new SubTile(new SubImage(mImage, mTileSize))); - } - } else if (mTiles.size() > nTiles) { - Log.i(LOGTAG, "Tile pool shrinking from " + mTiles.size() + " to " + nTiles); - - // Remove tiles from the beginning of the list, as these are - // least recently used tiles - for (int i = mTiles.size(); i > nTiles; i--) { - SubTile tile = mTiles.get(0); - if (tile.key != null) { - mPositionHash.remove(tile.key); - } - mTiles.remove(0); - } - } - - // A buffer size probably means a layout change, so invalidate all tiles. - invalidateTiles(); - } - - /** - * Returns a Long representing the given Point. Used for hashing. - */ - private Long longFromPoint(Point point) { - // Assign 32 bits for each dimension of the point. - return new Long((((long)point.x) << 32) | point.y); - } - - /** - * Performs the necessary functions to update the specified properties of - * a sub-tile. - */ - private void updateTile(GL10 gl, RenderContext context, SubTile tile, Point tileOrigin, Rect dirtyRect, boolean reused) { - tile.beginTransaction(null); - try { - if (reused) { - // Invalidate any area that isn't represented in the current - // buffer. This is done as SingleTileLayer always updates the - // entire width, regardless of the dirty-rect's width, and so - // can override existing data. - Point origin = getOrigin(); - Rect validRect = tile.getValidTextureArea(); - validRect.offset(tileOrigin.x - origin.x, tileOrigin.y - origin.y); - Region validRegion = new Region(validRect); - validRegion.op(mValidRegion, Region.Op.INTERSECT); - - // SingleTileLayer can't draw complex regions, so in that case, - // just invalidate the entire area. - tile.invalidateTexture(); - if (!validRegion.isComplex()) { - validRect.set(validRegion.getBounds()); - validRect.offset(origin.x - tileOrigin.x, origin.y - tileOrigin.y); - } - } else { - // Update tile metrics - tile.setOrigin(tileOrigin); - tile.setResolution(getResolution()); - - // Make sure that non-reused tiles are marked as invalid before - // uploading new content. - tile.invalidateTexture(); - - // (Re)Place in the position hash for quick retrieval. - if (tile.key != null) { - mPositionHash.remove(tile.key); - } - tile.key = longFromPoint(tileOrigin); - mPositionHash.put(tile.key, tile); - } - - // Invalidate the area we want to upload. - tile.invalidate(dirtyRect); - - // Perform updates and mark texture as valid. - if (!tile.performUpdates(gl, context)) { - Log.e(LOGTAG, "Sub-tile failed to update fully"); - } - } finally { - tile.endTransaction(); - } } @Override @@ -220,266 +165,120 @@ public class MultiTileLayer extends Layer { validateTiles(); - // Bail out early if we have nothing to do. - if (mDirtyRegion.isEmpty() || mTiles.isEmpty()) { - return true; - } + // Iterate over the tiles and decide which ones we'll be drawing + int dirtyTiles = 0; + boolean screenUpdateDone = false; + SingleTileLayer firstDirtyTile = null; + for (SingleTileLayer layer : mTiles) { + // First do a non-texture update to make sure coordinates are + // up-to-date. + boolean invalid = layer.getSkipTextureUpdate(); + layer.setSkipTextureUpdate(true); + layer.performUpdates(gl, context); - // Check that we're capable of updating from this origin. - Point origin = getOrigin(); - if ((origin.x % mTileSize.width) != 0 || (origin.y % mTileSize.height) != 0) { - Log.e(LOGTAG, "MultiTileLayer doesn't support non tile-aligned origins! (" + - origin.x + ", " + origin.y + ")"); - return true; - } + RectF layerBounds = layer.getBounds(context, new FloatSize(layer.getSize())); + boolean isDirty = layer.isDirty(); - // Transform the viewport into tile-space so we can see what part of the - // dirty region intersects with it. - // We update any tiles intersecting with the screen before tiles - // intersecting with the viewport. - // XXX Maybe we want to to split this update even further to update - // checkerboard area before updating screen regions with old data. - // Note that this could provide inconsistent views, so we may not - // want to do this. - Rect tilespaceViewport; - float scaleFactor = getResolution() / context.zoomFactor; - tilespaceViewport = RectUtils.roundOut(RectUtils.scale(context.viewport, scaleFactor)); - tilespaceViewport.offset(-origin.x, -origin.y); - - // Expand tile-space viewport to tile boundaries - tilespaceViewport.left = (tilespaceViewport.left / mTileSize.width) * mTileSize.width; - tilespaceViewport.right += mTileSize.width - 1; - tilespaceViewport.right = (tilespaceViewport.right / mTileSize.width) * mTileSize.width; - tilespaceViewport.top = (tilespaceViewport.top / mTileSize.height) * mTileSize.height; - tilespaceViewport.bottom += mTileSize.height - 1; - tilespaceViewport.bottom = (tilespaceViewport.bottom / mTileSize.height) * mTileSize.height; - - // Declare a region for storing the results of Region operations - Region opRegion = new Region(); - - // Test if the dirty region intersects with the screen - boolean updateVisible = false; - Region updateRegion = mDirtyRegion; - if (opRegion.op(tilespaceViewport, mDirtyRegion, Region.Op.INTERSECT)) { - updateVisible = true; - updateRegion = new Region(opRegion); - } - - // Invalidate any tiles that are due to be replaced if their resolution - // doesn't match the parent layer resolution, and any tiles that are - // off-screen and off-buffer, as we cannot guarantee their validity. - // - // Note that we also cannot guarantee the validity of on-screen, - // off-buffer tiles, but this is a rare case that we allow for - // optimisation purposes. - // - // XXX Ideally, we want to remove this second invalidation clause - // somehow. It may be possible to know if off-screen tiles are - // valid by monitoring reflows on the browser element, or - // something along these lines. - LinkedList invalidTiles = new LinkedList(); - Rect bounds = mValidRegion.getBounds(); - for (ListIterator i = mTiles.listIterator(); i.hasNext();) { - SubTile tile = i.next(); - - if (tile.key == null) { - continue; - } - - RectF tileBounds = tile.getBounds(context, new FloatSize(tile.getSize())); - Rect tilespaceTileBounds = - RectUtils.round(RectUtils.scale(tileBounds, scaleFactor)); - tilespaceTileBounds.offset(-origin.x, -origin.y); - - // First bracketed clause: Invalidate off-screen, off-buffer tiles - // Second: Invalidate visible tiles at the wrong resolution that have updates - if ((!Rect.intersects(bounds, tilespaceTileBounds) && - !Rect.intersects(tilespaceViewport, tilespaceTileBounds)) || - (!FloatUtils.fuzzyEquals(tile.getResolution(), getResolution()) && - opRegion.op(tilespaceTileBounds, updateRegion, Region.Op.INTERSECT))) { - tile.invalidateTexture(); - - // Add to the list of invalid tiles and remove from the main list - invalidTiles.add(tile); - i.remove(); - - // Remove from the position hash - mPositionHash.remove(tile.key); - tile.key = null; - } - } - - // Push invalid tiles to the head of the queue so they get used first - mTiles.addAll(0, invalidTiles); - - // Update tiles - // Note, it's <= as the buffer is over-allocated due to render-offsetting. - for (int y = origin.y; y <= origin.y + mBufferSize.height; y += mTileSize.height) { - for (int x = origin.x; x <= origin.x + mBufferSize.width; x += mTileSize.width) { - // Does this tile intersect with the dirty region? - Rect tilespaceTileRect = new Rect(x - origin.x, y - origin.y, - (x - origin.x) + mTileSize.width, - (y - origin.y) + mTileSize.height); - if (!opRegion.op(tilespaceTileRect, updateRegion, Region.Op.INTERSECT)) { - continue; - } - - // Dirty tile, find out if we already have this tile to reuse. - boolean reusedTile = true; - Point tileOrigin = new Point(x, y); - SubTile tile = mPositionHash.get(longFromPoint(tileOrigin)); - - // If we don't, get an unused tile (we store these at the head of the list). - if (tile == null) { - tile = mTiles.removeFirst(); - reusedTile = false; + if (isDirty) { + if (!RectF.intersects(layerBounds, context.viewport)) { + if (firstDirtyTile == null) + firstDirtyTile = layer; + dirtyTiles ++; + invalid = true; } else { - mTiles.remove(tile); - } - - // Place tile at the end of the tile-list so it isn't re-used. - mTiles.add(tile); - - // Work out the tile's invalid area in this tile's space. - if (opRegion.isComplex()) { - Log.w(LOGTAG, "MultiTileLayer encountered complex dirty region"); - } - Rect dirtyRect = opRegion.getBounds(); - dirtyRect.offset(origin.x - x, origin.y - y); - - // Update tile metrics and texture data - tile.x = (x - origin.x) / mTileSize.width; - tile.y = (y - origin.y) / mTileSize.height; - updateTile(gl, context, tile, tileOrigin, dirtyRect, reusedTile); - - // If this update isn't visible, we only want to update one - // tile at a time. - if (!updateVisible) { - mDirtyRegion.op(opRegion, Region.Op.XOR); - return mDirtyRegion.isEmpty(); + // This tile intersects with the screen and is dirty, + // update it immediately. + layer.setSkipTextureUpdate(false); + screenUpdateDone = true; + layer.performUpdates(gl, context); + invalid = false; } } + + // We use the SkipTextureUpdate flag as a marker of a tile's + // validity. This is required, as sometimes layers are drawn + // without updating first, and we mustn't draw tiles that have + // been marked as invalid that we haven't updated. + layer.setSkipTextureUpdate(invalid); } - // Remove the update region from the dirty region - mDirtyRegion.op(updateRegion, Region.Op.XOR); + // Now if no tiles that intersect with the screen were updated, update + // a single tile that doesn't (if there are any). This has the effect + // of spreading out non-critical texture upload over time, and smoothing + // upload-related hitches. + if (!screenUpdateDone && firstDirtyTile != null) { + firstDirtyTile.setSkipTextureUpdate(false); + firstDirtyTile.performUpdates(gl, context); + dirtyTiles --; + } - return mDirtyRegion.isEmpty(); + return (dirtyTiles == 0); + } + + private void refreshTileMetrics(Point origin, float resolution, boolean inTransaction) { + int x = 0, y = 0; + IntSize size = getSize(); + for (SingleTileLayer layer : mTiles) { + if (!inTransaction) + layer.beginTransaction(null); + + if (origin != null) + layer.setOrigin(new Point(origin.x + x, origin.y + y)); + if (resolution >= 0.0f) + layer.setResolution(resolution); + + if (!inTransaction) + layer.endTransaction(); + + x += mTileSize.width; + if (x >= size.width) { + x = 0; + y += mTileSize.height; + } + } + } + + @Override + public void setOrigin(Point newOrigin) { + super.setOrigin(newOrigin); + refreshTileMetrics(newOrigin, -1, true); + } + + @Override + public void setResolution(float newResolution) { + super.setResolution(newResolution); + refreshTileMetrics(null, newResolution, true); } @Override public void beginTransaction(LayerView aView) { super.beginTransaction(aView); - for (SubTile layer : mTiles) { + for (SingleTileLayer layer : mTiles) layer.beginTransaction(aView); - } } @Override public void endTransaction() { - for (SubTile layer : mTiles) { + for (SingleTileLayer layer : mTiles) layer.endTransaction(); - } super.endTransaction(); } @Override public void draw(RenderContext context) { - for (SubTile layer : mTiles) { + for (SingleTileLayer layer : mTiles) { + // We use the SkipTextureUpdate flag as a validity flag. If it's false, + // the contents of this tile are invalid and we shouldn't draw it. + if (layer.getSkipTextureUpdate()) + continue; + // Avoid work, only draw tiles that intersect with the viewport RectF layerBounds = layer.getBounds(context, new FloatSize(layer.getSize())); if (RectF.intersects(layerBounds, context.viewport)) layer.draw(context); } } - - /** - * Invalidates all sub-tiles. This should be called if the source backing - * this layer has changed. This method is only valid inside a transaction. - */ - public void invalidateTiles() { - if (!inTransaction()) { - throw new RuntimeException("invalidateTiles() is only valid inside a transaction"); - } - - for (SubTile tile : mTiles) { - // Remove tile from position hash and mark it as invalid - if (tile.key != null) { - mPositionHash.remove(tile.key); - tile.key = null; - } - tile.invalidateTexture(); - } - } - - /** - * A SingleTileLayer extension with fields for relevant tile data that - * MultiTileLayer requires. - */ - private static class SubTile extends SingleTileLayer { - public int x; - public int y; - - public Long key; - - public SubTile(SubImage aImage) { - super(aImage); - - aImage.tile = this; - } - } - - /** - * A CairoImage implementation that returns a tile from a parent CairoImage. - * This assumes that the parent image has a size that is a multiple of the - * tile size. - */ - private static class SubImage extends CairoImage { - public SubTile tile; - - private IntSize mTileSize; - private CairoImage mImage; - - public SubImage(CairoImage image, IntSize tileSize) { - mTileSize = tileSize; - mImage = image; - } - - @Override - public ByteBuffer getBuffer() { - // Create a ByteBuffer that shares the data of the original - // buffer, but is positioned and limited so that only the - // tile data is accessible. - IntSize bufferSize = mImage.getSize(); - int bpp = CairoUtils.bitsPerPixelForCairoFormat(getFormat()) / 8; - int index = (tile.y * (bufferSize.width / mTileSize.width + 1)) + tile.x; - - ByteBuffer buffer = mImage.getBuffer().slice(); - - try { - buffer.position(index * mTileSize.getArea() * bpp); - buffer = buffer.slice(); - buffer.limit(mTileSize.getArea() * bpp); - } catch (IllegalArgumentException e) { - Log.e(LOGTAG, "Tile image-data out of bounds! Tile: (" + - tile.x + ", " + tile.y + "), image (" + bufferSize + ")"); - return null; - } - - return buffer; - } - - @Override - public IntSize getSize() { - return mTileSize; - } - - @Override - public int getFormat() { - return mImage.getFormat(); - } - } } diff --git a/mobile/android/base/gfx/RectUtils.java b/mobile/android/base/gfx/RectUtils.java index 33877bf188e7..5d1ab6a838c8 100644 --- a/mobile/android/base/gfx/RectUtils.java +++ b/mobile/android/base/gfx/RectUtils.java @@ -99,18 +99,11 @@ public final class RectUtils { y + (rect.height() * scale)); } - /** Returns the nearest integer rect of the given rect. */ public static Rect round(RectF rect) { return new Rect(Math.round(rect.left), Math.round(rect.top), Math.round(rect.right), Math.round(rect.bottom)); } - /** Returns the smallest integer rect that encapsulates the given rect. */ - public static Rect roundOut(RectF rect) { - return new Rect((int)Math.floor(rect.left), (int)Math.floor(rect.top), - (int)Math.ceil(rect.right), (int)Math.ceil(rect.bottom)); - } - public static IntSize getSize(Rect rect) { return new IntSize(rect.width(), rect.height()); } diff --git a/mobile/android/base/gfx/SingleTileLayer.java b/mobile/android/base/gfx/SingleTileLayer.java index 940527df2a91..39d063d57955 100644 --- a/mobile/android/base/gfx/SingleTileLayer.java +++ b/mobile/android/base/gfx/SingleTileLayer.java @@ -43,7 +43,6 @@ import org.mozilla.gecko.gfx.IntSize; import org.mozilla.gecko.gfx.LayerController; import org.mozilla.gecko.gfx.TileLayer; import android.graphics.PointF; -import android.graphics.Rect; import android.graphics.RectF; import android.opengl.GLES11; import android.opengl.GLES11Ext; @@ -59,8 +58,6 @@ import javax.microedition.khronos.opengles.GL10; * TODO: Repeating textures really should be their own type of layer. */ public class SingleTileLayer extends TileLayer { - private static final String LOGTAG = "GeckoSingleTileLayer"; - public SingleTileLayer(CairoImage image) { this(false, image); } public SingleTileLayer(boolean repeat, CairoImage image) { @@ -74,11 +71,6 @@ public class SingleTileLayer extends TileLayer { if (!initialized()) return; - // If the texture contents is entirely invalid, we have nothing to draw. - Rect validTexture = getValidTextureArea(); - if (validTexture.isEmpty()) - return; - GLES11.glBindTexture(GL10.GL_TEXTURE_2D, getTextureID()); RectF bounds; @@ -87,27 +79,13 @@ public class SingleTileLayer extends TileLayer { RectF viewport = context.viewport; if (repeats()) { - if (!validTexture.equals(new Rect(0, 0, size.width, size.height))) { - Log.e(LOGTAG, "Drawing partial repeating textures is unsupported!"); - } - bounds = new RectF(0.0f, 0.0f, viewport.width(), viewport.height()); int width = Math.round(viewport.width()); int height = Math.round(-viewport.height()); cropRect = new int[] { 0, size.height, width, height }; } else { bounds = getBounds(context, new FloatSize(size)); - - float scaleFactor = bounds.width() / (float)size.width; - bounds.left += validTexture.left * scaleFactor; - bounds.top += validTexture.top * scaleFactor; - bounds.right -= (size.width - validTexture.right) * scaleFactor; - bounds.bottom -= (size.height - validTexture.bottom) * scaleFactor; - - cropRect = new int[] { validTexture.left, - validTexture.bottom, - validTexture.width(), - -validTexture.height() }; + cropRect = new int[] { 0, size.height, size.width, -size.height }; } GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, cropRect, diff --git a/mobile/android/base/gfx/TileLayer.java b/mobile/android/base/gfx/TileLayer.java index e99315142ed2..ad52229cfbcf 100644 --- a/mobile/android/base/gfx/TileLayer.java +++ b/mobile/android/base/gfx/TileLayer.java @@ -59,14 +59,14 @@ public abstract class TileLayer extends Layer { private final CairoImage mImage; private final boolean mRepeat; private IntSize mSize; - private Rect mValidTextureRect; + private boolean mSkipTextureUpdate; private int[] mTextureIDs; public TileLayer(boolean repeat, CairoImage image) { mRepeat = repeat; mImage = image; mSize = new IntSize(0, 0); - mValidTextureRect = new Rect(); + mSkipTextureUpdate = false; IntSize bufferSize = mImage.getSize(); mDirtyRect = new Rect(); @@ -132,27 +132,22 @@ public abstract class TileLayer extends Layer { } } - /** - * Tells the tile that its texture contents are invalid. This will also - * clear any invalidated area. - */ - public void invalidateTexture() { - mValidTextureRect.setEmpty(); - mDirtyRect.setEmpty(); + /** Tells the tile not to update the texture on the next update. */ + public void setSkipTextureUpdate(boolean skip) { + mSkipTextureUpdate = skip; } - /** - * Returns a handle to the valid texture area rectangle. Modifying this - * Rect will modify the valid texture area for this layer. - */ - public Rect getValidTextureArea() { - return mValidTextureRect; + public boolean getSkipTextureUpdate() { + return mSkipTextureUpdate; } @Override protected boolean performUpdates(GL10 gl, RenderContext context) { super.performUpdates(gl, context); + if (mSkipTextureUpdate) + return false; + // Reallocate the texture if the size has changed validateTexture(gl); @@ -160,20 +155,23 @@ public abstract class TileLayer extends Layer { if (!mImage.getSize().isPositive()) return true; - // Update the dirty region - uploadDirtyRect(gl, mDirtyRect); + // If we haven't allocated a texture, assume the whole region is dirty + if (mTextureIDs == null) + uploadFullTexture(gl); + else + uploadDirtyRect(gl, mDirtyRect); + mDirtyRect.setEmpty(); return true; } - private void uploadDirtyRect(GL10 gl, Rect dirtyRect) { + private void uploadFullTexture(GL10 gl) { IntSize bufferSize = mImage.getSize(); - Rect bufferRect = new Rect(0, 0, bufferSize.width, bufferSize.height); - - // Make sure the dirty region intersects with the buffer - dirtyRect.intersect(bufferRect); + uploadDirtyRect(gl, new Rect(0, 0, bufferSize.width, bufferSize.height)); + } + private void uploadDirtyRect(GL10 gl, Rect dirtyRect) { // If we have nothing to upload, just return for now if (dirtyRect.isEmpty()) return; @@ -183,11 +181,6 @@ public abstract class TileLayer extends Layer { if (imageBuffer == null) return; - // Mark the dirty region as valid. Note, we assume that the valid area - // can be enclosed by a rectangle (ideally we'd use a Region, but it'd - // be slower and it probably isn't necessary). - mValidTextureRect.union(dirtyRect); - boolean newlyCreated = false; if (mTextureIDs == null) { @@ -196,12 +189,15 @@ public abstract class TileLayer extends Layer { newlyCreated = true; } + IntSize bufferSize = mImage.getSize(); + Rect bufferRect = new Rect(0, 0, bufferSize.width, bufferSize.height); + int cairoFormat = mImage.getFormat(); CairoGLInfo glInfo = new CairoGLInfo(cairoFormat); bindAndSetGLParameters(gl); - if (newlyCreated || dirtyRect.equals(bufferRect)) { + if (newlyCreated || dirtyRect.contains(bufferRect)) { if (mSize.equals(bufferSize)) { gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, glInfo.internalFormat, mSize.width, mSize.height, 0, glInfo.format, glInfo.type, imageBuffer); @@ -215,6 +211,11 @@ public abstract class TileLayer extends Layer { } } + // Make sure that the dirty region intersects with the buffer rect, + // otherwise we'll end up with an invalid buffer pointer. + if (!Rect.intersects(dirtyRect, bufferRect)) + return; + /* * Upload the changed rect. We have to widen to the full width of the texture * because we can't count on the device having support for GL_EXT_unpack_subimage, @@ -231,8 +232,8 @@ public abstract class TileLayer extends Layer { } viewBuffer.position(position); - gl.glTexSubImage2D(gl.GL_TEXTURE_2D, 0, 0, dirtyRect.top, - bufferSize.width, dirtyRect.height(), + gl.glTexSubImage2D(gl.GL_TEXTURE_2D, 0, 0, dirtyRect.top, bufferSize.width, + Math.min(bufferSize.height - dirtyRect.top, dirtyRect.height()), glInfo.format, glInfo.type, viewBuffer); } diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index 00fe3ad2c786..29a458714a48 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -2023,7 +2023,6 @@ Tab.prototype = { // Is it on the top level? let contentDocument = aSubject; if (contentDocument == this.browser.contentDocument) { - sendMessageToJava({ gecko: { type: "Document:Shown" } }); ViewportHandler.updateMetadata(this); this.documentIdForCurrentViewport = ViewportHandler.getIdForDocument(contentDocument); } diff --git a/widget/android/AndroidJavaWrappers.cpp b/widget/android/AndroidJavaWrappers.cpp index 475eb7d6151e..6d348fd2bd69 100644 --- a/widget/android/AndroidJavaWrappers.cpp +++ b/widget/android/AndroidJavaWrappers.cpp @@ -107,7 +107,6 @@ jmethodID AndroidAddress::jGetThoroughfareMethod; jclass AndroidGeckoSoftwareLayerClient::jGeckoSoftwareLayerClientClass = 0; jmethodID AndroidGeckoSoftwareLayerClient::jLockBufferMethod = 0; jmethodID AndroidGeckoSoftwareLayerClient::jUnlockBufferMethod = 0; -jmethodID AndroidGeckoSoftwareLayerClient::jGetRenderOffsetMethod = 0; jmethodID AndroidGeckoSoftwareLayerClient::jBeginDrawingMethod = 0; jmethodID AndroidGeckoSoftwareLayerClient::jEndDrawingMethod = 0; jclass AndroidGeckoSurfaceView::jGeckoSurfaceViewClass = 0; @@ -324,9 +323,8 @@ AndroidGeckoSoftwareLayerClient::InitGeckoSoftwareLayerClientClass(JNIEnv *jEnv) jLockBufferMethod = getMethod("lockBuffer", "()Ljava/nio/ByteBuffer;"); jUnlockBufferMethod = getMethod("unlockBuffer", "()V"); - jGetRenderOffsetMethod = getMethod("getRenderOffset", "()Landroid/graphics/Point;"); - jBeginDrawingMethod = getMethod("beginDrawing", "(IILjava/lang/String;Z)Z"); - jEndDrawingMethod = getMethod("endDrawing", "(IIII)V"); + jBeginDrawingMethod = getMethod("beginDrawing", "(II)V"); + jEndDrawingMethod = getMethod("endDrawing", "(IIIILjava/lang/String;Z)V"); #endif } @@ -620,28 +618,21 @@ AndroidGeckoSoftwareLayerClient::UnlockBuffer() } void -AndroidGeckoSoftwareLayerClient::GetRenderOffset(nsIntPoint &aOffset) -{ - AndroidPoint offset(JNI(), JNI()->CallObjectMethod(wrapped_obj, jGetRenderOffsetMethod)); - aOffset.x = offset.X(); - aOffset.y = offset.Y(); -} - -bool -AndroidGeckoSoftwareLayerClient::BeginDrawing(int aWidth, int aHeight, const nsAString &aMetadata, bool aHasDirectTexture) +AndroidGeckoSoftwareLayerClient::BeginDrawing(int aWidth, int aHeight) { NS_ASSERTION(!isNull(), "BeginDrawing() called on null software layer client!"); AndroidBridge::AutoLocalJNIFrame(1); - jstring jMetadata = JNI()->NewString(nsPromiseFlatString(aMetadata).get(), aMetadata.Length()); - return JNI()->CallBooleanMethod(wrapped_obj, jBeginDrawingMethod, aWidth, aHeight, jMetadata, aHasDirectTexture); + return JNI()->CallVoidMethod(wrapped_obj, jBeginDrawingMethod, aWidth, aHeight); } void -AndroidGeckoSoftwareLayerClient::EndDrawing(const nsIntRect &aRect) +AndroidGeckoSoftwareLayerClient::EndDrawing(const nsIntRect &aRect, const nsAString &aMetadata, bool aHasDirectTexture) { NS_ASSERTION(!isNull(), "EndDrawing() called on null software layer client!"); AndroidBridge::AutoLocalJNIFrame(1); - return JNI()->CallVoidMethod(wrapped_obj, jEndDrawingMethod, aRect.x, aRect.y, aRect.width, aRect.height); + jstring jMetadata = JNI()->NewString(nsPromiseFlatString(aMetadata).get(), aMetadata.Length()); + return JNI()->CallVoidMethod(wrapped_obj, jEndDrawingMethod, aRect.x, aRect.y, aRect.width, + aRect.height, jMetadata, aHasDirectTexture); } jobject diff --git a/widget/android/AndroidJavaWrappers.h b/widget/android/AndroidJavaWrappers.h index 17832468cdc5..1bdd40a0bdd5 100644 --- a/widget/android/AndroidJavaWrappers.h +++ b/widget/android/AndroidJavaWrappers.h @@ -161,9 +161,8 @@ public: jobject LockBuffer(); unsigned char *LockBufferBits(); void UnlockBuffer(); - void GetRenderOffset(nsIntPoint &aOffset); - bool BeginDrawing(int aWidth, int aHeight, const nsAString &aMetadata, bool aHasDirectTexture); - void EndDrawing(const nsIntRect &aRect); + void BeginDrawing(int aWidth, int aHeight); + void EndDrawing(const nsIntRect &aRect, const nsAString &aMetadata, bool aHasDirectTexture); private: static jclass jGeckoSoftwareLayerClientClass; @@ -171,7 +170,6 @@ private: static jmethodID jUnlockBufferMethod; protected: - static jmethodID jGetRenderOffsetMethod; static jmethodID jBeginDrawingMethod; static jmethodID jEndDrawingMethod; }; diff --git a/widget/android/nsWindow.cpp b/widget/android/nsWindow.cpp index b949fd71d48c..b07846af6379 100644 --- a/widget/android/nsWindow.cpp +++ b/widget/android/nsWindow.cpp @@ -1195,22 +1195,13 @@ nsWindow::OnDraw(AndroidGeckoEvent *ae) return; } - nsAutoString metadata; - if (metadataProvider) { - metadataProvider->GetDrawMetadata(metadata); - } - AndroidGeckoSoftwareLayerClient &client = AndroidBridge::Bridge()->GetSoftwareLayerClient(); - if (!client.BeginDrawing(gAndroidBounds.width, gAndroidBounds.height, metadata, HasDirectTexture())) { - return; - } - - nsIntPoint renderOffset; - client.GetRenderOffset(renderOffset); + client.BeginDrawing(gAndroidBounds.width, gAndroidBounds.height); nsIntRect dirtyRect = ae->Rect().Intersect(nsIntRect(0, 0, gAndroidBounds.width, gAndroidBounds.height)); + nsAutoString metadata; unsigned char *bits = NULL; if (HasDirectTexture()) { if (sDirectTexture->Width() != gAndroidBounds.width || @@ -1229,32 +1220,37 @@ nsWindow::OnDraw(AndroidGeckoEvent *ae) int tileWidth = (gAndroidTileSize.width > 0) ? gAndroidTileSize.width : gAndroidBounds.width; int tileHeight = (gAndroidTileSize.height > 0) ? gAndroidTileSize.height : gAndroidBounds.height; + bool drawSuccess = true; int offset = 0; - // It is assumed that the buffer has been over-allocated so that not - // only is the tile-size constant, but that a render-offset of anything - // up to (but not including) the tile size could be accommodated. - for (int y = 0; y < gAndroidBounds.height + gAndroidTileSize.height; y += tileHeight) { - for (int x = 0; x < gAndroidBounds.width + gAndroidTileSize.width; x += tileWidth) { + for (int y = 0; y < gAndroidBounds.height; y += tileHeight) { + for (int x = 0; x < gAndroidBounds.width; x += tileWidth) { + int width = NS_MIN(tileWidth, gAndroidBounds.width - x); + int height = NS_MIN(tileHeight, gAndroidBounds.height - y); nsRefPtr targetSurface = new gfxImageSurface(bits + offset, - gfxIntSize(tileWidth, tileHeight), - tileWidth * 2, + gfxIntSize(width, height), + width * 2, gfxASurface::ImageFormatRGB16_565); - offset += tileWidth * tileHeight * 2; + offset += width * height * 2; if (targetSurface->CairoStatus()) { ALOG("### Failed to create a valid surface from the bitmap"); + drawSuccess = false; break; } else { - targetSurface->SetDeviceOffset(gfxPoint(renderOffset.x - x, - renderOffset.y - y)); + targetSurface->SetDeviceOffset(gfxPoint(-x, -y)); DrawTo(targetSurface, dirtyRect); } } } + + // Don't fill in the draw metadata on an unsuccessful draw + if (drawSuccess && metadataProvider) { + metadataProvider->GetDrawMetadata(metadata); + } } if (HasDirectTexture()) { @@ -1263,7 +1259,7 @@ nsWindow::OnDraw(AndroidGeckoEvent *ae) client.UnlockBuffer(); } - client.EndDrawing(dirtyRect); + client.EndDrawing(dirtyRect, metadata, HasDirectTexture()); return; #endif