Bug 708307 - Decouple texture size from tile size. r=pcwalton a=android-only

This removes the hard-coded limit of 1024x2048 tile sizes, and allows for
arbitrary tile-sizes. It will still only allocate texture sizes in powers of
two, however. It replaces the tile size with a buffered-area size, which can be
re-allocated as the screen dimensions change.
This commit is contained in:
Chris Lord 2011-12-14 19:41:37 +00:00
parent 689c7e4923
commit 39ec78e96c
13 changed files with 171 additions and 98 deletions

View File

@ -491,15 +491,6 @@ public class GeckoAppShell
/*
* The Gecko-side API: API methods that Gecko calls
*/
public static void scheduleRedraw() {
// Redraw everything
Rect rect = new Rect(0, 0, LayerController.TILE_WIDTH, LayerController.TILE_HEIGHT);
GeckoEvent event = new GeckoEvent(GeckoEvent.DRAW, rect);
event.mNativeWindow = 0;
sendEventToGecko(event);
}
public static void notifyIME(int type, int state) {
mInputConnection.notifyIME(type, state);
}

View File

@ -46,21 +46,22 @@ import java.nio.ByteBuffer;
/** A Cairo image that simply saves a buffer of pixel data. */
public class BufferedCairoImage extends CairoImage {
private ByteBuffer mBuffer;
private int mWidth, mHeight, mFormat;
private IntSize mSize;
private int mFormat;
private boolean mNeedToFreeBuffer = false;
/** Creates a buffered Cairo image from a byte buffer. */
public BufferedCairoImage(ByteBuffer inBuffer, int inWidth, int inHeight, int inFormat) {
mBuffer = inBuffer; mWidth = inWidth; mHeight = inHeight; mFormat = inFormat;
mBuffer = inBuffer; mSize = new IntSize(inWidth, inHeight); mFormat = inFormat;
}
/** Creates a buffered Cairo image from an Android bitmap. */
public BufferedCairoImage(Bitmap bitmap) {
mFormat = CairoUtils.bitmapConfigToCairoFormat(bitmap.getConfig());
mWidth = bitmap.getWidth();
mHeight = bitmap.getHeight();
mSize = new IntSize(bitmap.getWidth(), bitmap.getHeight());
mNeedToFreeBuffer = true;
mBuffer = GeckoAppShell.allocateDirectBuffer(mWidth * mHeight * 4);
// XXX Why is this * 4? Shouldn't it depend on mFormat?
mBuffer = GeckoAppShell.allocateDirectBuffer(mSize.getArea() * 4);
bitmap.copyPixelsToBuffer(mBuffer.asIntBuffer());
}
@ -78,9 +79,7 @@ public class BufferedCairoImage extends CairoImage {
@Override
public ByteBuffer getBuffer() { return mBuffer; }
@Override
public int getWidth() { return mWidth; }
@Override
public int getHeight() { return mHeight; }
public IntSize getSize() { return mSize; }
@Override
public int getFormat() { return mFormat; }
}

View File

@ -45,8 +45,7 @@ import java.nio.ByteBuffer;
public abstract class CairoImage {
public abstract ByteBuffer getBuffer();
public abstract int getWidth();
public abstract int getHeight();
public abstract IntSize getSize();
public abstract int getFormat();
public static final int FORMAT_INVALID = -1;

View File

@ -72,8 +72,9 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
private static final String LOGTAG = "GeckoSoftwareLayerClient";
private Context mContext;
private int mWidth, mHeight, mFormat;
private int mFormat;
private IntSize mScreenSize, mViewportSize;
private IntSize mBufferSize;
private ByteBuffer mBuffer;
private final SingleTileLayer mTileLayer;
@ -97,21 +98,15 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
public GeckoSoftwareLayerClient(Context context) {
mContext = context;
mWidth = LayerController.TILE_WIDTH;
mHeight = LayerController.TILE_HEIGHT;
mScreenSize = new IntSize(0, 0);
mBufferSize = new IntSize(0, 0);
mFormat = CairoImage.FORMAT_RGB16_565;
mScreenSize = new IntSize(1, 1);
mBuffer = GeckoAppShell.allocateDirectBuffer(mWidth * mHeight * 2);
mCairoImage = new CairoImage() {
@Override
public ByteBuffer getBuffer() { return mBuffer; }
@Override
public int getWidth() { return mWidth; }
@Override
public int getHeight() { return mHeight; }
public IntSize getSize() { return mBufferSize; }
@Override
public int getFormat() { return mFormat; }
};
@ -141,7 +136,6 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
layerController.notifyPanZoomControllerOfGeometryChange(false);
}
geometryChanged();
GeckoAppShell.registerGeckoEventListener("Viewport:Update", this);
GeckoAppShell.registerGeckoEventListener("Viewport:UpdateLater", this);
}
@ -209,7 +203,7 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
public Bitmap getBitmap() {
try {
Bitmap b = Bitmap.createBitmap(mWidth, mHeight,
Bitmap b = Bitmap.createBitmap(mBufferSize.width, mBufferSize.height,
CairoUtils.cairoFormatTobitmapConfig(mFormat));
b.copyPixelsFromBuffer(mBuffer.asIntBuffer());
return b;
@ -241,9 +235,28 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
if (metrics.widthPixels != mScreenSize.width ||
metrics.heightPixels != mScreenSize.height) {
mScreenSize = new IntSize(metrics.widthPixels, metrics.heightPixels);
int maxSize = getLayerController().getView().getMaxTextureSize();
// XXX Introduce tiling to solve this?
if (mScreenSize.width > maxSize || mScreenSize.height > maxSize)
throw new RuntimeException("Screen size of " + mScreenSize + " larger than maximum texture size of " + maxSize);
// Round to next power of two until we use NPOT texture support
mBufferSize = new IntSize(Math.min(maxSize, IntSize.nextPowerOfTwo(mScreenSize.width + LayerController.MIN_BUFFER.width)),
Math.min(maxSize, IntSize.nextPowerOfTwo(mScreenSize.height + LayerController.MIN_BUFFER.height)));
// Free the old buffer first, if it exists
if (mBuffer != null) {
GeckoAppShell.freeDirectBuffer(mBuffer);
mBuffer = null;
}
// * 2 because it's a 16-bit buffer (so 2 bytes per pixel).
mBuffer = GeckoAppShell.allocateDirectBuffer(mBufferSize.getArea() * 2);
Log.i(LOGTAG, "Screen-size changed to " + mScreenSize);
GeckoEvent event = new GeckoEvent(GeckoEvent.SIZE_CHANGED,
LayerController.TILE_WIDTH, LayerController.TILE_HEIGHT,
mBufferSize.width, mBufferSize.height,
metrics.widthPixels, metrics.heightPixels);
GeckoAppShell.sendEventToGecko(event);
}
@ -289,7 +302,7 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
ViewportMetrics viewportMetrics =
new ViewportMetrics(getLayerController().getViewportMetrics());
PointF viewportOffset = viewportMetrics.getOptimumViewportOffset();
PointF viewportOffset = viewportMetrics.getOptimumViewportOffset(mBufferSize);
viewportMetrics.setViewportOffset(viewportOffset);
viewportMetrics.setViewport(viewportMetrics.getClampedViewport());

View File

@ -62,10 +62,18 @@ public class IntSize {
}
}
public int getArea() {
return width * height;
}
public boolean equals(IntSize size) {
return ((size.width == width) && (size.height == height));
}
public boolean isPositive() {
return (width > 0 && height > 0);
}
@Override
public String toString() { return "(" + width + "," + height + ")"; }
@ -73,5 +81,23 @@ public class IntSize {
return new IntSize((int)Math.round(width * factor),
(int)Math.round(height * factor));
}
/* Returns the power of two that is greater than or equal to value */
public static int nextPowerOfTwo(int value) {
// code taken from http://acius2.blogspot.com/2007/11/calculating-next-power-of-2.html
if (0 == value--) {
return 1;
}
value = (value >> 1) | value;
value = (value >> 2) | value;
value = (value >> 4) | value;
value = (value >> 8) | value;
value = (value >> 16) | value;
return value + 1;
}
public IntSize nextPowerOfTwo() {
return new IntSize(nextPowerOfTwo(width), nextPowerOfTwo(height));
}
}

View File

@ -80,6 +80,9 @@ public abstract class Layer {
/** Subclasses override this function to draw the layer. */
public abstract void draw(RenderContext context);
/** Subclasses override this function to provide access to the size of the layer. */
public abstract IntSize getSize();
/** Given the intrinsic size of the layer, returns the pixel boundaries of the layer rect. */
protected RectF getBounds(RenderContext context, FloatSize size) {
float scaleFactor = context.zoomFactor / mResolution;
@ -168,20 +171,6 @@ public abstract class Layer {
}
}
/* Returns the power of two that is greater than or equal to value */
protected static int nextPowerOfTwo(int value) {
// code taken from http://acius2.blogspot.com/2007/11/calculating-next-power-of-2.html
if (0 == value--) {
return 1;
}
value = (value >> 1) | value;
value = (value >> 2) | value;
value = (value >> 4) | value;
value = (value >> 8) | value;
value = (value >> 16) | value;
return value + 1;
}
public static class RenderContext {
public final RectF viewport;
public final FloatSize pageSize;

View File

@ -85,9 +85,11 @@ public class LayerController {
private boolean mForceRedraw;
/* NB: These must be powers of two due to the OpenGL ES 1.x restriction on NPOT textures. */
public static final int TILE_WIDTH = 1024;
public static final int TILE_HEIGHT = 2048;
/* The extra area on the sides of the page that we want to buffer to help with
* smooth, asynchronous scrolling. Depending on a device's support for NPOT
* textures, this may be rounded up to the nearest power of two.
*/
public static final IntSize MIN_BUFFER = new IntSize(512, 1024);
/* If the visible rect is within the danger zone (measured in pixels from each edge of a tile),
* we start aggressively redrawing to minimize checkerboarding. */
@ -293,8 +295,12 @@ public class LayerController {
}
private RectF getTileRect() {
if (mRootLayer == null)
return new RectF();
float x = mRootLayer.getOrigin().x, y = mRootLayer.getOrigin().y;
return new RectF(x, y, x + TILE_WIDTH, y + TILE_HEIGHT);
IntSize layerSize = mRootLayer.getSize();
return new RectF(x, y, x + layerSize.width, y + layerSize.height);
}
public RectF restrictToPageSize(RectF aRect) {

View File

@ -90,6 +90,7 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
private final ScrollbarLayer mVertScrollLayer;
private final FadeRunnable mFadeRunnable;
private RenderContext mLastPageContext;
private int mMaxTextureSize;
// Dropped frames display
private int[] mFrameTimings;
@ -122,12 +123,17 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
checkFrameRateMonitorEnabled();
gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
gl.glClearDepthf(1.0f); /* FIXME: Is this needed? */
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);
gl.glShadeModel(GL10.GL_SMOOTH); /* FIXME: Is this needed? */
gl.glDisable(GL10.GL_DITHER);
gl.glEnable(GL10.GL_TEXTURE_2D);
int maxTextureSizeResult[] = new int[1];
gl.glGetIntegerv(GL10.GL_MAX_TEXTURE_SIZE, maxTextureSizeResult, 0);
mMaxTextureSize = maxTextureSizeResult[0];
}
public int getMaxTextureSize() {
return mMaxTextureSize;
}
/**

View File

@ -167,5 +167,9 @@ public class LayerView extends GLSurfaceView {
return System.nanoTime() - mRenderTime;
}
}
public int getMaxTextureSize() {
return mRenderer.getMaxTextureSize();
}
}

View File

@ -82,7 +82,8 @@ public class ScrollbarLayer extends TileLayer {
mVertical = vertical;
mBuffer = buffer;
mBitmap = Bitmap.createBitmap(image.getWidth(), image.getHeight(), Bitmap.Config.ARGB_8888);
IntSize size = image.getSize();
mBitmap = Bitmap.createBitmap(size.width, size.height, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
}
@ -100,7 +101,7 @@ public class ScrollbarLayer extends TileLayer {
public static ScrollbarLayer create(boolean vertical) {
// just create an empty image for now, it will get drawn
// on demand anyway
int imageSize = nextPowerOfTwo(BAR_SIZE);
int imageSize = IntSize.nextPowerOfTwo(BAR_SIZE);
ByteBuffer buffer = GeckoAppShell.allocateDirectBuffer(imageSize * imageSize * 4);
CairoImage image = new BufferedCairoImage(buffer, imageSize, imageSize, CairoImage.FORMAT_ARGB32);
return new ScrollbarLayer(image, vertical, buffer);

View File

@ -39,6 +39,7 @@ package org.mozilla.gecko.gfx;
import android.graphics.Rect;
import android.graphics.RectF;
import android.opengl.GLES20;
import android.util.Log;
import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11Ext;
@ -58,27 +59,18 @@ public abstract class TileLayer extends Layer {
private final ArrayList<Rect> mDirtyRects;
private final CairoImage mImage;
private final boolean mRepeat;
private final IntSize mSize;
private IntSize mSize;
private int[] mTextureIDs;
public TileLayer(boolean repeat, CairoImage image) {
mRepeat = repeat;
mImage = image;
mSize = new IntSize(image.getWidth(), image.getHeight());
mSize = new IntSize(0, 0);
mDirtyRects = new ArrayList<Rect>();
/*
* Assert that the image has a power-of-two size. OpenGL ES < 2.0 doesn't support NPOT
* textures and OpenGL ES doesn't seem to let us efficiently slice up a NPOT bitmap.
*/
int width = mImage.getWidth(), height = mImage.getHeight();
if ((width & (width - 1)) != 0 || (height & (height - 1)) != 0) {
throw new RuntimeException("TileLayer: NPOT images are unsupported (dimensions are " +
width + "x" + height + ")");
}
}
public IntSize getSize() { return mSize; }
@Override
public IntSize getSize() { return mImage.getSize(); }
protected boolean repeats() { return mRepeat; }
protected int getTextureID() { return mTextureIDs[0]; }
@ -101,13 +93,49 @@ public abstract class TileLayer extends Layer {
}
public void invalidate() {
invalidate(new Rect(0, 0, mSize.width, mSize.height));
IntSize bufferSize = mImage.getSize();
invalidate(new Rect(0, 0, bufferSize.width, bufferSize.height));
}
private void validateTexture() {
/* Calculate the ideal texture size. This must be a power of two if
* the texture is repeated or OpenGL ES 2.0 isn't supported, as
* OpenGL ES 2.0 is required for NPOT texture support (without
* extensions), but doesn't support repeating NPOT textures.
*
* XXX Currently, we don't pick a GLES 2.0 context, so always round.
*/
IntSize bufferSize = mImage.getSize();
IntSize textureSize = bufferSize;
textureSize = bufferSize.nextPowerOfTwo();
if (!textureSize.equals(mSize)) {
mSize = textureSize;
// Delete the old texture
if (mTextureIDs != null) {
TextureReaper.get().add(mTextureIDs);
mTextureIDs = null;
// XXX This won't be freed until the next frame is drawn, so we
// temporarily have a larger-than-necessary memory requirement.
// Is this what we want?
}
}
}
@Override
protected void performUpdates(GL10 gl) {
super.performUpdates(gl);
// Reallocate the texture if the size has changed
validateTexture();
// Don't do any work if the image has an invalid size.
if (!mImage.getSize().isPositive())
return;
if (mTextureIDs == null) {
uploadFullTexture(gl);
} else {
@ -119,43 +147,58 @@ public abstract class TileLayer extends Layer {
}
private void uploadFullTexture(GL10 gl) {
mTextureIDs = new int[1];
gl.glGenTextures(mTextureIDs.length, mTextureIDs, 0);
int cairoFormat = mImage.getFormat();
CairoGLInfo glInfo = new CairoGLInfo(cairoFormat);
bindAndSetGLParameters(gl);
gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, glInfo.internalFormat, mSize.width, mSize.height,
0, glInfo.format, glInfo.type, mImage.getBuffer());
IntSize bufferSize = mImage.getSize();
uploadDirtyRect(gl, new Rect(0, 0, bufferSize.width, bufferSize.height));
}
private void uploadDirtyRect(GL10 gl, Rect dirtyRect) {
if (mTextureIDs == null)
throw new RuntimeException("uploadDirtyRect() called with null texture ID!");
boolean newlyCreated = false;
if (mTextureIDs == null) {
mTextureIDs = new int[1];
gl.glGenTextures(mTextureIDs.length, mTextureIDs, 0);
newlyCreated = true;
}
IntSize bufferSize = mImage.getSize();
Rect bufferRect = new Rect(0, 0, bufferSize.width, bufferSize.height);
int width = mSize.width;
int cairoFormat = mImage.getFormat();
CairoGLInfo glInfo = new CairoGLInfo(cairoFormat);
bindAndSetGLParameters(gl);
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, mImage.getBuffer());
return;
} else {
gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, glInfo.internalFormat, mSize.width, mSize.height,
0, glInfo.format, glInfo.type, null);
gl.glTexSubImage2D(gl.GL_TEXTURE_2D, 0, 0, 0, bufferSize.width, bufferSize.height,
glInfo.format, glInfo.type, mImage.getBuffer());
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,
* and going line-by-line is too slow.
*
* XXX We should still use GL_EXT_unpack_subimage when available.
*/
Buffer viewBuffer = mImage.getBuffer().slice();
int bpp = CairoUtils.bitsPerPixelForCairoFormat(cairoFormat) / 8;
int position = dirtyRect.top * width * bpp;
int position = dirtyRect.top * bufferSize.width * bpp;
if (position > viewBuffer.limit()) {
Log.e(LOGTAG, "### Position outside tile! " + dirtyRect.top);
return;
}
viewBuffer.position(position);
gl.glTexSubImage2D(gl.GL_TEXTURE_2D, 0, 0, dirtyRect.top, width, dirtyRect.height(),
gl.glTexSubImage2D(gl.GL_TEXTURE_2D, 0, 0, dirtyRect.top, bufferSize.width, dirtyRect.height(),
glInfo.format, glInfo.type, viewBuffer);
}

View File

@ -44,6 +44,7 @@ import android.graphics.Rect;
import android.graphics.RectF;
import org.mozilla.gecko.FloatUtils;
import org.mozilla.gecko.gfx.FloatSize;
import org.mozilla.gecko.gfx.IntSize;
import org.mozilla.gecko.gfx.LayerController;
import org.mozilla.gecko.gfx.RectUtils;
import org.json.JSONException;
@ -64,8 +65,7 @@ public class ViewportMetrics {
private float mZoomFactor;
public ViewportMetrics() {
mPageSize = new FloatSize(LayerController.TILE_WIDTH,
LayerController.TILE_HEIGHT);
mPageSize = new FloatSize(1, 1);
mViewportRect = new RectF(0, 0, 1, 1);
mViewportOffset = new PointF(0, 0);
mZoomFactor = 1.0f;
@ -96,13 +96,13 @@ public class ViewportMetrics {
mZoomFactor = zoom;
}
public PointF getOptimumViewportOffset() {
public PointF getOptimumViewportOffset(IntSize displayportSize) {
// XXX We currently always position the viewport in the centre of the
// displayport, but we might want to optimise this during panning
// to minimise checkerboarding.
Point optimumOffset =
new Point((int)Math.round((LayerController.TILE_WIDTH - mViewportRect.width()) / 2),
(int)Math.round((LayerController.TILE_HEIGHT - mViewportRect.height()) / 2));
new Point((int)Math.round((displayportSize.width - mViewportRect.width()) / 2),
(int)Math.round((displayportSize.height - mViewportRect.height()) / 2));
/* XXX Until bug #524925 is fixed, changing the viewport origin will
* probably cause things to be slower than just having a smaller usable

View File

@ -79,10 +79,6 @@ using mozilla::unused;
#include "nsStringGlue.h"
// NB: Keep these in sync with LayerController.java in mobile/android/base and embedding/android/.
#define TILE_WIDTH 1024
#define TILE_HEIGHT 2048
using namespace mozilla;
using namespace mozilla::widget;
@ -310,7 +306,7 @@ nsWindow::ConfigureChildren(const nsTArray<nsIWidget::Configuration>& config)
void
nsWindow::RedrawAll()
{
nsIntRect entireRect(0, 0, TILE_WIDTH, TILE_HEIGHT);
nsIntRect entireRect(0, 0, gAndroidBounds.width, gAndroidBounds.height);
AndroidGeckoEvent *event = new AndroidGeckoEvent(AndroidGeckoEvent::DRAW, entireRect);
nsAppShell::gAppShell->PostEvent(event);
}
@ -1018,7 +1014,7 @@ nsWindow::DrawTo(gfxASurface *targetSurface, const nsIntRect &invalidRect)
if (coveringChildIndex == -1) {
nsPaintEvent event(true, NS_PAINT, this);
nsIntRect tileRect(0, 0, TILE_WIDTH, TILE_HEIGHT);
nsIntRect tileRect(0, 0, gAndroidBounds.width, gAndroidBounds.height);
event.region = boundsRect.Intersect(invalidRect).Intersect(tileRect);
switch (GetLayerManager(nsnull)->GetBackendType()) {
@ -1112,7 +1108,7 @@ nsWindow::OnDraw(AndroidGeckoEvent *ae)
unsigned char *bits = client.LockBufferBits();
nsRefPtr<gfxImageSurface> targetSurface =
new gfxImageSurface(bits, gfxIntSize(TILE_WIDTH, TILE_HEIGHT), TILE_WIDTH * 2,
new gfxImageSurface(bits, gfxIntSize(gAndroidBounds.width, gAndroidBounds.height), gAndroidBounds.width * 2,
gfxASurface::ImageFormatRGB16_565);
if (targetSurface->CairoStatus()) {
ALOG("### Failed to create a valid surface from the bitmap");