public class

RenderTexture

extends Texture
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.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
	// ===========================================================
}