package org.andengine.opengl.shader; import java.util.HashMap; import org.andengine.opengl.shader.constants.ShaderProgramConstants; import org.andengine.opengl.shader.exception.ShaderProgramCompileException; import org.andengine.opengl.shader.exception.ShaderProgramException; import org.andengine.opengl.shader.exception.ShaderProgramLinkException; import org.andengine.opengl.shader.source.IShaderSource; import org.andengine.opengl.shader.source.StringShaderSource; import org.andengine.opengl.util.GLState; import org.andengine.opengl.vbo.attribute.VertexBufferObjectAttribute; import org.andengine.opengl.vbo.attribute.VertexBufferObjectAttributes; import android.opengl.GLES20; /** * (c) Zynga 2011 * * @author Nicolas Gramlich <ngramlich@zynga.com> * @since 19:56:34 - 05.08.2011 */ public class ShaderProgram { // =========================================================== // Constants // =========================================================== private static final int[] HARDWAREID_CONTAINER = new int[1]; private static final int[] PARAMETERS_CONTAINER = new int[1]; private static final int[] LENGTH_CONTAINER = new int[1]; private static final int[] SIZE_CONTAINER = new int[1]; private static final int[] TYPE_CONTAINER = new int[1]; private static final int NAME_CONTAINER_SIZE = 64; private static final byte[] NAME_CONTAINER = new byte[ShaderProgram.NAME_CONTAINER_SIZE]; // =========================================================== // Fields // =========================================================== protected final IShaderSource mVertexShaderSource; protected final IShaderSource mFragmentShaderSource; protected int mProgramID = -1; protected boolean mCompiled; protected final HashMap<String, Integer> mUniformLocations = new HashMap<String, Integer>(); protected final HashMap<String, Integer> mAttributeLocations = new HashMap<String, Integer>(); // =========================================================== // Constructors // =========================================================== public ShaderProgram(final String pVertexShaderSource, final String pFragmentShaderSource) { this(new StringShaderSource(pVertexShaderSource), new StringShaderSource(pFragmentShaderSource)); } public ShaderProgram(final IShaderSource pVertexShaderSource, final IShaderSource pFragmentShaderSource) { this.mVertexShaderSource = pVertexShaderSource; this.mFragmentShaderSource = pFragmentShaderSource; } // =========================================================== // Getter & Setter // =========================================================== public boolean isCompiled() { return this.mCompiled; } public void setCompiled(final boolean pCompiled) { this.mCompiled = pCompiled; } public int getAttributeLocation(final String pAttributeName) { final Integer location = this.mAttributeLocations.get(pAttributeName); if(location != null) { return location.intValue(); } else { throw new ShaderProgramException("Unexpected attribute: '" + pAttributeName + "'. Existing attributes: " + this.mAttributeLocations.toString()); } } public int getAttributeLocationOptional(final String pAttributeName) { final Integer location = this.mAttributeLocations.get(pAttributeName); if(location != null) { return location.intValue(); } else { return ShaderProgramConstants.LOCATION_INVALID; } } public int getUniformLocation(final String pUniformName) { final Integer location = this.mUniformLocations.get(pUniformName); if(location != null) { return location.intValue(); } else { throw new ShaderProgramException("Unexpected uniform: '" + pUniformName + "'. Existing uniforms: " + this.mUniformLocations.toString()); } } public int getUniformLocationOptional(final String pUniformName) { final Integer location = this.mUniformLocations.get(pUniformName); if(location != null) { return location.intValue(); } else { return ShaderProgramConstants.LOCATION_INVALID; } } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== // =========================================================== // Methods // =========================================================== public void bind(final GLState pGLState, final VertexBufferObjectAttributes pVertexBufferObjectAttributes) throws ShaderProgramException { if(!this.mCompiled) { this.compile(pGLState); } pGLState.useProgram(this.mProgramID); pVertexBufferObjectAttributes.glVertexAttribPointers(); } public void unbind(final GLState pGLState) throws ShaderProgramException { // pGLState.useProgram(0); // TODO Does this have an positive/negative impact on performance? } public void delete(final GLState pGLState) { if(this.mCompiled) { this.mCompiled = false; pGLState.deleteProgram(this.mProgramID); this.mProgramID = -1; } } protected void compile(final GLState pGLState) throws ShaderProgramException { final String vertexShaderSource = this.mVertexShaderSource.getShaderSource(pGLState); final int vertexShaderID = ShaderProgram.compileShader(vertexShaderSource, GLES20.GL_VERTEX_SHADER); final String fragmentShaderSource = this.mFragmentShaderSource.getShaderSource(pGLState); final int fragmentShaderID = ShaderProgram.compileShader(fragmentShaderSource, GLES20.GL_FRAGMENT_SHADER); this.mProgramID = GLES20.glCreateProgram(); GLES20.glAttachShader(this.mProgramID, vertexShaderID); GLES20.glAttachShader(this.mProgramID, fragmentShaderID); try { this.link(pGLState); } catch (final ShaderProgramLinkException e) { throw new ShaderProgramLinkException("VertexShaderSource:\n##########################\n" + vertexShaderSource + "\n##########################" + "\n\nFragmentShaderSource:\n##########################\n" + fragmentShaderSource + "\n##########################", e); } GLES20.glDeleteShader(vertexShaderID); GLES20.glDeleteShader(fragmentShaderID); } protected void link(final GLState pGLState) throws ShaderProgramLinkException { GLES20.glLinkProgram(this.mProgramID); GLES20.glGetProgramiv(this.mProgramID, GLES20.GL_LINK_STATUS, ShaderProgram.HARDWAREID_CONTAINER, 0); if(ShaderProgram.HARDWAREID_CONTAINER[0] == 0) { throw new ShaderProgramLinkException(GLES20.glGetProgramInfoLog(this.mProgramID)); } this.initAttributeLocations(); this.initUniformLocations(); this.mCompiled = true; } private static int compileShader(final String pSource, final int pType) throws ShaderProgramException { final int shaderID = GLES20.glCreateShader(pType); if(shaderID == 0) { throw new ShaderProgramException("Could not create Shader of type: '" + pType + '"'); } GLES20.glShaderSource(shaderID, pSource); GLES20.glCompileShader(shaderID); GLES20.glGetShaderiv(shaderID, GLES20.GL_COMPILE_STATUS, ShaderProgram.HARDWAREID_CONTAINER, 0); if(ShaderProgram.HARDWAREID_CONTAINER[0] == 0) { throw new ShaderProgramCompileException(GLES20.glGetShaderInfoLog(shaderID), pSource); } return shaderID; } private void initUniformLocations() throws ShaderProgramLinkException { this.mUniformLocations.clear(); ShaderProgram.PARAMETERS_CONTAINER[0] = 0; GLES20.glGetProgramiv(this.mProgramID, GLES20.GL_ACTIVE_UNIFORMS, ShaderProgram.PARAMETERS_CONTAINER, 0); final int numUniforms = ShaderProgram.PARAMETERS_CONTAINER[0]; for(int i = 0; i < numUniforms; i++) { GLES20.glGetActiveUniform(this.mProgramID, i, ShaderProgram.NAME_CONTAINER_SIZE, ShaderProgram.LENGTH_CONTAINER, 0, ShaderProgram.SIZE_CONTAINER, 0, ShaderProgram.TYPE_CONTAINER, 0, ShaderProgram.NAME_CONTAINER, 0); int length = ShaderProgram.LENGTH_CONTAINER[0]; /* Some drivers do not report the actual length here, but zero. Then the name is '\0' terminated. */ if(length == 0) { while((length < ShaderProgram.NAME_CONTAINER_SIZE) && (ShaderProgram.NAME_CONTAINER[length] != '\0')) { length++; } } String name = new String(ShaderProgram.NAME_CONTAINER, 0, length); int location = GLES20.glGetUniformLocation(this.mProgramID, name); if(location == ShaderProgramConstants.LOCATION_INVALID) { /* Some drivers do not report an incorrect length. Then the name is '\0' terminated. */ length = 0; while(length < ShaderProgram.NAME_CONTAINER_SIZE && ShaderProgram.NAME_CONTAINER[length] != '\0') { length++; } name = new String(ShaderProgram.NAME_CONTAINER, 0, length); location = GLES20.glGetUniformLocation(this.mProgramID, name); if(location == ShaderProgramConstants.LOCATION_INVALID) { throw new ShaderProgramLinkException("Invalid location for uniform: '" + name + "'."); } } this.mUniformLocations.put(name, location); } } /** * TODO Is this actually needed? As the locations of {@link VertexBufferObjectAttribute}s are now 'predefined'. */ @Deprecated private void initAttributeLocations() { this.mAttributeLocations.clear(); ShaderProgram.PARAMETERS_CONTAINER[0] = 0; GLES20.glGetProgramiv(this.mProgramID, GLES20.GL_ACTIVE_ATTRIBUTES, ShaderProgram.PARAMETERS_CONTAINER, 0); final int numAttributes = ShaderProgram.PARAMETERS_CONTAINER[0]; for(int i = 0; i < numAttributes; i++) { GLES20.glGetActiveAttrib(this.mProgramID, i, ShaderProgram.NAME_CONTAINER_SIZE, ShaderProgram.LENGTH_CONTAINER, 0, ShaderProgram.SIZE_CONTAINER, 0, ShaderProgram.TYPE_CONTAINER, 0, ShaderProgram.NAME_CONTAINER, 0); int length = ShaderProgram.LENGTH_CONTAINER[0]; /* Some drivers do not report the actual length here, but zero. Then the name is '\0' terminated. */ if(length == 0) { while((length < ShaderProgram.NAME_CONTAINER_SIZE) && (ShaderProgram.NAME_CONTAINER[length] != '\0')) { length++; } } String name = new String(ShaderProgram.NAME_CONTAINER, 0, length); int location = GLES20.glGetAttribLocation(this.mProgramID, name); if(location == ShaderProgramConstants.LOCATION_INVALID) { /* Some drivers do not report an incorrect length. Then the name is '\0' terminated. */ length = 0; while(length < ShaderProgram.NAME_CONTAINER_SIZE && ShaderProgram.NAME_CONTAINER[length] != '\0') { length++; } name = new String(ShaderProgram.NAME_CONTAINER, 0, length); location = GLES20.glGetAttribLocation(this.mProgramID, name); if(location == ShaderProgramConstants.LOCATION_INVALID) { throw new ShaderProgramLinkException("Invalid location for attribute: '" + name + "'."); } } this.mAttributeLocations.put(name, location); } } public void setUniform(final String pUniformName, final float[] pGLMatrix) { GLES20.glUniformMatrix4fv(this.getUniformLocation(pUniformName), 1, false, pGLMatrix, 0); } public void setUniformOptional(final String pUniformName, final float[] pGLMatrix) { final int location = this.getUniformLocationOptional(pUniformName); if(location != ShaderProgramConstants.LOCATION_INVALID) { GLES20.glUniformMatrix4fv(this.getUniformLocationOptional(pUniformName), 1, false, pGLMatrix, 0); } } public void setUniform(final String pUniformName, final float pX) { GLES20.glUniform1f(this.getUniformLocation(pUniformName), pX); } public void setUniformOptional(final String pUniformName, final float pX) { final int location = this.getUniformLocationOptional(pUniformName); if(location != ShaderProgramConstants.LOCATION_INVALID) { GLES20.glUniform1f(this.getUniformLocationOptional(pUniformName), pX); } } public void setUniform(final String pUniformName, final float pX, final float pY) { GLES20.glUniform2f(this.getUniformLocation(pUniformName), pX, pY); } public void setUniformOptional(final String pUniformName, final float pX, final float pY) { final int location = this.getUniformLocationOptional(pUniformName); if(location != ShaderProgramConstants.LOCATION_INVALID) { GLES20.glUniform2f(this.getUniformLocationOptional(pUniformName), pX, pY); } } public void setUniform(final String pUniformName, final float pX, final float pY, final float pZ) { GLES20.glUniform3f(this.getUniformLocation(pUniformName), pX, pY, pZ); } public void setUniformOptional(final String pUniformName, final float pX, final float pY, final float pZ) { final int location = this.getUniformLocationOptional(pUniformName); if(location != ShaderProgramConstants.LOCATION_INVALID) { GLES20.glUniform3f(this.getUniformLocationOptional(pUniformName), pX, pY, pZ); } } public void setUniform(final String pUniformName, final float pX, final float pY, final float pZ, final float pW) { GLES20.glUniform4f(this.getUniformLocation(pUniformName), pX, pY, pZ, pW); } public void setUniformOptional(final String pUniformName, final float pX, final float pY, final float pZ, final float pW) { final int location = this.getUniformLocationOptional(pUniformName); if(location != ShaderProgramConstants.LOCATION_INVALID) { GLES20.glUniform4f(this.getUniformLocationOptional(pUniformName), pX, pY, pZ, pW); } } /** * @param pUniformName * @param pTexture the index of the Texture to use. Similar to {@link GLES20#GL_TEXTURE0}, {@link GLES20#GL_TEXTURE1}, ... except that it is <b><code>0</code></b> based. */ public void setTexture(final String pUniformName, final int pTexture) { GLES20.glUniform1i(this.getUniformLocation(pUniformName), pTexture); } /** * @param pUniformName * @param pTexture the index of the Texture to use. Similar to {@link GLES20#GL_TEXTURE0}, {@link GLES20#GL_TEXTURE1}, ... except that it is <b><code>0</code></b> based. */ public void setTextureOptional(final String pUniformName, final int pTexture) { final int location = this.getUniformLocationOptional(pUniformName); if(location != ShaderProgramConstants.LOCATION_INVALID) { GLES20.glUniform1i(location, pTexture); } } // =========================================================== // Inner and Anonymous Classes // =========================================================== }