package javax.media.opengl.util.glsl; import javax.media.opengl.util.*; import javax.media.opengl.*; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.nio.*; import java.io.PrintStream; public class ShaderState { public ShaderState() { } public boolean verbose() { return verbose; } public void setVerbose(boolean v) { verbose=v; } /** * Fetches the current shader state * * @see javax.media.opengl.util.glsl.ShaderState#glUseProgram(GL2ES2, boolean) * @see javax.media.opengl.util.glsl.ShaderState#getCurrent() */ public static synchronized ShaderState getCurrent() { return currentShaderState; } /** * Turns the shader program on * and set this ShaderState to current if on equals true * * @see javax.media.opengl.util.glsl.ShaderState#glUseProgram(GL2ES2, boolean) * @see javax.media.opengl.util.glsl.ShaderState#getCurrent() */ public synchronized void glUseProgram(GL2ES2 gl, boolean on) { if(on) { if(null!=shaderProgram) { shaderProgram.glUseProgram(gl, true); } else { throw new GLException("No program is attached"); } if(currentShaderState!=this) { currentShaderState = this; } } else if(null!=shaderProgram) { shaderProgram.glUseProgram(gl, false); } } public boolean linked() { return (null!=shaderProgram)?shaderProgram.linked():false; } public boolean inUse() { return (null!=shaderProgram)?shaderProgram.inUse():false; } /** * Attach or switch a shader program * * Attaching a shader program the first time, * as well as switching to another program on the fly, * while managing all attribute and uniform data. */ public synchronized void attachShaderProgram(GL2ES2 gl, ShaderProgram prog) { boolean prgInUse = false; // earmarked state if(null!=shaderProgram) { if(shaderProgram.equals(prog)) { // nothing to do .. if(DEBUG) { System.err.println("Info: attachShaderProgram: NOP: equal id: "+shaderProgram.id()); } return; } prgInUse = shaderProgram.inUse(); } if(DEBUG) { Throwable tX = new Throwable("Info: attachShaderProgram: BEGIN "+shaderProgram+" -> "+prog); tX.printStackTrace(); } // register new one shaderProgram = prog; if(null!=shaderProgram) { // reinstall all data .. shaderProgram.glUseProgram(gl, true); glResetAllVertexAttributes(gl); glResetAllUniforms(gl); if(!prgInUse) { shaderProgram.glUseProgram(gl, false); } } if(DEBUG) { System.err.println("Info: attachShaderProgram: END"); } } public ShaderProgram shaderProgram() { return shaderProgram; } /** * @see #glReleaseAllVertexAttributes * @see #glReleaseAllUniforms */ public synchronized void release(GL2ES2 gl) { release(gl, false, false); } public synchronized void release(GL2ES2 gl, boolean releaseProgramToo, boolean releaseShaderToo) { boolean prgInUse = false; if(null!=shaderProgram) { prgInUse = shaderProgram.inUse(); if(!prgInUse) { shaderProgram.glUseProgram(gl, true); } } glReleaseAllVertexAttributes(gl); glReleaseAllUniforms(gl); if(null!=shaderProgram) { if(releaseProgramToo) { shaderProgram.release(gl, releaseShaderToo); } else if(!prgInUse) { shaderProgram.glUseProgram(gl, false); } } } // // Shader attribute handling // /** * Binds an attribute to the shader. * This must be done before the program is linked ! * n name - 1 idx, where name is a uniq key * * @throws GLException is the program is already linked * * @see #glBindAttribLocation * @see javax.media.opengl.GL2ES2#glBindAttribLocation * @see #glGetAttribLocation * @see javax.media.opengl.GL2ES2#glGetAttribLocation * @see #getAttribLocation * @see #glReplaceShader */ public void glBindAttribLocation(GL2ES2 gl, int index, String name) { if(null==shaderProgram) throw new GLException("No program is attached"); if(shaderProgram.linked()) throw new GLException("Program is already linked"); Integer idx = new Integer(index); if(!attribMap2Idx.containsKey(name)) { attribMap2Idx.put(name, idx); gl.glBindAttribLocation(shaderProgram.program(), index, name); } } /** * Gets the index of a shader attribute. * This must be done after the program is linked ! * * @return -1 if there is no such attribute available, * otherwise >= 0 * @throws GLException is the program is not linked * * @see #glBindAttribLocation * @see javax.media.opengl.GL2ES2#glBindAttribLocation * @see #glGetAttribLocation * @see javax.media.opengl.GL2ES2#glGetAttribLocation * @see #getAttribLocation * @see #glReplaceShader */ public int glGetAttribLocation(GL2ES2 gl, String name) { if(!shaderProgram.linked()) throw new GLException("Program is not linked"); int index = getAttribLocation(name); if(0>index) { index = gl.glGetAttribLocation(shaderProgram.program(), name); if(0<=index) { Integer idx = new Integer(index); attribMap2Idx.put(name, idx); } else if(verbose) { Throwable tX = new Throwable("Info: glGetAttribLocation failed, no location for: "+name+", index: "+index); tX.printStackTrace(); } } return index; } protected int getAttribLocation(String name) { Integer idx = (Integer) attribMap2Idx.get(name); return (null!=idx)?idx.intValue():-1; } // // Enabled Vertex Arrays and its data // /** * Enable a vertex attribute array * * Even if the attribute is not found in the current shader, * it is stored in this state. * * @returns false, if the name is not found, otherwise true * * @throws GLException if the program is not in use * * @see #glEnableVertexAttribArray * @see #glDisableVertexAttribArray * @see #glVertexAttribPointer * @see #getVertexAttributePointer * @see #glReleaseAllVertexAttributes * @see #glResetAllVertexAttributes * @see #glReplaceShader */ public boolean glEnableVertexAttribArray(GL2ES2 gl, String name) { if(!shaderProgram.inUse()) throw new GLException("Program is not in use"); enabledVertexAttribArraySet.add(name); int index = glGetAttribLocation(gl, name); if(0>index) { if(verbose) { Throwable tX = new Throwable("Info: glEnableVertexAttribArray failed, no index for: "+name); tX.printStackTrace(); } return false; } if(DEBUG) { System.err.println("Info: glEnableVertexAttribArray: "+name); } gl.glEnableVertexAttribArray(index); return true; } public boolean isVertexAttribArrayEnabled(String name) { if(!shaderProgram.inUse()) throw new GLException("Program is not in use"); return enabledVertexAttribArraySet.contains(name); } /** * Disables a vertex attribute array * * Even if the attribute is not found in the current shader, * it is removed from this state. * * @returns false, if the name is not found, otherwise true * * @throws GLException if the program is not in use * * @see #glEnableVertexAttribArray * @see #glDisableVertexAttribArray * @see #glVertexAttribPointer * @see #getVertexAttributePointer * @see #glReleaseAllVertexAttributes * @see #glResetAllVertexAttributes * @see #glReplaceShader */ public boolean glDisableVertexAttribArray(GL2ES2 gl, String name) { if(!shaderProgram.inUse()) throw new GLException("Program is not in use"); enabledVertexAttribArraySet.remove(name); int index = glGetAttribLocation(gl, name); if(0>index) { if(verbose) { Throwable tX = new Throwable("Info: glDisableVertexAttribArray failed, no index for: "+name); tX.printStackTrace(); } return false; } if(DEBUG) { System.err.println("Info: glDisableVertexAttribArray: "+name); } gl.glDisableVertexAttribArray(index); return true; } /** * Set the vertex attribute data. * Enable the attribute, if it is not enabled yet. * * Even if the attribute is not found in the current shader, * it is stored in this state. * * @arg data the GLArrayData's name must match the attributes one, * it's index will be set with the attribute's location, * if found. * * @returns false, if the name is not found, otherwise true * * @throws GLException if the program is not in use * * @see #glEnableVertexAttribArray * @see #glDisableVertexAttribArray * @see #glVertexAttribPointer * @see #getVertexAttributePointer * @see #glReleaseAllVertexAttributes * @see #glResetAllVertexAttributes * @see #glReplaceShader */ public boolean glVertexAttribPointer(GL2ES2 gl, GLArrayData data) { if(!shaderProgram.inUse()) throw new GLException("Program is not in use"); if(!enabledVertexAttribArraySet.contains(data.getName())) { if(!glEnableVertexAttribArray(gl, data.getName())) { if(verbose) { Throwable tX = new Throwable("Info: glVertexAttribPointer: couldn't enable: "+data); tX.printStackTrace(); } } } int index = getAttribLocation(data.getName()); if(0>index) { if(verbose) { Throwable tX = new Throwable("Info: glVertexAttribPointer failed, no index for: "+data); tX.printStackTrace(); } } data.setLocation(index); vertexAttribMap2Data.put(data.getName(), data); if(0<=index) { // only pass the data, if the attribute exists in the current shader if(DEBUG) { System.err.println("Info: glVertexAttribPointer: "+data); } gl.glVertexAttribPointer(data); return true; } return false; } /** * Get the vertex attribute data, previously set. * * @returns the GLArrayData object, null if not previously set. * * @see #glEnableVertexAttribArray * @see #glDisableVertexAttribArray * @see #glVertexAttribPointer * @see #getVertexAttributePointer * @see #glReleaseAllVertexAttributes * @see #glResetAllVertexAttributes * @see #glReplaceShader */ public GLArrayData getVertexAttribPointer(String name) { return (GLArrayData) vertexAttribMap2Data.get(name); } /** * Releases all mapped vertex attribute data, * disables all enabled attributes and loses all indices * * @throws GLException is the program is not in use but the shaderProgram is set * * @see #glEnableVertexAttribArray * @see #glDisableVertexAttribArray * @see #glVertexAttribPointer * @see #getVertexAttributePointer * @see #glReleaseAllVertexAttributes * @see #glResetAllVertexAttributes * @see #glResetAllVertexAttributes * @see #glReplaceShader */ public void glReleaseAllVertexAttributes(GL2ES2 gl) { if(null!=shaderProgram) { if(!shaderProgram.inUse()) throw new GLException("Program is not in use"); for(Iterator iter = vertexAttribMap2Data.keySet().iterator(); iter.hasNext(); ) { if(!glDisableVertexAttribArray(gl, (String) iter.next())) { throw new GLException("Internal Error: mapped vertex attribute couldn't be disabled"); } } for(Iterator iter = enabledVertexAttribArraySet.iterator(); iter.hasNext(); ) { if(!glDisableVertexAttribArray(gl, (String) iter.next())) { throw new GLException("Internal Error: prev enabled vertex attribute couldn't be disabled"); } } } vertexAttribMap2Data.clear(); enabledVertexAttribArraySet.clear(); attribMap2Idx.clear(); } /** * Disables all vertex attribute arrays. * * Their enabled stated will be removed from this state only * if 'removeFromState' is true. * * This method purpose is more for debugging. * * @throws GLException is the program is not in use but the shaderProgram is set * * @see #glEnableVertexAttribArray * @see #glDisableVertexAttribArray * @see #glVertexAttribPointer * @see #getVertexAttributePointer * @see #glReleaseAllVertexAttributes * @see #glResetAllVertexAttributes * @see #glResetAllVertexAttributes * @see #glReplaceShader */ public void glDisableAllVertexAttributeArrays(GL2ES2 gl, boolean removeFromState) { if(!shaderProgram.inUse()) throw new GLException("Program is not in use"); for(Iterator iter = enabledVertexAttribArraySet.iterator(); iter.hasNext(); ) { String name = (String) iter.next(); if(removeFromState) { enabledVertexAttribArraySet.remove(name); } int index = glGetAttribLocation(gl, name); if(0<=index) { gl.glDisableVertexAttribArray(index); } } } /** * Reset all previously mapped vertex attribute data, * incl enabling them * * @throws GLException is the program is not in use * * @see #glEnableVertexAttribArray * @see #glDisableVertexAttribArray * @see #glVertexAttribPointer * @see #getVertexAttributePointer * @see #glReleaseAllVertexAttributes * @see #glResetAllVertexAttributes * @see #glReplaceShader */ public void glResetAllVertexAttributes(GL2ES2 gl) { if(!shaderProgram.inUse()) throw new GLException("Program is not in use"); attribMap2Idx.clear(); for(Iterator iter = enabledVertexAttribArraySet.iterator(); iter.hasNext(); ) { glEnableVertexAttribArray(gl, (String) iter.next()); } for(Iterator iter = vertexAttribMap2Data.values().iterator(); iter.hasNext(); ) { glVertexAttribPointer(gl, (GLArrayData) iter.next()); } } // // Shader Uniform handling // /** * Gets the index of a shader uniform. * This must be done when the program is in use ! * * @return -1 if there is no such attribute available, * otherwise >= 0 * @throws GLException is the program is not linked * * @see #glGetUniformLocation * @see javax.media.opengl.GL2ES2#glGetUniformLocation * @see #getUniformLocation * @see #glReplaceShader */ protected int glGetUniformLocation(GL2ES2 gl, String name) { if(!shaderProgram.inUse()) throw new GLException("Program is not in use"); int index = getUniformLocation(name); if(0>index) { index = gl.glGetUniformLocation(shaderProgram.program(), name); if(0<=index) { Integer idx = new Integer(index); uniformMap2Idx.put(name, idx); } else if(verbose) { Throwable tX = new Throwable("Info: glUniform failed, no location for: "+name+", index: "+index); tX.printStackTrace(); } } return index; } protected int getUniformLocation(String name) { Integer idx = (Integer) uniformMap2Idx.get(name); return (null!=idx)?idx.intValue():-1; } /** * Set the uniform data. * * Even if the uniform is not found in the current shader, * it is stored in this state. * * @arg data the GLUniforms's name must match the uniform one, * it's index will be set with the uniforms's location, * if found. * * * @returns false, if the name is not found, otherwise true * * @throws GLException if the program is not in use * * @see #glGetUniformLocation * @see javax.media.opengl.GL2ES2#glGetUniformLocation * @see #getUniformLocation * @see #glReplaceShader */ public boolean glUniform(GL2ES2 gl, GLUniformData data) { if(!shaderProgram.inUse()) throw new GLException("Program is not in use"); int location = glGetUniformLocation(gl, data.getName()); data.setLocation(location); uniformMap2Data.put(data.getName(), data); if(0<=location) { // only pass the data, if the uniform exists in the current shader if(DEBUG) { System.err.println("Info: glUniform: "+data); } gl.glUniform(data); } return true; } /** * Get the uniform data, previously set. * * @returns the GLUniformData object, null if not previously set. */ public GLUniformData getUniform(String name) { return (GLUniformData) uniformMap2Data.get(name); } /** * Releases all mapped uniform data * and loses all indices * * @throws GLException is the program is not in use */ public void glReleaseAllUniforms(GL2ES2 gl) { uniformMap2Data.clear(); uniformMap2Idx.clear(); } /** * Reset all previously mapped uniform data * * @throws GLException is the program is not in use */ public void glResetAllUniforms(GL2ES2 gl) { if(!shaderProgram.inUse()) throw new GLException("Program is not in use"); uniformMap2Idx.clear(); for(Iterator iter = uniformMap2Data.values().iterator(); iter.hasNext(); ) { glUniform(gl, (GLUniformData) iter.next()); } } public String toString() { StringBuffer buf = new StringBuffer(); buf.append("ShaderState["); buf.append(shaderProgram.toString()); buf.append(",EnabledStates: ["); for(Iterator iter = enabledVertexAttribArraySet.iterator(); iter.hasNext(); ) { buf.append("\n "); buf.append((String)iter.next()); } buf.append("], ["); for(Iterator iter = vertexAttribMap2Data.values().iterator(); iter.hasNext(); ) { buf.append("\n "); buf.append((GLArrayData) iter.next()); } buf.append("], ["); for(Iterator iter=uniformMap2Data.values().iterator(); iter.hasNext(); ) { buf.append("\n "); buf.append((GLUniformData) iter.next()); } buf.append("]"); return buf.toString(); } protected static final boolean DEBUG = false; protected boolean verbose = false; protected ShaderProgram shaderProgram=null; protected HashMap attribMap2Idx = new HashMap(); protected HashSet enabledVertexAttribArraySet = new HashSet(); protected HashMap vertexAttribMap2Data = new HashMap(); protected HashMap uniformMap2Idx = new HashMap(); protected HashMap uniformMap2Data = new HashMap(); protected static ShaderState currentShaderState = null; }