Commits

Michael Ludwig  committed 1ddeb85

Implement the model portion of the GLSL support.

  • Participants
  • Parent commits a984d14

Comments (0)

Files changed (10)

File src/com/ferox/resource/GlslProgram.java

+package com.ferox.resource;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.ferox.resource.GlslUniform.UniformType;
+import com.ferox.resource.GlslVertexAttribute.AttributeType;
+
+/** GlslProgram represents a custom shader program written in glsl.
+ * The exact shader model and glsl syntax supported depends on the 
+ * hardware support for that model.  At the moment only vertex and
+ * fragment shaders are allowed.  When geometry shaders become more
+ * common, they will likely be added in.
+ * 
+ * GlslPrograms are a fairly complicated resource for the Renderer
+ * to deal with.  Here are a few guidelines:
+ * - If either shader fails to compile, it should have a status of ERROR
+ * - If it fails to link, it has a status of ERROR
+ * - If valid, bound GlslVertexAttributes overlap attribute slots, it should
+ *   have a status of ERROR
+ * - Invalid attributes must be unbound
+ * - Invalid GlslUniforms must be left attached, unless they conflict with the
+ *   name of a valid uniform
+ * - Attributes and uniforms declared in the glsl code should be bound/attached
+ *   on a success if they aren't already.
+ * - Attached uniforms must have a status of ERROR if the program failed to
+ *   link.
+ * - Attached uniforms should be automatically updated and cleaned-up whenever the program
+ *   is updated/cleaned-up.
+ * 
+ * @author Michael Ludwig
+ *
+ */
+public class GlslProgram implements Resource {
+	/** The dirty descriptor used by instances of GlslProgram. */
+	public static class GlslProgramDirtyDescriptor {
+		private boolean uniformsDirty;
+		private boolean attrsDirty;
+		private boolean glslDirty;
+		
+		/** Return true if a GlslUniform was attached or detached. */
+		public boolean getUniformsDirty() { return this.uniformsDirty; }
+		
+		/** Return true if a GlslVertexAttribute was attached or detached. */
+		public boolean getAttributesDirty() { return this.attrsDirty; }
+		
+		/** Return true if the glsl source code has changed for this program.
+		 * If this is true, then the program's uniforms and attributes should
+		 * be reloaded, too. */
+		public boolean getGlslCodeDirty() { return this.glslDirty; }
+		
+		protected void clear() {
+			this.uniformsDirty = false;
+			this.attrsDirty = false;
+			this.glslDirty = false;
+		}
+	}
+	
+	private String[] vertexShader;
+	private String[] fragmentShader;
+	
+	private final Map<String, GlslUniform> attachedUniforms;
+	private final Map<String, GlslVertexAttribute> boundAttrs;
+	
+	private final Map<String, GlslUniform> readOnlyUniforms;
+	private final Map<String, GlslVertexAttribute> readOnlyAttrs;
+	
+	private final GlslProgramDirtyDescriptor dirty;
+	
+	private Object renderData;
+	
+	/** Create a new GlslProgram with empty source code. */
+	public GlslProgram() {
+		this(null, null);
+	}
+	
+	/** Create a new GlslProgram with the given vertex and fragment
+	 * shaders.  These string arrays will be passed directly into
+	 * setVertexShader() and setFragmentShader(). */
+	public GlslProgram(String[] vertex, String[] fragment) {
+		this.dirty = this.createDescriptor();
+		if (this.dirty == null)
+			throw new NullPointerException("Sub-class returned a null dirty descriptor");
+		
+		this.attachedUniforms = new HashMap<String, GlslUniform>();
+		this.boundAttrs = new HashMap<String, GlslVertexAttribute>();
+		
+		this.readOnlyUniforms = Collections.unmodifiableMap(this.attachedUniforms);
+		this.readOnlyAttrs = Collections.unmodifiableMap(this.boundAttrs);
+		
+		this.setVertexShader(vertex);
+		this.setFragmentShader(fragment);
+	}
+	
+	/** Functions identically to attachUniform() except that it is for GlslVertexAttributes.
+	 * This method does not check for overlap in binding slots, but if there is any overlap
+	 * at the time of a Renderer update(), this program must have its status set to ERROR. 
+	 * 
+	 * Instead of length, however, bindingSlot is used for the attribute.  See GlslVertexAttribute
+	 * for how the binding slot is used. */
+	public GlslVertexAttribute bindAttribute(String name, AttributeType type, int bindingSlot) throws IllegalStateException, IllegalArgumentException, NullPointerException {
+		if (name == null)
+			throw new NullPointerException("Name cannot be null");
+		
+		GlslVertexAttribute u = this.boundAttrs.get(name);
+		if (u != null)
+			throw new IllegalStateException("Cannot re-bind an attribute with the same name: " + name);
+		else
+			u = new GlslVertexAttribute(name, type, bindingSlot, this); // throws IllegalArgumentException/NullPointerException
+		
+		this.boundAttrs.put(name, u);
+		this.dirty.attrsDirty = true;
+		return u;
+	}
+	
+	/** Unbind an attribute so that it is no longer stored by this
+	 * GlslProgram.  After a GlslVertexAttribute is unbound, it
+	 * should no longer be used, since it represents invalid information.
+	 * 
+	 * Because a GlslVertexAttribute is not a resource, and is only 
+	 * a convenient packaging for the binding of glsl attributes, a Renderer
+	 * will unbind any attribute that is invalid. */
+	public GlslVertexAttribute unbindAttribute(String name) {
+		if (name == null)
+			return null;
+		
+		GlslVertexAttribute a = this.boundAttrs.remove(name);
+		if (a != null)
+			this.dirty.attrsDirty = true;
+		
+		return a;
+	}
+	
+	/** Return all currently bound GlslVertexAttributes, in an 
+	 * unmodifiable map from attribute name to GlslVertexAttribute. */
+	public Map<String, GlslVertexAttribute> getAttributes() {
+		return this.readOnlyAttrs;
+	}
+	
+	/** Attach a new GlslUniform instance to this GlslProgram so that it will
+	 * be automatically updated and cleaned-up with this program instance.
+	 * 
+	 * It is possible for programmers to manually attach the uniforms, to 
+	 * use the instances sooner, or they may let the Renderer attach all
+	 * uniforms detected by the glsl compiler.
+	 * 
+	 * This method will fail if name or type is null, if length < 0, 
+	 * or if there is an already attached uniform of the same name 
+	 * as requested.  That uniform must be detached before a successful,
+	 * new attach may be performed.
+	 * 
+	 * When a glsl program is updated, if it links successfully, it should
+	 * attach any uniform that is present in the code, that's not already
+	 * been attached.  It should not detach invalid uniforms unless they
+	 * conflict with the name of a valid uniform.  In either case, invalid
+	 * uniforms must have their status set to ERROR.
+	 * 
+	 * If a program is not linked successfully, all attached uniforms
+	 * must have a status of ERROR. */
+	public GlslUniform attachUniform(String name, UniformType type, int length) throws IllegalStateException, IllegalArgumentException, NullPointerException {
+		if (name == null)
+			throw new NullPointerException("Name cannot be null");
+		
+		GlslUniform u = this.attachedUniforms.get(name);
+		if (u != null)
+			throw new IllegalStateException("Cannot re-attach a uniform with the same name: " + name);
+		else
+			u = new GlslUniform(name, type, length, this); // throws IllegalArgumentException/NullPointerException
+		
+		this.attachedUniforms.put(name, u);
+		this.dirty.uniformsDirty = true;
+		return u;
+	}
+	
+	/** Detach and return the GlslUniform with the given name.
+	 * Returns null if a uniform wasn't previously attached with
+	 * that name, or if name is null.
+	 * 
+	 * This allows the given name to be used in an attachUniform()
+	 * call without having to worry about an IllegalStateException.
+	 * 
+	 * The uniform will be auto-matically cleaned-up the next time
+	 * the program is updated or cleaned-up and should no longer be
+	 * used in GlslShaders.  After that clean-up, its status will no
+	 * longer be updated by the program's.
+	 * 
+	 * The purpose of this method is to allow programmers to detach
+	 * uniforms that have a status of ERROR, or the for the Renderer
+	 * to remove erroneous uniforms conflicting with valid uniforms of
+	 * the same name. */
+	public GlslUniform detachUniform(String name) {
+		if (name == null)
+			return null;
+		
+		GlslUniform u = this.attachedUniforms.remove(name);
+		if (u != null)
+			this.dirty.uniformsDirty = true;
+		
+		return u;
+	}
+	
+	/** Return an unmodifiable map of all currently attached GlslUniforms,
+	 * accessed by the uniform name. */
+	public Map<String, GlslUniform> getUniforms() {
+		return this.readOnlyUniforms;
+	}
+	
+	/** Return the array of strings that store the glsl code for
+	 * executing the vertex stage of a pipeline.  If the returned 
+	 * array has a length of 0, then no vertex shader is used by
+	 * this program.
+	 * 
+	 * If the array is modified, there will be undefined results. */
+	public String[] getVertexShader() {
+		return this.vertexShader;
+	}
+	
+	/** Return the array of strings that store the glsl code for
+	 * executing the fragment stage of a pipeline.  If the returned
+	 * array has a length of 0, then no fragment shader is used
+	 * by this program.
+	 * 
+	 * If the array is modified, there will be undefined results. */
+	public String[] getFragmentShader() {
+		return this.fragmentShader;
+	}
+	
+	/** Set the glsl code that executes during the vertex stage of the pipeline.
+	 * If source is null, or (after ignoring null elements) there are no source
+	 * lines, getVertexShader() will return an array of length 0.
+	 * 
+	 * If not, then all non-null elements will be copied into a new array.
+	 * This is to prevent accidental tampering, and to allow programmers to
+	 * assume that all String elements in the returned array will not be null. */
+	public void setVertexShader(String[] source) {
+		if (source == null) {
+			this.vertexShader = new String[0];
+		} else {
+			int nonNullCount = 0;
+			for (int i = 0; i < source.length; i++) {
+				if (source[i] != null)
+					nonNullCount++;
+			}
+			
+			this.vertexShader = new String[nonNullCount];
+			nonNullCount = 0;
+			for (int i = 0; i < source.length; i++) {
+				if (source[i] != null) {
+					this.vertexShader[nonNullCount++] = source[i];
+				}
+			}
+		}
+		
+		this.dirty.glslDirty = true;
+	}
+	
+	/** Set the glsl code that executes during the fragment stage of the pipeline.
+	 * If source is null, or (after ignoring null elements) there are no source
+	 * lines, getFragmentShader() will return an array of length 0.
+	 * 
+	 * If not, then all non-null elements will be copied into a new array.
+	 * This is to prevent accidental tampering, and to allow programmers to
+	 * assume that all String elements in the returned array will not be null. */
+	public void setFragmentShader(String[] source) {
+		if (source == null) {
+			this.fragmentShader = new String[0];
+		} else {
+			int nonNullCount = 0;
+			for (int i = 0; i < source.length; i++) {
+				if (source[i] != null)
+					nonNullCount++;
+			}
+			
+			this.fragmentShader = new String[nonNullCount];
+			nonNullCount = 0;
+			for (int i = 0; i < source.length; i++) {
+				if (source[i] != null) {
+					this.fragmentShader[nonNullCount++] = source[i];
+				}
+			}
+		}
+		
+		this.dirty.glslDirty = true;
+	}
+	
+	/** Construct the dirty descriptor to use for this program. 
+	 * Must not return null, or the constructor will throw an
+	 * exception. */
+	protected GlslProgramDirtyDescriptor createDescriptor() {
+		return new GlslProgramDirtyDescriptor();
+	}
+	
+	@Override
+	public void clearDirtyDescriptor() {
+		this.dirty.clear();
+	}
+
+	@Override
+	public final GlslProgramDirtyDescriptor getDirtyDescriptor() {
+		return this.dirty;
+	}
+
+	@Override
+	public Object getResourceData() {
+		return this.renderData;
+	}
+
+	@Override
+	public void setResourceData(Object data) {
+		this.renderData = data;
+	}
+}

File src/com/ferox/resource/GlslUniform.java

+package com.ferox.resource;
+
+import org.openmali.vecmath.Matrix3f;
+import org.openmali.vecmath.Matrix4f;
+import org.openmali.vecmath.Vector2f;
+import org.openmali.vecmath.Vector2i;
+import org.openmali.vecmath.Vector3f;
+import org.openmali.vecmath.Vector4f;
+
+/** GlslUniform represents a "static" variable that is assigned
+ * values from outside the execution of its owning GlslProgram.
+ * 
+ * The actual values are assigned by a GlslShader, since it is likely
+ * that they will change when the GlslProgram does not.
+ * 
+ * In many respects GlslUniform's are Resources (and hence implement Resource),
+ * but in others, they are an anomaly.  They are updated and cleaned-up
+ * along with their owning GlslProgram.  It is not recommended that you update
+ * or clean-up GlslUniforms individually, as behavior is affected by a number
+ * of factors including the current status of its owning program. 
+ * 
+ * It is not necessary for you to request updates on a uniform after
+ * changing the value update policy.
+ * 
+ * Renderers should set the uniform's status to ERROR when it is used
+ * and its owning program has a status of CLEANED or ERROR.
+ * 
+ * @author Michael Ludwig
+ *
+ */
+public class GlslUniform implements Resource {
+	/** Each GlslUniform has a value type, which will
+	 * be one of the following possibilities.
+	 * 
+	 * SAMPLER_x refer to Texture accessors.  The values
+	 * for sampler variables are the texture units with which
+	 * to access a texture from. 
+	 * 
+	 * The given types may also be used in a GlslProgram as
+	 * an array.  This difference is determined by the GlslUniform
+	 * based on its length value. */
+	public static enum UniformType {
+		SAMPLER_1D, 		/** Must use int, Integer, or int[]. */ 
+		SAMPLER_2D, 		/** Must use int, Integer, or int[]. */ 
+		SAMPLER_3D, 		/** Must use int, Integer, or int[]. */ 
+		SAMPLER_CUBEMAP, 	/** Must use int, Integer, or int[]. */ 
+		SAMPLER_RECT, 		/** Must use int, Integer, or int[]. */ 
+		FLOAT,			    /** Must use float, Float, or float[]. */ 
+		INT, 				/** Must use int, Integer, or int[]. */ 
+		BOOLEAN,          	/** Must use boolean, Boolean, or boolean[]. */  
+		VEC2F,    			/** Must use Vector2f or Vector2f[]. */  
+		VEC3F,    			/** Must use Vector3f or Vector3f[]. */   
+		VEC4F,    			/** Must use Vector4f or Vector4f[]. */  
+		VEC2I,    			/** Must use Vector2i or Vector2i[]. */  
+		MAT3F,    			/** Must use Matrix3f or Matrix3f[]. */  
+		MAT4F,    			/** Must use Matrix4f or Matrix4f[]. */  
+	}
+	
+	/** To make the updates of a GlslUniform's value as fast as possible,
+	 * a Renderer is not expected to remember each uniform's last set value
+	 * and update that only when necessary.  
+	 * 
+	 * Instead each uniform has an update policy that determines when the
+	 * value stored by a GlslShader is passed to the graphics card.
+	 * 
+	 * When MANUAL is set, the value is only changed when the GlslShader
+	 * says that the value is dirty.  When PER_FRAME is used, it is updated
+	 * the first time the shader is used each frame.  Both of these assume
+	 * that all GlslShader's using the uniform's owner program use the same
+	 * value.
+	 * 
+	 * PER_INSTANCE forces the GlslShader to update the uniforms value each
+	 * time it is used.  This is useful when the uniform has multiple values
+	 * for different GlslShaders using it. */
+	public static enum ValueUpdatePolicy {
+		MANUAL, PER_FRAME, PER_INSTANCE
+	}
+	
+	private final UniformType type;
+	private final int length;
+	private final String name;
+	
+	private final GlslProgram owner;
+	
+	private ValueUpdatePolicy policy;
+
+	private Object renderData;
+	
+	/** GlslUniforms should only be constructed with a GlslProgram's attachUniform()
+	 * method. */
+	protected GlslUniform(String name, UniformType type, int length, GlslProgram owner) throws IllegalArgumentException, NullPointerException {
+		if (name == null)
+			throw new NullPointerException("Cannot specify a null name");
+		if (type == null)
+			throw new NullPointerException("Cannot specify a null uniform type");
+		if (owner == null)
+			throw new NullPointerException("Cannot create a GlslUniform with a null GlslProgram");
+		
+		if (length < 1)
+			throw new IllegalArgumentException("Cannot specify length < 1: " + length);
+		if (name.startsWith("gl"))
+			throw new IllegalArgumentException("Uniform names may not start with 'gl': " + name + ", they are reserved");
+		
+		this.name = name;
+		this.type = type;
+		this.owner = owner;
+		this.length = length;
+		
+		this.setValueUpdatePolicy(null);
+	}
+	
+	/** Return the update policy in use by this uniform. */
+	public ValueUpdatePolicy getValueUpdatePolicy() {
+		return this.policy;
+	}
+
+	/** Set the update policy to use.  If policy is null, then
+	 * PER_INSTANCE is used by default.  You do need to update
+	 * this resource to see this change have any effect. */
+	public void setValueUpdatePolicy(ValueUpdatePolicy policy) {
+		if (policy == null)
+			policy = ValueUpdatePolicy.PER_INSTANCE;
+		this.policy = policy;
+	}
+
+	/** Get the value type of this uniform.  If getLength() > 1, then
+	 * this is the component type of the array for the uniform. */
+	public UniformType getType() {
+		return this.type;
+	}
+
+	/** Return the number of primitives of getType() used by this uniform.
+	 * If it's > 1, then the uniform represents an array. */
+	public int getLength() {
+		return this.length;
+	}
+
+	/** Return the name of the uniform as declared in the glsl code
+	 * of the uniform's owner. */
+	public String getName() {
+		return this.name;
+	}
+
+	/** Return the GlslProgram that this uniform was declared in.
+	 * It could have been declared in either the vertex or the fragment
+	 * shader portion of the program. */
+	public GlslProgram getOwner() {
+		return this.owner;
+	}
+	
+	/** Return true if the value is of the correct type and
+	 * size to be assigned to this uniform in a GlslShader.
+	 * 
+	 * A primitive type is INT, FLOAT, or BOOLEAN.
+	 * For primitive non-array types, it uses the boxed classes
+	 * since the actual primitives will not be passed in.
+	 * 
+	 * Returns false if value is null. */
+	public boolean isValid(Object value) {
+		if (value == null)
+			return false;
+		
+		if (this.length > 1) {
+			switch(this.type) {
+			case BOOLEAN: return (value instanceof boolean[]) && ((boolean[]) value).length == this.length;
+			
+			case INT: case SAMPLER_1D: case SAMPLER_2D: case SAMPLER_3D: case SAMPLER_CUBEMAP: case SAMPLER_RECT:
+				return (value instanceof int[]) && ((int[]) value).length == this.length;
+				
+			case FLOAT: return (value instanceof float[]) && ((float[]) value).length == this.length;
+			
+			case MAT3F: return (value instanceof Matrix3f[]) && ((Matrix3f[]) value).length == this.length;
+			case MAT4F: return (value instanceof Matrix4f[]) && ((Matrix4f[]) value).length == this.length;
+			
+			case VEC2F: return (value instanceof Vector2f[]) && ((Vector2f[]) value).length == this.length;
+			case VEC2I: return (value instanceof Vector2i[]) && ((Vector2i[]) value).length == this.length;
+			case VEC3F: return (value instanceof Vector3f[]) && ((Vector3f[]) value).length == this.length;
+			case VEC4F: return (value instanceof Vector4f[]) && ((Vector4f[]) value).length == this.length;
+			}
+		} else {
+			switch(this.type) {
+			case BOOLEAN: return value instanceof Boolean;
+			
+			case INT: case SAMPLER_1D: case SAMPLER_2D: case SAMPLER_3D: case SAMPLER_CUBEMAP: case SAMPLER_RECT:
+				return value instanceof Integer;
+				
+			case FLOAT: return value instanceof Float;
+			
+			case MAT3F: return value instanceof Matrix3f;
+			case MAT4F: return value instanceof Matrix4f;
+			
+			case VEC2F: return value instanceof Vector2f;
+			case VEC2I: return value instanceof Vector2i;
+			case VEC3F: return value instanceof Vector3f;
+			case VEC4F: return value instanceof Vector4f;
+			}
+		}
+		
+		return false;
+	}
+	
+	@Override
+	public void clearDirtyDescriptor() {
+		// do nothing
+	}
+	
+	@Override
+	public Object getDirtyDescriptor() {
+		return null;
+	}
+	
+	@Override
+	public Object getResourceData() {
+		return this.renderData;
+	}
+	
+	@Override
+	public void setResourceData(Object data) {
+		this.renderData = data;
+	}
+}

File src/com/ferox/resource/GlslVertexAttribute.java

+package com.ferox.resource;
+
+/** GlslVertexAttribute represents a bindable vertex attribute
+ * in a glsl shader program.  Its binding slot is parallel to
+ * the vertex attribute units in a VertexArrayGeometry or a
+ * VertexBufferGeometry.
+ * 
+ * Each attribute has an associated type that determines the
+ * VertexArray that should be used to access it.  FLOAT needs
+ * an element size of 1, VECxF requires an element size of 
+ * x, MATyF requires an element size of y and takes up y
+ * consecutive slots, where each slot represents a column
+ * in that matrix.
+ * 
+ * @author Michael Ludwig
+ *
+ */
+public class GlslVertexAttribute {
+	/** The type of the declared vertex attribute.  The
+	 * matrix types use 2, 3, or 4 consecutive attribute 
+	 * slots to fill all values of the attribute. */
+	public static enum AttributeType {
+		FLOAT, VEC2F, VEC3F, VEC4F, MAT2F, MAT3F, MAT4F
+	}
+	
+	private final AttributeType type;
+	private final int bindingSlot;
+	private final String name;
+	
+	private final GlslProgram owner;
+	
+	/** Attributes should only be created through bindAttribute() in
+	 * a GlslProgram instance. */
+	protected GlslVertexAttribute(String name, AttributeType type, int bindingSlot, GlslProgram owner) throws IllegalArgumentException, NullPointerException {
+		if (name == null)
+			throw new NullPointerException("Name cannot be null");
+		if (type == null)
+			throw new NullPointerException("Attribute type cannot be null");
+		if (owner == null)
+			throw new NullPointerException("GlslProgram cannot be null");
+		
+		if (bindingSlot < 0)
+			throw new IllegalArgumentException("BindingSlot must be >= 0: " + bindingSlot);
+		if (name.startsWith("gl"))
+			throw new IllegalArgumentException("Name cannot start with 'gl': " + name + ", that is reserved");
+		
+		this.type = type;
+		this.name = name;
+		this.owner = owner;
+		
+		this.bindingSlot = bindingSlot;
+	}
+
+	/** Return the attribute type of this GlslVertexAttribute. */
+	public AttributeType getType() {
+		return this.type;
+	}
+
+	/** Return the lowest generic attribute slot used by this
+	 * attribute.  If getType() returns a matrix type, consecutive
+	 * slots after this one will also be used. */
+	public int getBindingSlot() {
+		return this.bindingSlot;
+	}
+
+	/** Return the name of the attribute, as declared in this
+	 * instance's owning program glsl code. */
+	public String getName() {
+		return this.name;
+	}
+
+	/** Return the GlslProgram that this attribute is defined 
+	 * within. */
+	public GlslProgram getOwner() {
+		return this.owner;
+	}
+}

File src/com/ferox/state/GlslShader.java

+package com.ferox.state;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.ferox.resource.GlslProgram;
+import com.ferox.resource.GlslUniform;
+
+/** GlslShader wraps a single GlslProgram and provides the
+ * functionality to bind uniforms with values.  The GlslProgram
+ * cannot be null and a shader's program will not change during
+ * its lifetime.
+ * 
+ * It is highly recommended that declared uniforms in a program
+ * always have a set value in the GlslShader.  If a uniform is not
+ * set in the shader, the program will use the last set value for
+ * the uniform, which is undetermined when rendering.
+ * 
+ * @author Michael Ludwig
+ *
+ */
+public class GlslShader implements State {
+	/** UniformBinding is the union of a GlslUniform and its
+	 * set value for a GlslShader instance.  It also has a boolean
+	 * property marking it as dirty, which is only of use if the
+	 * uniform has a ValueUpdatePolicy of MANUAL. */
+	public static class UniformBinding {
+		private GlslUniform variable;
+		private Object value;
+		private boolean isDirty;
+		
+		/** Returns true if the uniform's value should be updated if its
+		 * policy if MANUAL.  This is ignored if the policy is not MANUAL. */
+		public boolean isDirty() { return this.isDirty; }
+		
+		/** Set whether or not the value in this uniform is dirty.
+		 * As above, it's only used if the uniform's policy is MANUAL. 
+		 * 
+		 * This should be set to false after a Renderer updates the value
+		 * of the uniform. */
+		public void setDirty(boolean dirty) { this.isDirty = dirty; }
+		
+		/** Get the current value for the uniform.  Changes made to this
+		 * instance will only be visible if the policy if PER_INSTANCE, 
+		 * or PER_FRAME (but only changes before the frame, not during),
+		 * or MANUAL (if isDirty() returns true). */
+		public Object getValue() { return this.value; }
+		
+		/** Get the GlslUniform for this binding. */
+		public GlslUniform getUniform() { return this.variable; }
+	}
+	
+	private final Map<GlslUniform, Integer> indexMap;
+	private final List<UniformBinding> uniforms;
+	private final List<UniformBinding> readOnlyUniforms;
+	
+	private final GlslProgram program;
+	
+	private Object stateData;
+	
+	/** Construct a GlslShader with the given program.  A NullPointerException
+	 * is thrown if the program is null.  No uniforms are bound, but it is 
+	 * highly recommended to set the uniform values before actually using the
+	 * GlslShader to get the expected outcome. */
+	public GlslShader(GlslProgram program) throws NullPointerException {
+		if (program == null)
+			throw new NullPointerException("Program cannot be null");
+		
+		this.indexMap = new IdentityHashMap<GlslUniform, Integer>();
+		this.uniforms = new ArrayList<UniformBinding>();
+		this.readOnlyUniforms = Collections.unmodifiableList(this.uniforms);
+		
+		this.program = program;
+	}
+	
+	/** Return the GlslProgram that is activated whenever this state is added to
+	 * an Appearance. This will not be null. */
+	public GlslProgram getProgram() {
+		return this.program;
+	}
+	
+	/** Set the value to be used for the given uniform in this GlslShader.
+	 * If the uniform is null, a NullPointerException is thrown.
+	 * If the uniform's owner isn't this GlslShader's program, then an IllegalArgumentException
+	 * is thrown. Also, one is thrown if the value isn't value as determined by
+	 * uniform's isValid() method.
+	 * 
+	 * If value is null, then this removes any previous UniformBinding for the given uniform.
+	 * If it isn't, the value is stored in a UniformBinding for the uniform (possibly re-using
+	 * an existing instance).  The binding is marked as dirty so that MANUAL uniforms will
+	 * be updated accordingly. */
+	public void setUniform(GlslUniform uniform, Object value) throws IllegalArgumentException, NullPointerException {
+		if (uniform == null)
+			throw new NullPointerException("Uniform cannot be null");
+		if (uniform.getOwner() != this.program)
+			throw new IllegalArgumentException("Can only set uniforms that match this shader's GlslProgram, not: " + uniform.getOwner());
+		
+		Integer index = this.indexMap.get(uniform);
+
+		if (value == null) {
+			// remove the uniform from the shader
+			if (index != null) {
+				this.uniforms.remove(index.intValue());
+				this.updateIndexMap();
+			}
+		} else {
+			if (!uniform.isValid(value))
+				throw new IllegalArgumentException("Value is not valid for the given uniform: " + uniform.getName() + " " + value);
+			// set the value, and mark it as dirty
+			UniformBinding binding;
+			if (index != null) {
+				// re-use existing binding object
+				binding = this.uniforms.get(index.intValue());
+			} else {
+				// need a new binding
+				binding = new UniformBinding();
+				binding.variable = uniform;
+				this.uniforms.add(binding);
+				this.updateIndexMap();
+			}
+			
+			binding.isDirty = true;
+			binding.value = value;
+		}
+	}
+	
+	/** Return the uniform binding for the given uniform.
+	 * If uniform is null, has a different GlslProgram than this
+	 * shader, or it hasn't ever been set on this shader, null
+	 * is returned. */
+	public UniformBinding getUniform(GlslUniform uniform) {
+		if (uniform == null || uniform.getOwner() != this.program)
+			return null;
+		
+		Integer i = this.indexMap.get(uniform);
+		return (i == null ? null : this.uniforms.get(i.intValue()));
+	}
+	
+	/** Return an unmodifiable list of all currently set uniforms
+	 * as a list of UniformBindings.  It can be assumed that any
+	 * bindings present will have uniforms that satisfy:
+	 *   uniform.getOwner() == this.getProgram() */
+	public List<UniformBinding> getSetUniforms() {
+		return this.readOnlyUniforms;
+	}
+	
+	/* Re-calculate the indices of the index map.  This must be called each time
+	 * uniforms has an element added or removed. */
+	private void updateIndexMap() {
+		this.indexMap.clear();
+		int size = this.uniforms.size();
+		for (int i = 0; i < size; i++) {
+			this.indexMap.put(this.uniforms.get(i).variable, Integer.valueOf(i));
+		}
+	}
+
+	@Override
+	public Role getRole() {
+		return Role.SHADER;
+	}
+
+	@Override
+	public Object getStateData() {
+		return this.stateData;
+	}
+
+	@Override
+	public void setStateData(Object data) {
+		this.stateData = data;
+	}
+}

File src/com/ferox/state/glsl/Shader.java

-package com.ferox.state.glsl;
-
-public class Shader {
-
-}

File src/com/ferox/state/glsl/ShaderProgram.java

-package com.ferox.state.glsl;
-
-public class ShaderProgram {
-
-}

File src/com/ferox/state/glsl/ShaderState.java

-package com.ferox.state.glsl;
-
-public class ShaderState {
-
-}

File src/com/ferox/state/glsl/Uniform.java

-package com.ferox.state.glsl;
-
-public class Uniform {
-
-}

File src/com/ferox/state/glsl/VertexAttribute.java

-package com.ferox.state.glsl;
-
-public class VertexAttribute {
-
-}

File test/com/ferox/scene/CubeTest.java

 		
 		spotLight.setLocalBounds(new BoundSphere(BOUNDS));
 		spotLight.getLocalTransform().getTranslation().set(0f, 0f, 0f);
-		
 		view.add(spotLight);
 		
 		Light directionLight = new DirectionLight(new Vector3f(-1f, -1f, -1f));
 		this.window.addRenderPass(new BasicRenderPass(colorShape, ortho));
 		this.window.addRenderPass(new BasicRenderPass(depthShape, ortho));
 		
-		this.window.setVSyncEnabled(true);
-		
 		return sceneDepth;
 	}