1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- 2 * This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 package org.mozilla.gecko.gfx; 7 8 import android.graphics.Rect; 9 import android.opengl.GLES20; 10 import android.util.Log; 11 12 import java.nio.ByteBuffer; 13 14 /** 15 * Base class for tile layers, which encapsulate the logic needed to draw textured tiles in OpenGL 16 * ES. 17 */ 18 public abstract class TileLayer extends Layer { 19 private static final String LOGTAG = "GeckoTileLayer"; 20 21 private final Rect mDirtyRect; 22 private IntSize mSize; 23 private int[] mTextureIDs; 24 25 protected final CairoImage mImage; 26 getImage()27 public CairoImage getImage() { 28 return mImage; 29 } 30 31 public enum PaintMode { NORMAL, REPEAT, STRETCH }; 32 private PaintMode mPaintMode; 33 TileLayer(CairoImage image, PaintMode paintMode)34 public TileLayer(CairoImage image, PaintMode paintMode) { 35 super(image == null ? null : image.getSize()); 36 37 mPaintMode = paintMode; 38 mImage = image; 39 mSize = new IntSize(0, 0); 40 mDirtyRect = new Rect(); 41 } 42 repeats()43 protected boolean repeats() { return mPaintMode == PaintMode.REPEAT; } stretches()44 protected boolean stretches() { return mPaintMode == PaintMode.STRETCH; } getTextureID()45 protected int getTextureID() { return mTextureIDs[0]; } initialized()46 protected boolean initialized() { return mImage != null && mTextureIDs != null; } 47 48 @Override finalize()49 protected void finalize() throws Throwable { 50 try { 51 if (mTextureIDs != null) 52 TextureReaper.get().add(mTextureIDs); 53 } finally { 54 super.finalize(); 55 } 56 } 57 destroy()58 public void destroy() { 59 try { 60 if (mImage != null) { 61 mImage.destroy(); 62 } 63 } catch (Exception ex) { 64 Log.e(LOGTAG, "error clearing buffers: ", ex); 65 } 66 } 67 setPaintMode(PaintMode mode)68 public void setPaintMode(PaintMode mode) { 69 mPaintMode = mode; 70 } 71 72 /** 73 * Invalidates the entire buffer so that it will be uploaded again. Only valid inside a 74 * transaction. 75 */ invalidate()76 public void invalidate() { 77 if (!inTransaction()) 78 throw new RuntimeException("invalidate() is only valid inside a transaction"); 79 IntSize bufferSize = mImage.getSize(); 80 mDirtyRect.set(0, 0, bufferSize.width, bufferSize.height); 81 } 82 validateTexture()83 private void validateTexture() { 84 /* Calculate the ideal texture size. This must be a power of two if 85 * the texture is repeated or OpenGL ES 2.0 isn't supported, as 86 * OpenGL ES 2.0 is required for NPOT texture support (without 87 * extensions), but doesn't support repeating NPOT textures. 88 * 89 * XXX Currently, we don't pick a GLES 2.0 context, so always round. 90 */ 91 IntSize textureSize = mImage.getSize().nextPowerOfTwo(); 92 93 if (!textureSize.equals(mSize)) { 94 mSize = textureSize; 95 96 // Delete the old texture 97 if (mTextureIDs != null) { 98 TextureReaper.get().add(mTextureIDs); 99 mTextureIDs = null; 100 101 // Free the texture immediately, so we don't incur a 102 // temporarily increased memory usage. 103 TextureReaper.get().reap(); 104 } 105 } 106 } 107 108 @Override performUpdates(RenderContext context)109 protected void performUpdates(RenderContext context) { 110 super.performUpdates(context); 111 112 // Reallocate the texture if the size has changed 113 validateTexture(); 114 115 // Don't do any work if the image has an invalid size. 116 if (!mImage.getSize().isPositive()) 117 return; 118 119 // If we haven't allocated a texture, assume the whole region is dirty 120 if (mTextureIDs == null) { 121 uploadFullTexture(); 122 } else { 123 uploadDirtyRect(mDirtyRect); 124 } 125 126 mDirtyRect.setEmpty(); 127 } 128 uploadFullTexture()129 private void uploadFullTexture() { 130 IntSize bufferSize = mImage.getSize(); 131 uploadDirtyRect(new Rect(0, 0, bufferSize.width, bufferSize.height)); 132 } 133 uploadDirtyRect(Rect dirtyRect)134 private void uploadDirtyRect(Rect dirtyRect) { 135 // If we have nothing to upload, just return for now 136 if (dirtyRect.isEmpty()) 137 return; 138 139 // It's possible that the buffer will be null, check for that and return 140 ByteBuffer imageBuffer = mImage.getBuffer(); 141 if (imageBuffer == null) 142 return; 143 144 if (mTextureIDs == null) { 145 mTextureIDs = new int[1]; 146 GLES20.glGenTextures(mTextureIDs.length, mTextureIDs, 0); 147 } 148 149 int cairoFormat = mImage.getFormat(); 150 CairoGLInfo glInfo = new CairoGLInfo(cairoFormat); 151 152 bindAndSetGLParameters(); 153 154 // XXX TexSubImage2D is too broken to rely on Adreno, and very slow 155 // on other chipsets, so we always upload the entire buffer. 156 IntSize bufferSize = mImage.getSize(); 157 158 GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, glInfo.internalFormat, mSize.width, 159 mSize.height, 0, glInfo.format, glInfo.type, imageBuffer); 160 161 } 162 bindAndSetGLParameters()163 private void bindAndSetGLParameters() { 164 GLES20.glActiveTexture(GLES20.GL_TEXTURE0); 165 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIDs[0]); 166 GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, 167 GLES20.GL_LINEAR); 168 GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, 169 GLES20.GL_LINEAR); 170 171 int repeatMode = repeats() ? GLES20.GL_REPEAT : GLES20.GL_CLAMP_TO_EDGE; 172 GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, repeatMode); 173 GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, repeatMode); 174 } 175 } 176 177