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
// ===========================================================
}