package org.andengine.opengl.util; import java.nio.Buffer; import java.nio.ByteOrder; import java.util.Arrays; import javax.microedition.khronos.egl.EGLConfig; import org.andengine.BuildConfig; import org.andengine.engine.options.RenderOptions; import org.andengine.opengl.exception.GLException; import org.andengine.opengl.exception.GLFrameBufferException; import org.andengine.opengl.shader.constants.ShaderProgramConstants; import org.andengine.opengl.texture.PixelFormat; import org.andengine.opengl.texture.render.RenderTexture; import org.andengine.opengl.view.ConfigChooser; import org.andengine.util.debug.Debug; import android.graphics.Bitmap; import android.opengl.GLES20; import android.opengl.GLUtils; import android.opengl.Matrix; /** * (c) 2010 Nicolas Gramlich * (c) 2011 Zynga Inc. * * @author Nicolas Gramlich * @since 18:00:43 - 08.03.2010 */ public class GLState { // =========================================================== // Constants // =========================================================== public static final int GL_UNPACK_ALIGNMENT_DEFAULT = 4; // =========================================================== // Fields // =========================================================== private final int[] mHardwareIDContainer = new int[1]; private String mVersion; private String mRenderer; private String mExtensions; private int mMaximumVertexAttributeCount; private int mMaximumVertexShaderUniformVectorCount; private int mMaximumFragmentShaderUniformVectorCount; private int mMaximumTextureSize; private int mMaximumTextureUnits; private int mCurrentArrayBufferID = -1; private int mCurrentIndexBufferID = -1; private int mCurrentShaderProgramID = -1; private final int[] mCurrentBoundTextureIDs = new int[GLES20.GL_TEXTURE31 - GLES20.GL_TEXTURE0]; private int mCurrentFramebufferID = -1; private int mCurrentActiveTextureIndex = 0; private int mCurrentSourceBlendMode = -1; private int mCurrentDestinationBlendMode = -1; private boolean mDitherEnabled = true; private boolean mDepthTestEnabled = true; private boolean mScissorTestEnabled = false; private boolean mBlendEnabled = false; private boolean mCullingEnabled = false; private float mLineWidth = 1; private final GLMatrixStack mModelViewGLMatrixStack = new GLMatrixStack(); private final GLMatrixStack mProjectionGLMatrixStack = new GLMatrixStack(); private final float[] mModelViewGLMatrix = new float[GLMatrixStack.GLMATRIX_SIZE]; private final float[] mProjectionGLMatrix = new float[GLMatrixStack.GLMATRIX_SIZE]; private final float[] mModelViewProjectionGLMatrix = new float[GLMatrixStack.GLMATRIX_SIZE]; // =========================================================== // Getter & Setter // =========================================================== public String getVersion() { return this.mVersion; } public String getRenderer() { return this.mRenderer; } public String getExtensions() { return this.mExtensions; } public int getMaximumVertexAttributeCount() { return this.mMaximumVertexAttributeCount; } public int getMaximumVertexShaderUniformVectorCount() { return this.mMaximumVertexShaderUniformVectorCount; } public int getMaximumFragmentShaderUniformVectorCount() { return this.mMaximumFragmentShaderUniformVectorCount; } public int getMaximumTextureUnits() { return this.mMaximumTextureUnits; } public int getMaximumTextureSize() { return this.mMaximumTextureSize; } // =========================================================== // Methods // =========================================================== public void reset(final RenderOptions pRenderOptions, final ConfigChooser pConfigChooser, final EGLConfig pEGLConfig) { this.mVersion = GLES20.glGetString(GLES20.GL_VERSION); this.mRenderer = GLES20.glGetString(GLES20.GL_RENDERER); this.mExtensions = GLES20.glGetString(GLES20.GL_EXTENSIONS); this.mMaximumVertexAttributeCount = this.getInteger(GLES20.GL_MAX_VERTEX_ATTRIBS); this.mMaximumVertexShaderUniformVectorCount = this.getInteger(GLES20.GL_MAX_VERTEX_UNIFORM_VECTORS); this.mMaximumFragmentShaderUniformVectorCount = this.getInteger(GLES20.GL_MAX_FRAGMENT_UNIFORM_VECTORS); this.mMaximumTextureUnits = this.getInteger(GLES20.GL_MAX_TEXTURE_IMAGE_UNITS); this.mMaximumTextureSize = this.getInteger(GLES20.GL_MAX_TEXTURE_SIZE); if(BuildConfig.DEBUG) { Debug.d("VERSION: " + this.mVersion); Debug.d("RENDERER: " + this.mRenderer); Debug.d("EGLCONFIG: " + EGLConfig.class.getSimpleName() + "(Red=" + pConfigChooser.getRedSize() + ", Green=" + pConfigChooser.getGreenSize() + ", Blue=" + pConfigChooser.getBlueSize() + ", Alpha=" + pConfigChooser.getAlphaSize() + ", Depth=" + pConfigChooser.getDepthSize() + ", Stencil=" + pConfigChooser.getStencilSize() + ")"); Debug.d("EXTENSIONS: " + this.mExtensions); Debug.d("MAX_VERTEX_ATTRIBS: " + this.mMaximumVertexAttributeCount); Debug.d("MAX_VERTEX_UNIFORM_VECTORS: " + this.mMaximumVertexShaderUniformVectorCount); Debug.d("MAX_FRAGMENT_UNIFORM_VECTORS: " + this.mMaximumFragmentShaderUniformVectorCount); Debug.d("MAX_TEXTURE_IMAGE_UNITS: " + this.mMaximumTextureUnits); Debug.d("MAX_TEXTURE_SIZE: " + this.mMaximumTextureSize); } this.mModelViewGLMatrixStack.reset(); this.mProjectionGLMatrixStack.reset(); this.mCurrentArrayBufferID = -1; this.mCurrentIndexBufferID = -1; this.mCurrentShaderProgramID = -1; Arrays.fill(this.mCurrentBoundTextureIDs, -1); this.mCurrentFramebufferID = -1; this.mCurrentActiveTextureIndex = 0; this.mCurrentSourceBlendMode = -1; this.mCurrentDestinationBlendMode = -1; this.enableDither(); this.enableDepthTest(); this.disableBlend(); this.disableCulling(); GLES20.glEnableVertexAttribArray(ShaderProgramConstants.ATTRIBUTE_POSITION_LOCATION); GLES20.glEnableVertexAttribArray(ShaderProgramConstants.ATTRIBUTE_COLOR_LOCATION); GLES20.glEnableVertexAttribArray(ShaderProgramConstants.ATTRIBUTE_TEXTURECOORDINATES_LOCATION); this.mLineWidth = 1; } public boolean isScissorTestEnabled() { return this.mScissorTestEnabled; } /** * @return the previous state. */ public boolean enableScissorTest() { if(this.mScissorTestEnabled) { return true; } this.mScissorTestEnabled = true; GLES20.glEnable(GLES20.GL_SCISSOR_TEST); return false; } /** * @return the previous state. */ public boolean disableScissorTest() { if(!this.mScissorTestEnabled) { return false; } this.mScissorTestEnabled = false; GLES20.glDisable(GLES20.GL_SCISSOR_TEST); return true; } /** * @return the previous state. */ public boolean setScissorTestEnabled(final boolean pEnabled) { if(pEnabled) { return this.enableScissorTest(); } else { return this.disableScissorTest(); } } public boolean isBlendEnabled() { return this.mBlendEnabled; } /** * @return the previous state. */ public boolean enableBlend() { if(this.mBlendEnabled) { return true; } this.mBlendEnabled = true; GLES20.glEnable(GLES20.GL_BLEND); return false; } /** * @return the previous state. */ public boolean disableBlend() { if(!this.mBlendEnabled) { return false; } this.mBlendEnabled = false; GLES20.glDisable(GLES20.GL_BLEND); return true; } /** * @return the previous state. */ public boolean setBlendEnabled(final boolean pEnabled) { if(pEnabled) { return this.enableBlend(); } else { return this.disableBlend(); } } public boolean isCullingEnabled() { return this.mCullingEnabled; } /** * @return the previous state. */ public boolean enableCulling() { if(this.mCullingEnabled) { return true; } this.mCullingEnabled = true; GLES20.glEnable(GLES20.GL_CULL_FACE); return false; } /** * @return the previous state. */ public boolean disableCulling() { if(!this.mCullingEnabled) { return false; } this.mCullingEnabled = false; GLES20.glDisable(GLES20.GL_CULL_FACE); return true; } /** * @return the previous state. */ public boolean setCullingEnabled(final boolean pEnabled) { if(pEnabled) { return this.enableCulling(); } else { return this.disableCulling(); } } public boolean isDitherEnabled() { return this.mDitherEnabled; } /** * @return the previous state. */ public boolean enableDither() { if(this.mDitherEnabled) { return true; } this.mDitherEnabled = true; GLES20.glEnable(GLES20.GL_DITHER); return false; } /** * @return the previous state. */ public boolean disableDither() { if(!this.mDitherEnabled) { return false; } this.mDitherEnabled = false; GLES20.glDisable(GLES20.GL_DITHER); return true; } /** * @return the previous state. */ public boolean setDitherEnabled(final boolean pEnabled) { if(pEnabled) { return this.enableDither(); } else { return this.disableDither(); } } public boolean isDepthTestEnabled() { return this.mDepthTestEnabled; } /** * @return the previous state. */ public boolean enableDepthTest() { if(this.mDepthTestEnabled) { return true; } this.mDepthTestEnabled = true; GLES20.glEnable(GLES20.GL_DEPTH_TEST); return false; } /** * @return the previous state. */ public boolean disableDepthTest() { if(!this.mDepthTestEnabled) { return false; } this.mDepthTestEnabled = false; GLES20.glDisable(GLES20.GL_DEPTH_TEST); return true; } /** * @return the previous state. */ public boolean setDepthTestEnabled(final boolean pEnabled) { if(pEnabled) { return this.enableDepthTest(); } else { return this.disableDepthTest(); } } public int generateBuffer() { GLES20.glGenBuffers(1, this.mHardwareIDContainer, 0); return this.mHardwareIDContainer[0]; } public int generateArrayBuffer(final int pSize, final int pUsage) { GLES20.glGenBuffers(1, this.mHardwareIDContainer, 0); final int hardwareBufferID = this.mHardwareIDContainer[0]; this.bindArrayBuffer(hardwareBufferID); GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, pSize, null, pUsage); this.bindArrayBuffer(0); return hardwareBufferID; } public void bindArrayBuffer(final int pHardwareBufferID) { if(this.mCurrentArrayBufferID != pHardwareBufferID) { this.mCurrentArrayBufferID = pHardwareBufferID; GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, pHardwareBufferID); } } public void deleteArrayBuffer(final int pHardwareBufferID) { if(this.mCurrentArrayBufferID == pHardwareBufferID) { this.mCurrentArrayBufferID = -1; } this.mHardwareIDContainer[0] = pHardwareBufferID; GLES20.glDeleteBuffers(1, this.mHardwareIDContainer, 0); } public int generateIndexBuffer(final int pSize, final int pUsage) { GLES20.glGenBuffers(1, this.mHardwareIDContainer, 0); final int hardwareBufferID = this.mHardwareIDContainer[0]; this.bindIndexBuffer(hardwareBufferID); GLES20.glBufferData(GLES20.GL_ELEMENT_ARRAY_BUFFER, pSize, null, pUsage); this.bindIndexBuffer(0); return hardwareBufferID; } public void bindIndexBuffer(final int pHardwareBufferID) { if(this.mCurrentIndexBufferID != pHardwareBufferID) { this.mCurrentIndexBufferID = pHardwareBufferID; GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, pHardwareBufferID); } } public void deleteIndexBuffer(final int pHardwareBufferID) { if(this.mCurrentIndexBufferID == pHardwareBufferID) { this.mCurrentIndexBufferID = -1; } this.mHardwareIDContainer[0] = pHardwareBufferID; GLES20.glDeleteBuffers(1, this.mHardwareIDContainer, 0); } public int generateFramebuffer() { GLES20.glGenFramebuffers(1, this.mHardwareIDContainer, 0); return this.mHardwareIDContainer[0]; } public void bindFramebuffer(final int pFramebufferID) { GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, pFramebufferID); } public int getFramebufferStatus() { return GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER); } public void checkFramebufferStatus() throws GLFrameBufferException, GLException { final int framebufferStatus = this.getFramebufferStatus(); switch(framebufferStatus) { case GLES20.GL_FRAMEBUFFER_COMPLETE: return; case GLES20.GL_FRAMEBUFFER_UNSUPPORTED: throw new GLFrameBufferException(framebufferStatus, "GL_FRAMEBUFFER_UNSUPPORTED"); case GLES20.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: throw new GLFrameBufferException(framebufferStatus, "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"); case GLES20.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: throw new GLFrameBufferException(framebufferStatus, "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS"); case GLES20.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: throw new GLFrameBufferException(framebufferStatus, "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"); case 0: this.checkError(); default: throw new GLFrameBufferException(framebufferStatus); } } public int getActiveFramebuffer() { return this.getInteger(GLES20.GL_FRAMEBUFFER_BINDING); } public void deleteFramebuffer(final int pHardwareFramebufferID) { if(this.mCurrentFramebufferID == pHardwareFramebufferID) { this.mCurrentFramebufferID = -1; } this.mHardwareIDContainer[0] = pHardwareFramebufferID; GLES20.glDeleteFramebuffers(1, this.mHardwareIDContainer, 0); } public void useProgram(final int pShaderProgramID) { if(this.mCurrentShaderProgramID != pShaderProgramID) { this.mCurrentShaderProgramID = pShaderProgramID; GLES20.glUseProgram(pShaderProgramID); } } public void deleteProgram(final int pShaderProgramID) { if(this.mCurrentShaderProgramID == pShaderProgramID) { this.mCurrentShaderProgramID = -1; } GLES20.glDeleteProgram(pShaderProgramID); } public int generateTexture() { GLES20.glGenTextures(1, this.mHardwareIDContainer, 0); return this.mHardwareIDContainer[0]; } public boolean isTexture(final int pHardwareTextureID) { return GLES20.glIsTexture(pHardwareTextureID); } /** * @return {@link GLES20#GL_TEXTURE0} to {@link GLES20#GL_TEXTURE31} */ public int getActiveTexture() { return this.mCurrentActiveTextureIndex + GLES20.GL_TEXTURE0; } /** * @param pGLActiveTexture from {@link GLES20#GL_TEXTURE0} to {@link GLES20#GL_TEXTURE31}. */ public void activeTexture(final int pGLActiveTexture) { final int activeTextureIndex = pGLActiveTexture - GLES20.GL_TEXTURE0; if(pGLActiveTexture != this.mCurrentActiveTextureIndex) { this.mCurrentActiveTextureIndex = activeTextureIndex; GLES20.glActiveTexture(pGLActiveTexture); } } /** * @see {@link GLState#forceBindTexture(GLES20, int)} * @param GLES20 * @param pHardwareTextureID */ public void bindTexture(final int pHardwareTextureID) { if(this.mCurrentBoundTextureIDs[this.mCurrentActiveTextureIndex] != pHardwareTextureID) { this.mCurrentBoundTextureIDs[this.mCurrentActiveTextureIndex] = pHardwareTextureID; GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, pHardwareTextureID); } } public void deleteTexture(final int pHardwareTextureID) { if(this.mCurrentBoundTextureIDs[this.mCurrentActiveTextureIndex] == pHardwareTextureID) { this.mCurrentBoundTextureIDs[this.mCurrentActiveTextureIndex] = -1; } this.mHardwareIDContainer[0] = pHardwareTextureID; GLES20.glDeleteTextures(1, this.mHardwareIDContainer, 0); } public void blendFunction(final int pSourceBlendMode, final int pDestinationBlendMode) { if(this.mCurrentSourceBlendMode != pSourceBlendMode || this.mCurrentDestinationBlendMode != pDestinationBlendMode) { this.mCurrentSourceBlendMode = pSourceBlendMode; this.mCurrentDestinationBlendMode = pDestinationBlendMode; GLES20.glBlendFunc(pSourceBlendMode, pDestinationBlendMode); } } public void lineWidth(final float pLineWidth) { if(this.mLineWidth != pLineWidth) { this.mLineWidth = pLineWidth; GLES20.glLineWidth(pLineWidth); } } public void pushModelViewGLMatrix() { this.mModelViewGLMatrixStack.glPushMatrix(); } public void popModelViewGLMatrix() { this.mModelViewGLMatrixStack.glPopMatrix(); } public void loadModelViewGLMatrixIdentity() { this.mModelViewGLMatrixStack.glLoadIdentity(); } public void translateModelViewGLMatrixf(final float pX, final float pY, final float pZ) { this.mModelViewGLMatrixStack.glTranslatef(pX, pY, pZ); } public void rotateModelViewGLMatrixf(final float pAngle, final float pX, final float pY, final float pZ) { this.mModelViewGLMatrixStack.glRotatef(pAngle, pX, pY, pZ); } public void scaleModelViewGLMatrixf(final float pScaleX, final float pScaleY, final int pScaleZ) { this.mModelViewGLMatrixStack.glScalef(pScaleX, pScaleY, pScaleZ); } public void skewModelViewGLMatrixf(final float pSkewX, final float pSkewY) { this.mModelViewGLMatrixStack.glSkewf(pSkewX, pSkewY); } public void orthoModelViewGLMatrixf(final float pLeft, final float pRight, final float pBottom, final float pTop, final float pZNear, final float pZFar) { this.mModelViewGLMatrixStack.glOrthof(pLeft, pRight, pBottom, pTop, pZNear, pZFar); } public void pushProjectionGLMatrix() { this.mProjectionGLMatrixStack.glPushMatrix(); } public void popProjectionGLMatrix() { this.mProjectionGLMatrixStack.glPopMatrix(); } public void loadProjectionGLMatrixIdentity() { this.mProjectionGLMatrixStack.glLoadIdentity(); } public void translateProjectionGLMatrixf(final float pX, final float pY, final float pZ) { this.mProjectionGLMatrixStack.glTranslatef(pX, pY, pZ); } public void rotateProjectionGLMatrixf(final float pAngle, final float pX, final float pY, final float pZ) { this.mProjectionGLMatrixStack.glRotatef(pAngle, pX, pY, pZ); } public void scaleProjectionGLMatrixf(final float pScaleX, final float pScaleY, final float pScaleZ) { this.mProjectionGLMatrixStack.glScalef(pScaleX, pScaleY, pScaleZ); } public void skewProjectionGLMatrixf(final float pSkewX, final float pSkewY) { this.mProjectionGLMatrixStack.glSkewf(pSkewX, pSkewY); } public void orthoProjectionGLMatrixf(final float pLeft, final float pRight, final float pBottom, final float pTop, final float pZNear, final float pZFar) { this.mProjectionGLMatrixStack.glOrthof(pLeft, pRight, pBottom, pTop, pZNear, pZFar); } public float[] getModelViewGLMatrix() { this.mModelViewGLMatrixStack.getMatrix(this.mModelViewGLMatrix); return this.mModelViewGLMatrix; } public float[] getProjectionGLMatrix() { this.mProjectionGLMatrixStack.getMatrix(this.mProjectionGLMatrix); return this.mProjectionGLMatrix; } public float[] getModelViewProjectionGLMatrix() { Matrix.multiplyMM(this.mModelViewProjectionGLMatrix, 0, this.mProjectionGLMatrixStack.mMatrixStack, this.mProjectionGLMatrixStack.mMatrixStackOffset, this.mModelViewGLMatrixStack.mMatrixStack, this.mModelViewGLMatrixStack.mMatrixStackOffset); return this.mModelViewProjectionGLMatrix; } public void resetModelViewGLMatrixStack() { this.mModelViewGLMatrixStack.reset(); } public void resetProjectionGLMatrixStack() { this.mProjectionGLMatrixStack.reset(); } public void resetGLMatrixStacks() { this.mModelViewGLMatrixStack.reset(); this.mProjectionGLMatrixStack.reset(); } /** * <b>Note:</b> does not pre-multiply the alpha channel!</br> * Except that difference, same as: {@link GLUtils#texSubImage2D(int, int, int, int, Bitmap, int, int)}</br> * </br> * See topic: '<a href="http://groups.google.com/group/android-developers/browse_thread/thread/baa6c33e63f82fca">PNG loading that doesn't premultiply alpha?</a>' * @param pBorder */ public void glTexImage2D(final int pTarget, final int pLevel, final Bitmap pBitmap, final int pBorder, final PixelFormat pPixelFormat) { final Buffer pixelBuffer = GLHelper.getPixels(pBitmap, pPixelFormat, ByteOrder.BIG_ENDIAN); GLES20.glTexImage2D(pTarget, pLevel, pPixelFormat.getGLInternalFormat(), pBitmap.getWidth(), pBitmap.getHeight(), pBorder, pPixelFormat.getGLFormat(), pPixelFormat.getGLType(), pixelBuffer); } /** * <b>Note:</b> does not pre-multiply the alpha channel!</br> * Except that difference, same as: {@link GLUtils#texSubImage2D(int, int, int, int, Bitmap, int, int)}</br> * </br> * See topic: '<a href="http://groups.google.com/group/android-developers/browse_thread/thread/baa6c33e63f82fca">PNG loading that doesn't premultiply alpha?</a>' */ public void glTexSubImage2D(final int pTarget, final int pLevel, final int pX, final int pY, final Bitmap pBitmap, final PixelFormat pPixelFormat) { final Buffer pixelBuffer = GLHelper.getPixels(pBitmap, pPixelFormat, ByteOrder.BIG_ENDIAN); GLES20.glTexSubImage2D(pTarget, pLevel, pX, pY, pBitmap.getWidth(), pBitmap.getHeight(), pPixelFormat.getGLFormat(), pPixelFormat.getGLType(), pixelBuffer); } /** * Tells the OpenGL driver to send all pending commands to the GPU immediately. * * @see {@link GLState#finish()}, * {@link RenderTexture#end(GLState, boolean, boolean)}. */ public void flush() { GLES20.glFlush(); } /** * Tells the OpenGL driver to send all pending commands to the GPU immediately, * and then blocks until the effects of those commands have been completed on the GPU. * Since this is a costly method it should be only called when really needed. * * @see {@link GLState#flush()}, * {@link RenderTexture#end(GLState, boolean, boolean)}. */ public void finish() { GLES20.glFinish(); } public int getInteger(final int pAttribute) { GLES20.glGetIntegerv(pAttribute, this.mHardwareIDContainer, 0); return this.mHardwareIDContainer[0]; } public int getError() { return GLES20.glGetError(); } public void checkError() throws GLException { final int error = GLES20.glGetError(); if(error != GLES20.GL_NO_ERROR) { throw new GLException(error); } } public void clearError() { GLES20.glGetError(); } // =========================================================== // Inner and Anonymous Classes // =========================================================== }