summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>2015-02-13 20:35:40 +0900
committerMiklos Vajna <vmiklos@collabora.co.uk>2015-02-16 09:20:51 +0100
commit0ccf2c77e55c11393baca166369e3ffe5d2863c2 (patch)
treebd0839fac3ba0f5b573254205e79701d61ef810e
parent0e98675b693c925c54a3820596dbad130ad73b35 (diff)
android: discard image buffer as soon as we upload to a texture
Before TileLayer (which SubTile was a subclass of) held the image buffer for the whole life cycle of the tile, which wastes memory in case of SubTile as the image doesn't change (or changes only when invalidated) for the whole tile lifecycle. This change changes the SubTile inheritance to a general Layer and adds modified logic from SingleTileLayer and TileLayer. In addition it now discards the buffer as soon as it has been uploaded to a texture so that it doesn't takes up double the memory anymore. Change-Id: Ia104687d2df529182af412102459952b53e87950
-rw-r--r--android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java7
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/SubTile.java236
2 files changed, 237 insertions, 6 deletions
diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
index c28806c9e1d1..44cb7e3f9e34 100644
--- a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
+++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
@@ -7,6 +7,7 @@ import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
+import org.mozilla.gecko.gfx.BufferedCairoImage;
import org.mozilla.gecko.gfx.CairoImage;
import org.mozilla.gecko.gfx.ComposedTileLayer;
import org.mozilla.gecko.gfx.GeckoLayerClient;
@@ -38,7 +39,8 @@ public class LOKitThread extends Thread implements TileProvider.TileInvalidation
CairoImage image = mTileProvider.createTile(tileId.x, tileId.y, tileId.size, tileId.zoom);
if (image != null) {
mLayerClient.beginDrawing();
- SubTile tile = new SubTile(image, tileId);
+ SubTile tile = new SubTile(tileId);
+ tile.setImage(image);
composedTileLayer.addTile(tile);
mLayerClient.endDrawing();
mLayerClient.forceRender();
@@ -57,7 +59,8 @@ public class LOKitThread extends Thread implements TileProvider.TileInvalidation
mLayerClient.invalidateTiles(tiles, rect);
for (SubTile tile : tiles) {
- mTileProvider.rerenderTile(tile.getImage(), tile.id.x, tile.id.y, tile.id.size, tile.id.zoom);
+ CairoImage image = mTileProvider.createTile(tile.id.x, tile.id.y, tile.id.size, tile.id.zoom);
+ tile.setImage(image);
}
mLayerClient.beginDrawing();
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/SubTile.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/SubTile.java
index b5af410735f4..342dc3c12086 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/SubTile.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/SubTile.java
@@ -6,16 +6,64 @@
package org.mozilla.gecko.gfx;
import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.graphics.RegionIterator;
+import android.opengl.GLES20;
+import android.util.Log;
import org.libreoffice.TileIdentifier;
-public class SubTile extends SingleTileLayer {
- public boolean markedForRemoval = false;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+
+public class SubTile extends Layer {
+ private static String LOGTAG = SubTile.class.getSimpleName();
public final TileIdentifier id;
- public SubTile(CairoImage mImage, TileIdentifier id) {
- super(mImage);
+ private final RectF mBounds;
+ private final RectF mTextureBounds;
+ private final RectF mViewport;
+ private final Rect mIntBounds;
+ private final Rect mSubRect;
+ private final RectF mSubRectF;
+ private final Region mMaskedBounds;
+ private final Rect mCropRect;
+ private final RectF mObjRectF;
+ private final float[] mCoords;
+
+ public boolean markedForRemoval = false;
+
+ private CairoImage mImage;
+ private IntSize mSize;
+ private int[] mTextureIDs;
+ private boolean mDirtyTile;
+
+ public SubTile(TileIdentifier id) {
+ super();
this.id = id;
+
+ mBounds = new RectF();
+ mTextureBounds = new RectF();
+ mViewport = new RectF();
+ mIntBounds = new Rect();
+ mSubRect = new Rect();
+ mSubRectF = new RectF();
+ mMaskedBounds = new Region();
+ mCropRect = new Rect();
+ mObjRectF = new RectF();
+ mCoords = new float[20];
+
+ mImage = null;
+ mTextureIDs = null;
+ mSize = new IntSize(0, 0);
+ mDirtyTile = false;
+ }
+
+ public void setImage(CairoImage image) {
+ if (image.getSize().isPositive()) {
+ this.mImage = image;
+ }
}
public void refreshTileMetrics() {
@@ -25,4 +73,184 @@ public class SubTile extends SingleTileLayer {
public void markForRemoval() {
markedForRemoval = true;
}
+
+ protected int getTextureID() {
+ return mTextureIDs[0];
+ }
+
+ protected boolean initialized() {
+ return mTextureIDs != null;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ destroyImage();
+ cleanTexture(false);
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private void cleanTexture(boolean immediately) {
+ if (mTextureIDs != null) {
+ TextureReaper.get().add(mTextureIDs);
+ mTextureIDs = null;
+ if (immediately) {
+ TextureReaper.get().reap();
+ }
+ }
+ }
+
+ public void destroy() {
+ try {
+ destroyImage();
+ cleanTexture(false);
+ } catch (Exception ex) {
+ Log.e(LOGTAG, "Error clearing buffers: ", ex);
+ }
+ }
+
+ public void destroyImage() {
+ if (mImage != null) {
+ mImage.destroy();
+ mImage = null;
+ }
+ }
+
+ /**
+ * Invalidates the entire buffer so that it will be uploaded again. Only valid inside a
+ * transaction.
+ */
+ public void invalidate() {
+ if (!inTransaction()) {
+ throw new RuntimeException("invalidate() is only valid inside a transaction");
+ }
+ if (mImage == null) {
+ return;
+ }
+ mDirtyTile = true;
+ }
+
+ /**
+ * Remove the texture if the image is of different size than the current uploaded texture.
+ */
+ private void validateTexture() {
+ IntSize textureSize = mImage.getSize().nextPowerOfTwo();
+
+ if (!textureSize.equals(mSize)) {
+ mSize = textureSize;
+ cleanTexture(true);
+ }
+ }
+
+ @Override
+ protected void performUpdates(RenderContext context) {
+ super.performUpdates(context);
+ if (mImage == null && !mDirtyTile) {
+ return;
+ }
+ validateTexture();
+ uploadNewTexture();
+ mDirtyTile = false;
+ }
+
+ private void uploadNewTexture() {
+ ByteBuffer imageBuffer = mImage.getBuffer();
+ if (imageBuffer == null) {
+ return;
+ }
+
+ if (mTextureIDs == null) {
+ mTextureIDs = new int[1];
+ GLES20.glGenTextures(mTextureIDs.length, mTextureIDs, 0);
+ }
+
+ int cairoFormat = mImage.getFormat();
+ CairoGLInfo glInfo = new CairoGLInfo(cairoFormat);
+
+ bindAndSetGLParameters();
+
+ IntSize bufferSize = mImage.getSize();
+
+ GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, glInfo.internalFormat,
+ mSize.width, mSize.height, 0, glInfo.format, glInfo.type, imageBuffer);
+
+ destroyImage();
+ }
+
+ private void bindAndSetGLParameters() {
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIDs[0]);
+
+ GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
+ GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
+ GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
+ GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
+ }
+
+ @Override
+ public void draw(RenderContext context) {
+ // mTextureIDs may be null here during startup if Layer.java's draw method
+ // failed to acquire the transaction lock and call performUpdates.
+ if (!initialized())
+ return;
+
+ mViewport.set(context.viewport);
+
+ mBounds.set(getBounds(context));
+ mTextureBounds.set(mBounds);
+
+ mBounds.roundOut(mIntBounds);
+ mMaskedBounds.set(mIntBounds);
+
+ // XXX Possible optimisation here, form this array so we can draw it in
+ // a single call.
+ RegionIterator iterator = new RegionIterator(mMaskedBounds);
+ while (iterator.next(mSubRect)) {
+ // Compensate for rounding errors at the edge of the tile caused by
+ // the roundOut above
+ mSubRectF.set(Math.max(mBounds.left, (float) mSubRect.left),
+ Math.max(mBounds.top, (float) mSubRect.top),
+ Math.min(mBounds.right, (float) mSubRect.right),
+ Math.min(mBounds.bottom, (float) mSubRect.bottom));
+
+ // This is the left/top/right/bottom of the rect, relative to the
+ // bottom-left of the layer, to use for texture coordinates.
+ mCropRect.set(Math.round(mSubRectF.left - mBounds.left),
+ Math.round(mBounds.bottom - mSubRectF.top),
+ Math.round(mSubRectF.right - mBounds.left),
+ Math.round(mBounds.bottom - mSubRectF.bottom));
+
+ mObjRectF.set(mSubRectF.left - mViewport.left,
+ mViewport.bottom - mSubRectF.bottom,
+ mSubRectF.right - mViewport.left,
+ mViewport.bottom - mSubRectF.top);
+
+ fillRectCoordBuffer(mCoords, mObjRectF, mViewport.width(), mViewport.height(), mCropRect, mTextureBounds.width(), mTextureBounds.height());
+
+ FloatBuffer coordBuffer = context.coordBuffer;
+ int positionHandle = context.positionHandle;
+ int textureHandle = context.textureHandle;
+
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTextureID());
+
+ // Make sure we are at position zero in the buffer
+ coordBuffer.position(0);
+ coordBuffer.put(mCoords);
+
+ // Unbind any the current array buffer so we can use client side buffers
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
+
+ // Vertex coordinates are x,y,z starting at position 0 into the buffer.
+ coordBuffer.position(0);
+ GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20, coordBuffer);
+
+ // Texture coordinates are texture_x, texture_y starting at position 3 into the buffer.
+ coordBuffer.position(3);
+ GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20, coordBuffer);
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+ }
+ }
}