mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07: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
9426018c8c
commit
d70b23ac39
@ -80,9 +80,15 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
|
|||||||
private ByteBuffer mBuffer;
|
private ByteBuffer mBuffer;
|
||||||
private Layer mTileLayer;
|
private Layer mTileLayer;
|
||||||
|
|
||||||
/* The viewport rect that Gecko is currently displaying. */
|
/* The viewport that Gecko is currently displaying. */
|
||||||
private ViewportMetrics mGeckoViewport;
|
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 CairoImage mCairoImage;
|
||||||
|
|
||||||
private static final IntSize TILE_SIZE = new IntSize(256, 256);
|
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);
|
mScreenSize = new IntSize(0, 0);
|
||||||
mBufferSize = new IntSize(0, 0);
|
mBufferSize = new IntSize(0, 0);
|
||||||
mFormat = CairoImage.FORMAT_RGB16_565;
|
mFormat = CairoImage.FORMAT_RGB16_565;
|
||||||
|
mRenderOffset = new Point(0, 0);
|
||||||
|
|
||||||
mCairoImage = new CairoImage() {
|
mCairoImage = new CairoImage() {
|
||||||
@Override
|
@Override
|
||||||
@ -117,8 +124,6 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
|
|||||||
@Override
|
@Override
|
||||||
public int getFormat() { return mFormat; }
|
public int getFormat() { return mFormat; }
|
||||||
};
|
};
|
||||||
|
|
||||||
mTileLayer = new MultiTileLayer(mCairoImage, TILE_SIZE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getWidth() {
|
public int getWidth() {
|
||||||
@ -151,6 +156,8 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
|
|||||||
|
|
||||||
GeckoAppShell.registerGeckoEventListener("Viewport:UpdateAndDraw", this);
|
GeckoAppShell.registerGeckoEventListener("Viewport:UpdateAndDraw", this);
|
||||||
GeckoAppShell.registerGeckoEventListener("Viewport:UpdateLater", 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
|
// This needs to happen before a call to sendResizeEventIfNecessary
|
||||||
// happens, but only needs to be called once. As that is only called by
|
// 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();
|
sendResizeEventIfNecessary();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setHasDirectTexture(boolean hasDirectTexture) {
|
private boolean setHasDirectTexture(boolean hasDirectTexture) {
|
||||||
if (hasDirectTexture == mHasDirectTexture)
|
if (mTileLayer != null && hasDirectTexture == mHasDirectTexture)
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
mHasDirectTexture = hasDirectTexture;
|
mHasDirectTexture = hasDirectTexture;
|
||||||
|
|
||||||
@ -173,6 +180,7 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
|
|||||||
if (mHasDirectTexture) {
|
if (mHasDirectTexture) {
|
||||||
mTileLayer = new WidgetTileLayer(mCairoImage);
|
mTileLayer = new WidgetTileLayer(mCairoImage);
|
||||||
tileSize = new IntSize(0, 0);
|
tileSize = new IntSize(0, 0);
|
||||||
|
mRenderOffset.set(0, 0);
|
||||||
} else {
|
} else {
|
||||||
mTileLayer = new MultiTileLayer(mCairoImage, TILE_SIZE);
|
mTileLayer = new MultiTileLayer(mCairoImage, TILE_SIZE);
|
||||||
tileSize = TILE_SIZE;
|
tileSize = TILE_SIZE;
|
||||||
@ -186,18 +194,62 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
|
|||||||
// Force a resize event to be sent because the results of this
|
// Force a resize event to be sent because the results of this
|
||||||
// are different depending on what tile system we're using
|
// are different depending on what tile system we're using
|
||||||
sendResizeEventIfNecessary(true);
|
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);
|
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) {
|
if (mBufferSize.width != width || mBufferSize.height != height) {
|
||||||
mBufferSize = new IntSize(width, 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).
|
// Reallocate the buffer if necessary
|
||||||
int size = mBufferSize.getArea() * 2;
|
int bpp = CairoUtils.bitsPerPixelForCairoFormat(mFormat) / 8;
|
||||||
|
int size = realBufferSize.getArea() * bpp;
|
||||||
if (mBuffer == null || mBuffer.capacity() != size) {
|
if (mBuffer == null || mBuffer.capacity() != size) {
|
||||||
// Free the old buffer
|
// Free the old buffer
|
||||||
if (mBuffer != null) {
|
if (mBuffer != null) {
|
||||||
@ -208,23 +260,24 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
|
|||||||
mBuffer = GeckoAppShell.allocateDirectBuffer(size);
|
mBuffer = GeckoAppShell.allocateDirectBuffer(size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateViewport(String viewportDescription, final boolean onlyUpdatePageSize) {
|
private void updateViewport(final boolean onlyUpdatePageSize) {
|
||||||
try {
|
|
||||||
JSONObject viewportObject = new JSONObject(viewportDescription);
|
|
||||||
|
|
||||||
// save and restore the viewport size stored in java; never let the
|
// save and restore the viewport size stored in java; never let the
|
||||||
// JS-side viewport dimensions override the java-side ones because
|
// JS-side viewport dimensions override the java-side ones because
|
||||||
// java is the One True Source of this information, and allowing JS
|
// java is the One True Source of this information, and allowing JS
|
||||||
// to override can lead to race conditions where this data gets clobbered.
|
// to override can lead to race conditions where this data gets clobbered.
|
||||||
FloatSize viewportSize = getLayerController().getViewportSize();
|
FloatSize viewportSize = getLayerController().getViewportSize();
|
||||||
mGeckoViewport = new ViewportMetrics(viewportObject);
|
mGeckoViewport = mNewGeckoViewport;
|
||||||
mGeckoViewport.setSize(viewportSize);
|
mGeckoViewport.setSize(viewportSize);
|
||||||
|
|
||||||
LayerController controller = getLayerController();
|
LayerController controller = getLayerController();
|
||||||
PointF displayportOrigin = mGeckoViewport.getDisplayportOrigin();
|
PointF displayportOrigin = mGeckoViewport.getDisplayportOrigin();
|
||||||
mTileLayer.setOrigin(PointUtils.round(displayportOrigin));
|
Point tileOrigin = PointUtils.round(displayportOrigin);
|
||||||
|
tileOrigin.offset(-mRenderOffset.x, -mRenderOffset.y);
|
||||||
|
mTileLayer.setOrigin(tileOrigin);
|
||||||
mTileLayer.setResolution(mGeckoViewport.getZoomFactor());
|
mTileLayer.setResolution(mGeckoViewport.getZoomFactor());
|
||||||
|
|
||||||
if (onlyUpdatePageSize) {
|
if (onlyUpdatePageSize) {
|
||||||
@ -234,31 +287,26 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
|
|||||||
mGeckoViewport.getZoomFactor()))
|
mGeckoViewport.getZoomFactor()))
|
||||||
controller.setPageSize(mGeckoViewport.getPageSize());
|
controller.setPageSize(mGeckoViewport.getPageSize());
|
||||||
} else {
|
} else {
|
||||||
Log.d(LOGTAG, "Received viewport update from gecko");
|
|
||||||
controller.setViewportMetrics(mGeckoViewport);
|
controller.setViewportMetrics(mGeckoViewport);
|
||||||
controller.abortPanZoomAnimation();
|
controller.abortPanZoomAnimation();
|
||||||
}
|
}
|
||||||
} catch (JSONException e) {
|
|
||||||
Log.e(LOGTAG, "Bad viewport description: " + viewportDescription);
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO: Would be cleaner if this took an android.graphics.Rect instead, but that would require
|
* TODO: Would be cleaner if this took an android.graphics.Rect instead, but that would require
|
||||||
* a little more JNI magic.
|
* 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()) {
|
synchronized (getLayerController()) {
|
||||||
try {
|
try {
|
||||||
updateViewport(metadata, !mUpdateViewportOnEndDraw);
|
updateViewport(!mUpdateViewportOnEndDraw);
|
||||||
mUpdateViewportOnEndDraw = false;
|
mUpdateViewportOnEndDraw = false;
|
||||||
|
|
||||||
|
if (mTileLayer instanceof MultiTileLayer) {
|
||||||
Rect rect = new Rect(x, y, x + width, y + height);
|
Rect rect = new Rect(x, y, x + width, y + height);
|
||||||
|
rect.offset(mRenderOffset.x, mRenderOffset.y);
|
||||||
setHasDirectTexture(hasDirectTexture);
|
|
||||||
|
|
||||||
if (!mHasDirectTexture)
|
|
||||||
((MultiTileLayer)mTileLayer).invalidate(rect);
|
((MultiTileLayer)mTileLayer).invalidate(rect);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
endTransaction(mTileLayer);
|
endTransaction(mTileLayer);
|
||||||
}
|
}
|
||||||
@ -277,29 +325,28 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
|
|||||||
ByteBuffer tileBuffer = mBuffer.slice();
|
ByteBuffer tileBuffer = mBuffer.slice();
|
||||||
int bpp = CairoUtils.bitsPerPixelForCairoFormat(mFormat) / 8;
|
int bpp = CairoUtils.bitsPerPixelForCairoFormat(mFormat) / 8;
|
||||||
|
|
||||||
for (int y = 0; y < 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) {
|
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
|
// 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));
|
CairoUtils.cairoFormatTobitmapConfig(mFormat));
|
||||||
tile.copyPixelsFromBuffer(tileBuffer.asIntBuffer());
|
tile.copyPixelsFromBuffer(tileBuffer.asIntBuffer());
|
||||||
|
|
||||||
// Copy the tile to the master Bitmap and recycle it
|
// 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();
|
tile.recycle();
|
||||||
|
|
||||||
// Progress the buffer to the next tile
|
// Progress the buffer to the next tile
|
||||||
tileBuffer.position(tileSize.getArea() * bpp);
|
tileBuffer.position(TILE_SIZE.getArea() * bpp);
|
||||||
tileBuffer = tileBuffer.slice();
|
tileBuffer = tileBuffer.slice();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bitmap getBitmap() {
|
public Bitmap getBitmap() {
|
||||||
|
if (mTileLayer == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
// Begin a tile transaction, otherwise the buffer can be destroyed while
|
// Begin a tile transaction, otherwise the buffer can be destroyed while
|
||||||
// we're reading from it.
|
// we're reading from it.
|
||||||
beginTransaction(mTileLayer);
|
beginTransaction(mTileLayer);
|
||||||
@ -313,10 +360,6 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
|
|||||||
b = Bitmap.createBitmap(mBufferSize.width, mBufferSize.height,
|
b = Bitmap.createBitmap(mBufferSize.width, mBufferSize.height,
|
||||||
CairoUtils.cairoFormatTobitmapConfig(mFormat));
|
CairoUtils.cairoFormatTobitmapConfig(mFormat));
|
||||||
copyPixelsFromMultiTileLayer(b);
|
copyPixelsFromMultiTileLayer(b);
|
||||||
} else if (mTileLayer instanceof SingleTileLayer) {
|
|
||||||
b = Bitmap.createBitmap(mBufferSize.width, mBufferSize.height,
|
|
||||||
CairoUtils.cairoFormatTobitmapConfig(mFormat));
|
|
||||||
b.copyPixelsFromBuffer(mBuffer.asIntBuffer());
|
|
||||||
} else {
|
} else {
|
||||||
Log.w(LOGTAG, "getBitmap() called on a layer (" + mTileLayer + ") we don't know how to get a bitmap from");
|
Log.w(LOGTAG, "getBitmap() called on a layer (" + mTileLayer + ") we don't know how to get a bitmap from");
|
||||||
}
|
}
|
||||||
@ -336,6 +379,10 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
|
|||||||
return mBuffer;
|
return mBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Point getRenderOffset() {
|
||||||
|
return mRenderOffset;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gecko calls this function to signal that it is done with the back buffer. After this call,
|
* 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.
|
* it is forbidden for Gecko to touch the buffer.
|
||||||
@ -370,7 +417,7 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
|
|||||||
|
|
||||||
// Round up depending on layer implementation to remove texture wastage
|
// Round up depending on layer implementation to remove texture wastage
|
||||||
if (mTileLayer instanceof MultiTileLayer) {
|
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,
|
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);
|
((mScreenSize.height + LayerController.MIN_BUFFER.height - 1) / TILE_SIZE.height + 1) * TILE_SIZE.height);
|
||||||
|
|
||||||
@ -381,7 +428,7 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
|
|||||||
if (mScreenSize.width > maxSize || mScreenSize.height > maxSize)
|
if (mScreenSize.width > maxSize || mScreenSize.height > maxSize)
|
||||||
throw new RuntimeException("Screen size of " + mScreenSize + " larger than maximum texture size of " + 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)),
|
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)));
|
Math.min(maxSize, IntSize.nextPowerOfTwo(mScreenSize.height + LayerController.MIN_BUFFER.height)));
|
||||||
}
|
}
|
||||||
@ -452,6 +499,16 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
|
|||||||
GeckoAppShell.sendEventToGecko(new GeckoEvent(GeckoEvent.DRAW, rect));
|
GeckoAppShell.sendEventToGecko(new GeckoEvent(GeckoEvent.DRAW, rect));
|
||||||
} else if ("Viewport:UpdateLater".equals(event)) {
|
} else if ("Viewport:UpdateLater".equals(event)) {
|
||||||
mUpdateViewportOnEndDraw = true;
|
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
|
* 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) {
|
public final boolean update(GL10 gl, RenderContext context) {
|
||||||
|
boolean startTransaction = true;
|
||||||
if (mTransactionLock.isHeldByCurrentThread()) {
|
if (mTransactionLock.isHeldByCurrentThread()) {
|
||||||
throw new RuntimeException("draw() called while transaction lock held by this " +
|
startTransaction = false;
|
||||||
"thread?!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mTransactionLock.tryLock()) {
|
// 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 {
|
try {
|
||||||
return performUpdates(gl, context);
|
return performUpdates(gl, context);
|
||||||
} finally {
|
} finally {
|
||||||
|
if (startTransaction) {
|
||||||
|
mInTransaction = false;
|
||||||
mTransactionLock.unlock();
|
mTransactionLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Subclasses override this function to draw the layer. */
|
/** Subclasses override this function to draw the layer. */
|
||||||
@ -109,7 +117,6 @@ public abstract class Layer {
|
|||||||
mTransactionLock.lock();
|
mTransactionLock.lock();
|
||||||
mView = aView;
|
mView = aView;
|
||||||
mInTransaction = true;
|
mInTransaction = true;
|
||||||
mNewResolution = mResolution;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void beginTransaction() {
|
public void beginTransaction() {
|
||||||
|
@ -101,12 +101,30 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
|
|||||||
|
|
||||||
CairoImage backgroundImage = new BufferedCairoImage(controller.getBackgroundPattern());
|
CairoImage backgroundImage = new BufferedCairoImage(controller.getBackgroundPattern());
|
||||||
mBackgroundLayer = new SingleTileLayer(true, backgroundImage);
|
mBackgroundLayer = new SingleTileLayer(true, backgroundImage);
|
||||||
|
mBackgroundLayer.beginTransaction(null);
|
||||||
|
try {
|
||||||
|
mBackgroundLayer.invalidate();
|
||||||
|
} finally {
|
||||||
|
mBackgroundLayer.endTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
CairoImage checkerboardImage = new BufferedCairoImage(controller.getCheckerboardPattern());
|
CairoImage checkerboardImage = new BufferedCairoImage(controller.getCheckerboardPattern());
|
||||||
mCheckerboardLayer = new SingleTileLayer(true, checkerboardImage);
|
mCheckerboardLayer = new SingleTileLayer(true, checkerboardImage);
|
||||||
|
mCheckerboardLayer.beginTransaction(null);
|
||||||
|
try {
|
||||||
|
mCheckerboardLayer.invalidate();
|
||||||
|
} finally {
|
||||||
|
mCheckerboardLayer.endTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
CairoImage shadowImage = new BufferedCairoImage(controller.getShadowPattern());
|
CairoImage shadowImage = new BufferedCairoImage(controller.getShadowPattern());
|
||||||
mShadowLayer = new NinePatchTileLayer(shadowImage);
|
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);
|
IntSize frameRateLayerSize = new IntSize(FRAME_RATE_METER_WIDTH, FRAME_RATE_METER_HEIGHT);
|
||||||
mFrameRateLayer = TextLayer.create(frameRateLayerSize, "-- ms/--");
|
mFrameRateLayer = TextLayer.create(frameRateLayerSize, "-- ms/--");
|
||||||
|
@ -37,15 +37,21 @@
|
|||||||
|
|
||||||
package org.mozilla.gecko.gfx;
|
package org.mozilla.gecko.gfx;
|
||||||
|
|
||||||
|
import org.mozilla.gecko.FloatUtils;
|
||||||
import org.mozilla.gecko.gfx.CairoImage;
|
import org.mozilla.gecko.gfx.CairoImage;
|
||||||
import org.mozilla.gecko.gfx.IntSize;
|
import org.mozilla.gecko.gfx.IntSize;
|
||||||
import org.mozilla.gecko.gfx.SingleTileLayer;
|
import org.mozilla.gecko.gfx.SingleTileLayer;
|
||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
|
import android.graphics.PointF;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.RectF;
|
import android.graphics.RectF;
|
||||||
|
import android.graphics.Region;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import java.lang.Long;
|
||||||
import java.nio.ByteBuffer;
|
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;
|
import javax.microedition.khronos.opengles.GL10;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -57,9 +63,12 @@ public class MultiTileLayer extends Layer {
|
|||||||
private static final String LOGTAG = "GeckoMultiTileLayer";
|
private static final String LOGTAG = "GeckoMultiTileLayer";
|
||||||
|
|
||||||
private final CairoImage mImage;
|
private final CairoImage mImage;
|
||||||
private IntSize mTileSize;
|
private final IntSize mTileSize;
|
||||||
private IntSize mBufferSize;
|
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) {
|
public MultiTileLayer(CairoImage image, IntSize tileSize) {
|
||||||
super();
|
super();
|
||||||
@ -67,27 +76,37 @@ public class MultiTileLayer extends Layer {
|
|||||||
mImage = image;
|
mImage = image;
|
||||||
mTileSize = tileSize;
|
mTileSize = tileSize;
|
||||||
mBufferSize = new IntSize(0, 0);
|
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) {
|
public void invalidate(Rect dirtyRect) {
|
||||||
if (!inTransaction())
|
if (!inTransaction()) {
|
||||||
throw new RuntimeException("invalidate() is only valid inside a transaction");
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void invalidate() {
|
mDirtyRegion.union(dirtyRect);
|
||||||
for (SubTile layer : mTiles)
|
mValidRegion.union(dirtyRect);
|
||||||
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
|
@Override
|
||||||
@ -95,61 +114,104 @@ public class MultiTileLayer extends Layer {
|
|||||||
return mImage.getSize();
|
return mImage.getSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes sure there are enough tiles to accommodate the buffer image.
|
||||||
|
*/
|
||||||
private void validateTiles() {
|
private void validateTiles() {
|
||||||
IntSize size = getSize();
|
IntSize size = getSize();
|
||||||
|
|
||||||
if (size.equals(mBufferSize))
|
if (size.equals(mBufferSize))
|
||||||
return;
|
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 SubTile(subImage, x, y));
|
|
||||||
offset += layerSize.getArea() * bpp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set tile origins and resolution
|
|
||||||
refreshTileMetrics(getOrigin(), getResolution(), false);
|
|
||||||
|
|
||||||
mBufferSize = size;
|
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
|
@Override
|
||||||
@ -158,96 +220,167 @@ public class MultiTileLayer extends Layer {
|
|||||||
|
|
||||||
validateTiles();
|
validateTiles();
|
||||||
|
|
||||||
// Iterate over the tiles and decide which ones we'll be drawing
|
// Bail out early if we have nothing to do.
|
||||||
int dirtyTiles = 0;
|
if (mDirtyRegion.isEmpty() || mTiles.isEmpty()) {
|
||||||
boolean screenUpdateDone = false;
|
return true;
|
||||||
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);
|
|
||||||
|
|
||||||
RectF layerBounds = layer.getBounds(context, new FloatSize(layer.getSize()));
|
// Check that we're capable of updating from this origin.
|
||||||
boolean isDirty = layer.isDirty();
|
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) {
|
// Transform the viewport into tile-space so we can see what part of the
|
||||||
if (!RectF.intersects(layerBounds, context.viewport)) {
|
// dirty region intersects with it.
|
||||||
if (firstDirtyTile == null)
|
// We update any tiles intersecting with the screen before tiles
|
||||||
firstDirtyTile = layer;
|
// intersecting with the viewport.
|
||||||
dirtyTiles ++;
|
// XXX Maybe we want to to split this update even further to update
|
||||||
invalid = true;
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
} else {
|
} else {
|
||||||
// This tile intersects with the screen and is dirty,
|
mTiles.remove(tile);
|
||||||
// update it immediately.
|
}
|
||||||
layer.setSkipTextureUpdate(false);
|
|
||||||
screenUpdateDone = true;
|
// Place tile at the end of the tile-list so it isn't re-used.
|
||||||
layer.performUpdates(gl, context);
|
mTiles.add(tile);
|
||||||
invalid = false;
|
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We use the SkipTextureUpdate flag as a marker of a tile's
|
// Remove the update region from the dirty region
|
||||||
// validity. This is required, as sometimes layers are drawn
|
mDirtyRegion.op(updateRegion, Region.Op.XOR);
|
||||||
// without updating first, and we mustn't draw tiles that have
|
|
||||||
// been marked as invalid that we haven't updated.
|
|
||||||
layer.setSkipTextureUpdate(invalid);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now if no tiles that intersect with the screen were updated, update
|
return mDirtyRegion.isEmpty();
|
||||||
// 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 (dirtyTiles == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void beginTransaction(LayerView aView) {
|
public void beginTransaction(LayerView aView) {
|
||||||
super.beginTransaction(aView);
|
super.beginTransaction(aView);
|
||||||
|
|
||||||
for (SubTile layer : mTiles)
|
for (SubTile layer : mTiles) {
|
||||||
layer.beginTransaction(aView);
|
layer.beginTransaction(aView);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void endTransaction() {
|
public void endTransaction() {
|
||||||
for (SubTile layer : mTiles)
|
for (SubTile layer : mTiles) {
|
||||||
layer.endTransaction();
|
layer.endTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
super.endTransaction();
|
super.endTransaction();
|
||||||
}
|
}
|
||||||
@ -255,11 +388,6 @@ public class MultiTileLayer extends Layer {
|
|||||||
@Override
|
@Override
|
||||||
public void draw(RenderContext context) {
|
public void draw(RenderContext context) {
|
||||||
for (SubTile layer : mTiles) {
|
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
|
// Avoid work, only draw tiles that intersect with the viewport
|
||||||
RectF layerBounds = layer.getBounds(context, new FloatSize(layer.getSize()));
|
RectF layerBounds = layer.getBounds(context, new FloatSize(layer.getSize()));
|
||||||
if (RectF.intersects(layerBounds, context.viewport))
|
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 x;
|
||||||
public int y;
|
public int y;
|
||||||
|
|
||||||
public SubTile(CairoImage mImage, int mX, int mY) {
|
public Long key;
|
||||||
super(mImage);
|
|
||||||
x = mX;
|
public SubTile(SubImage aImage) {
|
||||||
y = mY;
|
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));
|
y + (rect.height() * scale));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns the nearest integer rect of the given rect. */
|
||||||
public static Rect round(RectF rect) {
|
public static Rect round(RectF rect) {
|
||||||
return new Rect(Math.round(rect.left), Math.round(rect.top),
|
return new Rect(Math.round(rect.left), Math.round(rect.top),
|
||||||
Math.round(rect.right), Math.round(rect.bottom));
|
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) {
|
public static IntSize getSize(Rect rect) {
|
||||||
return new IntSize(rect.width(), rect.height());
|
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.LayerController;
|
||||||
import org.mozilla.gecko.gfx.TileLayer;
|
import org.mozilla.gecko.gfx.TileLayer;
|
||||||
import android.graphics.PointF;
|
import android.graphics.PointF;
|
||||||
|
import android.graphics.Rect;
|
||||||
import android.graphics.RectF;
|
import android.graphics.RectF;
|
||||||
import android.opengl.GLES11;
|
import android.opengl.GLES11;
|
||||||
import android.opengl.GLES11Ext;
|
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.
|
* TODO: Repeating textures really should be their own type of layer.
|
||||||
*/
|
*/
|
||||||
public class SingleTileLayer extends TileLayer {
|
public class SingleTileLayer extends TileLayer {
|
||||||
|
private static final String LOGTAG = "GeckoSingleTileLayer";
|
||||||
|
|
||||||
public SingleTileLayer(CairoImage image) { this(false, image); }
|
public SingleTileLayer(CairoImage image) { this(false, image); }
|
||||||
|
|
||||||
public SingleTileLayer(boolean repeat, CairoImage image) {
|
public SingleTileLayer(boolean repeat, CairoImage image) {
|
||||||
@ -71,6 +74,11 @@ public class SingleTileLayer extends TileLayer {
|
|||||||
if (!initialized())
|
if (!initialized())
|
||||||
return;
|
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());
|
GLES11.glBindTexture(GL10.GL_TEXTURE_2D, getTextureID());
|
||||||
|
|
||||||
RectF bounds;
|
RectF bounds;
|
||||||
@ -79,13 +87,27 @@ public class SingleTileLayer extends TileLayer {
|
|||||||
RectF viewport = context.viewport;
|
RectF viewport = context.viewport;
|
||||||
|
|
||||||
if (repeats()) {
|
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());
|
bounds = new RectF(0.0f, 0.0f, viewport.width(), viewport.height());
|
||||||
int width = Math.round(viewport.width());
|
int width = Math.round(viewport.width());
|
||||||
int height = Math.round(-viewport.height());
|
int height = Math.round(-viewport.height());
|
||||||
cropRect = new int[] { 0, size.height, width, height };
|
cropRect = new int[] { 0, size.height, width, height };
|
||||||
} else {
|
} else {
|
||||||
bounds = getBounds(context, new FloatSize(size));
|
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,
|
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 CairoImage mImage;
|
||||||
private final boolean mRepeat;
|
private final boolean mRepeat;
|
||||||
private IntSize mSize;
|
private IntSize mSize;
|
||||||
private boolean mSkipTextureUpdate;
|
private Rect mValidTextureRect;
|
||||||
private int[] mTextureIDs;
|
private int[] mTextureIDs;
|
||||||
|
|
||||||
public TileLayer(boolean repeat, CairoImage image) {
|
public TileLayer(boolean repeat, CairoImage image) {
|
||||||
mRepeat = repeat;
|
mRepeat = repeat;
|
||||||
mImage = image;
|
mImage = image;
|
||||||
mSize = new IntSize(0, 0);
|
mSize = new IntSize(0, 0);
|
||||||
mSkipTextureUpdate = false;
|
mValidTextureRect = new Rect();
|
||||||
|
|
||||||
IntSize bufferSize = mImage.getSize();
|
IntSize bufferSize = mImage.getSize();
|
||||||
mDirtyRect = new Rect();
|
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) {
|
* Tells the tile that its texture contents are invalid. This will also
|
||||||
mSkipTextureUpdate = skip;
|
* 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
|
@Override
|
||||||
protected boolean performUpdates(GL10 gl, RenderContext context) {
|
protected boolean performUpdates(GL10 gl, RenderContext context) {
|
||||||
super.performUpdates(gl, context);
|
super.performUpdates(gl, context);
|
||||||
|
|
||||||
if (mSkipTextureUpdate)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Reallocate the texture if the size has changed
|
// Reallocate the texture if the size has changed
|
||||||
validateTexture(gl);
|
validateTexture(gl);
|
||||||
|
|
||||||
@ -155,23 +160,20 @@ public abstract class TileLayer extends Layer {
|
|||||||
if (!mImage.getSize().isPositive())
|
if (!mImage.getSize().isPositive())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// If we haven't allocated a texture, assume the whole region is dirty
|
// Update the dirty region
|
||||||
if (mTextureIDs == null)
|
|
||||||
uploadFullTexture(gl);
|
|
||||||
else
|
|
||||||
uploadDirtyRect(gl, mDirtyRect);
|
uploadDirtyRect(gl, mDirtyRect);
|
||||||
|
|
||||||
mDirtyRect.setEmpty();
|
mDirtyRect.setEmpty();
|
||||||
|
|
||||||
return true;
|
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) {
|
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 we have nothing to upload, just return for now
|
||||||
if (dirtyRect.isEmpty())
|
if (dirtyRect.isEmpty())
|
||||||
return;
|
return;
|
||||||
@ -181,6 +183,11 @@ public abstract class TileLayer extends Layer {
|
|||||||
if (imageBuffer == null)
|
if (imageBuffer == null)
|
||||||
return;
|
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;
|
boolean newlyCreated = false;
|
||||||
|
|
||||||
if (mTextureIDs == null) {
|
if (mTextureIDs == null) {
|
||||||
@ -189,15 +196,12 @@ public abstract class TileLayer extends Layer {
|
|||||||
newlyCreated = true;
|
newlyCreated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
IntSize bufferSize = mImage.getSize();
|
|
||||||
Rect bufferRect = new Rect(0, 0, bufferSize.width, bufferSize.height);
|
|
||||||
|
|
||||||
int cairoFormat = mImage.getFormat();
|
int cairoFormat = mImage.getFormat();
|
||||||
CairoGLInfo glInfo = new CairoGLInfo(cairoFormat);
|
CairoGLInfo glInfo = new CairoGLInfo(cairoFormat);
|
||||||
|
|
||||||
bindAndSetGLParameters(gl);
|
bindAndSetGLParameters(gl);
|
||||||
|
|
||||||
if (newlyCreated || dirtyRect.contains(bufferRect)) {
|
if (newlyCreated || dirtyRect.equals(bufferRect)) {
|
||||||
if (mSize.equals(bufferSize)) {
|
if (mSize.equals(bufferSize)) {
|
||||||
gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, glInfo.internalFormat, mSize.width, mSize.height,
|
gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, glInfo.internalFormat, mSize.width, mSize.height,
|
||||||
0, glInfo.format, glInfo.type, imageBuffer);
|
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
|
* 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,
|
* 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);
|
viewBuffer.position(position);
|
||||||
gl.glTexSubImage2D(gl.GL_TEXTURE_2D, 0, 0, dirtyRect.top, bufferSize.width,
|
gl.glTexSubImage2D(gl.GL_TEXTURE_2D, 0, 0, dirtyRect.top,
|
||||||
Math.min(bufferSize.height - dirtyRect.top, dirtyRect.height()),
|
bufferSize.width, dirtyRect.height(),
|
||||||
glInfo.format, glInfo.type, viewBuffer);
|
glInfo.format, glInfo.type, viewBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2023,6 +2023,7 @@ Tab.prototype = {
|
|||||||
// Is it on the top level?
|
// Is it on the top level?
|
||||||
let contentDocument = aSubject;
|
let contentDocument = aSubject;
|
||||||
if (contentDocument == this.browser.contentDocument) {
|
if (contentDocument == this.browser.contentDocument) {
|
||||||
|
sendMessageToJava({ gecko: { type: "Document:Shown" } });
|
||||||
ViewportHandler.updateMetadata(this);
|
ViewportHandler.updateMetadata(this);
|
||||||
this.documentIdForCurrentViewport = ViewportHandler.getIdForDocument(contentDocument);
|
this.documentIdForCurrentViewport = ViewportHandler.getIdForDocument(contentDocument);
|
||||||
}
|
}
|
||||||
|
@ -107,6 +107,7 @@ jmethodID AndroidAddress::jGetThoroughfareMethod;
|
|||||||
jclass AndroidGeckoSoftwareLayerClient::jGeckoSoftwareLayerClientClass = 0;
|
jclass AndroidGeckoSoftwareLayerClient::jGeckoSoftwareLayerClientClass = 0;
|
||||||
jmethodID AndroidGeckoSoftwareLayerClient::jLockBufferMethod = 0;
|
jmethodID AndroidGeckoSoftwareLayerClient::jLockBufferMethod = 0;
|
||||||
jmethodID AndroidGeckoSoftwareLayerClient::jUnlockBufferMethod = 0;
|
jmethodID AndroidGeckoSoftwareLayerClient::jUnlockBufferMethod = 0;
|
||||||
|
jmethodID AndroidGeckoSoftwareLayerClient::jGetRenderOffsetMethod = 0;
|
||||||
jmethodID AndroidGeckoSoftwareLayerClient::jBeginDrawingMethod = 0;
|
jmethodID AndroidGeckoSoftwareLayerClient::jBeginDrawingMethod = 0;
|
||||||
jmethodID AndroidGeckoSoftwareLayerClient::jEndDrawingMethod = 0;
|
jmethodID AndroidGeckoSoftwareLayerClient::jEndDrawingMethod = 0;
|
||||||
jclass AndroidGeckoSurfaceView::jGeckoSurfaceViewClass = 0;
|
jclass AndroidGeckoSurfaceView::jGeckoSurfaceViewClass = 0;
|
||||||
@ -323,8 +324,9 @@ AndroidGeckoSoftwareLayerClient::InitGeckoSoftwareLayerClientClass(JNIEnv *jEnv)
|
|||||||
|
|
||||||
jLockBufferMethod = getMethod("lockBuffer", "()Ljava/nio/ByteBuffer;");
|
jLockBufferMethod = getMethod("lockBuffer", "()Ljava/nio/ByteBuffer;");
|
||||||
jUnlockBufferMethod = getMethod("unlockBuffer", "()V");
|
jUnlockBufferMethod = getMethod("unlockBuffer", "()V");
|
||||||
jBeginDrawingMethod = getMethod("beginDrawing", "(II)V");
|
jGetRenderOffsetMethod = getMethod("getRenderOffset", "()Landroid/graphics/Point;");
|
||||||
jEndDrawingMethod = getMethod("endDrawing", "(IIIILjava/lang/String;Z)V");
|
jBeginDrawingMethod = getMethod("beginDrawing", "(IILjava/lang/String;Z)Z");
|
||||||
|
jEndDrawingMethod = getMethod("endDrawing", "(IIII)V");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -618,21 +620,28 @@ AndroidGeckoSoftwareLayerClient::UnlockBuffer()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
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!");
|
NS_ASSERTION(!isNull(), "BeginDrawing() called on null software layer client!");
|
||||||
AndroidBridge::AutoLocalJNIFrame(1);
|
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
|
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!");
|
NS_ASSERTION(!isNull(), "EndDrawing() called on null software layer client!");
|
||||||
AndroidBridge::AutoLocalJNIFrame(1);
|
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);
|
||||||
return JNI()->CallVoidMethod(wrapped_obj, jEndDrawingMethod, aRect.x, aRect.y, aRect.width,
|
|
||||||
aRect.height, jMetadata, aHasDirectTexture);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jobject
|
jobject
|
||||||
|
@ -161,8 +161,9 @@ public:
|
|||||||
jobject LockBuffer();
|
jobject LockBuffer();
|
||||||
unsigned char *LockBufferBits();
|
unsigned char *LockBufferBits();
|
||||||
void UnlockBuffer();
|
void UnlockBuffer();
|
||||||
void BeginDrawing(int aWidth, int aHeight);
|
void GetRenderOffset(nsIntPoint &aOffset);
|
||||||
void EndDrawing(const nsIntRect &aRect, const nsAString &aMetadata, bool aHasDirectTexture);
|
bool BeginDrawing(int aWidth, int aHeight, const nsAString &aMetadata, bool aHasDirectTexture);
|
||||||
|
void EndDrawing(const nsIntRect &aRect);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static jclass jGeckoSoftwareLayerClientClass;
|
static jclass jGeckoSoftwareLayerClientClass;
|
||||||
@ -170,6 +171,7 @@ private:
|
|||||||
static jmethodID jUnlockBufferMethod;
|
static jmethodID jUnlockBufferMethod;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
static jmethodID jGetRenderOffsetMethod;
|
||||||
static jmethodID jBeginDrawingMethod;
|
static jmethodID jBeginDrawingMethod;
|
||||||
static jmethodID jEndDrawingMethod;
|
static jmethodID jEndDrawingMethod;
|
||||||
};
|
};
|
||||||
|
@ -1195,13 +1195,22 @@ nsWindow::OnDraw(AndroidGeckoEvent *ae)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nsAutoString metadata;
|
||||||
|
if (metadataProvider) {
|
||||||
|
metadataProvider->GetDrawMetadata(metadata);
|
||||||
|
}
|
||||||
|
|
||||||
AndroidGeckoSoftwareLayerClient &client =
|
AndroidGeckoSoftwareLayerClient &client =
|
||||||
AndroidBridge::Bridge()->GetSoftwareLayerClient();
|
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));
|
nsIntRect dirtyRect = ae->Rect().Intersect(nsIntRect(0, 0, gAndroidBounds.width, gAndroidBounds.height));
|
||||||
|
|
||||||
nsAutoString metadata;
|
|
||||||
unsigned char *bits = NULL;
|
unsigned char *bits = NULL;
|
||||||
if (HasDirectTexture()) {
|
if (HasDirectTexture()) {
|
||||||
if (sDirectTexture->Width() != gAndroidBounds.width ||
|
if (sDirectTexture->Width() != gAndroidBounds.width ||
|
||||||
@ -1220,37 +1229,32 @@ nsWindow::OnDraw(AndroidGeckoEvent *ae)
|
|||||||
int tileWidth = (gAndroidTileSize.width > 0) ? gAndroidTileSize.width : gAndroidBounds.width;
|
int tileWidth = (gAndroidTileSize.width > 0) ? gAndroidTileSize.width : gAndroidBounds.width;
|
||||||
int tileHeight = (gAndroidTileSize.height > 0) ? gAndroidTileSize.height : gAndroidBounds.height;
|
int tileHeight = (gAndroidTileSize.height > 0) ? gAndroidTileSize.height : gAndroidBounds.height;
|
||||||
|
|
||||||
bool drawSuccess = true;
|
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
|
|
||||||
for (int y = 0; y < gAndroidBounds.height; y += tileHeight) {
|
// It is assumed that the buffer has been over-allocated so that not
|
||||||
for (int x = 0; x < gAndroidBounds.width; x += tileWidth) {
|
// only is the tile-size constant, but that a render-offset of anything
|
||||||
int width = NS_MIN(tileWidth, gAndroidBounds.width - x);
|
// up to (but not including) the tile size could be accommodated.
|
||||||
int height = NS_MIN(tileHeight, gAndroidBounds.height - y);
|
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 =
|
nsRefPtr<gfxImageSurface> targetSurface =
|
||||||
new gfxImageSurface(bits + offset,
|
new gfxImageSurface(bits + offset,
|
||||||
gfxIntSize(width, height),
|
gfxIntSize(tileWidth, tileHeight),
|
||||||
width * 2,
|
tileWidth * 2,
|
||||||
gfxASurface::ImageFormatRGB16_565);
|
gfxASurface::ImageFormatRGB16_565);
|
||||||
|
|
||||||
offset += width * height * 2;
|
offset += tileWidth * tileHeight * 2;
|
||||||
|
|
||||||
if (targetSurface->CairoStatus()) {
|
if (targetSurface->CairoStatus()) {
|
||||||
ALOG("### Failed to create a valid surface from the bitmap");
|
ALOG("### Failed to create a valid surface from the bitmap");
|
||||||
drawSuccess = false;
|
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
targetSurface->SetDeviceOffset(gfxPoint(-x, -y));
|
targetSurface->SetDeviceOffset(gfxPoint(renderOffset.x - x,
|
||||||
|
renderOffset.y - y));
|
||||||
DrawTo(targetSurface, dirtyRect);
|
DrawTo(targetSurface, dirtyRect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't fill in the draw metadata on an unsuccessful draw
|
|
||||||
if (drawSuccess && metadataProvider) {
|
|
||||||
metadataProvider->GetDrawMetadata(metadata);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (HasDirectTexture()) {
|
if (HasDirectTexture()) {
|
||||||
@ -1259,7 +1263,7 @@ nsWindow::OnDraw(AndroidGeckoEvent *ae)
|
|||||||
client.UnlockBuffer();
|
client.UnlockBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
client.EndDrawing(dirtyRect, metadata, HasDirectTexture());
|
client.EndDrawing(dirtyRect);
|
||||||
return;
|
return;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user