/** * 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.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import com.jogamp.opengl.GL; import com.jogamp.opengl.GL2ES2; import com.jogamp.opengl.GLArrayData; import com.jogamp.opengl.GLException; import com.jogamp.opengl.GLUniformData; import jogamp.opengl.Debug; import com.jogamp.common.ExceptionUtils; import com.jogamp.common.os.Platform; import com.jogamp.common.util.PropertyAccess; import com.jogamp.opengl.util.GLArrayDataEditable; /** * ShaderState allows to sharing data between shader programs, * while updating the attribute and uniform locations when switching. * <p> * This allows seamless switching of programs using <i>almost</i> same data * but performing different artifacts. * </p> * <p> * A {@link #useProgram(GL2ES2, boolean) used} ShaderState is attached to the current GL context * and can be retrieved via {@link #getShaderState(GL)}. * </p> */ public class ShaderState { public static final boolean DEBUG; static { Debug.initSingleton(); DEBUG = PropertyAccess.isPropertyDefined("jogl.debug.GLSLState", true); } public ShaderState() { } public boolean verbose() { return verbose; } public void setVerbose(final boolean v) { verbose = DEBUG || v; } /** * Returns the attached user object for the given name to this ShaderState. */ public final Object getAttachedObject(final 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(final String name, final 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(final String name) { return attachedObjectsByString.remove(name); } /** * Turns the shader program on or off.<br> * * @throws GLException if no program is attached * * @see com.jogamp.opengl.util.glsl.ShaderState#useProgram(GL2ES2, boolean) */ public synchronized void useProgram(final GL2ES2 gl, final boolean on) throws GLException { if(null==shaderProgram) { throw new GLException("No program is attached"); } if(on) { 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); if(resetAllShaderData) { 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 * * <p>Attaching a shader program the first time, * as well as switching to another program on the fly, * while managing all attribute and uniform data.</p> * * <p>[Re]sets all data and use program in case of a program switch.</p> * * <p>Use program, {@link #useProgram(GL2ES2, boolean)}, * if <code>enable</code> is <code>true</code>.</p> * * @return true if shader program was attached, otherwise false (already attached) * * @throws GLException if program was not linked and linking fails */ public synchronized boolean attachShaderProgram(final GL2ES2 gl, final ShaderProgram prog, final boolean enable) throws GLException { if(verbose) { final int curId = (null!=shaderProgram)?shaderProgram.id():-1; final int newId = (null!=prog)?prog.id():-1; System.err.println("ShaderState: attachShaderProgram: "+curId+" -> "+newId+" (enable: "+enable+")\n\t"+shaderProgram+"\n\t"+prog); if(DEBUG) { ExceptionUtils.dumpStack(System.err); } } if(null!=shaderProgram) { if(shaderProgram.equals(prog)) { if(enable) { useProgram(gl, true); } // nothing else to do .. if(verbose) { System.err.println("ShaderState: attachShaderProgram: No switch, equal id: "+shaderProgram.id()+", enabling "+enable); } return false; } if(shaderProgram.inUse()) { if(null != prog && enable) { shaderProgram.notifyNotInUse(); } else { // no new 'enabled' program - disable useProgram(gl, false); } } resetAllShaderData = true; } // register new one shaderProgram = prog; if(null!=shaderProgram) { // [re]set all data and use program if switching program, // or use program if program is linked if(resetAllShaderData || enable) { useProgram(gl, true); // may reset all data if(!enable) { useProgram(gl, false); } } } if(DEBUG) { System.err.println("Info: attachShaderProgram: END"); } return true; } public ShaderProgram shaderProgram() { return shaderProgram; } /** * Calls {@link #release(GL2ES2, boolean, boolean, boolean) release(gl, true, true, true)} * * @see #glReleaseAllVertexAttributes * @see #glReleaseAllUniforms * @see #release(GL2ES2, boolean, boolean, boolean) */ public synchronized void destroy(final GL2ES2 gl) { release(gl, true, true, true); attachedObjectsByString.clear(); } /** * Calls {@link #release(GL2ES2, boolean, boolean, boolean) release(gl, false, false, false)} * * @see #glReleaseAllVertexAttributes * @see #glReleaseAllUniforms * @see #release(GL2ES2, boolean, boolean, boolean) */ public synchronized void releaseAllData(final GL2ES2 gl) { release(gl, false, false, false); } /** * @see #glReleaseAllVertexAttributes * @see #glReleaseAllUniforms * @see ShaderProgram#release(GL2ES2, boolean) */ public synchronized void release(final GL2ES2 gl, final boolean destroyBoundAttributes, final boolean destroyShaderProgram, final boolean destroyShaderCode) { if(null!=shaderProgram && shaderProgram.linked() ) { shaderProgram.useProgram(gl, false); } if(destroyBoundAttributes) { for(final Iterator<GLArrayData> iter = managedAttributes.iterator(); iter.hasNext(); ) { iter.next().destroy(gl); } } releaseAllAttributes(gl); releaseAllUniforms(gl); if(null!=shaderProgram && destroyShaderProgram) { shaderProgram.release(gl, destroyShaderCode); } } // // Shader attribute handling // /** * Gets the cached location of a shader attribute. * * @return -1 if there is no such attribute available, * otherwise >= 0 * * @see #bindAttribLocation(GL2ES2, int, String) * @see #bindAttribLocation(GL2ES2, int, GLArrayData) * @see #getAttribLocation(GL2ES2, String) * @see GL2ES2#glGetAttribLocation(int, String) */ public int getCachedAttribLocation(final String name) { final Integer idx = activeAttribLocationMap.get(name); return (null!=idx)?idx.intValue():-1; } /** * Get the previous cached vertex attribute data. * * @return the GLArrayData object, null if not previously set. * * @see #ownAttribute(GLArrayData, boolean) * * @see #glEnableVertexAttribArray * @see #glDisableVertexAttribArray * @see #glVertexAttribPointer * @see #getVertexAttribPointer * @see #glReleaseAllVertexAttributes * @see #glResetAllVertexAttributes * @see ShaderProgram#glReplaceShader */ public GLArrayData getAttribute(final String name) { return activeAttribDataMap.get(name); } public boolean isActiveAttribute(final GLArrayData attribute) { return attribute == activeAttribDataMap.get(attribute.getName()); } /** * Binds or unbinds the {@link GLArrayData} lifecycle to this ShaderState. * * <p>If an attribute location is cached (ie {@link #bindAttribLocation(GL2ES2, int, String)}) * it is promoted to the {@link GLArrayData} instance.</p> * * <p>The attribute will be destroyed with {@link #destroy(GL2ES2)} * and it's location will be reset when switching shader with {@link #attachShaderProgram(GL2ES2, ShaderProgram)}.</p> * * <p>The data will not be transfered to the GPU, use {@link #vertexAttribPointer(GL2ES2, GLArrayData)} additionally.</p> * * <p>The data will also be {@link GLArrayData#associate(Object, boolean) associated} with this ShaderState.</p> * * @param attribute the {@link GLArrayData} which lifecycle shall be managed * @param own true if <i>owning</i> shall be performs, false if <i>disowning</i>. * * @see #bindAttribLocation(GL2ES2, int, String) * @see #getAttribute(String) * @see GLArrayData#associate(Object, boolean) */ public void ownAttribute(final GLArrayData attribute, final boolean own) { if(own) { final int location = getCachedAttribLocation(attribute.getName()); if(0<=location) { attribute.setLocation(location); } managedAttributes.add(managedAttributes.size(), attribute); } else { managedAttributes.remove(attribute); } attribute.associate(this, own); } public boolean ownsAttribute(final 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 #getCachedAttribLocation(String)} * before or after linking. * * @throws GLException if no program is attached * @throws GLException if the program is already linked * * @see com.jogamp.opengl.GL2ES2#glBindAttribLocation(int, int, String) * @see #getAttribLocation(GL2ES2, String) * @see #getCachedAttribLocation(String) */ public void bindAttribLocation(final GL2ES2 gl, final int location, final String name) { if(null==shaderProgram) throw new GLException("No program is attached"); if(shaderProgram.linked()) throw new GLException("Program is already linked"); activeAttribLocationMap.put(name, Integer.valueOf(location)); 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 #getCachedAttribLocation(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 com.jogamp.opengl.GL2ES2#glBindAttribLocation(int, int, String) * @see #getAttribLocation(GL2ES2, String) * @see #getCachedAttribLocation(String) * @see #getAttribute(String) */ public void bindAttribLocation(final GL2ES2 gl, final int location, final GLArrayData data) { if(null==shaderProgram) throw new GLException("No program is attached"); if(shaderProgram.linked()) throw new GLException("Program is already linked"); final String name = data.getName(); activeAttribLocationMap.put(name, Integer.valueOf(location)); data.setLocation(gl, shaderProgram.program(), location); activeAttribDataMap.put(data.getName(), data); } /** * Gets the location of a shader attribute with given <code>name</code>.<br> * Uses either the cached value {@link #getCachedAttribLocation(String)} if valid, * or the GLSL queried via {@link GL2ES2#glGetAttribLocation(int, String)}.<br> * The location 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 #getCachedAttribLocation(String) * @see #bindAttribLocation(GL2ES2, int, GLArrayData) * @see #bindAttribLocation(GL2ES2, int, String) * @see GL2ES2#glGetAttribLocation(int, String) */ public int getAttribLocation(final GL2ES2 gl, final String name) { if(null==shaderProgram) throw new GLException("No program is attached"); int location = getCachedAttribLocation(name); if(0>location) { if(!shaderProgram.linked()) throw new GLException("Program is not linked"); location = gl.glGetAttribLocation(shaderProgram.program(), name); if(0<=location) { activeAttribLocationMap.put(name, Integer.valueOf(location)); if(DEBUG) { System.err.println("ShaderState: glGetAttribLocation: "+name+", loc: "+location); } } else if(verbose) { System.err.println("ShaderState: glGetAttribLocation failed, no location for: "+name+", loc: "+location); if(DEBUG) { ExceptionUtils.dumpStack(System.err); } } } return location; } /** * Validates and returns the location of a shader attribute.<br> * Uses either the cached value {@link #getCachedAttribLocation(String)} if valid, * or the GLSL queried via {@link GL2ES2#glGetAttribLocation(int, String)}.<br> * The location will be cached and set in the * {@link GLArrayData} object. * * @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 #getCachedAttribLocation(String) * @see #bindAttribLocation(GL2ES2, int, GLArrayData) * @see #bindAttribLocation(GL2ES2, int, String) * @see GL2ES2#glGetAttribLocation(int, String) * @see #getAttribute(String) */ public int getAttribLocation(final GL2ES2 gl, final GLArrayData data) { if(null==shaderProgram) throw new GLException("No program is attached"); final String name = data.getName(); int location = getCachedAttribLocation(name); if(0<=location) { data.setLocation(location); } else { if(!shaderProgram.linked()) throw new GLException("Program is not linked"); location = data.setLocation(gl, shaderProgram.program()); if(0<=location) { activeAttribLocationMap.put(name, Integer.valueOf(location)); if(DEBUG) { System.err.println("ShaderState: glGetAttribLocation: "+name+", loc: "+location); } } else if(verbose) { System.err.println("ShaderState: glGetAttribLocation failed, no location for: "+name+", loc: "+location); if(DEBUG) { ExceptionUtils.dumpStack(System.err); } } } 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(final String name) { final Boolean v = activedAttribEnabledMap.get(name); return null != v && v.booleanValue(); } /** * @return true if the {@link GLArrayData} attribute is enable */ public final boolean isVertexAttribArrayEnabled(final GLArrayData data) { return isVertexAttribArrayEnabled(data.getName()); } private boolean enableVertexAttribArray(final GL2ES2 gl, final String name, int location) { activedAttribEnabledMap.put(name, Boolean.TRUE); if(0>location) { location = getAttribLocation(gl, name); if(0>location) { if(verbose) { System.err.println("ShaderState: glEnableVertexAttribArray failed, no index for: "+name); if(DEBUG) { ExceptionUtils.dumpStack(System.err); } } return false; } } if(DEBUG) { System.err.println("ShaderState: 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(final GL2ES2 gl, final 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(final GL2ES2 gl, final 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(final GL2ES2 gl, final String name, int location) { activedAttribEnabledMap.put(name, Boolean.FALSE); if(0>location) { location = getAttribLocation(gl, name); if(0>location) { if(verbose) { System.err.println("ShaderState: glDisableVertexAttribArray failed, no index for: "+name); if(DEBUG) { ExceptionUtils.dumpStack(System.err); } } return false; } } if(DEBUG) { System.err.println("ShaderState: 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(final GL2ES2 gl, final 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(final GL2ES2 gl, final GLArrayData data) { if(0 > data.getLocation()) { getAttribLocation(gl, data); } return disableVertexAttribArray(gl, data.getName(), data.getLocation()); } /** * Set the {@link GLArrayData} vertex attribute data, if it's location is valid, i.e. ≥ 0. * <p> * This method uses the {@link GLArrayData}'s location if valid, i.e. ≥ 0.<br/> * If data's location is invalid, it will be retrieved via {@link #getAttribLocation(GL2ES2, GLArrayData)}, * set and cached in this state. * </p> * * @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(final GL2ES2 gl, final GLArrayData data) { int location = data.getLocation(); if(0 > location) { location = getAttribLocation(gl, data); } if(0 <= location) { // only pass the data, if the attribute exists in the current shader if(DEBUG) { System.err.println("ShaderState: 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(final GL2ES2 gl) { if(null!=shaderProgram) { for(final Iterator<GLArrayData> iter = activeAttribDataMap.values().iterator(); iter.hasNext(); ) { disableVertexAttribArray(gl, iter.next()); } for(final Iterator<String> iter = activedAttribEnabledMap.keySet().iterator(); iter.hasNext(); ) { disableVertexAttribArray(gl, iter.next()); } } activeAttribDataMap.clear(); activedAttribEnabledMap.clear(); activeAttribLocationMap.clear(); managedAttributes.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. * * @see #glEnableVertexAttribArray * @see #glDisableVertexAttribArray * @see #glVertexAttribPointer * @see #getVertexAttribPointer * @see #glReleaseAllVertexAttributes * @see #glResetAllVertexAttributes * @see #glResetAllVertexAttributes * @see ShaderProgram#glReplaceShader */ public void disableAllVertexAttributeArrays(final GL2ES2 gl, final boolean removeFromState) { for(final Iterator<String> iter = activedAttribEnabledMap.keySet().iterator(); iter.hasNext(); ) { final String name = iter.next(); if(removeFromState) { activedAttribEnabledMap.remove(name); } final int index = getAttribLocation(gl, name); if(0<=index) { gl.glDisableVertexAttribArray(index); } } } private final void relocateAttribute(final GL2ES2 gl, final GLArrayData attribute) { // get new location .. note: 'activeAttribLocationMap' is cleared before final String name = attribute.getName(); final int loc = attribute.setLocation(gl, shaderProgram.program()); if(0<=loc) { activeAttribLocationMap.put(name, Integer.valueOf(loc)); if(DEBUG) { System.err.println("ShaderState: relocateAttribute: "+name+", loc: "+loc); } if(isVertexAttribArrayEnabled(name)) { // enable attrib, VBO and pass location/data gl.glEnableVertexAttribArray(loc); } if( attribute.isVBO() ) { gl.glBindBuffer(GL.GL_ARRAY_BUFFER, attribute.getVBOName()); gl.glVertexAttribPointer(attribute); gl.glBindBuffer(GL.GL_ARRAY_BUFFER, 0); } else { gl.glVertexAttribPointer(attribute); } } } /** * Reset all previously enabled mapped vertex attribute data. * * <p> * Attribute data is bound to the GL state, i.e. VBO data itself will not be updated. * </p> * * <p> * Attribute location and it's data assignment is bound to the program, * hence both are updated. * </p> * * <p> * Note: Such update could only be prevented, * if tracking am attribute/program dirty flag. * </p> * * @throws GLException is the program is not linked * * @see #attachShaderProgram(GL2ES2, ShaderProgram) */ private final void resetAllAttributes(final GL2ES2 gl) { if(!shaderProgram.linked()) throw new GLException("Program is not linked"); activeAttribLocationMap.clear(); for(int i=0; i<managedAttributes.size(); i++) { managedAttributes.get(i).setLocation(-1); } for(final Iterator<GLArrayData> iter = activeAttribDataMap.values().iterator(); iter.hasNext(); ) { relocateAttribute(gl, iter.next()); } } private final void setAttribute(final GL2ES2 gl, final GLArrayData attribute) { // get new location .. final String name = attribute.getName(); final int loc = attribute.getLocation(); if(0<=loc) { bindAttribLocation(gl, loc, name); if(isVertexAttribArrayEnabled(name)) { // enable attrib, VBO and pass location/data gl.glEnableVertexAttribArray(loc); } if( attribute.isVBO() ) { gl.glBindBuffer(GL.GL_ARRAY_BUFFER, attribute.getVBOName()); gl.glVertexAttribPointer(attribute); gl.glBindBuffer(GL.GL_ARRAY_BUFFER, 0); } else { gl.glVertexAttribPointer(attribute); } } } /** * preserves the attribute location .. (program not linked) */ private final void setAllAttributes(final GL2ES2 gl) { for(final Iterator<GLArrayData> iter = activeAttribDataMap.values().iterator(); iter.hasNext(); ) { setAttribute(gl, iter.next()); } } // // Shader Uniform handling // /** * Gets the cached location of the shader uniform. * * @return -1 if there is no such uniform available, * otherwise >= 0 */ public final int getCachedUniformLocation(final String name) { final Integer idx = activeUniformLocationMap.get(name); return (null!=idx)?idx.intValue():-1; } /** * Bind the {@link GLUniform} lifecycle to this ShaderState. * * <p>If a uniform location is cached it is promoted to the {@link GLUniformData} instance.</p> * * <p>The attribute will be destroyed with {@link #destroy(GL2ES2)} * and it's location will be reset when switching shader with {@link #attachShaderProgram(GL2ES2, ShaderProgram)}.</p> * * <p>The data will not be transfered to the GPU, use {@link #uniform(GL2ES2, GLUniformData)} additionally.</p> * * @param uniform the {@link GLUniformData} which lifecycle shall be managed * * @see #getUniform(String) */ public void ownUniform(final GLUniformData uniform) { final int location = getCachedUniformLocation(uniform.getName()); if(0<=location) { uniform.setLocation(location); } activeUniformDataMap.put(uniform.getName(), uniform); managedUniforms.add(uniform); } public boolean ownsUniform(final GLUniformData uniform) { return managedUniforms.contains(uniform); } /** * Gets the location of a shader uniform with given <code>name</code>.<br> * Uses either the cached value {@link #getCachedUniformLocation(String)} if valid, * or the GLSL queried via {@link GL2ES2#glGetUniformLocation(int, String)}.<br> * The location will be cached. * <p> * The current shader program ({@link #attachShaderProgram(GL2ES2, ShaderProgram)}) * must be in use ({@link #useProgram(GL2ES2, boolean) }) !</p> * * @return -1 if there is no such attribute available, * otherwise >= 0 * @throws GLException is the program is not linked * * @see #glGetUniformLocation * @see com.jogamp.opengl.GL2ES2#glGetUniformLocation * @see #getUniformLocation * @see ShaderProgram#glReplaceShader */ public final int getUniformLocation(final GL2ES2 gl, final String name) { if(!shaderProgram.inUse()) throw new GLException("Program is not in use"); int location = getCachedUniformLocation(name); if(0>location) { if(!shaderProgram.linked()) throw new GLException("Program is not linked"); location = gl.glGetUniformLocation(shaderProgram.program(), name); if(0<=location) { activeUniformLocationMap.put(name, Integer.valueOf(location)); } else if(verbose) { System.err.println("ShaderState: glUniform failed, no location for: "+name+", index: "+location); if(DEBUG) { ExceptionUtils.dumpStack(System.err); } } } return location; } /** * Validates and returns the location of a shader uniform.<br> * Uses either the cached value {@link #getCachedUniformLocation(String)} if valid, * or the GLSL queried via {@link GL2ES2#glGetUniformLocation(int, String)}.<br> * The location will be cached and set in the * {@link GLUniformData} object. * <p> * The current shader program ({@link #attachShaderProgram(GL2ES2, ShaderProgram)}) * must be in use ({@link #useProgram(GL2ES2, boolean) }) !</p> * * @return -1 if there is no such attribute available, * otherwise >= 0 * @throws GLException is the program is not linked * * @see #glGetUniformLocation * @see com.jogamp.opengl.GL2ES2#glGetUniformLocation * @see #getUniformLocation * @see ShaderProgram#glReplaceShader */ public int getUniformLocation(final GL2ES2 gl, final GLUniformData data) { if(!shaderProgram.inUse()) throw new GLException("Program is not in use"); final String name = data.getName(); int location = getCachedUniformLocation(name); if(0<=location) { data.setLocation(location); } else { if(!shaderProgram.linked()) throw new GLException("Program is not linked"); location = data.setLocation(gl, shaderProgram.program()); if(0<=location) { activeUniformLocationMap.put(name, Integer.valueOf(location)); } else if(verbose) { System.err.println("ShaderState: glUniform failed, no location for: "+name+", index: "+location); if(DEBUG) { ExceptionUtils.dumpStack(System.err); } } } activeUniformDataMap.put(name, data); return location; } /** * Set the uniform data, if it's location is valid, i.e. ≥ 0. * <p> * This method uses the {@link GLUniformData}'s location if valid, i.e. ≥ 0.<br/> * If data's location is invalid, it will be retrieved via {@link #getUniformLocation(GL2ES2, GLUniformData)}, * set and cached in this state. * </p> * * @return false, if the location could not be determined, otherwise true * * @see #glGetUniformLocation * @see com.jogamp.opengl.GL2ES2#glGetUniformLocation * @see com.jogamp.opengl.GL2ES2#glUniform * @see #getUniformLocation * @see ShaderProgram#glReplaceShader */ public boolean uniform(final GL2ES2 gl, final GLUniformData data) { if(!shaderProgram.inUse()) throw new GLException("Program is not in use"); int location = data.getLocation(); if(0>location) { location = getUniformLocation(gl, data); } if(0<=location) { // only pass the data, if the uniform exists in the current shader if(DEBUG) { System.err.println("ShaderState: glUniform: "+data); } gl.glUniform(data); return true; } return false; } /** * Get the uniform data, previously set. * * @return the GLUniformData object, null if not previously set. */ public GLUniformData getUniform(final String name) { return activeUniformDataMap.get(name); } /** * Releases all mapped uniform data * and loses all indices */ public void releaseAllUniforms(final GL2ES2 gl) { activeUniformDataMap.clear(); activeUniformLocationMap.clear(); managedUniforms.clear(); } /** * Reset all previously mapped uniform data * <p> * Uniform data and location is bound to the program, * hence both are updated. * </p> * <p> * Note: Such update could only be prevented, * if tracking a uniform/program dirty flag. * </p> * * @throws GLException is the program is not in use * * @see #attachShaderProgram(GL2ES2, ShaderProgram) */ private final void resetAllUniforms(final GL2ES2 gl) { if(!shaderProgram.inUse()) throw new GLException("Program is not in use"); activeUniformLocationMap.clear(); for(final Iterator<GLUniformData> iter = managedUniforms.iterator(); iter.hasNext(); ) { iter.next().setLocation(-1); } for(final Iterator<GLUniformData> iter = activeUniformDataMap.values().iterator(); iter.hasNext(); ) { final GLUniformData data = iter.next(); final int loc = data.setLocation(gl, shaderProgram.program()); if( 0 <= loc ) { // only pass the data, if the uniform exists in the current shader activeUniformLocationMap.put(data.getName(), Integer.valueOf(loc)); if(DEBUG) { System.err.println("ShaderState: resetAllUniforms: "+data); } gl.glUniform(data); } } } public StringBuilder toString(StringBuilder sb, final boolean alsoUnlocated) { if(null==sb) { sb = new StringBuilder(); } sb.append("ShaderState[ "); sb.append(Platform.getNewline()).append(" "); if(null != shaderProgram) { shaderProgram.toString(sb); } else { sb.append("ShaderProgram: null"); } sb.append(Platform.getNewline()).append(" enabledAttributes ["); { final Iterator<String> names = activedAttribEnabledMap.keySet().iterator(); final Iterator<Boolean> values = activedAttribEnabledMap.values().iterator(); while( names.hasNext() ) { sb.append(Platform.getNewline()).append(" ").append(names.next()).append(": ").append(values.next()); } } sb.append(Platform.getNewline()).append(" ],").append(" activeAttributes ["); for(final Iterator<GLArrayData> iter = activeAttribDataMap.values().iterator(); iter.hasNext(); ) { final GLArrayData ad = iter.next(); if( alsoUnlocated || 0 <= ad.getLocation() ) { sb.append(Platform.getNewline()).append(" ").append(ad); } } sb.append(Platform.getNewline()).append(" ],").append(" managedAttributes ["); for(final Iterator<GLArrayData> iter = managedAttributes.iterator(); iter.hasNext(); ) { final GLArrayData ad = iter.next(); if( alsoUnlocated || 0 <= ad.getLocation() ) { sb.append(Platform.getNewline()).append(" ").append(ad); } } sb.append(Platform.getNewline()).append(" ],").append(" activeUniforms ["); for(final Iterator<GLUniformData> iter=activeUniformDataMap.values().iterator(); iter.hasNext(); ) { final GLUniformData ud = iter.next(); if( alsoUnlocated || 0 <= ud.getLocation() ) { sb.append(Platform.getNewline()).append(" ").append(ud); } } sb.append(Platform.getNewline()).append(" ],").append(" managedUniforms ["); for(final Iterator<GLUniformData> iter = managedUniforms.iterator(); iter.hasNext(); ) { final GLUniformData ud = iter.next(); if( alsoUnlocated || 0 <= ud.getLocation() ) { sb.append(Platform.getNewline()).append(" ").append(ud); } } sb.append(Platform.getNewline()).append(" ]").append(Platform.getNewline()).append("]"); return sb; } @Override public String toString() { return toString(null, DEBUG).toString(); } private boolean verbose = DEBUG; private ShaderProgram shaderProgram=null; private final HashMap<String, Boolean> activedAttribEnabledMap = new HashMap<String, Boolean>(); private final HashMap<String, Integer> activeAttribLocationMap = new HashMap<String, Integer>(); private final HashMap<String, GLArrayData> activeAttribDataMap = new HashMap<String, GLArrayData>(); private final ArrayList<GLArrayData> managedAttributes = new ArrayList<GLArrayData>(); private final HashMap<String, Integer> activeUniformLocationMap = new HashMap<String, Integer>(); private final HashMap<String, GLUniformData> activeUniformDataMap = new HashMap<String, GLUniformData>(); private final ArrayList<GLUniformData> managedUniforms = new ArrayList<GLUniformData>(); private final HashMap<String, Object> attachedObjectsByString = new HashMap<String, Object>(); private boolean resetAllShaderData = false; }