michael@0: /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko.gfx; michael@0: michael@0: import android.graphics.Rect; michael@0: import android.opengl.GLES20; michael@0: import android.util.Log; michael@0: michael@0: import java.nio.ByteBuffer; michael@0: michael@0: /** michael@0: * Base class for tile layers, which encapsulate the logic needed to draw textured tiles in OpenGL michael@0: * ES. michael@0: */ michael@0: public abstract class TileLayer extends Layer { michael@0: private static final String LOGTAG = "GeckoTileLayer"; michael@0: michael@0: private final Rect mDirtyRect; michael@0: private IntSize mSize; michael@0: private int[] mTextureIDs; michael@0: michael@0: protected final CairoImage mImage; michael@0: michael@0: public enum PaintMode { NORMAL, REPEAT, STRETCH }; michael@0: private PaintMode mPaintMode; michael@0: michael@0: public TileLayer(CairoImage image, PaintMode paintMode) { michael@0: super(image.getSize()); michael@0: michael@0: mPaintMode = paintMode; michael@0: mImage = image; michael@0: mSize = new IntSize(0, 0); michael@0: mDirtyRect = new Rect(); michael@0: } michael@0: michael@0: protected boolean repeats() { return mPaintMode == PaintMode.REPEAT; } michael@0: protected boolean stretches() { return mPaintMode == PaintMode.STRETCH; } michael@0: protected int getTextureID() { return mTextureIDs[0]; } michael@0: protected boolean initialized() { return mImage != null && mTextureIDs != null; } michael@0: michael@0: @Override michael@0: protected void finalize() throws Throwable { michael@0: try { michael@0: if (mTextureIDs != null) michael@0: TextureReaper.get().add(mTextureIDs); michael@0: } finally { michael@0: super.finalize(); michael@0: } michael@0: } michael@0: michael@0: public void destroy() { michael@0: try { michael@0: if (mImage != null) { michael@0: mImage.destroy(); michael@0: } michael@0: } catch (Exception ex) { michael@0: Log.e(LOGTAG, "error clearing buffers: ", ex); michael@0: } michael@0: } michael@0: michael@0: public void setPaintMode(PaintMode mode) { michael@0: mPaintMode = mode; michael@0: } michael@0: michael@0: /** michael@0: * Invalidates the entire buffer so that it will be uploaded again. Only valid inside a michael@0: * transaction. michael@0: */ michael@0: michael@0: public void invalidate() { michael@0: if (!inTransaction()) michael@0: throw new RuntimeException("invalidate() is only valid inside a transaction"); michael@0: IntSize bufferSize = mImage.getSize(); michael@0: mDirtyRect.set(0, 0, bufferSize.width, bufferSize.height); michael@0: } michael@0: michael@0: private void validateTexture() { michael@0: /* Calculate the ideal texture size. This must be a power of two if michael@0: * the texture is repeated or OpenGL ES 2.0 isn't supported, as michael@0: * OpenGL ES 2.0 is required for NPOT texture support (without michael@0: * extensions), but doesn't support repeating NPOT textures. michael@0: * michael@0: * XXX Currently, we don't pick a GLES 2.0 context, so always round. michael@0: */ michael@0: IntSize textureSize = mImage.getSize().nextPowerOfTwo(); michael@0: michael@0: if (!textureSize.equals(mSize)) { michael@0: mSize = textureSize; michael@0: michael@0: // Delete the old texture michael@0: if (mTextureIDs != null) { michael@0: TextureReaper.get().add(mTextureIDs); michael@0: mTextureIDs = null; michael@0: michael@0: // Free the texture immediately, so we don't incur a michael@0: // temporarily increased memory usage. michael@0: TextureReaper.get().reap(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: protected void performUpdates(RenderContext context) { michael@0: super.performUpdates(context); michael@0: michael@0: // Reallocate the texture if the size has changed michael@0: validateTexture(); michael@0: michael@0: // Don't do any work if the image has an invalid size. michael@0: if (!mImage.getSize().isPositive()) michael@0: return; michael@0: michael@0: // If we haven't allocated a texture, assume the whole region is dirty michael@0: if (mTextureIDs == null) { michael@0: uploadFullTexture(); michael@0: } else { michael@0: uploadDirtyRect(mDirtyRect); michael@0: } michael@0: michael@0: mDirtyRect.setEmpty(); michael@0: } michael@0: michael@0: private void uploadFullTexture() { michael@0: IntSize bufferSize = mImage.getSize(); michael@0: uploadDirtyRect(new Rect(0, 0, bufferSize.width, bufferSize.height)); michael@0: } michael@0: michael@0: private void uploadDirtyRect(Rect dirtyRect) { michael@0: // If we have nothing to upload, just return for now michael@0: if (dirtyRect.isEmpty()) michael@0: return; michael@0: michael@0: // It's possible that the buffer will be null, check for that and return michael@0: ByteBuffer imageBuffer = mImage.getBuffer(); michael@0: if (imageBuffer == null) michael@0: return; michael@0: michael@0: if (mTextureIDs == null) { michael@0: mTextureIDs = new int[1]; michael@0: GLES20.glGenTextures(mTextureIDs.length, mTextureIDs, 0); michael@0: } michael@0: michael@0: int cairoFormat = mImage.getFormat(); michael@0: CairoGLInfo glInfo = new CairoGLInfo(cairoFormat); michael@0: michael@0: bindAndSetGLParameters(); michael@0: michael@0: // XXX TexSubImage2D is too broken to rely on on Adreno, and very slow michael@0: // on other chipsets, so we always upload the entire buffer. michael@0: IntSize bufferSize = mImage.getSize(); michael@0: if (mSize.equals(bufferSize)) { michael@0: GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, glInfo.internalFormat, mSize.width, michael@0: mSize.height, 0, glInfo.format, glInfo.type, imageBuffer); michael@0: } else { michael@0: // Our texture has been expanded to the next power of two. michael@0: // XXX We probably never want to take this path, so throw an exception. michael@0: throw new RuntimeException("Buffer/image size mismatch in TileLayer!"); michael@0: } michael@0: } michael@0: michael@0: private void bindAndSetGLParameters() { michael@0: GLES20.glActiveTexture(GLES20.GL_TEXTURE0); michael@0: GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIDs[0]); michael@0: GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, michael@0: GLES20.GL_LINEAR); michael@0: GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, michael@0: GLES20.GL_LINEAR); michael@0: michael@0: int repeatMode = repeats() ? GLES20.GL_REPEAT : GLES20.GL_CLAMP_TO_EDGE; michael@0: GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, repeatMode); michael@0: GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, repeatMode); michael@0: } michael@0: } michael@0: