/**
* Copyright 2010 JogAmp Community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of JogAmp Community.
*/
package com.jogamp.opengl.util.glsl;
import java.security.AccessController;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import javax.media.opengl.GL;
import javax.media.opengl.GL2ES2;
import javax.media.opengl.GLArrayData;
import javax.media.opengl.GLContext;
import javax.media.opengl.GLException;
import javax.media.opengl.GLUniformData;
import jogamp.opengl.Debug;
import com.jogamp.common.os.Platform;
import com.jogamp.common.util.IntObjectHashMap;
import com.jogamp.opengl.util.GLArrayDataEditable;
public class ShaderState {
public static final boolean DEBUG = Debug.isPropertyDefined("jogl.debug.GLSLState", true, AccessController.getContext());
public ShaderState() {
}
public boolean verbose() { return verbose; }
public void setVerbose(boolean v) { verbose=v; }
/**
* Fetches the current shader state from this thread (TLS) current GLContext
*
* @see com.jogamp.opengl.util.glsl.ShaderState#useProgram(GL2ES2, boolean)
* @see com.jogamp.opengl.util.glsl.ShaderState#getShaderState(GL)
* @see com.jogamp.opengl.util.glsl.ShaderState#getCurrentShaderState()
*/
public static synchronized ShaderState getCurrentShaderState() {
return getShaderState(GLContext.getCurrentGL());
}
/**
* Fetches the shader state from the GL object's GLContext
*
* @param gl the GL object referencing the GLContext
*
* @see com.jogamp.opengl.util.glsl.ShaderState#useProgram(GL2ES2, boolean)
* @see com.jogamp.opengl.util.glsl.ShaderState#getShaderState(GL)
* @see com.jogamp.opengl.util.glsl.ShaderState#getCurrentShaderState()
*/
public static synchronized ShaderState getShaderState(GL gl) {
return (ShaderState) gl.getContext().getAttachedObject(ShaderState.class.getName());
}
/**
* Returns the attached user object for the given name to this ShaderState.
*/
public final Object getAttachedObject(String name) {
return attachedObjectsByString.get(name);
}
/**
* Attach user object for the given name to this ShaderState.
* Returns the previously set object or null.
*
* @return the previous mapped object or null if none
*/
public final Object attachObject(String name, Object obj) {
return attachedObjectsByString.put(name, obj);
}
/**
* @param name name of the mapped object to detach
*
* @return the previous mapped object or null if none
*/
public final Object detachObject(String name) {
return attachedObjectsByString.remove(name);
}
/**
* Returns the attached user object for the given name to this ShaderState.
*/
public final Object getAttachedObject(int name) {
return attachedObjectsByInt.get(name);
}
/**
* Attach user object for the given name to this ShaderState.
* Returns the previously set object or null.
*/
public final Object attachObject(int name, Object obj) {
return attachedObjectsByInt.put(name, obj);
}
public final Object detachObject(int name) {
return attachedObjectsByInt.remove(name);
}
/**
* Turns the shader program on or off.
* Puts this ShaderState to to the thread local storage (TLS),
* if on
is true
.
*
* @throws GLException if no program is attached
*
* @see com.jogamp.opengl.util.glsl.ShaderState#useProgram(GL2ES2, boolean)
* @see com.jogamp.opengl.util.glsl.ShaderState#getShaderState(GL)
* @see com.jogamp.opengl.util.glsl.ShaderState#getCurrentShaderState()
*/
public synchronized void useProgram(GL2ES2 gl, boolean on) throws GLException {
if(null==shaderProgram) { throw new GLException("No program is attached"); }
if(on) {
// update the current ShaderState to the TLS ..
gl.getContext().attachObject(ShaderState.class.getName(), this);
if(shaderProgram.linked()) {
shaderProgram.useProgram(gl, true);
if(resetAllShaderData) {
resetAllAttributes(gl);
resetAllUniforms(gl);
}
} else if(resetAllShaderData) {
setAllAttributes(gl);
if(!shaderProgram.link(gl, System.err)) {
throw new GLException("could not link program: "+shaderProgram);
}
shaderProgram.useProgram(gl, true);
resetAllUniforms(gl);
}
resetAllShaderData = false;
} else {
shaderProgram.useProgram(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.
* *[Re]sets all data and use program in case of a program switch.
* Use program if linked in case of a 1st time attachment.
If an attribute location is cached (ie {@link #bindAttribLocation(GL2ES2, int, String)}) * it is promoted to the {@link GLArrayData} instance.
* *The attribute will be destroyed with {@link #destroy(GL2ES2)} * and it's location will be reset when switching shader with {@link #attachShaderProgram(GL2ES2, ShaderProgram)}.
* *The data will not be transfered to the GPU, use {@link #vertexAttribPointer(GL2ES2, GLArrayData)} additionally.
* * @param attribute the {@link GLArrayData} which lifecycle shall be managed * @param own true if owning shall be performs, false if disowning. * * @see #bindAttribLocation(GL2ES2, int, String) * @see #getAttribute(String) */ public void ownAttribute(GLArrayData attribute, boolean own) { if(own) { final int location = getAttribLocation(attribute.getName()); if(0<=location) { attribute.setLocation(location); } managedAttributes.add(managedAttributes.size(), attribute); } else { managedAttributes.remove(attribute); } } public boolean ownsAttribute(GLArrayData attribute) { return managedAttributes.contains(attribute); } /** * Binds a shader attribute to a location. * Multiple names can be bound to one location. * The value will be cached and can be retrieved via {@link #getAttribLocation(String)} * before or after linking. * * @throws GLException if no program is attached * @throws GLException if the program is already linked * * @see javax.media.opengl.GL2ES2#glBindAttribLocation(int, int, String) * @see #getAttribLocation(GL2ES2, String) * @see #getAttribLocation(String) */ public void bindAttribLocation(GL2ES2 gl, int location, String name) { if(null==shaderProgram) throw new GLException("No program is attached"); if(shaderProgram.linked()) throw new GLException("Program is already linked"); final Integer loc = new Integer(location); activeAttribLocationMap.put(name, loc); gl.glBindAttribLocation(shaderProgram.program(), location, name); } /** * Binds a shader {@link GLArrayData} attribute to a location. * Multiple names can be bound to one location. * The value will be cached and can be retrieved via {@link #getAttribLocation(String)} * and {@link #getAttribute(String)}before or after linking. * The {@link GLArrayData}'s location will be set as well. * * @throws GLException if no program is attached * @throws GLException if the program is already linked * * @see javax.media.opengl.GL2ES2#glBindAttribLocation(int, int, String) * @see #getAttribLocation(GL2ES2, String) * @see #getAttribLocation(String) * @see #getAttribute(String) */ public void bindAttribLocation(GL2ES2 gl, int location, GLArrayData data) { bindAttribLocation(gl, location, data.getName()); data.setLocation(location); activeAttribDataMap.put(data.getName(), data); } /** * Gets the location of a shader attribute, * either the cached value {@link #getAttribLocation(String)} if valid or * the retrieved one {@link GL2ES2#glGetAttribLocation(int, String)}. * In the latter case the value will be cached. * * @return -1 if there is no such attribute available, * otherwise >= 0 * @throws GLException if no program is attached * @throws GLException if the program is not linked and no location was cached. * * @see #getAttribLocation(String) * @see #bindAttribLocation(GL2ES2, int, GLArrayData) * @see #bindAttribLocation(GL2ES2, int, String) * @see GL2ES2#glGetAttribLocation(int, String) */ public int getAttribLocation(GL2ES2 gl, String name) { if(null==shaderProgram) throw new GLException("No program is attached"); int location = getAttribLocation(name); if(0>location) { if(!shaderProgram.linked()) throw new GLException("Program is not linked"); location = gl.glGetAttribLocation(shaderProgram.program(), name); if(0<=location) { Integer idx = new Integer(location); activeAttribLocationMap.put(name, idx); if(DEBUG) { System.err.println("Info: glGetAttribLocation: "+name+", loc: "+location); } } else if(verbose) { Throwable tX = new Throwable("Info: glGetAttribLocation failed, no location for: "+name+", loc: "+location); tX.printStackTrace(); } } return location; } /** * Gets the location of a shader attribute, * either the cached value {@link #getAttribLocation(String)} if valid or * the retrieved one {@link GL2ES2#glGetAttribLocation(int, String)}. * In the latter case the value will be cached. * The {@link GLArrayData}'s location will be set as well. * * @return -1 if there is no such attribute available, * otherwise >= 0 * * @throws GLException if no program is attached * @throws GLException if the program is not linked and no location was cached. * * @see #getAttribLocation(String) * @see #bindAttribLocation(GL2ES2, int, GLArrayData) * @see #bindAttribLocation(GL2ES2, int, String) * @see GL2ES2#glGetAttribLocation(int, String) * @see #getAttribute(String) */ public int getAttribLocation(GL2ES2 gl, GLArrayData data) { int location = getAttribLocation(gl, data.getName()); data.setLocation(location); activeAttribDataMap.put(data.getName(), data); return location; } // // Enabled Vertex Arrays and its data // /** * @return true if the named attribute is enable */ public final boolean isVertexAttribArrayEnabled(String name) { return enabledAttributes.contains(name); } /** * @return true if the {@link GLArrayData} attribute is enable */ public final boolean isVertexAttribArrayEnabled(GLArrayData data) { return isVertexAttribArrayEnabled(data.getName()); } private boolean enableVertexAttribArray(GL2ES2 gl, String name, int location) { enabledAttributes.add(name); if(0>location) { location = getAttribLocation(gl, name); if(0>location) { 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+", loc: "+location); } gl.glEnableVertexAttribArray(location); return true; } /** * Enables a vertex attribute array. * * This method retrieves the the location via {@link #getAttribLocation(GL2ES2, GLArrayData)} * hence {@link #enableVertexAttribArray(GL2ES2, GLArrayData)} shall be preferred. * * Even if the attribute is not found in the current shader, * it is marked enabled in this state. * * @return false, if the name is not found, otherwise true * * @throws GLException if the program is not linked and no location was cached. * * @see #glEnableVertexAttribArray * @see #glDisableVertexAttribArray * @see #glVertexAttribPointer * @see #getVertexAttribPointer */ public boolean enableVertexAttribArray(GL2ES2 gl, String name) { return enableVertexAttribArray(gl, name, -1); } /** * Enables a vertex attribute array, usually invoked by {@link GLArrayDataEditable#enableBuffer(GL, boolean)}. * * This method uses the {@link GLArrayData}'s location if set * and is the preferred alternative to {@link #enableVertexAttribArray(GL2ES2, String)}. * If data location is unset it will be retrieved via {@link #getAttribLocation(GL2ES2, GLArrayData)} set * and cached in this state. * * Even if the attribute is not found in the current shader, * it is marked enabled in this state. * * @return false, if the name is not found, otherwise true * * @throws GLException if the program is not linked and no location was cached. * * @see #glEnableVertexAttribArray * @see #glDisableVertexAttribArray * @see #glVertexAttribPointer * @see #getVertexAttribPointer * @see GLArrayDataEditable#enableBuffer(GL, boolean) */ public boolean enableVertexAttribArray(GL2ES2 gl, GLArrayData data) { if(0 > data.getLocation()) { getAttribLocation(gl, data); } else { // ensure data is the current bound one activeAttribDataMap.put(data.getName(), data); } return enableVertexAttribArray(gl, data.getName(), data.getLocation()); } private boolean disableVertexAttribArray(GL2ES2 gl, String name, int location) { enabledAttributes.remove(name); if(0>location) { location = getAttribLocation(gl, name); if(0>location) { 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(location); return true; } /** * Disables a vertex attribute array * * This method retrieves the the location via {@link #getAttribLocation(GL2ES2, GLArrayData)} * hence {@link #disableVertexAttribArray(GL2ES2, GLArrayData)} shall be preferred. * * Even if the attribute is not found in the current shader, * it is removed from this state enabled list. * * @return false, if the name is not found, otherwise true * * @throws GLException if no program is attached * @throws GLException if the program is not linked and no location was cached. * * @see #glEnableVertexAttribArray * @see #glDisableVertexAttribArray * @see #glVertexAttribPointer * @see #getVertexAttribPointer */ public boolean disableVertexAttribArray(GL2ES2 gl, String name) { return disableVertexAttribArray(gl, name, -1); } /** * Disables a vertex attribute array * * This method uses the {@link GLArrayData}'s location if set * and is the preferred alternative to {@link #disableVertexAttribArray(GL2ES2, String)}. * If data location is unset it will be retrieved via {@link #getAttribLocation(GL2ES2, GLArrayData)} set * and cached in this state. * * Even if the attribute is not found in the current shader, * it is removed from this state enabled list. * * @return false, if the name is not found, otherwise true * * @throws GLException if no program is attached * @throws GLException if the program is not linked and no location was cached. * * @see #glEnableVertexAttribArray * @see #glDisableVertexAttribArray * @see #glVertexAttribPointer * @see #getVertexAttribPointer */ public boolean disableVertexAttribArray(GL2ES2 gl, GLArrayData data) { if(0 > data.getLocation()) { getAttribLocation(gl, data); } return disableVertexAttribArray(gl, data.getName(), data.getLocation()); } /** * Set the {@link GLArrayData} vertex attribute data. * * This method uses the {@link GLArrayData}'s location if set. * If data location is unset it will be retrieved via {@link #getAttribLocation(GL2ES2, GLArrayData)}, set * and cached in this state. * * @return false, if the location could not be determined, otherwise true * * @throws GLException if no program is attached * @throws GLException if the program is not linked and no location was cached. * * @see #glEnableVertexAttribArray * @see #glDisableVertexAttribArray * @see #glVertexAttribPointer * @see #getVertexAttribPointer */ public boolean vertexAttribPointer(GL2ES2 gl, GLArrayData data) { int location = data.getLocation(); if(0 > location) { location = getAttribLocation(gl, data); } /* else { done via enable .. // ensure data is the current bound one activeAttribDataMap.put(data.getName(), data); } */ if(0 <= location) { // 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; } /** * Releases all mapped vertex attribute data, * disables all enabled attributes and loses all indices * * @see #glEnableVertexAttribArray * @see #glDisableVertexAttribArray * @see #glVertexAttribPointer * @see #getVertexAttribPointer * @see #glReleaseAllVertexAttributes * @see #glResetAllVertexAttributes * @see #glResetAllVertexAttributes * @see ShaderProgram#glReplaceShader */ public void releaseAllAttributes(GL2ES2 gl) { if(null!=shaderProgram) { for(IteratorAttribute data is bound to the GL state
*Attribute location is bound to the program
* *However, since binding an attribute to a location via {@link #bindAttribLocation(GL2ES2, int, GLArrayData)} * must happen before linking and we try to promote the attributes to the new program, * we have to gather the probably new location etc.
* * @throws GLException is the program is not linked * * @see #attachShaderProgram(GL2ES2, ShaderProgram) */ private final void resetAllAttributes(GL2ES2 gl) { if(!shaderProgram.linked()) throw new GLException("Program is not linked"); activeAttribLocationMap.clear(); for(IteratorIf a uniform location is cached it is promoted to the {@link GLUniformData} instance.
* *The attribute will be destroyed with {@link #destroy(GL2ES2)} * and it's location will be reset when switching shader with {@link #attachShaderProgram(GL2ES2, ShaderProgram)}.
* *The data will not be transfered to the GPU, use {@link #uniform(GL2ES2, GLUniformData)} additionally.
* * @param uniform the {@link GLUniformData} which lifecycle shall be managed * * @see #getUniform(String) */ public void ownUniform(GLUniformData uniform) { final int location = getUniformLocation(uniform.getName()); if(0<=location) { uniform.setLocation(location); } activeUniformDataMap.put(uniform.getName(), uniform); managedUniforms.add(uniform); } public boolean ownsUniform(GLUniformData uniform) { return managedUniforms.contains(uniform); } /** * 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 ShaderProgram#glReplaceShader */ protected final int getUniformLocation(GL2ES2 gl, String name) { if(!shaderProgram.inUse()) throw new GLException("Program is not in use"); int location = getUniformLocation(name); if(0>location) { location = gl.glGetUniformLocation(shaderProgram.program(), name); if(0<=location) { Integer idx = new Integer(location); activeUniformLocationMap.put(name, idx); } else if(verbose) { Throwable tX = new Throwable("Info: glUniform failed, no location for: "+name+", index: "+location); tX.printStackTrace(); } } return location; } protected final int getUniformLocation(String name) { Integer idx = (Integer) activeUniformLocationMap.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. * * @param data the GLUniforms's name must match the uniform one, * it's index will be set with the uniforms's location, * if found. * * * @return 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 ShaderProgram#glReplaceShader */ public boolean uniform(GL2ES2 gl, GLUniformData data) { if(!shaderProgram.inUse()) throw new GLException("Program is not in use"); int location = data.getLocation(); if(0>location) { location = getUniformLocation(gl, data.getName()); data.setLocation(location); } activeUniformDataMap.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. * * @return the GLUniformData object, null if not previously set. */ public GLUniformData getUniform(String name) { return activeUniformDataMap.get(name); } /** * Releases all mapped uniform data * and loses all indices */ public void releaseAllUniforms(GL2ES2 gl) { activeUniformDataMap.clear(); activeUniformLocationMap.clear(); managedUniforms.clear(); } /** * Reset all previously mapped uniform data * * Uniform data and location is bound to the program, * hence both are updated here * * @throws GLException is the program is not in use * * @see #attachShaderProgram(GL2ES2, ShaderProgram) */ private final void resetAllUniforms(GL2ES2 gl) { if(!shaderProgram.inUse()) throw new GLException("Program is not in use"); activeUniformLocationMap.clear(); for(Iterator