package org.andengine.opengl.texture.compressed.pvr; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import org.andengine.BuildConfig; import org.andengine.opengl.texture.ITextureStateListener; import org.andengine.opengl.texture.PixelFormat; import org.andengine.opengl.texture.Texture; import org.andengine.opengl.texture.TextureManager; import org.andengine.opengl.texture.TextureOptions; import org.andengine.opengl.texture.compressed.pvr.pixelbufferstrategy.GreedyPVRTexturePixelBufferStrategy; import org.andengine.opengl.texture.compressed.pvr.pixelbufferstrategy.IPVRTexturePixelBufferStrategy; import org.andengine.opengl.texture.compressed.pvr.pixelbufferstrategy.IPVRTexturePixelBufferStrategy.IPVRTexturePixelBufferStrategyBufferManager; import org.andengine.opengl.util.GLState; import org.andengine.util.StreamUtils; import org.andengine.util.adt.DataConstants; import org.andengine.util.adt.array.ArrayUtils; import org.andengine.util.adt.io.out.ByteBufferOutputStream; import org.andengine.util.debug.Debug; import org.andengine.util.math.MathUtils; import android.opengl.GLES20; /** * (c) 2010 Nicolas Gramlich * (c) 2011 Zynga Inc. * * @author Nicolas Gramlich * @since 16:18:10 - 13.07.2011 */ public abstract class PVRTexture extends Texture { // =========================================================== // Constants // =========================================================== public static final int FLAG_MIPMAP = (1 << 8); // has mip map levels public static final int FLAG_TWIDDLE = (1 << 9); // is twiddled public static final int FLAG_BUMPMAP = (1 << 10); // has normals encoded for a bump map public static final int FLAG_TILING = (1 << 11); // is bordered for tiled pvr public static final int FLAG_CUBEMAP = (1 << 12); // is a cubemap/skybox public static final int FLAG_FALSEMIPCOL = (1 << 13); // are there false colored MIP levels public static final int FLAG_VOLUME = (1 << 14); // is this a volume texture public static final int FLAG_ALPHA = (1 << 15); // v2.1 is there transparency info in the texture public static final int FLAG_VERTICALFLIP = (1 << 16); // v2.1 is the texture vertically flipped // =========================================================== // Fields // =========================================================== private final PVRTextureHeader mPVRTextureHeader; private final IPVRTexturePixelBufferStrategy mPVRTexturePixelBufferStrategy; // =========================================================== // Constructors // =========================================================== public PVRTexture(final TextureManager pTextureManager, final PVRTextureFormat pPVRTextureFormat) throws IllegalArgumentException, IOException { this(pTextureManager, pPVRTextureFormat, new GreedyPVRTexturePixelBufferStrategy(), TextureOptions.DEFAULT, null); } public PVRTexture(final TextureManager pTextureManager, final PVRTextureFormat pPVRTextureFormat, final IPVRTexturePixelBufferStrategy pPVRTexturePixelBufferStrategy) throws IllegalArgumentException, IOException { this(pTextureManager, pPVRTextureFormat, pPVRTexturePixelBufferStrategy, TextureOptions.DEFAULT, null); } public PVRTexture(final TextureManager pTextureManager, final PVRTextureFormat pPVRTextureFormat, final ITextureStateListener pTextureStateListener) throws IllegalArgumentException, IOException { this(pTextureManager, pPVRTextureFormat, new GreedyPVRTexturePixelBufferStrategy(), TextureOptions.DEFAULT, pTextureStateListener); } public PVRTexture(final TextureManager pTextureManager, final PVRTextureFormat pPVRTextureFormat, final IPVRTexturePixelBufferStrategy pPVRTexturePixelBufferStrategy, final ITextureStateListener pTextureStateListener) throws IllegalArgumentException, IOException { this(pTextureManager, pPVRTextureFormat, pPVRTexturePixelBufferStrategy, TextureOptions.DEFAULT, pTextureStateListener); } public PVRTexture(final TextureManager pTextureManager, final PVRTextureFormat pPVRTextureFormat, final TextureOptions pTextureOptions) throws IllegalArgumentException, IOException { this(pTextureManager, pPVRTextureFormat, new GreedyPVRTexturePixelBufferStrategy(), pTextureOptions, null); } public PVRTexture(final TextureManager pTextureManager, final PVRTextureFormat pPVRTextureFormat, final IPVRTexturePixelBufferStrategy pPVRTexturePixelBufferStrategy, final TextureOptions pTextureOptions) throws IllegalArgumentException, IOException { this(pTextureManager, pPVRTextureFormat, pPVRTexturePixelBufferStrategy, pTextureOptions, null); } public PVRTexture(final TextureManager pTextureManager, final PVRTextureFormat pPVRTextureFormat, final TextureOptions pTextureOptions, final ITextureStateListener pTextureStateListener) throws IllegalArgumentException, IOException { this(pTextureManager, pPVRTextureFormat, new GreedyPVRTexturePixelBufferStrategy(), pTextureOptions, pTextureStateListener); } public PVRTexture(final TextureManager pTextureManager, final PVRTextureFormat pPVRTextureFormat, final IPVRTexturePixelBufferStrategy pPVRTexturePixelBufferStrategy, final TextureOptions pTextureOptions, final ITextureStateListener pTextureStateListener) throws IllegalArgumentException, IOException { super(pTextureManager, pPVRTextureFormat.getPixelFormat(), pTextureOptions, pTextureStateListener); this.mPVRTexturePixelBufferStrategy = pPVRTexturePixelBufferStrategy; InputStream inputStream = null; try { inputStream = this.getInputStream(); this.mPVRTextureHeader = new PVRTextureHeader(StreamUtils.streamToBytes(inputStream, PVRTextureHeader.SIZE)); } finally { StreamUtils.close(inputStream); } if(this.mPVRTextureHeader.getPVRTextureFormat().getPixelFormat() != pPVRTextureFormat.getPixelFormat()) { throw new IllegalArgumentException("Other PVRTextureFormat: '" + this.mPVRTextureHeader.getPVRTextureFormat().getPixelFormat() + "' found than expected: '" + pPVRTextureFormat.getPixelFormat() + "'."); } if(this.mPVRTextureHeader.getPVRTextureFormat().isCompressed()) { // TODO && ! GLHELPER_EXTENSION_PVRTC] ) { throw new IllegalArgumentException("Invalid PVRTextureFormat: '" + this.mPVRTextureHeader.getPVRTextureFormat() + "'."); } if(this.hasMipMaps()) { switch(pTextureOptions.mMinFilter){ case GLES20.GL_NEAREST_MIPMAP_NEAREST: case GLES20.GL_NEAREST_MIPMAP_LINEAR: case GLES20.GL_LINEAR_MIPMAP_NEAREST: case GLES20.GL_LINEAR_MIPMAP_LINEAR: break; default: if(BuildConfig.DEBUG) { Debug.w("This '" + this.getClass().getSimpleName() + "' contains mipmaps, but the provided '" + pTextureOptions.getClass().getSimpleName() + "' don't have MipMaps enabled on the MinFilter!"); } } } this.mUpdateOnHardwareNeeded = true; } // =========================================================== // Getter & Setter // =========================================================== @Override public int getWidth() { return this.mPVRTextureHeader.getWidth(); } @Override public int getHeight() { return this.mPVRTextureHeader.getHeight(); } public boolean hasMipMaps() { return this.mPVRTextureHeader.getNumMipmaps() > 0; } public PVRTextureHeader getPVRTextureHeader() { return this.mPVRTextureHeader; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== protected abstract InputStream onGetInputStream() throws IOException; public InputStream getInputStream() throws IOException { return this.onGetInputStream(); } @Override protected void writeTextureToHardware(final GLState pGLState) throws IOException { final IPVRTexturePixelBufferStrategyBufferManager pvrTextureLoadStrategyManager = this.mPVRTexturePixelBufferStrategy.newPVRTexturePixelBufferStrategyManager(this); int width = this.getWidth(); int height = this.getHeight(); final int dataLength = this.mPVRTextureHeader.getDataLength(); final int bytesPerPixel = this.mPVRTextureHeader.getBitsPerPixel() / DataConstants.BITS_PER_BYTE; /* Adjust unpack alignment. */ GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1); int currentLevel = 0; int currentPixelDataOffset = 0; while (currentPixelDataOffset < dataLength) { if (currentLevel > 0 && (width != height || MathUtils.nextPowerOfTwo(width) != width)) { Debug.w("Mipmap level '" + currentLevel + "' is not squared. Width: '" + width + "', height: '" + height + "'. Texture won't render correctly."); } final int currentPixelDataSize = height * width * bytesPerPixel; /* Load the current level. */ this.mPVRTexturePixelBufferStrategy.loadPVRTextureData(pvrTextureLoadStrategyManager, width, height, bytesPerPixel, this.mPixelFormat, currentLevel, currentPixelDataOffset, currentPixelDataSize); currentPixelDataOffset += currentPixelDataSize; /* Prepare next mipmap level. */ width = Math.max(width / 2, 1); height = Math.max(height / 2, 1); currentLevel++; } /* Restore default unpack alignment. */ GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, GLState.GL_UNPACK_ALIGNMENT_DEFAULT); } // =========================================================== // Methods // =========================================================== public ByteBuffer getPVRTextureBuffer() throws IOException { final InputStream inputStream = this.getInputStream(); try { final ByteBufferOutputStream os = new ByteBufferOutputStream(DataConstants.BYTES_PER_KILOBYTE, DataConstants.BYTES_PER_MEGABYTE / 2); StreamUtils.copy(inputStream, os); return os.toByteBuffer(); } finally { StreamUtils.close(inputStream); } } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static class PVRTextureHeader { // =========================================================== // Constants // =========================================================== static final byte[] MAGIC_IDENTIFIER = { (byte)'P', (byte)'V', (byte)'R', (byte)'!' }; public static final int SIZE = 13 * DataConstants.BYTES_PER_INT; private static final int FORMAT_FLAG_MASK = 0x0FF; // =========================================================== // Fields // =========================================================== private final ByteBuffer mDataByteBuffer; private final PVRTextureFormat mPVRTextureFormat; // =========================================================== // Constructors // =========================================================== public PVRTextureHeader(final byte[] pData) { this.mDataByteBuffer = ByteBuffer.wrap(pData); this.mDataByteBuffer.rewind(); this.mDataByteBuffer.order(ByteOrder.LITTLE_ENDIAN); /* Check magic bytes. */ if(!ArrayUtils.equals(pData, 11 * DataConstants.BYTES_PER_INT, PVRTextureHeader.MAGIC_IDENTIFIER, 0, PVRTextureHeader.MAGIC_IDENTIFIER.length)) { throw new IllegalArgumentException("Invalid " + this.getClass().getSimpleName() + "!"); } this.mPVRTextureFormat = PVRTextureFormat.fromID(this.getFlags() & PVRTextureHeader.FORMAT_FLAG_MASK); } // =========================================================== // Getter & Setter // =========================================================== public PVRTextureFormat getPVRTextureFormat() { return this.mPVRTextureFormat; } public int headerLength() { return this.mDataByteBuffer.getInt(0 * DataConstants.BYTES_PER_INT); // TODO Constants } public int getHeight() { return this.mDataByteBuffer.getInt(1 * DataConstants.BYTES_PER_INT); } public int getWidth() { return this.mDataByteBuffer.getInt(2 * DataConstants.BYTES_PER_INT); } public int getNumMipmaps() { return this.mDataByteBuffer.getInt(3 * DataConstants.BYTES_PER_INT); } public int getFlags() { return this.mDataByteBuffer.getInt(4 * DataConstants.BYTES_PER_INT); } public int getDataLength() { return this.mDataByteBuffer.getInt(5 * DataConstants.BYTES_PER_INT); } public int getBitsPerPixel() { return this.mDataByteBuffer.getInt(6 * DataConstants.BYTES_PER_INT); } public int getBitmaskRed() { return this.mDataByteBuffer.getInt(7 * DataConstants.BYTES_PER_INT); } public int getBitmaskGreen() { return this.mDataByteBuffer.getInt(8 * DataConstants.BYTES_PER_INT); } public int getBitmaskBlue() { return this.mDataByteBuffer.getInt(9 * DataConstants.BYTES_PER_INT); } public int getBitmaskAlpha() { return this.mDataByteBuffer.getInt(10 * DataConstants.BYTES_PER_INT); } public boolean hasAlpha() { return this.getBitmaskAlpha() != 0; } public int getPVRTag() { return this.mDataByteBuffer.getInt(11 * DataConstants.BYTES_PER_INT); } public int numSurfs() { return this.mDataByteBuffer.getInt(12 * DataConstants.BYTES_PER_INT); } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== // =========================================================== // Methods // =========================================================== // =========================================================== // Inner and Anonymous Classes // =========================================================== } public static enum PVRTextureFormat { // =========================================================== // Elements // =========================================================== RGBA_4444(0x10, false, PixelFormat.RGBA_4444), RGBA_5551(0x11, false, PixelFormat.RGBA_5551), RGBA_8888(0x12, false, PixelFormat.RGBA_8888), RGB_565(0x13, false, PixelFormat.RGB_565), // RGB_555( 0x14, ...), // RGB_888( 0x15, ...), I_8(0x16, false, PixelFormat.I_8), AI_88(0x17, false, PixelFormat.AI_88), // PVRTC_2(0x18, GL10.GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG, true, TextureFormat.???), // PVRTC_4(0x19, GL10.GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG, true, TextureFormat.???), // BGRA_8888(0x1A, GL10.GL_RGBA, TextureFormat.???), A_8(0x1B, false, PixelFormat.A_8); // =========================================================== // Constants // =========================================================== // =========================================================== // Fields // =========================================================== private final int mID; private final boolean mCompressed; private final PixelFormat mPixelFormat; // =========================================================== // Constructors // =========================================================== private PVRTextureFormat(final int pID, final boolean pCompressed, final PixelFormat pPixelFormat) { this.mID = pID; this.mCompressed = pCompressed; this.mPixelFormat = pPixelFormat; } public static PVRTextureFormat fromID(final int pID) { final PVRTextureFormat[] pvrTextureFormats = PVRTextureFormat.values(); final int pvrTextureFormatCount = pvrTextureFormats.length; for(int i = 0; i < pvrTextureFormatCount; i++) { final PVRTextureFormat pvrTextureFormat = pvrTextureFormats[i]; if(pvrTextureFormat.mID == pID) { return pvrTextureFormat; } } throw new IllegalArgumentException("Unexpected " + PVRTextureFormat.class.getSimpleName() + "-ID: '" + pID + "'."); } public static PVRTextureFormat fromPixelFormat(final PixelFormat pPixelFormat) throws IllegalArgumentException { switch(pPixelFormat) { case RGBA_8888: return PVRTextureFormat.RGBA_8888; case RGBA_4444: return PVRTextureFormat.RGBA_4444; case RGB_565: return PVRTextureFormat.RGB_565; default: throw new IllegalArgumentException("Unsupported " + PixelFormat.class.getName() + ": '" + pPixelFormat + "'."); } } // =========================================================== // Getter & Setter // =========================================================== public int getID() { return this.mID; } public boolean isCompressed() { return this.mCompressed; } public PixelFormat getPixelFormat() { return this.mPixelFormat; } // =========================================================== // Methods from SuperClass/Interfaces // =========================================================== // =========================================================== // Methods // =========================================================== // =========================================================== // Inner and Anonymous Classes // =========================================================== } }