Enhanced GLSL handling

HI! I am AFJ (Away From Java) for some undetermined time. Back of couple of months i had written some code and text about GLSLShaderObjectsState, and would not want that go to waste. So here is my version of GLSL/LWJGLGLSLShaderObjectsState:


package com.jme.scene.state;

import com.jme.util.ShaderAttributeStore;
import java.io.IOException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;

import com.jme.math.Matrix3f;
import com.jme.math.Matrix4f;
import com.jme.util.ShaderAttribute;
import com.jme.util.ShaderUniform;
import com.jme.util.export.InputCapsule;
import com.jme.util.export.JMEExporter;
import com.jme.util.export.JMEImporter;
import com.jme.util.export.OutputCapsule;

/**
 * Implementation of the GL_ARB_shader_objects extension.
 *
 * @author Thomas Hourdel
 */
public abstract class GLSLShaderObjectsState extends RenderState {

    public ShaderAttributeStore store = new ShaderAttributeStore();

    /**
     * <code>isSupported</code> determines if the ARB_shader_objects extension
     * is supported by current graphics configuration.
     *
     * @return if ARB shader objects are supported
     */
    public abstract boolean isSupported();

    /**
     * <code>relinkProgram</code> instructs openGL to relink the associated
     * program and sets the attributes.  This should be used after setting
     * ShaderAttributes.
     */
    public abstract void relinkProgram();

    /**
     * Set an uniform value for this shader object.
     *
     * @param var
     *            uniform variable to change
     * @param value
     *            the new value
     */

    public void setUniform(String var, int value) {
        store.setUniform(var, value);
    }

    /**
     * Set an uniform value for this shader object.
     *
     * @param var
     *            uniform variable to change
     * @param value
     *            the new value
     */

    public void setUniform(String var, float value) {
        store.setUniform(var, value);
    }

    /**
     * Set an uniform value for this shader object.
     *
     * @param var
     *            uniform variable to change
     * @param value1
     *            the new value
     * @param value2
     *            the new value
     */

    public void setUniform(String var, int value1, int value2) {
        store.setUniform(var, value1, value2);
    }

    /**
     * Set an uniform value for this shader object.
     *
     * @param var
     *            uniform variable to change
     * @param value1
     *            the new value
     * @param value2
     *            the new value
     */

    public void setUniform(String var, float value1, float value2) {
        store.setUniform(var, value1, value2);
    }

    /**
     * Set an uniform value for this shader object.
     *
     * @param var
     *            uniform variable to change
     * @param value1
     *            the new value
     * @param value2
     *            the new value
     * @param value3
     *            the new value
     */

    public void setUniform(String var, int value1, int value2, int value3) {
        store.setUniform(var, value1, value2, value3);
    }

    /**
     * Set an uniform value for this shader object.
     *
     * @param var
     *            uniform variable to change
     * @param value1
     *            the new value
     * @param value2
     *            the new value
     * @param value3
     *            the new value
     */

    public void setUniform(String var, float value1, float value2, float value3) {
        store.setUniform(var, value1, value2, value3);
    }

    /**
     * Set an uniform value for this shader object.
     *
     * @param var
     *            uniform variable to change
     * @param value1
     *            the new value
     * @param value2
     *            the new value
     * @param value3
     *            the new value
     * @param value4
     *            the new value
     */

    public void setUniform(String var, int value1, int value2, int value3,
            int value4) {
        store.setUniform(var, value1, value2, value3, value4);
    }

    /**
     * Set an uniform value for this shader object.
     *
     * @param var
     *            uniform variable to change
     * @param value1
     *            the new value
     * @param value2
     *            the new value
     * @param value3
     *            the new value
     * @param value4
     *            the new value
     */

    public void setUniform(String var, float value1, float value2,
            float value3, float value4) {
        store.setUniform(var, value1, value2, value3, value4);
    }

    /**
     * Set an uniform value for this shader object.
     *
     * @param var
     *            uniform variable to change
     * @param value
     *            the new value (a float buffer of size 4)
     * @param transpose
     *            transpose the matrix ?
     */

    public void setUniform(String var, float value[], boolean transpose) {
        store.setUniform(var, value, transpose);
    }

    /**
     * Set an uniform value for this shader object.
     *
     * @param var
     *            uniform variable to change
     * @param value
     *            the new value
     * @param transpose
     *            transpose the matrix ?
     */

    public void setUniform(String var, Matrix3f value, boolean transpose) {
        store.setUniform(var, value, transpose);
    }

    /**
     * Set an uniform value for this shader object.
     *
     * @param var
     *            uniform variable to change
     * @param value
     *            the new value
     * @param transpose
     *            transpose the matrix ?
     */

    public void setUniform(String var, Matrix4f value, boolean transpose) {
        store.setUniform(var, value, transpose);
    }

    /**
     * <code>clearUniforms</code> clears all uniform values from this state.
     *
     */
    public void clearUniforms() {
        store.clearUniforms();
    }

    /**
     * Set an attribute pointer value for this shader object.
     *
     * @param var
     *            attribute variable to change
     */
    public void setAttributePointer(String var, int size, boolean normalized,
            int stride, FloatBuffer data) {
        store.setAttributePointer(var, size, normalized, stride, data);
    }

    /**
     * Set an attribute pointer value for this shader object.
     *
     * @param var
     *            attribute variable to change
     */
    public void setAttributePointer(String var, int size, boolean normalized,
            boolean unsigned, int stride, ByteBuffer data) {
        store.setAttributePointer(var, size, normalized, unsigned, stride, data);
    }

    /**
     * Set an attribute pointer value for this shader object.
     *
     * @param var
     *            attribute variable to change
     */
    public void setAttributePointer(String var, int size, boolean normalized,
            boolean unsigned, int stride, IntBuffer data) {
        store.setAttributePointer(var, size, normalized, unsigned, stride, data);
    }

    /**
     * Set an attribute pointer value for this shader object.
     *
     * @param var
     *            attribute variable to change
     */
    public void setAttributePointer(String var, int size, boolean normalized,
            boolean unsigned, int stride, ShortBuffer data) {
        store.setAttributePointer(var, size, normalized, unsigned, stride, data);
    }

    /**
     * <code>clearAttributes</code> clears all attribute values from this state.
     *
     */
    public void clearAttributes() {
        store.clearAttributes();
    }
   
    /**
     * @return RS_SHADER_OBJECTS
     * @see com.jme.scene.state.RenderState#getType()
     */
    public int getType() {
        return RS_GLSL_SHADER_OBJECTS;
    }
      
    /**
     * <code>load</code> loads the shader object from the specified file. The
     * program must be in ASCII format. We delegate the loading to each
     * implementation because we do not know in what format the underlying API
     * wants the data.
     *
     * @param vert
     *            text file containing the vertex shader object
     * @param frag
     *            text file containing the fragment shader object
     */
    public abstract void load(URL vert, URL frag);
   
    public abstract void load(String vert, String frag);
  
    public void release() {
        this.setEnabled(false);
    }
   
    public void write(JMEExporter e) throws IOException {
        super.write(e);
        OutputCapsule capsule = e.getCapsule(this);
        capsule.writeSavableArrayList(store.uniforms,"uniforms", new ArrayList<ShaderUniform>());
        capsule.writeSavableArrayList(store.attribs,"attribs", new ArrayList<ShaderAttribute>());
    }

    @SuppressWarnings("unchecked")
   public void read(JMEImporter e) throws IOException {
        super.read(e);
        InputCapsule capsule = e.getCapsule(this);
        store.uniforms = capsule.readSavableArrayList("uniforms", new ArrayList<ShaderUniform>());
        store.attribs = capsule.readSavableArrayList("attribs", new ArrayList<ShaderAttribute>());
    }
   
    public Class getClassTag() {
        return GLSLShaderObjectsState.class;
    }
}



The change in GLSLShaderObjectsState is the usage of ShaderAttributeStore instead of ArrayList-s. More on why this change, later on.

Now the LWJGL part, part one:



package com.jme.scene.state.lwjgl;

import com.jme.util.ShaderAttributeStore;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.logging.Level;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.ARBFragmentShader;
import org.lwjgl.opengl.ARBShaderObjects;
import org.lwjgl.opengl.ARBVertexProgram;
import org.lwjgl.opengl.ARBVertexShader;
import org.lwjgl.opengl.GLContext;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;

import com.jme.scene.state.GLSLShaderObjectsState;
import com.jme.util.LoggingSystem;
import com.jme.util.ShaderAttribute;
import com.jme.util.ShaderUniform;
//import com.jme.util.geom.BufferUtils;
import java.io.InputStream;

/**
 * Implementation of the GL_ARB_shader_objects extension.
 *
 * @author Thomas Hourdel
 * @author Joshua Slack (attributes)
 */
public class LWJGLShaderObjectsState extends GLSLShaderObjectsState {

    private static final long serialVersionUID = 1L;

    // current program in effect
    private static int currentProgram = -1;
   
    /** OpenGL id for this program. * */
    private int programID = -1;

    private int vertexShaderID = -1;
   
    private int fragmentShaderID = -1;   
   
    private int numAttributes = -1;
           
    /**
     * Attribute id to assign to the next vertex attribute
     */
    protected int assignAttributeId;

    /**
     * Keep track of the original shader state after cloning
     */
    protected LWJGLShaderObjectsState original;
   
    public LWJGLShaderObjectsState() {
        super();
        // get the number of supported shader attributes
        if(isSupported()) {
            IntBuffer buf = BufferUtils.createIntBuffer(16);
            GL11.glGetInteger(GL20.GL_MAX_VERTEX_ATTRIBS, buf);
            numAttributes = buf.get(0);
            assignAttributeId = numAttributes-1;
        }
    }
   
    protected LWJGLShaderObjectsState(LWJGLShaderObjectsState original) {
        super();
        this.original = original;
        this.currentProgram = original.currentProgram;
        this.programID = original.programID;
        this.vertexShaderID = original.vertexShaderID;
        this.fragmentShaderID = original.fragmentShaderID;
    }
   
    /**
     * Determines if the current OpenGL context supports the
     * GL_ARB_shader_objects extension.
     *
     * @see com.jme.scene.state.ShaderObjectsState#isSupported()
     */
    public boolean isSupported() {
        return GLContext.getCapabilities().GL_ARB_shader_objects;
    }

    /**
     * <code>relinkProgram</code> instructs openGL to relink the associated
     * program.  This should be used after setting ShaderAttributes.
     */
    public void relinkProgram() {
        if(store.attribs.size() > numAttributes) {
            LoggingSystem.getLogger().log(Level.WARNING, "Too many shader attributes");
        }
        ARBShaderObjects.glLinkProgramARB(programID);
       
        // because nVidia cards share shader attribute id's with vertex, normal and texcoord
        // attributes (from 0 to 15), start allocating attributes from
        // top to bottom
        ByteBuffer nameBuf = BufferUtils.createByteBuffer(64);
        for (int x = 0, aSize = store.attribs.size(); x<aSize && x<numAttributes-1; x++) {
            ShaderAttribute attrib = (ShaderAttribute)store.attribs.get(x);
            getAttribLoc(attrib);
        }
       
        // set the uniforms id's
        if (!store.uniforms.isEmpty()) {
            for (int x = store.uniforms.size(); --x >= 0; ) {
                getUniLoc((ShaderUniform) store.uniforms.get(x));
            }
        }
    }

    private int getAttribLoc(ShaderAttribute attrib) {
        if(attrib.attributeID == -1) {
            ByteBuffer nameBuf = BufferUtils.createByteBuffer(attrib.name.getBytes().length+1);
            nameBuf.clear();
            nameBuf.put(attrib.name.getBytes());
            nameBuf.rewind();
            attrib.attributeID=ARBVertexShader.glGetAttribLocationARB(programID, nameBuf);
            if(attrib.attributeID == -1) {
                attrib.attributeID = getNextAttributeId();
                ARBVertexShader.glBindAttribLocationARB(programID, attrib.attributeID, nameBuf);
            }
        }
        return attrib.attributeID;
    }
   
    private int getNextAttributeId() {
        if(original != null) return original.getNextAttributeId();
        return assignAttributeId--;
    }
   
    /**
     * Get uniform variable location according to his string name.
     *
     * @param name
     *            uniform variable name
     */
    private int getUniLoc(ShaderUniform uniform) {
        if (uniform.uniformID == -1) {
            ByteBuffer nameBuf = BufferUtils
               .createByteBuffer(uniform.name.getBytes().length+1);
            nameBuf.clear();
            nameBuf.put(uniform.name.getBytes());
            nameBuf.rewind();
           
            uniform.uniformID = ARBShaderObjects.glGetUniformLocationARB(programID, nameBuf);
            if(uniform.uniformID==-1) uniform.uniformID=-2;
        }
        return uniform.uniformID;
    }
  
    /**
     * Load an URL and grab content into a ByteBuffer.
     *
     * @param url
     *            the url to load
     */

    private ByteBuffer load(java.net.URL url) {
        try {
            byte shaderCode[] = null;
            ByteBuffer shaderByteBuffer = null;

            BufferedInputStream bufferedInputStream = new BufferedInputStream(
                    url.openStream());
            DataInputStream dataStream = new DataInputStream(
                    bufferedInputStream);
            dataStream.readFully(shaderCode = new byte[bufferedInputStream
                    .available()]);
            bufferedInputStream.close();
            dataStream.close();
            shaderByteBuffer = BufferUtils.createByteBuffer(shaderCode.length);
            shaderByteBuffer.put(shaderCode);
            shaderByteBuffer.rewind();

            return shaderByteBuffer;
        } catch (Exception e) {
            LoggingSystem.getLogger().log(Level.SEVERE,
                   "Could not load shader object: " + e);
           LoggingSystem.getLogger().throwing(getClass().getName(),
                   "load(URL)", e);
           return null;
        }
    }
   
    private ByteBuffer load(String data) {
        try {
            byte[] bytes = data.getBytes();
            System.out.println("bytes length " + bytes.length);
            ByteBuffer program = BufferUtils.createByteBuffer(bytes.length);
            program.put(bytes);
            program.rewind();
            return program;
        } catch (Exception e) {
            LoggingSystem.getLogger().log(Level.SEVERE,
                    "Could not load fragment program: " + e);
            LoggingSystem.getLogger().throwing(getClass().getName(),
                    "load(URL)", e);
            return null;
        }
    }

    /**
     * Loads the shader object. Use null for an empty vertex or empty fragment shader.
     *
     * @see com.jme.scene.state.ShaderObjectsState#load(java.net.URL,
     *      java.net.URL)
     */
   
    public void load(URL vert, URL frag) {
        ByteBuffer vertexByteBuffer = vert != null ? load(vert) : null;
        ByteBuffer fragmentByteBuffer = frag!= null ? load(frag) : null;
        load(vertexByteBuffer, fragmentByteBuffer);
    }
   
    public void load(String vert, String frag) {
        ByteBuffer vertexByteBuffer = vert != null ? load(vert) : null;
        ByteBuffer fragmentByteBuffer = frag!= null ? load(frag) : null;
        load(vertexByteBuffer, fragmentByteBuffer);
    }
   
    private void load(ByteBuffer vertexByteBuffer, ByteBuffer fragmentByteBuffer) {

        if (vertexByteBuffer == null && fragmentByteBuffer == null) return;

        programID = ARBShaderObjects.glCreateProgramObjectARB();

        if (vertexByteBuffer != null) {
            vertexShaderID = ARBShaderObjects.glCreateShaderObjectARB(ARBVertexShader.GL_VERTEX_SHADER_ARB);

            // Create the sources
            ARBShaderObjects.glShaderSourceARB(vertexShaderID, vertexByteBuffer);

            // Compile the vertex shader
            IntBuffer compiled = BufferUtils.createIntBuffer(1);
            ARBShaderObjects.glCompileShaderARB(vertexShaderID);
            ARBShaderObjects.glGetObjectParameterARB(vertexShaderID, ARBShaderObjects.GL_OBJECT_COMPILE_STATUS_ARB, compiled);
            checkProgramError(compiled, vertexShaderID);

            // Attatch the program
            ARBShaderObjects.glAttachObjectARB(programID, vertexShaderID);
        }

        if (fragmentByteBuffer != null) {
            fragmentShaderID = ARBShaderObjects.glCreateShaderObjectARB(ARBFragmentShader.GL_FRAGMENT_SHADER_ARB);

            // Create the sources
            ARBShaderObjects.glShaderSourceARB(fragmentShaderID, fragmentByteBuffer);

            // Compile the fragment shader
            IntBuffer compiled = BufferUtils.createIntBuffer(1);
            ARBShaderObjects.glCompileShaderARB(fragmentShaderID);
            ARBShaderObjects.glGetObjectParameterARB(fragmentShaderID, ARBShaderObjects.GL_OBJECT_COMPILE_STATUS_ARB, compiled);
            checkProgramError(compiled, fragmentShaderID);

            // Attatch the program
            ARBShaderObjects.glAttachObjectARB(programID, fragmentShaderID);
        }

        ARBShaderObjects.glLinkProgramARB(programID);
       
    }

    /**
     * Check for program errors. If an error is detected, program exits.
     *
     * @param compiled
     *            the compiler state for a given shader
     * @param id
     *            shader's id
     */
    private void checkProgramError(IntBuffer compiled, int id) {

        if (compiled.get(0) == 0) {
            IntBuffer iVal = BufferUtils.createIntBuffer(1);
            ARBShaderObjects.glGetObjectParameterARB(id,
                    ARBShaderObjects.GL_OBJECT_INFO_LOG_LENGTH_ARB, iVal);
            int length = iVal.get();
            String out = null;

            if (length > 0) {
                ByteBuffer infoLog = BufferUtils.createByteBuffer(length);

                iVal.flip();
                ARBShaderObjects.glGetInfoLogARB(id, iVal, infoLog);

                byte[] infoBytes = new byte[length];
                infoLog.get(infoBytes);
                out = new String(infoBytes);
            }

            LoggingSystem.getLogger().log(Level.SEVERE, out);
        }
    }

    /**
     * Applies those shader objects to the current scene. Checks if the
     * GL_ARB_shader_objects extension is supported before attempting to enable
     * those objects.
     *
     * @see com.jme.scene.state.RenderState#apply()
     */
   
    public void apply() {
        if(useProgram()) {
            applyAttribs(store.attribs);
            applyUniforms(store.uniforms);
        }
    }
   



Part two:



    public void apply(ShaderAttributeStore attributeStore) {
        if(useProgram()) {
            applyAttribs(attributeStore.attribs);
            applyUniforms(attributeStore.uniforms);
        }
    }
   
    private boolean useProgram() {
        if (isSupported()) {
            if (isEnabled() && programID != -1) {
                // Use the shader...
                if( currentProgram != programID ) {
                    ARBShaderObjects.glUseProgramObjectARB(programID);
                    currentProgram = programID;
                }
                return true;
            } else {
                if( currentProgram != -1)
                    ARBShaderObjects.glUseProgramObjectARB(0);
                currentProgram = -1;
                return false;
            }
        }
        return false;
    }
   
    private void applyAttribs(ArrayList attribs) {
        // Assign attribs...
        if (!attribs.isEmpty()) {
            for (int x = attribs.size(); --x >= 0; ) {
                ShaderAttribute attVar = (ShaderAttribute) attribs.get(x);
                if(attVar.attributeID<0) {
                    // dont set invalid attributes
                    continue;
                }
                switch (attVar.type) {
                case ShaderAttribute.SU_POINTER_FLOAT:
                    ARBVertexProgram.glVertexAttribPointerARB(
                            attVar.attributeID,
                            attVar.size,
                            attVar.normalized,
                            attVar.stride,
                            (FloatBuffer)attVar.data);
                    ARBVertexProgram.glEnableVertexAttribArrayARB(attVar.attributeID);
                    break;
                case ShaderAttribute.SU_POINTER_BYTE:
                    ARBVertexProgram.glVertexAttribPointerARB(
                            attVar.attributeID,
                            attVar.size,
                            attVar.unsigned,
                            attVar.normalized,
                            attVar.stride,
                            (ByteBuffer)attVar.data);
                    ARBVertexProgram.glEnableVertexAttribArrayARB(attVar.attributeID);
                    break;
                case ShaderAttribute.SU_POINTER_INT:
                    ARBVertexProgram.glVertexAttribPointerARB(
                            attVar.attributeID,
                            attVar.size,
                            attVar.unsigned,
                            attVar.normalized,
                            attVar.stride,
                            (IntBuffer)attVar.data);
                    ARBVertexProgram.glEnableVertexAttribArrayARB(attVar.attributeID);
                    break;
                case ShaderAttribute.SU_POINTER_SHORT:
                    ARBVertexProgram.glVertexAttribPointerARB(
                            attVar.attributeID,
                            attVar.size,
                            attVar.unsigned,
                            attVar.normalized,
                            attVar.stride,
                            (ShortBuffer)attVar.data);
                    ARBVertexProgram.glEnableVertexAttribArrayARB(attVar.attributeID);
                    break;
                default: // happens with per mesh specific attribs
                    break;
                }
            }
        }
    }
   
    private void applyUniforms(ArrayList uniforms) {
        // Assign uniforms...
        if (!uniforms.isEmpty()) {
            int uniformId;
            for (int x = uniforms.size(); --x >= 0; ) {
                ShaderUniform uniformVar = (ShaderUniform) uniforms.get(x);
                uniformId=getUniLoc(uniformVar);
                if(uniformId != -2 && uniformId != -1) {
                    switch (uniformVar.type) {
                    case ShaderUniform.SU_INT:
                        ARBShaderObjects.glUniform1iARB(
                                uniformId,
                                uniformVar.vint[0]);
                        break;
                    case ShaderUniform.SU_INT2:
                        ARBShaderObjects.glUniform2iARB(
                                uniformId,
                                uniformVar.vint[0], uniformVar.vint[1]);
                        break;
                    case ShaderUniform.SU_INT3:
                        ARBShaderObjects.glUniform3iARB(
                                uniformId,
                                uniformVar.vint[0], uniformVar.vint[1],
                                uniformVar.vint[2]);
                        break;
                    case ShaderUniform.SU_INT4:
                        ARBShaderObjects.glUniform4iARB(
                                uniformId,

                                uniformVar.vint[0], uniformVar.vint[1],
                                uniformVar.vint[2], uniformVar.vint[3]);
                        break;
                    case ShaderUniform.SU_FLOAT:
                        ARBShaderObjects.glUniform1fARB(
                                uniformId,
                                uniformVar.vfloat[0]);
                        break;
                    case ShaderUniform.SU_FLOAT2:
                        ARBShaderObjects.glUniform2fARB(
                                uniformId,
                                uniformVar.vfloat[0],
                                uniformVar.vfloat[1]);
                        break;
                    case ShaderUniform.SU_FLOAT3:
                        ARBShaderObjects.glUniform3fARB(
                                uniformId,
                                uniformVar.vfloat[0],
                                uniformVar.vfloat[1],
                                uniformVar.vfloat[2]);
                        break;
                    case ShaderUniform.SU_FLOAT4:
                        ARBShaderObjects.glUniform4fARB(
                                uniformId,
                                uniformVar.vfloat[0],
                                uniformVar.vfloat[1],
                                uniformVar.vfloat[2],
                                uniformVar.vfloat[3]);
                        break;
                    case ShaderUniform.SU_MATRIX2:
                        if (uniformVar.matrixBuffer == null)
                            uniformVar.matrixBuffer = org.lwjgl.BufferUtils.createFloatBuffer(4);
                        uniformVar.matrixBuffer.clear();
                        uniformVar.matrixBuffer.put(uniformVar.matrix2f);
                        uniformVar.matrixBuffer.rewind();
                        ARBShaderObjects.glUniformMatrix2ARB(
                                uniformId,
                                uniformVar.transpose, uniformVar.matrixBuffer);
                        break;
                    case ShaderUniform.SU_MATRIX3:
                        if (uniformVar.matrixBuffer == null)
                            uniformVar.matrixBuffer = uniformVar.matrix3f.toFloatBuffer();
                        else
                            uniformVar.matrix3f.fillFloatBuffer(uniformVar.matrixBuffer);
                        ARBShaderObjects.glUniformMatrix3ARB(
                                uniformId,
                                uniformVar.transpose,
                                uniformVar.matrixBuffer);
                        break;
                    case ShaderUniform.SU_MATRIX4:
                        if (uniformVar.matrixBuffer == null)
                            uniformVar.matrixBuffer = uniformVar.matrix4f.toFloatBuffer();
                        else
                            uniformVar.matrix4f.fillFloatBuffer(uniformVar.matrixBuffer);
                        ARBShaderObjects.glUniformMatrix4ARB(
                                uniformId,
                                uniformVar.transpose,
                                uniformVar.matrixBuffer);
                        break;
                    default: // happens with per mesh specific uniforms
                        break;
                    }
                }
            }
        }
    }
   
    public void clearAttributes() {
        super.clearAttributes();
        assignAttributeId = numAttributes;
    }
   
    public void release() {
        super.release();
        if(vertexShaderID!=-1) {
            ARBShaderObjects.glDeleteObjectARB(vertexShaderID);
            vertexShaderID=-1;
        }
        if(fragmentShaderID!=-1) {
            ARBShaderObjects.glDeleteObjectARB(fragmentShaderID);
            vertexShaderID=-1;
        }
        if(programID!=-1) {
            ARBShaderObjects.glDeleteObjectARB(programID);
            programID=-1;
        }
    }
   
    public LWJGLShaderObjectsState createClone() {
        return new LWJGLShaderObjectsState(this);
    }
   
    public int queueCompare(GLSLShaderObjectsState other) {
        LWJGLShaderObjectsState oth=(LWJGLShaderObjectsState) other;
        if ( programID == oth.programID ) return 0;
        if ( programID == -1 ) return -1;
        if ( oth.programID == -1 ) return 1;
        return programID < oth.programID?-1:1;
    }
}

Now the ShaderAttributeStore:



package com.jme.util;

import com.jme.math.Matrix3f;
import com.jme.math.Matrix4f;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;

/**
 *
 * @author vear
 */
public class ShaderAttributeStore {
   
    // uniforms and attribs with value
    public ArrayList uniforms = new ArrayList();
    public ArrayList attribs = new ArrayList();
   
    private boolean useAttribArrayVBO = false;
   
    /** Creates a new instance of ShaderAttributeStore */
    public ShaderAttributeStore() {
    }
   
    public ArrayList getUniforms() {
        return uniforms;
    }
   
    public ArrayList getAttributes() {
        return attribs;
    }
   
    // register an uniform with unknown type
    // uniform will be filled on a per material basis
    public void setUniform(String var) {
        ShaderUniform object = getShaderUniform(var, ShaderUniform.SU_UNKNOWN);
        if (object.uniformID == -1)
            uniforms.add(object);
    }

    /**
     * Set an uniform value for this shader object.
     *
     * @param var
     *            uniform variable to change
     * @param value
     *            the new value
     */

    public void setUniform(String var, int value) {
        ShaderUniform object = getShaderUniform(var, ShaderUniform.SU_INT);
        if (object.vint == null)
            object.vint = new int[1];
        object.vint[0] = value;
        if (object.uniformID == -1)
            uniforms.add(object);
    }

    /**
     * Set an uniform value for this shader object.
     *
     * @param var
     *            uniform variable to change
     * @param value
     *            the new value
     */

    public void setUniform(String var, float value) {
        ShaderUniform object = getShaderUniform(var, ShaderUniform.SU_FLOAT);
        if( object.vfloat==null )
            object.vfloat = new float[1];
        object.vfloat[0] = value;
        if (object.uniformID == -1)
            uniforms.add(object);
    }

    /**
     * Set an uniform value for this shader object.
     *
     * @param var
     *            uniform variable to change
     * @param value1
     *            the new value
     * @param value2
     *            the new value
     */

    public void setUniform(String var, int value1, int value2) {
        ShaderUniform object = getShaderUniform(var, ShaderUniform.SU_INT2);
        if( object.vint == null )
            object.vint = new int[2];
        object.vint[0] = value1;
        object.vint[1] = value2;
        if (object.uniformID == -1)
            uniforms.add(object);
    }

    /**
     * Set an uniform value for this shader object.
     *
     * @param var
     *            uniform variable to change
     * @param value1
     *            the new value
     * @param value2
     *            the new value
     */

    public void setUniform(String var, float value1, float value2) {
        ShaderUniform object = getShaderUniform(var, ShaderUniform.SU_FLOAT2);
        if( object.vfloat == null )
            object.vfloat = new float[2];
        object.vfloat[0] = value1;
        object.vfloat[1] = value2;
        if (object.uniformID == -1)
            uniforms.add(object);
    }

    /**
     * Set an uniform value for this shader object.
     *
     * @param var
     *            uniform variable to change
     * @param value1
     *            the new value
     * @param value2
     *            the new value
     * @param value3
     *            the new value
     */

    public void setUniform(String var, int value1, int value2, int value3) {
        ShaderUniform object = getShaderUniform(var, ShaderUniform.SU_INT3);
        if( object.vint == null )
            object.vint = new int[3];
        object.vint[0] = value1;
        object.vint[1] = value2;
        object.vint[2] = value3;
        if (object.uniformID == -1)
            uniforms.add(object);
    }

    /**
     * Set an uniform value for this shader object.
     *
     * @param var
     *            uniform variable to change
     * @param value1
     *            the new value
     * @param value2
     *            the new value
     * @param value3
     *            the new value
     */

    public void setUniform(String var, float value1, float value2, float value3) {
        ShaderUniform object = getShaderUniform(var, ShaderUniform.SU_FLOAT3);
        if( object.vfloat == null )
            object.vfloat = new float[3];
        object.vfloat[0] = value1;
        object.vfloat[1] = value2;
        object.vfloat[2] = value3;
        if (object.uniformID == -1)
            uniforms.add(object);
    }

    public void setUniform(String var, Vector3f vec) {
        setUniform(var, vec.x, vec.y, vec.z);
    }
   
    public void setUniform(String var, ColorRGBA c) {
        setUniform(var, c.r, c.g, c.b, c.a);
    }
   
    /**
     * Set an uniform value for this shader object.
     *
     * @param var
     *            uniform variable to change
     * @param value1
     *            the new value
     * @param value2
     *            the new value
     * @param value3
     *            the new value
     * @param value4
     *            the new value
     */

    public void setUniform(String var, int value1, int value2, int value3,
            int value4) {
        ShaderUniform object = getShaderUniform(var, ShaderUniform.SU_INT4);
        if( object.vint == null )
            object.vint = new int[4];
        object.vint[0] = value1;
        object.vint[1] = value2;
        object.vint[2] = value3;
        object.vint[3] = value4;
        if (object.uniformID == -1)
            uniforms.add(object);
    }

    /**
     * Set an uniform value for this shader object.
     *
     * @param var
     *            uniform variable to change
     * @param value1
     *            the new value
     * @param value2
     *            the new value
     * @param value3
     *            the new value
     * @param value4
     *            the new value
     */

    public void setUniform(String var, float value1, float value2,
            float value3, float value4) {
        ShaderUniform object = getShaderUniform(var, ShaderUniform.SU_FLOAT4);
        if( object.vfloat == null )
            object.vfloat = new float[4];
        object.vfloat[0] = value1;
        object.vfloat[1] = value2;
        object.vfloat[2] = value3;
        object.vfloat[3] = value4;
        if (object.uniformID == -1)
            uniforms.add(object);
    }

    /**
     * Set an uniform value for this shader object.
     *
     * @param var
     *            uniform variable to change
     * @param value
     *            the new value (a float buffer of size 4)
     * @param transpose
     *            transpose the matrix ?
     */

    public void setUniform(String var, float value[], boolean transpose) {
        if (value.length != 4) return;

        ShaderUniform object = getShaderUniform(var, ShaderUniform.SU_MATRIX2);
        if( object.matrix2f == null)
            object.matrix2f = new float[4];
        object.matrix2f = value;
        object.transpose = transpose;
        if (object.uniformID == -1)
            uniforms.add(object);
    }

    /**
     * Set an uniform value for this shader object.
     *
     * @param var
     *            uniform variable to change
     * @param value
     *            the new value
     * @param transpose
     *            transpose the matrix ?
     */

    public void setUniform(String var, Matrix3f value, boolean transpose) {
        ShaderUniform object = getShaderUniform(var, ShaderUniform.SU_MATRIX3);
        object.matrix3f = value;
        object.transpose = transpose;
        if (object.uniformID == -1)
            uniforms.add(object);
    }

    /**
     * Set an uniform value for this shader object.
     *
     * @param var
     *            uniform variable to change
     * @param value
     *            the new value
     * @param transpose
     *            transpose the matrix ?
     */

    public void setUniform(String var, Matrix4f value, boolean transpose) {
        ShaderUniform object = getShaderUniform(var, ShaderUniform.SU_MATRIX4);
        object.matrix4f = value;
        object.transpose = transpose;
        if (object.uniformID == -1)
            uniforms.add(object);
    }

    /**
     * <code>clearUniforms</code> clears all uniform values from this state.
     *
     */
    public void clearUniforms() {
        uniforms.clear();
    }
   
    // register an new shader attribute
    // which will be specified on per material basis
    public void setAttribute(String var) {
        ShaderAttribute object = getShaderAttribute(var, ShaderAttribute.SU_UNKNOWN);
        if (object.attributeID == -1)
            attribs.add(object);
    }
   

    /**
     * Set an attribute pointer value for this shader object.
     *
     * @param var
     *            attribute variable to change
     */
    public void setAttributePointer(String var, int size, boolean normalized,
            int stride, FloatBuffer data) {
        ShaderAttribute object = getShaderAttribute(var, ShaderAttribute.SU_POINTER_FLOAT);
        object.size = size;
        object.normalized = normalized;
        object.stride = stride;
        object.data = data;
        if (object.attributeID == -1)
            attribs.add(object);
    }

    /**
     * Set an attribute pointer value for this shader object.
     *
     * @param var
     *            attribute variable to change
     */
    public void setAttributePointer(String var, int size, boolean normalized,
            boolean unsigned, int stride, ByteBuffer data) {
        ShaderAttribute object = getShaderAttribute(var, ShaderAttribute.SU_POINTER_BYTE);
        object.size = size;
        object.normalized = normalized;
        object.unsigned = unsigned;
        object.stride = stride;
        object.data = data;
        if (object.attributeID == -1)
            attribs.add(object);
    }

    /**
     * Set an attribute pointer value for this shader object.
     *
     * @param var
     *            attribute variable to change
     */
    public void setAttributePointer(String var, int size, boolean normalized,
            boolean unsigned, int stride, IntBuffer data) {
        ShaderAttribute object = getShaderAttribute(var, ShaderAttribute.SU_POINTER_INT);
        object.size = size;
        object.normalized = normalized;
        object.unsigned = unsigned;
        object.stride = stride;
        object.data = data;
        if (object.attributeID == -1)
            attribs.add(object);
    }

    /**
     * Set an attribute pointer value for this shader object.
     *
     * @param var
     *            attribute variable to change
     */
    public void setAttributePointer(String var, int size, boolean normalized,
            boolean unsigned, int stride, ShortBuffer data) {
        ShaderAttribute object = getShaderAttribute(var, ShaderAttribute.SU_POINTER_SHORT);
        object.size = size;
        object.normalized = normalized;
        object.unsigned = unsigned;
        object.stride = stride;
        object.data = data;
        if (object.attributeID == -1)
            attribs.add(object);
    }

    /**
     * <code>clearAttributes</code> clears all attribute values from this state.
     *
     */
    public void clearAttributes() {
        attribs.clear();
    }
   
    private ShaderUniform getShaderUniform(String name, int type) {
        ShaderUniform temp = getShaderUniform(name);
        if( temp==null )
            temp= new ShaderUniform(name, type);
        else
            temp.type = type;
        return temp;
    }

    public ShaderUniform getShaderUniform(String name) {
        for (int x = uniforms.size(); --x >= 0; ) {
            ShaderUniform temp = (ShaderUniform) uniforms.get(x);
            if (name.equals(temp.name))
                return temp;
        }
        return null;
    }
   
    private ShaderAttribute getShaderAttribute(String name, int type) {
        ShaderAttribute temp = getShaderAttribute(name);
        if( temp == null ) {
            temp = new ShaderAttribute(name, type);
        } else
            temp.type=type;
        return temp;
    }
   
    public ShaderAttribute getShaderAttribute(String name) {
        for (int x = attribs.size(); --x >= 0; ) {
            ShaderAttribute temp = (ShaderAttribute)attribs.get(x);
            if (name.equals(temp.name))
                return temp;
        }
        return null;
    }
   
    public Buffer getShaderAttributePointerData(String name) {
        ShaderAttribute temp = getShaderAttribute(name);
        if( temp == null )
            return null;
        return temp.data;
    }
   
    public void setVBOAttribArrayEnabled(boolean enabled) {
        this.useAttribArrayVBO = enabled;
    }
}

A line to be added to ShaderUniform.java and to ShaderAttribute.java:



    public final static int SU_UNKNOWN = -1;



The text/explanation i have written will follow in the next post. Please note that i have not tested the above source with the latest CVS.

As noted in the LWJGLTextureState thread, i have some fixes and some ideas to enhance jME's working

with shaders.



jME is currently fixed functionality based. There are options for programmable rendering, but there isn't much in LWJGLRenderer to back GLSL.



I'll begin with conceptual issues.


  1. Integrate FragmentProgramState, VertexProgramState with GLSLShaderObjectsState.

    Why? There cannot be a program and a shader active the same time (they basically being the same

    thing). This is not the main reason tho. By using only one state for programmable rendering, other programmable flavors can be added, like Cg. There would be one state, but 3 different subtypes and implementations. And the renderstate list would not grow if Cg is eventually done by someone. This is conceptually a big change, but

    the least relevant to issues i tried solving.


  2. Shaders are hub of data. Most renderstates are isolated in terms of data need. For example

    MaterialState only needs to know some colors, but nothing else. TextureState needs Textures, but does

    not need to know anything about the geometry. Shaders get data from the fixed functionality state, but there is data which it needs additionally:

    a. Texture unit numbers from TextureState

    Shader uniforms holding texture unit numbers are not property of the shader or anything else,

    but only of the TextureState.

    b. Attribute arrays from geometry

    Attribute arrays hold geometry data. They hold per vertex data, and those data are

    tied with the rendered mesh. They are most comparable to texture coord data.

    If you think in terms of bump-mapping, binormals and tangents are just as good

    geometry data as normals are.

    c. Lights and Camera from the scene

    Shaders get data from all parts of the scene, not just from the SceneElement they are attached

    to. Camera is on top of the scene, and lights can be anywhere. Lights are accessible trough

    the fixed function state, so this is not the best example, but the idea is to get data

    effective at the mesh into the shader.



    There is no mechanism for getting those data into the shader uniforms and attributes, so setting those

    data must be currently done by overridden draw() methods.


  3. A shader will not render only a single mesh. In a scene there will be multiple shaders, each working

    on lots of batches. Data dependent on the mesh should be stored with the mesh, and not

    in the shader state itself. There should be a way to use the same shader with multiple meshes, with different

    options (shader uniforms and attribute arrays).



    I came up with two different solutions for points 2 and 3.



    The first solution:

    Made LWJGLShaderObjects clonable. The clone holds a reference to the original shader, and uses the same programID (is the same shader instance on the vid card), but does not copy shader uniforms and attribute arrays. Each batch then uses a clone of the original shader, and sets uniforms and attribute arrays as needed. This works because to the applyStates method, the clones seem as different renderstates. There is a fix to make relinkProgram obsolete, which interfered with attribute array ID numbering when using clones.



    The second solution which i have not finished:



    ShaderAttributeStore class



    Holds the uniforms and attributes. Property of GLSLShaderObjects and of GeomBatch. Uniforms stored in GLSLShaderObject are global to the scene. Uniforms and attribute arrays stored in GeomBatch

    are specific to that batch, think of its usage like the array of texcoords buffers.
  4. Additional geometry data (for example binormals, tangents or morph data) would be stored in GeomBatch (in its attribute store), this is the logical place to put them when constructing batches, also the logical place to save/load them. OpenGL also threats attribute arrays as global to the context, and not local to the currently active shader. So, an attribute array pointer set to attribute ID 15 will be accessible to all shaders as

    attribute with ID 15, and not just to the shader which was active at the time when the pointer was set. This is another reason for shader attribute arrays be placed into GeomBatch.
  5. Attribute arrays would be accessible to Renderer in prepVBO almost like vertices or normals, so VBO could be implemented for attrib arrays. GLSLShaderObjects would only set uniforms. Setting of attribute arrays would be done in LWJGLRenderer.
  6. Additionally there could be a mechanism like there is for renderstates, which would propagate/combine/merge shader uniform values down the scene tree. This would require putting a ShaderAttributeStore into Node

    or even SceneElement, and not only GeomBatch. While this is the first step towards shader-based scenegraph

    i haven't tought about all its technicalities.



    The first solution solves the problem of per-mesh uniforms and attribute arrrays. Its a quick hack

    limited to get the job done. The second solution is which opens up the possibilities of using shaders

    more extensively, and in a more logical way.



    Now the technical issues, and fixes:



    In my morphing terrain demo one of the demos was not working on nVidia. The problem lies in the way

    nVidia uses attribute ID's, namely it handles shader attributes in the same bunch with fixed functionality

    geometry attributes. Vertex data are the same as shader attribute 0, normals are attribute ID 1 and so on,

    attribute ID 15 is texcoord 8. GLSLShaderObjectsState assigns attribute ID's from 0 up, which clashes

    with fixed function attributes. I fixed it to assign attribute ID's from top (max attribute ID) to bottom,

    so that fixed function attributes would only be "overwritten" only if you use lots 16+ (on 6600) attributes.



    "relinkProgram" is evil. It rearranges attribute ID's, and it must be called prior to using the shader the first

    time. There is "glGetUniformLocationARB" for getting uniform ID's, and there is as well "glGetAttribLocationARB"

    for gettin attribute ID's. So the change makes attribute ID handling the same as uniform ID handling, and

    relinkProgram is obsolete.



    Removal of "setAttribute": this way of setting attributes is only working in OpenGL immediate mode, when each

    vertex is specified with a separate OpenGL call. jME uses arrays only, so the only way to specify attributes is

    through "setAttributePointer".



    Handling unknown uniforms: when setting a uniform unknown in the shader, currently the uniform was added

    to the uniforms array again-and-again consuming memory, and slowing the app to the halt.

    I've changed: unknown uniforms are set to id -2, and uniforms with ID -2 are ignored.



    A still solvable issue is the direct access to ShaderUniforms, and not only trough the renderstate. Changing uniforms involves a String lookup by name in the uniforms array. This lookup should be avoided. The best way would be to implement "ShaderUniform.setValue", and make ShaderUniforms publicly accessible from ShaderAttributeStore (getUniform). This change i did not make, but would be another logical step.



    Another change (which have vanished as i updated from CVS) is the changed "compare" method in RenderQueue, which sorts objects first by the shader they use, then by texture state.



    A change to LWJGLRenderer prepVBO is missing, which gets the ShaderAttributeStore from GeomBatch, gets VBO ID's for the attrib arrays, and writes those ID's back into ShaderAttribute.



    Hope the text was readable, and a dev will impelement the changes.

All this code and text and no reaction yet?



I think mrCoder had improving shader support on his list somewhere…though I'm not sure how far he's into that right now, you might have done some of his work for him.

It's definitely something to be kept on the radar.  MrCoder is getting ready for his wedding though, so you'll have to be understanding if he's not all over this right away. :slight_smile:

very very interesting. as soon as i get to that on my todo list i will ask for your help and input and try out all your code! thanks



wedding in a week yes…on a more fun note, some water stuff will be checked in before that :wink: