mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-27 04:38:02 +00:00
Bug 717283 - Use tiles on-demand. r=pcwalton, snorp
Instead of tying the tile-buffer in MultiTileLayer directly to the back-buffer of the page, make sure rendering is always aligned to the tile grid and use tiles on-demand. This makes better use of tiles when panning/zooming, and opens up the route for further optimisations.
This commit is contained in:
parent
8308adeb13
commit
4c0bbbf9ab
@ -80,9 +80,15 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
|
||||
private ByteBuffer mBuffer;
|
||||
private Layer mTileLayer;
|
||||
|
||||
/* The viewport rect that Gecko is currently displaying. */
|
||||
/* The viewport 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);
|
||||
@ -108,6 +114,7 @@ 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
|
||||
@ -117,8 +124,6 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
|
||||
@Override
|
||||
public int getFormat() { return mFormat; }
|
||||
};
|
||||
|
||||
mTileLayer = new MultiTileLayer(mCairoImage, TILE_SIZE);
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
@ -151,6 +156,8 @@ 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
|
||||
@ -163,9 +170,9 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
|
||||
sendResizeEventIfNecessary();
|
||||
}
|
||||
|
||||
private void setHasDirectTexture(boolean hasDirectTexture) {
|
||||
if (hasDirectTexture == mHasDirectTexture)
|
||||
return;
|
||||
private boolean setHasDirectTexture(boolean hasDirectTexture) {
|
||||
if (mTileLayer != null && hasDirectTexture == mHasDirectTexture)
|
||||
return false;
|
||||
|
||||
mHasDirectTexture = hasDirectTexture;
|
||||
|
||||
@ -173,6 +180,7 @@ 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;
|
||||
@ -186,18 +194,57 @@ 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 void beginDrawing(int width, int height) {
|
||||
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;
|
||||
|
||||
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.
|
||||
Point oldOrigin = PointUtils.round(mGeckoViewport.getDisplayportOrigin());
|
||||
Point origin = PointUtils.round(mNewGeckoViewport.getDisplayportOrigin());
|
||||
|
||||
if (!origin.equals(oldOrigin)) {
|
||||
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);
|
||||
|
||||
// Reallocate the buffer if necessary
|
||||
// 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);
|
||||
|
||||
// * 2 because it's a 16-bit buffer (so 2 bytes per pixel).
|
||||
int size = mBufferSize.getArea() * 2;
|
||||
// Reallocate the buffer if necessary
|
||||
int bpp = CairoUtils.bitsPerPixelForCairoFormat(mFormat) / 8;
|
||||
int size = realBufferSize.getArea() * bpp;
|
||||
if (mBuffer == null || mBuffer.capacity() != size) {
|
||||
// Free the old buffer
|
||||
if (mBuffer != null) {
|
||||
@ -208,39 +255,35 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
|
||||
mBuffer = GeckoAppShell.allocateDirectBuffer(size);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateViewport(String viewportDescription, final boolean onlyUpdatePageSize) {
|
||||
try {
|
||||
JSONObject viewportObject = new JSONObject(viewportDescription);
|
||||
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);
|
||||
|
||||
// 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);
|
||||
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());
|
||||
|
||||
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);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -248,17 +291,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, String metadata, boolean hasDirectTexture) {
|
||||
public void endDrawing(int x, int y, int width, int height) {
|
||||
synchronized (getLayerController()) {
|
||||
try {
|
||||
updateViewport(metadata, !mUpdateViewportOnEndDraw);
|
||||
updateViewport(!mUpdateViewportOnEndDraw);
|
||||
mUpdateViewportOnEndDraw = false;
|
||||
Rect rect = new Rect(x, y, x + width, y + height);
|
||||
|
||||
setHasDirectTexture(hasDirectTexture);
|
||||
|
||||
if (!mHasDirectTexture)
|
||||
if (mTileLayer instanceof MultiTileLayer) {
|
||||
Rect rect = new Rect(x, y, x + width, y + height);
|
||||
rect.offset(mRenderOffset.x, mRenderOffset.y);
|
||||
((MultiTileLayer)mTileLayer).invalidate(rect);
|
||||
}
|
||||
} finally {
|
||||
endTransaction(mTileLayer);
|
||||
}
|
||||
@ -277,29 +320,28 @@ 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) {
|
||||
// Calculate tile size
|
||||
IntSize tileSize = new IntSize(Math.min(mBufferSize.width - x, TILE_SIZE.width),
|
||||
Math.min(mBufferSize.height - y, TILE_SIZE.height));
|
||||
|
||||
for (int y = 0; y <= mBufferSize.height; y += TILE_SIZE.height) {
|
||||
for (int x = 0; x <= mBufferSize.width; x += TILE_SIZE.width) {
|
||||
// Create a Bitmap from this tile
|
||||
Bitmap tile = Bitmap.createBitmap(tileSize.width, tileSize.height,
|
||||
Bitmap tile = Bitmap.createBitmap(TILE_SIZE.width, TILE_SIZE.height,
|
||||
CairoUtils.cairoFormatTobitmapConfig(mFormat));
|
||||
tile.copyPixelsFromBuffer(tileBuffer.asIntBuffer());
|
||||
|
||||
// Copy the tile to the master Bitmap and recycle it
|
||||
c.drawBitmap(tile, x, y, null);
|
||||
c.drawBitmap(tile, x - mRenderOffset.x, y - mRenderOffset.y, null);
|
||||
tile.recycle();
|
||||
|
||||
// Progress the buffer to the next tile
|
||||
tileBuffer.position(tileSize.getArea() * bpp);
|
||||
tileBuffer.position(TILE_SIZE.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);
|
||||
@ -311,12 +353,8 @@ 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");
|
||||
}
|
||||
@ -336,6 +374,10 @@ 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.
|
||||
@ -370,7 +412,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, respecting maximum texture size
|
||||
// Round to the next multiple of the tile 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);
|
||||
|
||||
@ -381,7 +423,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
|
||||
// Round to next power of two until we have NPOT texture support, respecting maximum texture size
|
||||
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)));
|
||||
}
|
||||
@ -452,6 +494,16 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -63,23 +63,31 @@ public abstract class Layer {
|
||||
|
||||
/**
|
||||
* Updates the layer. This returns false if there is still work to be done
|
||||
* after this update.
|
||||
* 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.
|
||||
*/
|
||||
public final boolean update(GL10 gl, RenderContext context) {
|
||||
boolean startTransaction = true;
|
||||
if (mTransactionLock.isHeldByCurrentThread()) {
|
||||
throw new RuntimeException("draw() called while transaction lock held by this " +
|
||||
"thread?!");
|
||||
startTransaction = false;
|
||||
}
|
||||
|
||||
if (mTransactionLock.tryLock()) {
|
||||
try {
|
||||
return performUpdates(gl, context);
|
||||
} finally {
|
||||
// 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;
|
||||
mTransactionLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Subclasses override this function to draw the layer. */
|
||||
@ -109,7 +117,6 @@ public abstract class Layer {
|
||||
mTransactionLock.lock();
|
||||
mView = aView;
|
||||
mInTransaction = true;
|
||||
mNewResolution = mResolution;
|
||||
}
|
||||
|
||||
public void beginTransaction() {
|
||||
|
@ -101,12 +101,30 @@ 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/--");
|
||||
|
@ -37,15 +37,21 @@
|
||||
|
||||
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.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.ListIterator;
|
||||
import javax.microedition.khronos.opengles.GL10;
|
||||
|
||||
/**
|
||||
@ -57,9 +63,12 @@ public class MultiTileLayer extends Layer {
|
||||
private static final String LOGTAG = "GeckoMultiTileLayer";
|
||||
|
||||
private final CairoImage mImage;
|
||||
private IntSize mTileSize;
|
||||
private final IntSize mTileSize;
|
||||
private IntSize mBufferSize;
|
||||
private final ArrayList<SubTile> mTiles;
|
||||
private Region mDirtyRegion;
|
||||
private Region mValidRegion;
|
||||
private final LinkedList<SubTile> mTiles;
|
||||
private final HashMap<Long, SubTile> mPositionHash;
|
||||
|
||||
public MultiTileLayer(CairoImage image, IntSize tileSize) {
|
||||
super();
|
||||
@ -67,27 +76,37 @@ public class MultiTileLayer extends Layer {
|
||||
mImage = image;
|
||||
mTileSize = tileSize;
|
||||
mBufferSize = new IntSize(0, 0);
|
||||
mTiles = new ArrayList<SubTile>();
|
||||
mDirtyRegion = new Region();
|
||||
mValidRegion = new Region();
|
||||
mTiles = new LinkedList<SubTile>();
|
||||
mPositionHash = new HashMap<Long, SubTile>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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");
|
||||
|
||||
for (SubTile layer : mTiles) {
|
||||
IntSize tileSize = layer.getSize();
|
||||
Rect tileRect = new Rect(layer.x, layer.y, layer.x + tileSize.width, layer.y + tileSize.height);
|
||||
|
||||
if (tileRect.intersect(dirtyRect)) {
|
||||
tileRect.offset(-layer.x, -layer.y);
|
||||
layer.invalidate(tileRect);
|
||||
}
|
||||
}
|
||||
|
||||
mDirtyRegion.union(dirtyRect);
|
||||
mValidRegion.union(dirtyRect);
|
||||
}
|
||||
|
||||
public void invalidate() {
|
||||
for (SubTile layer : mTiles)
|
||||
layer.invalidate();
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -95,61 +114,104 @@ 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;
|
||||
mBufferSize = size;
|
||||
|
||||
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);
|
||||
// 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);
|
||||
|
||||
return tileBuffer;
|
||||
}
|
||||
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);
|
||||
|
||||
@Override
|
||||
public IntSize getSize() {
|
||||
return layerSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFormat() {
|
||||
return format;
|
||||
}
|
||||
};
|
||||
|
||||
mTiles.add(new SubTile(subImage, x, y));
|
||||
offset += layerSize.getArea() * bpp;
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
// Set tile origins and resolution
|
||||
refreshTileMetrics(getOrigin(), getResolution(), false);
|
||||
// A buffer size probably means a layout change, so invalidate all tiles.
|
||||
invalidateTiles();
|
||||
}
|
||||
|
||||
mBufferSize = size;
|
||||
/**
|
||||
* 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
|
||||
@ -158,96 +220,167 @@ public class MultiTileLayer extends Layer {
|
||||
|
||||
validateTiles();
|
||||
|
||||
// Iterate over the tiles and decide which ones we'll be drawing
|
||||
int dirtyTiles = 0;
|
||||
boolean screenUpdateDone = false;
|
||||
SubTile firstDirtyTile = null;
|
||||
for (SubTile 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);
|
||||
// Bail out early if we have nothing to do.
|
||||
if (mDirtyRegion.isEmpty() || mTiles.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
RectF layerBounds = layer.getBounds(context, new FloatSize(layer.getSize()));
|
||||
boolean isDirty = layer.isDirty();
|
||||
// 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;
|
||||
}
|
||||
|
||||
if (isDirty) {
|
||||
if (!RectF.intersects(layerBounds, context.viewport)) {
|
||||
if (firstDirtyTile == null)
|
||||
firstDirtyTile = layer;
|
||||
dirtyTiles ++;
|
||||
invalid = true;
|
||||
} else {
|
||||
// This tile intersects with the screen and is dirty,
|
||||
// update it immediately.
|
||||
layer.setSkipTextureUpdate(false);
|
||||
screenUpdateDone = true;
|
||||
layer.performUpdates(gl, context);
|
||||
invalid = false;
|
||||
}
|
||||
// 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<SubTile> invalidTiles = new LinkedList<SubTile>();
|
||||
Rect bounds = mValidRegion.getBounds();
|
||||
for (ListIterator<SubTile> i = mTiles.listIterator(); i.hasNext();) {
|
||||
SubTile tile = i.next();
|
||||
|
||||
if (tile.key == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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 --;
|
||||
// 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;
|
||||
} else {
|
||||
mTiles.removeLastOccurrence(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (dirtyTiles == 0);
|
||||
}
|
||||
// Remove the update region from the dirty region
|
||||
mDirtyRegion.op(updateRegion, Region.Op.XOR);
|
||||
|
||||
private void refreshTileMetrics(Point origin, float resolution, boolean inTransaction) {
|
||||
IntSize size = getSize();
|
||||
for (SubTile layer : mTiles) {
|
||||
if (!inTransaction)
|
||||
layer.beginTransaction(null);
|
||||
|
||||
if (origin != null)
|
||||
layer.setOrigin(new Point(origin.x + layer.x, origin.y + layer.y));
|
||||
if (resolution >= 0.0f)
|
||||
layer.setResolution(resolution);
|
||||
|
||||
if (!inTransaction)
|
||||
layer.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
return mDirtyRegion.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginTransaction(LayerView aView) {
|
||||
super.beginTransaction(aView);
|
||||
|
||||
for (SubTile layer : mTiles)
|
||||
for (SubTile layer : mTiles) {
|
||||
layer.beginTransaction(aView);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endTransaction() {
|
||||
for (SubTile layer : mTiles)
|
||||
for (SubTile layer : mTiles) {
|
||||
layer.endTransaction();
|
||||
}
|
||||
|
||||
super.endTransaction();
|
||||
}
|
||||
@ -255,11 +388,6 @@ public class MultiTileLayer extends Layer {
|
||||
@Override
|
||||
public void draw(RenderContext context) {
|
||||
for (SubTile 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))
|
||||
@ -267,14 +395,90 @@ public class MultiTileLayer extends Layer {
|
||||
}
|
||||
}
|
||||
|
||||
class SubTile extends SingleTileLayer {
|
||||
/**
|
||||
* 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 SubTile(CairoImage mImage, int mX, int mY) {
|
||||
super(mImage);
|
||||
x = mX;
|
||||
y = mY;
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -99,11 +99,18 @@ 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());
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ 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;
|
||||
@ -58,6 +59,8 @@ 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) {
|
||||
@ -71,6 +74,11 @@ 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;
|
||||
@ -79,13 +87,27 @@ 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));
|
||||
cropRect = new int[] { 0, size.height, size.width, -size.height };
|
||||
|
||||
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() };
|
||||
}
|
||||
|
||||
GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, cropRect,
|
||||
|
@ -59,14 +59,14 @@ public abstract class TileLayer extends Layer {
|
||||
private final CairoImage mImage;
|
||||
private final boolean mRepeat;
|
||||
private IntSize mSize;
|
||||
private boolean mSkipTextureUpdate;
|
||||
private Rect mValidTextureRect;
|
||||
private int[] mTextureIDs;
|
||||
|
||||
public TileLayer(boolean repeat, CairoImage image) {
|
||||
mRepeat = repeat;
|
||||
mImage = image;
|
||||
mSize = new IntSize(0, 0);
|
||||
mSkipTextureUpdate = false;
|
||||
mValidTextureRect = new Rect();
|
||||
|
||||
IntSize bufferSize = mImage.getSize();
|
||||
mDirtyRect = new Rect();
|
||||
@ -132,22 +132,27 @@ public abstract class TileLayer extends Layer {
|
||||
}
|
||||
}
|
||||
|
||||
/** Tells the tile not to update the texture on the next update. */
|
||||
public void setSkipTextureUpdate(boolean skip) {
|
||||
mSkipTextureUpdate = skip;
|
||||
/**
|
||||
* Tells the tile that its texture contents are invalid. This will also
|
||||
* clear any invalidated area.
|
||||
*/
|
||||
public void invalidateTexture() {
|
||||
mValidTextureRect.setEmpty();
|
||||
mDirtyRect.setEmpty();
|
||||
}
|
||||
|
||||
public boolean getSkipTextureUpdate() {
|
||||
return mSkipTextureUpdate;
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
@ -155,23 +160,20 @@ public abstract class TileLayer extends Layer {
|
||||
if (!mImage.getSize().isPositive())
|
||||
return true;
|
||||
|
||||
// If we haven't allocated a texture, assume the whole region is dirty
|
||||
if (mTextureIDs == null)
|
||||
uploadFullTexture(gl);
|
||||
else
|
||||
uploadDirtyRect(gl, mDirtyRect);
|
||||
|
||||
// Update the dirty region
|
||||
uploadDirtyRect(gl, mDirtyRect);
|
||||
mDirtyRect.setEmpty();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void uploadFullTexture(GL10 gl) {
|
||||
IntSize bufferSize = mImage.getSize();
|
||||
uploadDirtyRect(gl, new Rect(0, 0, bufferSize.width, bufferSize.height));
|
||||
}
|
||||
|
||||
private void uploadDirtyRect(GL10 gl, Rect dirtyRect) {
|
||||
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);
|
||||
|
||||
// If we have nothing to upload, just return for now
|
||||
if (dirtyRect.isEmpty())
|
||||
return;
|
||||
@ -181,6 +183,11 @@ 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) {
|
||||
@ -189,15 +196,12 @@ 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.contains(bufferRect)) {
|
||||
if (newlyCreated || dirtyRect.equals(bufferRect)) {
|
||||
if (mSize.equals(bufferSize)) {
|
||||
gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, glInfo.internalFormat, mSize.width, mSize.height,
|
||||
0, glInfo.format, glInfo.type, imageBuffer);
|
||||
@ -211,11 +215,6 @@ 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,
|
||||
@ -232,8 +231,8 @@ public abstract class TileLayer extends Layer {
|
||||
}
|
||||
|
||||
viewBuffer.position(position);
|
||||
gl.glTexSubImage2D(gl.GL_TEXTURE_2D, 0, 0, dirtyRect.top, bufferSize.width,
|
||||
Math.min(bufferSize.height - dirtyRect.top, dirtyRect.height()),
|
||||
gl.glTexSubImage2D(gl.GL_TEXTURE_2D, 0, 0, dirtyRect.top,
|
||||
bufferSize.width, dirtyRect.height(),
|
||||
glInfo.format, glInfo.type, viewBuffer);
|
||||
}
|
||||
|
||||
|
@ -2004,6 +2004,7 @@ 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);
|
||||
}
|
||||
|
@ -107,6 +107,7 @@ 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;
|
||||
@ -323,8 +324,9 @@ AndroidGeckoSoftwareLayerClient::InitGeckoSoftwareLayerClientClass(JNIEnv *jEnv)
|
||||
|
||||
jLockBufferMethod = getMethod("lockBuffer", "()Ljava/nio/ByteBuffer;");
|
||||
jUnlockBufferMethod = getMethod("unlockBuffer", "()V");
|
||||
jBeginDrawingMethod = getMethod("beginDrawing", "(II)V");
|
||||
jEndDrawingMethod = getMethod("endDrawing", "(IIIILjava/lang/String;Z)V");
|
||||
jGetRenderOffsetMethod = getMethod("getRenderOffset", "()Landroid/graphics/Point;");
|
||||
jBeginDrawingMethod = getMethod("beginDrawing", "(IILjava/lang/String;Z)Z");
|
||||
jEndDrawingMethod = getMethod("endDrawing", "(IIII)V");
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -618,21 +620,28 @@ AndroidGeckoSoftwareLayerClient::UnlockBuffer()
|
||||
}
|
||||
|
||||
void
|
||||
AndroidGeckoSoftwareLayerClient::BeginDrawing(int aWidth, int aHeight)
|
||||
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)
|
||||
{
|
||||
NS_ASSERTION(!isNull(), "BeginDrawing() called on null software layer client!");
|
||||
AndroidBridge::AutoLocalJNIFrame(1);
|
||||
return JNI()->CallVoidMethod(wrapped_obj, jBeginDrawingMethod, aWidth, aHeight);
|
||||
jstring jMetadata = JNI()->NewString(nsPromiseFlatString(aMetadata).get(), aMetadata.Length());
|
||||
return JNI()->CallBooleanMethod(wrapped_obj, jBeginDrawingMethod, aWidth, aHeight, jMetadata, aHasDirectTexture);
|
||||
}
|
||||
|
||||
void
|
||||
AndroidGeckoSoftwareLayerClient::EndDrawing(const nsIntRect &aRect, const nsAString &aMetadata, bool aHasDirectTexture)
|
||||
AndroidGeckoSoftwareLayerClient::EndDrawing(const nsIntRect &aRect)
|
||||
{
|
||||
NS_ASSERTION(!isNull(), "EndDrawing() called on null software layer client!");
|
||||
AndroidBridge::AutoLocalJNIFrame(1);
|
||||
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);
|
||||
return JNI()->CallVoidMethod(wrapped_obj, jEndDrawingMethod, aRect.x, aRect.y, aRect.width, aRect.height);
|
||||
}
|
||||
|
||||
jobject
|
||||
|
@ -161,8 +161,9 @@ public:
|
||||
jobject LockBuffer();
|
||||
unsigned char *LockBufferBits();
|
||||
void UnlockBuffer();
|
||||
void BeginDrawing(int aWidth, int aHeight);
|
||||
void EndDrawing(const nsIntRect &aRect, const nsAString &aMetadata, bool aHasDirectTexture);
|
||||
void GetRenderOffset(nsIntPoint &aOffset);
|
||||
bool BeginDrawing(int aWidth, int aHeight, const nsAString &aMetadata, bool aHasDirectTexture);
|
||||
void EndDrawing(const nsIntRect &aRect);
|
||||
|
||||
private:
|
||||
static jclass jGeckoSoftwareLayerClientClass;
|
||||
@ -170,6 +171,7 @@ private:
|
||||
static jmethodID jUnlockBufferMethod;
|
||||
|
||||
protected:
|
||||
static jmethodID jGetRenderOffsetMethod;
|
||||
static jmethodID jBeginDrawingMethod;
|
||||
static jmethodID jEndDrawingMethod;
|
||||
};
|
||||
|
@ -1195,13 +1195,22 @@ nsWindow::OnDraw(AndroidGeckoEvent *ae)
|
||||
return;
|
||||
}
|
||||
|
||||
nsAutoString metadata;
|
||||
if (metadataProvider) {
|
||||
metadataProvider->GetDrawMetadata(metadata);
|
||||
}
|
||||
|
||||
AndroidGeckoSoftwareLayerClient &client =
|
||||
AndroidBridge::Bridge()->GetSoftwareLayerClient();
|
||||
client.BeginDrawing(gAndroidBounds.width, gAndroidBounds.height);
|
||||
if (!client.BeginDrawing(gAndroidBounds.width, gAndroidBounds.height, metadata, HasDirectTexture())) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsIntPoint renderOffset;
|
||||
client.GetRenderOffset(renderOffset);
|
||||
|
||||
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 ||
|
||||
@ -1220,37 +1229,32 @@ 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;
|
||||
|
||||
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);
|
||||
// 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) {
|
||||
|
||||
nsRefPtr<gfxImageSurface> targetSurface =
|
||||
new gfxImageSurface(bits + offset,
|
||||
gfxIntSize(width, height),
|
||||
width * 2,
|
||||
gfxIntSize(tileWidth, tileHeight),
|
||||
tileWidth * 2,
|
||||
gfxASurface::ImageFormatRGB16_565);
|
||||
|
||||
offset += width * height * 2;
|
||||
offset += tileWidth * tileHeight * 2;
|
||||
|
||||
if (targetSurface->CairoStatus()) {
|
||||
ALOG("### Failed to create a valid surface from the bitmap");
|
||||
drawSuccess = false;
|
||||
break;
|
||||
} else {
|
||||
targetSurface->SetDeviceOffset(gfxPoint(-x, -y));
|
||||
targetSurface->SetDeviceOffset(gfxPoint(renderOffset.x - x,
|
||||
renderOffset.y - y));
|
||||
DrawTo(targetSurface, dirtyRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't fill in the draw metadata on an unsuccessful draw
|
||||
if (drawSuccess && metadataProvider) {
|
||||
metadataProvider->GetDrawMetadata(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
if (HasDirectTexture()) {
|
||||
@ -1259,7 +1263,7 @@ nsWindow::OnDraw(AndroidGeckoEvent *ae)
|
||||
client.UnlockBuffer();
|
||||
}
|
||||
|
||||
client.EndDrawing(dirtyRect, metadata, HasDirectTexture());
|
||||
client.EndDrawing(dirtyRect);
|
||||
return;
|
||||
#endif
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user