package org.andengine.opengl.texture.render;
import java.io.IOException;
import java.nio.IntBuffer;
import org.andengine.opengl.exception.GLException;
import org.andengine.opengl.exception.GLFrameBufferException;
import org.andengine.opengl.exception.RenderTextureInitializationException;
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.util.GLHelper;
import org.andengine.opengl.util.GLState;
import org.andengine.util.color.Color;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.opengl.GLES20;
/**
* The general workflow with a {@link RenderTexture} is: {@link RenderTexture#init(GLState)} -> {@link RenderTexture#begin(GLState)} -> {@link RenderTexture#end(GLState)} -> {@link RenderTexture#destroy(GLState)}.
*
* (c) Zynga 2011
*
* @author Nicolas Gramlich <ngramlich@zynga.com>
* @since 07:13:05 - 24.08.2011
*/
public class RenderTexture extends Texture {
// ===========================================================
// Constants
// ===========================================================
private static final int[] VIEWPORT_CONTAINER = new int[4];
private static final float[] CLEARCOLOR_CONTAINER = new float[4];
private static final int VIEWPORT_CONTAINER_X_INDEX = 0;
private static final int VIEWPORT_CONTAINER_Y_INDEX = RenderTexture.VIEWPORT_CONTAINER_X_INDEX + 1;
private static final int VIEWPORT_CONTAINER_WIDTH_INDEX = RenderTexture.VIEWPORT_CONTAINER_Y_INDEX + 1;
private static final int VIEWPORT_CONTAINER_HEIGHT_INDEX = RenderTexture.VIEWPORT_CONTAINER_WIDTH_INDEX + 1;
private static final int CLEARCOLOR_CONTAINER_RED_INDEX = 0;
private static final int CLEARCOLOR_CONTAINER_GREEN_INDEX = RenderTexture.CLEARCOLOR_CONTAINER_RED_INDEX + 1;
private static final int CLEARCOLOR_CONTAINER_BLUE_INDEX = RenderTexture.CLEARCOLOR_CONTAINER_GREEN_INDEX + 1;
private static final int CLEARCOLOR_CONTAINER_ALPHA_INDEX = RenderTexture.CLEARCOLOR_CONTAINER_BLUE_INDEX + 1;
// ===========================================================
// Fields
// ===========================================================
protected final PixelFormat mPixelFormat;
protected final int mWidth;
protected final int mHeight;
protected int mFramebufferObjectID;
private int mPreviousFramebufferObjectID;
private int mPreviousViewPortX;
private int mPreviousViewPortY;
private int mPreviousViewPortWidth;
private int mPreviousViewPortHeight;
private boolean mInitialized;
// ===========================================================
// Constructors
// ===========================================================
public RenderTexture(final TextureManager pTextureManager, final int pWidth, final int pHeight) {
this(pTextureManager, pWidth, pHeight, PixelFormat.RGBA_8888, TextureOptions.NEAREST);
}
public RenderTexture(final TextureManager pTextureManager, final int pWidth, final int pHeight, final PixelFormat pPixelFormat) {
this(pTextureManager, pWidth, pHeight, pPixelFormat, TextureOptions.NEAREST);
}
public RenderTexture(final TextureManager pTextureManager, final int pWidth, final int pHeight, final TextureOptions pTextureOptions) {
this(pTextureManager, pWidth, pHeight, PixelFormat.RGBA_8888, pTextureOptions);
}
public RenderTexture(final TextureManager pTextureManager, final int pWidth, final int pHeight, final PixelFormat pPixelFormat, final TextureOptions pTextureOptions) {
this(pTextureManager, pWidth, pHeight, pPixelFormat, pTextureOptions, null);
}
public RenderTexture(final TextureManager pTextureManager, final int pWidth, final int pHeight, final PixelFormat pPixelFormat, final TextureOptions pTextureOptions, final ITextureStateListener pTextureStateListener) {
super(pTextureManager, pPixelFormat, pTextureOptions, pTextureStateListener);
this.mWidth = pWidth;
this.mHeight = pHeight;
this.mPixelFormat = pPixelFormat;
}
// ===========================================================
// Getter & Setter
// ===========================================================
@Override
public int getWidth() {
return this.mWidth;
}
@Override
public int getHeight() {
return this.mHeight;
}
public boolean isInitialized() {
return this.mInitialized;
}
// ===========================================================
// Methods for/from SuperClass/Interfaces
// ===========================================================
@Override
protected void writeTextureToHardware(final GLState pGLState) {
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, this.mPixelFormat.getGLInternalFormat(), this.mWidth, this.mHeight, 0, this.mPixelFormat.getGLFormat(), this.mPixelFormat.getGLType(), null);
}
// ===========================================================
// Methods
// ===========================================================
/**
* @param pGLState
* @throws RenderTextureInitializationException when this {@link RenderTexture} could not be initialized. The {@link GLException} contains the error code. When this exception is throw, all cleanup will be automatically performed through {@link RenderTexture#destroy(GLState)}.
*/
public void init(final GLState pGLState) throws GLFrameBufferException, GLException {
this.savePreviousFramebufferObjectID(pGLState);
try {
this.loadToHardware(pGLState);
} catch (final IOException e) {
/* Can not happen. */
}
/* The texture to render to must not be bound. */
pGLState.bindTexture(0);
/* Generate FBO. */
this.mFramebufferObjectID = pGLState.generateFramebuffer();
pGLState.bindFramebuffer(this.mFramebufferObjectID);
/* Attach texture to FBO. */
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, this.mHardwareTextureID, 0);
try {
pGLState.checkFramebufferStatus();
} catch (final GLException e) {
this.destroy(pGLState);
throw new RenderTextureInitializationException(e);
} finally {
this.restorePreviousFramebufferObjectID(pGLState);
}
this.mInitialized = true;
}
/**
* @see {@link RenderTexture#end(GLState)},
* {@link RenderTexture#end(GLState, boolean, boolean}}.
*/
public void begin(final GLState pGLState) {
this.begin(pGLState, false, false);
}
/**
* @see {@link RenderTexture#end(GLState)},
* {@link RenderTexture#end(GLState, boolean, boolean}}.
*
* @param pColor the {@link Color} to clear this {@link RenderTexture}.
*/
public void begin(final GLState pGLState, final Color pColor) {
this.begin(pGLState, pColor.getRed(), pColor.getGreen(), pColor.getBlue(), pColor.getAlpha());
}
/**
* @see {@link RenderTexture#end(GLState)},
* {@link RenderTexture#end(GLState, boolean, boolean}}.
*
* @param pRed the red portion of the color to clear this {@link RenderTexture}.
* @param pGreen the green portion of the color to clear this {@link RenderTexture}.
* @param pBlue the blue portion of the color to clear this {@link RenderTexture}.
* @param pAlpha the alpha portion of the color to clear this {@link RenderTexture}.
*/
public void begin(final GLState pGLState, final float pRed, final float pGreen, final float pBlue, final float pAlpha) {
this.begin(pGLState, false, false, pRed, pGreen, pBlue, pAlpha);
}
/**
* @see {@link RenderTexture#end(GLState)},
* {@link RenderTexture#end(GLState, boolean, boolean}}.
*
* @param pColor the {@link Color} to clear this {@link RenderTexture}.
*/
public void begin(final GLState pGLState, final boolean pFlipX, final boolean pFlipY, final Color pColor) {
this.begin(pGLState, pFlipX, pFlipY, pColor.getRed(), pColor.getGreen(), pColor.getBlue(), pColor.getAlpha());
}
/**
* @see {@link RenderTexture#end(GLState)},
* {@link RenderTexture#end(GLState, boolean, boolean}}.
*
* @param pRed the red portion of the color to clear this {@link RenderTexture}.
* @param pGreen the green portion of the color to clear this {@link RenderTexture}.
* @param pBlue the blue portion of the color to clear this {@link RenderTexture}.
* @param pAlpha the alpha portion of the color to clear this {@link RenderTexture}.
*/
public void begin(final GLState pGLState, final boolean pFlipX, final boolean pFlipY, final float pRed, final float pGreen, final float pBlue, final float pAlpha) {
this.begin(pGLState, pFlipX, pFlipY);
/* Save clear color. */
GLES20.glGetFloatv(GLES20.GL_COLOR_CLEAR_VALUE, RenderTexture.CLEARCOLOR_CONTAINER, 0);
GLES20.glClearColor(pRed, pGreen, pBlue, pAlpha);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
/* Restore clear color. */
GLES20.glClearColor(RenderTexture.CLEARCOLOR_CONTAINER[RenderTexture.CLEARCOLOR_CONTAINER_RED_INDEX], RenderTexture.CLEARCOLOR_CONTAINER[RenderTexture.CLEARCOLOR_CONTAINER_GREEN_INDEX], RenderTexture.CLEARCOLOR_CONTAINER[RenderTexture.CLEARCOLOR_CONTAINER_BLUE_INDEX], RenderTexture.CLEARCOLOR_CONTAINER[RenderTexture.CLEARCOLOR_CONTAINER_ALPHA_INDEX]);
}
/**
* @see {@link RenderTexture#end(GLState)},
* {@link RenderTexture#end(GLState, boolean, boolean}}.
*/
public void begin(final GLState pGLState, final boolean pFlipX, final boolean pFlipY) {
this.savePreviousViewport();
GLES20.glViewport(0, 0, this.mWidth, this.mHeight);
pGLState.pushProjectionGLMatrix();
final float left;
final float right;
final float bottom;
final float top;
if(pFlipX) {
left = this.mWidth;
right = 0;
} else {
left = 0;
right = this.mWidth;
}
if(pFlipY) {
top = this.mHeight;
bottom = 0;
} else {
top = 0;
bottom = this.mHeight;
}
pGLState.orthoProjectionGLMatrixf(left, right, bottom, top, -1, 1);
this.savePreviousFramebufferObjectID(pGLState);
pGLState.bindFramebuffer(this.mFramebufferObjectID);
pGLState.pushModelViewGLMatrix();
pGLState.loadModelViewGLMatrixIdentity();
}
/**
* @see {@link GLState#flush()}.
*/
public void flush(final GLState pGLState) {
pGLState.flush();
}
/**
* @see {@link GLState#finish()}.
*/
public void finish(final GLState pGLState) {
pGLState.finish();
}
/**
* @see {@link RenderTexture#begin(GLState)},
* {@link RenderTexture#begin(GLState, boolean, boolean)},
* {@link RenderTexture#begin(GLState, Color)},
* {@link RenderTexture#begin(GLState, float, float, float, float)},
* {@link RenderTexture#begin(GLState, boolean, boolean, Color)}.
* {@link RenderTexture#begin(GLState, boolean, boolean, float, float, float, float)}.
*/
public void end(final GLState pGLState) {
this.end(pGLState, false, false);
}
/**
* @param pGLState
* @param pFlush {@link GLState#flush()} has lower preference than pFinish.
* @param pFinish {@link GLState#finish()} had higher preference than pFlush.
*
* @see {@link RenderTexture#begin(GLState)},
* {@link RenderTexture#begin(GLState, boolean, boolean)},
* {@link RenderTexture#begin(GLState, Color)},
* {@link RenderTexture#begin(GLState, float, float, float, float)},
* {@link RenderTexture#begin(GLState, boolean, boolean, Color)}.
* {@link RenderTexture#begin(GLState, boolean, boolean, float, float, float, float)}.
*/
public void end(final GLState pGLState, final boolean pFlush, final boolean pFinish) {
if(pFinish) {
this.finish(pGLState);
} else if(pFlush) {
this.flush(pGLState);
}
pGLState.popModelViewGLMatrix();
this.restorePreviousFramebufferObjectID(pGLState);
pGLState.popProjectionGLMatrix();
this.resotorePreviousViewport();
}
public void destroy(final GLState pGLState) {
this.unloadFromHardware(pGLState);
pGLState.deleteFramebuffer(this.mFramebufferObjectID);
this.mInitialized = false;
}
protected void savePreviousFramebufferObjectID(final GLState pGLState) {
this.mPreviousFramebufferObjectID = pGLState.getActiveFramebuffer();
}
protected void restorePreviousFramebufferObjectID(final GLState pGLState) {
pGLState.bindFramebuffer(this.mPreviousFramebufferObjectID);
}
protected void savePreviousViewport() {
GLES20.glGetIntegerv(GLES20.GL_VIEWPORT, RenderTexture.VIEWPORT_CONTAINER, 0);
this.mPreviousViewPortX = RenderTexture.VIEWPORT_CONTAINER[RenderTexture.VIEWPORT_CONTAINER_X_INDEX];
this.mPreviousViewPortY = RenderTexture.VIEWPORT_CONTAINER[RenderTexture.VIEWPORT_CONTAINER_Y_INDEX];
this.mPreviousViewPortWidth = RenderTexture.VIEWPORT_CONTAINER[RenderTexture.VIEWPORT_CONTAINER_WIDTH_INDEX];
this.mPreviousViewPortHeight = RenderTexture.VIEWPORT_CONTAINER[RenderTexture.VIEWPORT_CONTAINER_HEIGHT_INDEX];
}
protected void resotorePreviousViewport() {
GLES20.glViewport(this.mPreviousViewPortX, this.mPreviousViewPortY, this.mPreviousViewPortWidth, this.mPreviousViewPortHeight);
}
public int[] getPixelsARGB_8888(final GLState pGLState) {
return this.getPixelsARGB_8888(pGLState, 0, 0, this.mWidth, this.mHeight);
}
public int[] getPixelsARGB_8888(final GLState pGLState, final int pX, final int pY, final int pWidth, final int pHeight) {
final int[] pixelsRGBA_8888 = new int[pWidth * pHeight];
final IntBuffer glPixelBuffer = IntBuffer.wrap(pixelsRGBA_8888);
glPixelBuffer.position(0);
this.begin(pGLState);
GLES20.glReadPixels(pX, pY, pWidth, pHeight, this.mPixelFormat.getGLFormat(), this.mPixelFormat.getGLType(), glPixelBuffer);
this.end(pGLState);
return GLHelper.convertRGBA_8888toARGB_8888(pixelsRGBA_8888);
}
public Bitmap getBitmap(final GLState pGLState) {
return this.getBitmap(pGLState, 0, 0, this.mWidth, this.mHeight);
}
public Bitmap getBitmap(final GLState pGLState, final int pX, final int pY, final int pWidth, final int pHeight) {
if(this.mPixelFormat != PixelFormat.RGBA_8888) {
throw new IllegalStateException("Currently only 'PixelFormat." + PixelFormat.RGBA_8888 + "' is supported to be retrieved as a Bitmap.");
}
return Bitmap.createBitmap(this.getPixelsARGB_8888(pGLState, pX, pY, pWidth, pHeight), pWidth, pHeight, Config.ARGB_8888);
}
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
}