package org.anddev.andengine.opengl.texture.compressed.pvr;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import javax.microedition.khronos.opengles.GL10;
import org.anddev.andengine.opengl.texture.Texture;
import org.anddev.andengine.opengl.texture.TextureOptions;
import org.anddev.andengine.util.ArrayUtils;
import org.anddev.andengine.util.ByteBufferOutputStream;
import org.anddev.andengine.util.Debug;
import org.anddev.andengine.util.MathUtils;
import org.anddev.andengine.util.StreamUtils;
import org.anddev.andengine.util.constants.DataConstants;
/**
* [16:32:42] Ricardo Quesada: "quick tip for PVR + NPOT + RGBA4444 textures: Don't forget to pack the bytes: glPixelStorei(GL_UNPACK_ALIGNMENT,1);"
*
* (c) 2010 Nicolas Gramlich
* (c) 2011 Zynga Inc.
*
* @author Nicolas Gramlich
* @since 16:18:10 - 13.07.2011
* @see https://github.com/cocos2d/cocos2d-iphone/blob/develop/cocos2d/CCTexturePVR.m
*/
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;
// ===========================================================
// Constructors
// ===========================================================
public PVRTexture(final PVRTextureFormat pPVRTextureFormat) throws IllegalArgumentException, IOException {
this(pPVRTextureFormat, TextureOptions.DEFAULT, null);
}
public PVRTexture(final PVRTextureFormat pPVRTextureFormat, final ITextureStateListener pTextureStateListener) throws IllegalArgumentException, IOException {
this(pPVRTextureFormat, TextureOptions.DEFAULT, pTextureStateListener);
}
public PVRTexture(final PVRTextureFormat pPVRTextureFormat, final TextureOptions pTextureOptions) throws IllegalArgumentException, IOException {
this(pPVRTextureFormat, pTextureOptions, null);
}
public PVRTexture(final PVRTextureFormat pPVRTextureFormat, final TextureOptions pTextureOptions, final ITextureStateListener pTextureStateListener) throws IllegalArgumentException, IOException {
super(pPVRTextureFormat.getPixelFormat(), pTextureOptions, pTextureStateListener);
InputStream inputStream = null;
try {
inputStream = this.getInputStream();
this.mPVRTextureHeader = new PVRTextureHeader(StreamUtils.streamToBytes(inputStream, PVRTextureHeader.SIZE));
} finally {
StreamUtils.close(inputStream);
}
if(!MathUtils.isPowerOfTwo(this.getWidth()) || !MathUtils.isPowerOfTwo(this.getHeight())) { // TODO GLHelper.EXTENSIONS_NON_POWER_OF_TWO
throw new IllegalArgumentException("mWidth and mHeight must be a power of 2!");
}
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() + "'.");
}
this.mUpdateOnHardwareNeeded = true;
}
// ===========================================================
// Getter & Setter
// ===========================================================
@Override
public int getWidth() {
return this.mPVRTextureHeader.getWidth();
}
@Override
public int getHeight() {
return this.mPVRTextureHeader.getHeight();
}
public PVRTextureHeader getPVRTextureHeader() {
return this.mPVRTextureHeader;
}
// ===========================================================
// Methods for/from SuperClass/Interfaces
// ===========================================================
protected abstract InputStream onGetInputStream() throws IOException;
protected InputStream getInputStream() throws IOException {
return this.onGetInputStream();
}
@Override
protected void generateHardwareTextureID(final GL10 pGL) {
// // TODO
// if(this.mMipMapCount > 0) {
pGL.glPixelStorei(GL10.GL_UNPACK_ALIGNMENT, 1);
// }
super.generateHardwareTextureID(pGL);
}
@Override
protected void writeTextureToHardware(final GL10 pGL) throws IOException {
final ByteBuffer pvrDataBuffer = this.getPVRDataBuffer();
int width = this.getWidth();
int height = this.getHeight();
final int dataLength = this.mPVRTextureHeader.getDataLength();
final int glFormat = this.mPixelFormat.getGLFormat();
final int glType = this.mPixelFormat.getGLType();
final int bytesPerPixel = this.mPVRTextureHeader.getBitsPerPixel() / DataConstants.BITS_PER_BYTE;
/* Calculate the data size for each texture level and respect the minimum number of blocks. */
int mipmapLevel = 0;
int currentPixelDataOffset = 0;
while (currentPixelDataOffset < dataLength) {
final int currentPixelDataSize = width * height * bytesPerPixel;
if (mipmapLevel > 0 && (width != height || MathUtils.nextPowerOfTwo(width) != width)) {
Debug.w(String.format("Mipmap level '%u' is not squared. Width: '%u', height: '%u'. Texture won't render correctly.", mipmapLevel, width, height));
}
pvrDataBuffer.position(PVRTextureHeader.SIZE + currentPixelDataOffset);
pvrDataBuffer.limit(PVRTextureHeader.SIZE + currentPixelDataOffset + currentPixelDataSize);
ByteBuffer pixelBuffer = pvrDataBuffer.slice();
pGL.glTexImage2D(GL10.GL_TEXTURE_2D, mipmapLevel, glFormat, width, height, 0, glFormat, glType, pixelBuffer);
currentPixelDataOffset += currentPixelDataSize;
/* Prepare next mipmap level. */
width = Math.max(width >> 1, 1);
height = Math.max(height >> 1, 1);
mipmapLevel++;
}
}
// ===========================================================
// Methods
// ===========================================================
protected ByteBuffer getPVRDataBuffer() 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
// ===========================================================
public 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
// ===========================================================
}
}