Commits

Michael Ludwig committed 07a16a5

Implement glsl uniforms and attributes.

Comments (0)

Files changed (9)

src/com/ferox/core/states/atoms/GLSLAttribute.java

+package com.ferox.core.states.atoms;
+
+import com.ferox.core.states.manager.Geometry;
+
+public class GLSLAttribute {
+	public static enum AttributeType {
+		FLOAT, VEC2F, VEC3F, VEC4F, MAT2F, MAT3F, MAT4F
+	}
+	
+	private AttributeType type;
+	private int binding;
+	private String name;
+	
+	public GLSLAttribute(String name, AttributeType type) throws NullPointerException, IllegalArgumentException {
+		if (name == null || type == null)
+			throw new NullPointerException("Name or type can't be null: " + name + " " + type);
+		if (name.indexOf("gl") == 0)
+			throw new IllegalArgumentException("Name cannot begin with reserved 'gl': " + name);
+		this.name = name;
+		this.type = type;
+		this.binding = 0;
+	}
+
+	public int getBinding() {
+		return binding;
+	}
+
+	public void setBinding(int binding) {
+		this.binding = Math.max(0, binding);
+		if (Geometry.getMaxVertexAttributes() > 0)
+			this.binding = Math.min(this.binding, Geometry.getMaxVertexAttributes());
+	}
+
+	public AttributeType getType() {
+		return type;
+	}
+
+	public String getName() {
+		return name;
+	}
+	
+	public boolean equals(Object other) {
+		if (other == null || !(other instanceof GLSLAttribute))
+			return false;
+		if (other == this)
+			return true;
+		GLSLAttribute a = (GLSLAttribute)other;
+		return a.name.equals(this.type) && a.type == this.type;
+	}
+	
+	public int hashCode() {
+		return this.name.hashCode() ^ this.type.hashCode();
+	}
+}

src/com/ferox/core/states/atoms/GLSLShaderObject.java

 		}
 	}
 	
-	@Override
-	public void update(RenderManager manager) {
+	//@Override
+	/*public void update(RenderManager manager) {
 		Iterator<GLSLShaderProgram> p = this.linkedPrograms.keySet().iterator();
 		while (p.hasNext()) 
 			p.next().update(manager);
 		super.update(manager);
-	}
+	}*/
 	
 	boolean linkToProgram(GLSLShaderProgram prog) {
 		if (this.linkedPrograms.containsKey(prog))

src/com/ferox/core/states/atoms/GLSLShaderProgram.java

 package com.ferox.core.states.atoms;
 
+import java.util.Collections;
+import java.util.Set;
+
 import com.ferox.core.states.NullUnit;
 import com.ferox.core.states.StateAtom;
 import com.ferox.core.states.StateUnit;
 	private GLSLShaderObject[] shaders;
 	private boolean hasVertexShader;
 	private boolean hasFragmentShader;
+	
 	private String infoLog;
 	private boolean compiled;
+	private Set<GLSLUniform> allUniforms;
+	private Set<GLSLAttribute> allAttrs;
 	
 	public GLSLShaderProgram() {
 		this(null);
 	 */
 	public GLSLShaderProgram(GLSLShaderObject[] shaders) {
 		super();
-		this.compiled = false;
-		this.infoLog = "";
+		this.setCompiled(false, "");
 		this.setShaders(shaders);
 	}
 
 	public void setCompiled(boolean compiled, String msg) {
 		this.compiled = compiled;
 		this.infoLog = msg;
+		if (!this.compiled)
+			this.allUniforms = null;
 	}
 	
 	/**
 		return this.hasVertexShader;
 	}
 
+	public void setAvailableUniforms(Set<GLSLUniform> uniforms) {
+		if (uniforms == null)
+			this.allUniforms = null;
+		else
+			this.allUniforms = Collections.unmodifiableSet(uniforms);
+	}
+	
+	public Set<GLSLUniform> getAvailableUniforms() {
+		return this.allUniforms;
+	}
+	
+	public GLSLUniform getUniformByName(String name) {
+		if (name == null || this.allUniforms == null)
+			return null;
+		for (GLSLUniform u : this.allUniforms) {
+			if (u.getName().equals(name))
+				return u;
+		}
+		return null;
+	}
+	
+	public void setAvailableVertexAttributes(Set<GLSLAttribute> attrs) {
+		if (attrs == null)
+			this.allAttrs = null;
+		else
+			this.allAttrs = Collections.unmodifiableSet(attrs);
+	}
+	
+	public Set<GLSLAttribute> getAvailableVertexAttributes() {
+		return this.allAttrs;
+	}
+	
+	public GLSLAttribute getVertexAttributeByName(String name) {
+		if (name == null || this.allAttrs == null)
+			return null;
+		for (GLSLAttribute u : this.allAttrs) {
+			if (u.getName().equals(name))
+				return u;
+		}
+		return null;
+	}
+	
 	/**
 	 * Sets the shader objects for this program.  Ignores null shader object elements in the array.
 	 * If all shaders present are null (or if the array is null), then this program will behave as the fixed function

src/com/ferox/core/states/atoms/GLSLUniform.java

+package com.ferox.core.states.atoms;
+
+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;
+
+public class GLSLUniform {
+	public static enum UniformType {
+		FLOAT(Float.class), INT(Integer.class), BOOLEAN(Boolean.class), 
+		VEC2F(Vector2f.class), VEC3F(Vector3f.class), VEC4F(Vector4f.class), 
+		VEC2I(Vector2i.class),
+		MAT3F(Matrix3f.class), MAT4F(Matrix4f.class),
+		FLOAT_ARRAY(float[].class), INT_ARRAY(int[].class), BOOLEAN_ARRAY(boolean[].class),
+		VEC2F_ARRAY(Vector2f[].class), VEC3F_ARRAY(Vector3f[].class), VEC4F_ARRAY(Vector4f[].class),
+		VEC2I_ARRAY(Vector2i[].class),
+		MAT3F_ARRAY(Matrix3f[].class), MAT4F_ARRAY(Matrix4f[].class);
+		
+		private Class<?> validTypes;
+		private UniformType(Class<?> valid) {
+			this.validTypes = valid;
+		}
+		
+		public boolean isValidValue(Object value) {
+			if (value == null)
+				return false;
+			Class<?> t = value.getClass();
+			return this.validTypes.isAssignableFrom(t);
+		}
+	}
+	
+	private String name;
+	private UniformType type;
+	private int count;
+	
+	public GLSLUniform(String name, UniformType type, int size) throws NullPointerException, IllegalArgumentException {
+		if (name == null || type == null)
+			throw new NullPointerException("Name and type can't be null: " + name + " " + type);
+		if (name.indexOf("gl") == 0)
+			throw new IllegalArgumentException("Name cannot begin with prefix 'gl': " + name);
+		this.name = name;
+		this.type = type;
+		this.count = Math.max(1, size);
+	}
+	
+	public String getName() {
+		return this.name;
+	}
+	
+	public UniformType getType() {
+		return this.type;
+	}
+	
+	public int getSize() {
+		return this.count;
+	}
+	
+	public boolean equals(Object obj) {
+		if (obj == null || !(obj instanceof GLSLUniform))
+			return false;
+		if (obj == this)
+			return true;
+		GLSLUniform u = (GLSLUniform)obj;
+		return u.name.equals(this.name) && u.type == this.type;
+	}
+	
+	public int hashCode() {
+		return this.name.hashCode() ^ this.type.hashCode();
+	}
+}

src/com/ferox/core/states/manager/GLSLShaderProgramManager.java

 package com.ferox.core.states.manager;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
 import com.ferox.core.states.StateAtom;
 import com.ferox.core.states.atoms.GLSLShaderProgram;
+import com.ferox.core.states.atoms.GLSLUniform;
 
 public class GLSLShaderProgramManager extends UniqueStateManager<GLSLShaderProgram> {
+	public static class GLSLValuePair {
+		private GLSLUniform variable;
+		private Object value;
 		
+		public GLSLValuePair(GLSLUniform var, Object val) {
+			this.variable = var;
+			this.value = val;
+		}
+
+		public GLSLUniform getVariable() {
+			return variable;
+		}
+
+		public Object getValue() {
+			return value;
+		}
+	}
+	
+	private HashMap<GLSLUniform, Object> uniforms;
+	private ArrayList<GLSLValuePair> listUniforms;
+	private List<GLSLValuePair> readOnly;
+	
 	public GLSLShaderProgramManager() {
 		super();
+		this.init();
 	}
 	
 	public GLSLShaderProgramManager(GLSLShaderProgram state) {
 		super(state);
+		this.init();
 	}
 
+	private void init() {
+		this.uniforms = new HashMap<GLSLUniform, Object>();
+		this.listUniforms = new ArrayList<GLSLValuePair>();
+		this.readOnly = Collections.unmodifiableList(this.listUniforms);
+	}
+	
 	@Override
 	public Class<? extends StateAtom> getAtomType() {
 		return GLSLShaderProgram.class;
 	}
+	
+	public List<GLSLValuePair> getUniforms() {
+		return this.readOnly;
+	}
+	
+	public void setUniform(GLSLUniform u, Object val) throws IllegalArgumentException {
+		if (u != null) {
+			if (val == null) {
+				if (this.uniforms.remove(u) != null) {
+					int index = -1;
+					for (int i = 0; i < this.listUniforms.size(); i++) {
+						if (this.listUniforms.get(i).variable.equals(u)) {
+							index = i;
+							break;
+						}
+					}
+					this.listUniforms.remove(index);
+					this.invalidateAssociatedStateTrees();
+				}
+			} else {
+				if (!u.getType().isValidValue(val))
+					throw new IllegalArgumentException("Cannot set a value for a uniform with incompatible type, uniform=" + u.getType() + " value=" + val.getClass().getSimpleName());
+				if (!this.uniforms.containsKey(u)) {
+					GLSLValuePair p = new GLSLValuePair(u, val);
+					this.listUniforms.add(p);
+					this.invalidateAssociatedStateTrees();
+				} else {
+					for (int i = 0; i < this.listUniforms.size(); i++) {
+						if (this.listUniforms.get(i).variable.equals(u)) {
+							this.listUniforms.get(i).value = val;
+							break;
+						}
+					}
+				}
+				
+				this.uniforms.put(u, val);
+			}
+		}
+	}
+	
+	public Object getUniform(GLSLUniform u) {
+		return this.uniforms.get(u);
+	}
 }

src/com/ferox/core/states/manager/UniqueStateManager.java

 		UniqueStateManager<S> pM = (UniqueStateManager<S>)previous;
 		if (previous != null)
 			prev = pM.state;
-		if (previous == this || prev == this.state)
+		if (previous == this)
 			return;
 		if (this.state != null) {
 			manager.getRenderContext().getStateAtomPeer(this.state).prepareManager(this, previous);

src/com/ferox/impl/jsr231/peers/JOGLGLSLShaderObjectPeer.java

 	}
 	
 	public void validateStateAtom(StateAtom atom) throws StateUpdateException {
-		if (RenderManager.getSystemCapabilities().areGLSLShadersSupported()) {
+		if (!RenderManager.getSystemCapabilities().areGLSLShadersSupported()) {
 			((GLSLShaderObject)atom).setCompiled(false, "GLSL isn't supported");
 			throw new StateUpdateException(atom, "GLSL is not supported on this device");
 		}

src/com/ferox/impl/jsr231/peers/JOGLGLSLShaderProgramPeer.java

 package com.ferox.impl.jsr231.peers;
 
 import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 
 import javax.media.opengl.GL;
 
+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;
+
 import com.ferox.core.renderer.RenderManager;
+import com.ferox.core.scene.Transform;
 import com.ferox.core.states.NullUnit;
 import com.ferox.core.states.StateAtom;
+import com.ferox.core.states.StateManager;
 import com.ferox.core.states.StateUpdateException;
 import com.ferox.core.states.StateAtom.StateRecord;
+import com.ferox.core.states.atoms.GLSLAttribute;
 import com.ferox.core.states.atoms.GLSLShaderObject;
 import com.ferox.core.states.atoms.GLSLShaderProgram;
+import com.ferox.core.states.atoms.GLSLUniform;
+import com.ferox.core.states.atoms.GLSLAttribute.AttributeType;
+import com.ferox.core.states.atoms.GLSLUniform.UniformType;
+import com.ferox.core.states.manager.GLSLShaderProgramManager;
+import com.ferox.core.states.manager.GLSLShaderProgramManager.GLSLValuePair;
+import com.ferox.core.util.BufferUtil;
 import com.ferox.impl.jsr231.JOGLRenderContext;
-import com.sun.opengl.util.BufferUtil;
 
 public class JOGLGLSLShaderProgramPeer extends SimplePeer<GLSLShaderProgram, GLSLProgramRecord> {
+	private static class UniformRecord {
+		private static int[] intCache = new int[0];
+		private static float[] floatCache = new float[16];
+		
+		private int location;
+		private GLSLUniform variable;
+		private Object lastValue;
+		
+		public boolean needsUpdate(Object value) {
+			if (value == this.lastValue)
+				return false;
+			
+			switch(this.variable.getType()) {
+			case VEC2F:	case VEC2I:	case VEC3F:	case VEC4F:	case MAT3F:	case MAT4F:
+			case FLOAT:	case INT: case BOOLEAN:
+				return this.lastValue == null || !this.lastValue.equals(value);
+			case VEC2F_ARRAY: case VEC3F_ARRAY: case VEC4F_ARRAY: case VEC2I_ARRAY:
+			case MAT3F_ARRAY: case MAT4F_ARRAY:
+				return Arrays.equals((Object[])this.lastValue, (Object[])value);
+			case FLOAT_ARRAY:
+				return Arrays.equals((float[])this.lastValue, (float[])value);
+			case INT_ARRAY:
+				return Arrays.equals((int[])this.lastValue, (int[])value);
+			case BOOLEAN_ARRAY:
+				return Arrays.equals((boolean[])this.lastValue, (boolean[])value);
+			}
+			return false;
+		}
+		
+		public void setValue(Object value, GL gl) {
+			if (this.needsUpdate(value)) {
+				switch(this.variable.getType()) {
+				case VEC2F: setUniform((Vector2f)value, this.location, gl); break;
+				case VEC3F: setUniform((Vector3f)value, this.location, gl); break;
+				case VEC4F: setUniform((Vector4f)value, this.location, gl); break;
+				case VEC2I: setUniform((Vector2i)value, this.location, gl); break;
+				case MAT3F: setUniform((Matrix3f)value, this.location, gl); break;
+				case MAT4F: setUniform((Matrix4f)value, this.location, gl); break;
+				case FLOAT: setUniform((Float)value, this.location, gl); break;
+				case INT: setUniform((Integer)value, this.location, gl); break;
+				case BOOLEAN: setUniform((Boolean)value, this.location, gl); break;
+				
+				case VEC2F_ARRAY: setUniform((Vector2f[])value, this.location, Math.min(this.variable.getSize(), ((Vector2f[])value).length), gl); break;
+				case VEC3F_ARRAY: setUniform((Vector3f[])value, this.location, Math.min(this.variable.getSize(), ((Vector3f[])value).length),gl); break;
+				case VEC4F_ARRAY: setUniform((Vector4f[])value, this.location, Math.min(this.variable.getSize(), ((Vector4f[])value).length),gl); break;
+				case VEC2I_ARRAY: setUniform((Vector2i[])value, this.location, Math.min(this.variable.getSize(), ((Vector2i[])value).length),gl); break;
+				case MAT3F_ARRAY: setUniform((Matrix3f[])value, this.location, Math.min(this.variable.getSize(), ((Matrix3f[])value).length),gl); break;
+				case MAT4F_ARRAY: setUniform((Matrix4f[])value, this.location, Math.min(this.variable.getSize(), ((Matrix4f[])value).length),gl); break;
+				case FLOAT_ARRAY: setUniform((float[])value, this.location, Math.min(this.variable.getSize(), ((float[])value).length),gl); break;
+				case INT_ARRAY: setUniform((int[])value, this.location, Math.min(this.variable.getSize(), ((int[])value).length),gl); break;
+				case BOOLEAN_ARRAY: setUniform((boolean[])value, this.location, Math.min(this.variable.getSize(), ((boolean[])value).length),gl); break;
+				}
+				this.lastValue = value;
+			}
+		}
+		
+		private static void setUniform(Vector2f v, int loc, GL gl) {
+			gl.glUniform2f(loc, v.x, v.y);
+		}
+		
+		private static void setUniform(Vector2f[] v, int loc, int count, GL gl) {
+			if (floatCache.length < count << 1)
+				floatCache = new float[count << 1];
+			for (int i = 0; i < count; i++) 
+				fillVector2f(floatCache, v[i], i << 1);
+			gl.glUniform2fv(loc, count, floatCache, 0);
+		}
+		
+		private static void setUniform(Vector3f v, int loc, GL gl) {
+			gl.glUniform3f(loc, v.x, v.y, v.z);
+		}
+		
+		private static void setUniform(Vector3f[] v, int loc, int count, GL gl) {
+			if (floatCache.length < count * 3)
+				floatCache = new float[count * 3];
+			for (int i = 0; i < count; i++) 
+				fillVector3f(floatCache, v[i], i * 3);
+			gl.glUniform3fv(loc, count, floatCache, 0);
+		}
+
+		private static void setUniform(Vector4f v, int loc, GL gl) {
+			gl.glUniform4f(loc, v.x, v.y, v.z, v.w);
+		}
+
+		private static void setUniform(Vector4f[] v, int loc, int count, GL gl) {
+			if (floatCache.length < count << 2)
+				floatCache = new float[count << 2];
+			for (int i = 0; i < count; i++) 
+				fillVector4f(floatCache, v[i], i << 2);
+			gl.glUniform4fv(loc, count, floatCache, 0);
+		}
+		
+		private static void setUniform(Vector2i v, int loc, GL gl) {
+			gl.glUniform2i(loc, v.x, v.y);
+		}
+		
+		private static void setUniform(Vector2i[] v, int loc, int count, GL gl) {
+			if (intCache.length < count << 1)
+				intCache = new int[count << 1];
+			for (int i = 0; i < count; i++) 
+				fillVector2i(intCache, v[i], i << 1);
+			gl.glUniform2iv(loc, count, intCache, 0);
+		}
+		
+		private static void setUniform(Matrix3f m, int loc, GL gl) {
+			if (floatCache.length < 9)
+				floatCache = new float[9];
+			fillMatrix3f(floatCache, m, 0);
+			gl.glUniformMatrix3fv(loc, 1, false, floatCache, 0);
+		}
+		
+		private static void setUniform(Matrix3f[] v, int loc, int count, GL gl) {
+			if (floatCache.length < count * 9)
+				floatCache = new float[count * 9];
+			for (int i = 0; i < count; i++) 
+				fillMatrix3f(floatCache, v[i], i * 9);
+			gl.glUniformMatrix3fv(loc, count, false, floatCache, 0);
+		}
+		
+		private static void setUniform(Matrix4f m, int loc, GL gl) {
+			if (floatCache.length < 16)
+				floatCache = new float[16];
+			Transform.getOpenGLMatrix(m, floatCache, 0);
+			gl.glUniformMatrix4fv(loc, 1, false, floatCache, 0);
+		}
+		
+		private static void setUniform(Matrix4f[] v, int loc, int count, GL gl) {
+			if (floatCache.length < count << 4)
+				floatCache = new float[count << 4];
+			for (int i = 0; i < count; i++) 
+				Transform.getOpenGLMatrix(v[i], floatCache, i << 4);
+			gl.glUniformMatrix4fv(loc, count, false, floatCache, 0);
+		}
+		
+		private static void setUniform(float f, int loc, GL gl) {
+			gl.glUniform1f(loc, f);
+		}
+		
+		private static void setUniform(float[] f, int loc, int count, GL gl) {
+			gl.glUniform1fv(loc, count, f, 0);
+		}
+		
+		private static void setUniform(int i, int loc, GL gl) {
+			gl.glUniform1i(i, loc);
+		}
+		
+		private static void setUniform(int[] f, int loc, int count, GL gl) {
+			gl.glUniform1iv(loc, count, f, 0);
+		}
+		
+		private static void setUniform(boolean b, int loc, GL gl) {
+			gl.glUniform1i((b ? 1 : 0), loc);
+		}
+		
+		private static void setUniform(boolean[] f, int loc, int count, GL gl) {
+			if (intCache.length < count) 
+				intCache = new int[count];
+			for (int i = 0; i < count; i++)
+				intCache[i] = (f[i] ? 1 : 0);
+			gl.glUniform1iv(loc, count, intCache, 0);
+		}
+		
+		private static void fillVector2f(float[] cache, Vector2f v, int off) {
+			cache[0 + off] = v.x;
+			cache[1 + off] = v.y;
+		}
+		
+		private static void fillVector3f(float[] cache, Vector3f v, int off) {
+			cache[0 + off] = v.x;
+			cache[1 + off] = v.y;
+			cache[2 + off] = v.z;
+		}
+		
+		private static void fillVector4f(float[] cache, Vector4f v, int off) {
+			cache[0 + off] = v.x;
+			cache[1 + off] = v.y;
+			cache[2 + off] = v.z;
+			cache[3 + off] = v.w;
+		}
+		
+		private static void fillVector2i(int[] cache, Vector2i v, int off) {
+			cache[0 + off] = v.x;
+			cache[1 + off] = v.y;
+		}
+		
+		private static void fillMatrix3f(float[] cache, Matrix3f m, int off) {
+			cache[0 + off] = m.m00;
+			cache[1 + off] = m.m10;
+			cache[2 + off] = m.m20;
+			cache[3 + off] = m.m01;
+			cache[4 + off] = m.m11;
+			cache[5 + off] = m.m21;
+			cache[6 + off] = m.m02;
+			cache[7 + off] = m.m12;
+			cache[8 + off] = m.m22;
+		}
+	}
+	
+	private static class AttributeRecord {
+		private int location;
+		
+		public void updateAttribute(GLSLAttribute na, int prog, GL gl) {
+			if (na.getBinding() != this.location) {
+				gl.glBindAttribLocation(prog, na.getBinding(), na.getName());
+				this.location = na.getBinding();
+			}
+		}
+	}
+	
+	private HashMap<GLSLUniform, UniformRecord>[] uniforms;
+	private HashMap<GLSLAttribute, AttributeRecord>[] attrs;
+	private GLSLShaderProgramManager lastManager;
+	
 	public JOGLGLSLShaderProgramPeer(JOGLRenderContext context) {
 		super(context);
+		this.uniforms = new HashMap[1];
+		this.attrs = new HashMap[1];
 	}
 	
 	protected void applyState(GLSLShaderProgram prevA, GLSLProgramRecord prev, GLSLShaderProgram nextA, GLSLProgramRecord next, GL gl) {
-		if (next.compiled) 
+		if (next.compiled) {
 			gl.glUseProgram(next.id);
-		else if (prev.compiled)
+			this.applyUniforms(this.lastManager, nextA, next, gl);
+			this.updateAttributeBindings(nextA, next, gl);
+		} else if (prev != null && prev.compiled)
 			gl.glUseProgram(0);
 	}
 
 	public void validateStateAtom(StateAtom atom) throws StateUpdateException {
-		if (RenderManager.getSystemCapabilities().areGLSLShadersSupported())
+		if (!RenderManager.getSystemCapabilities().areGLSLShadersSupported()) {
+			((GLSLShaderProgram)atom).setCompiled(false, "GLSL isn't supported");
 			throw new StateUpdateException(atom, "GLSL is not supported on this device");
+		}
 	}
 	
 	public void cleanupStateAtom(StateRecord record) {
 		GLSLProgramRecord p = (GLSLProgramRecord)record;
-		if (p.id > 0)
+		if (p.id > 0) {
+			this.cleanupUniforms(p);
 			this.deleteProgram(p, this.context.getGL());
+		}
 	}
 
 	public StateRecord initializeStateAtom(StateAtom a) {
 			r.id = gl.glCreateProgram();
 			this.linkAndCompile(r, atom, this.context);
 		}
-		return null;
+		return r;
 	}
 	
 	protected void restoreState(GLSLShaderProgram cleanA, GLSLProgramRecord cleanR, GL gl) {
 			gl.glUseProgram(0);
 	}
 
+	public void prepareManager(StateManager manager, StateManager prev) {
+		GLSLShaderProgramManager m = (GLSLShaderProgramManager)manager;
+		this.lastManager = m;
+		if (prev != null) {
+			GLSLShaderProgram p = ((GLSLShaderProgramManager)prev).getStateAtom();
+			GLSLShaderProgram sm = m.getStateAtom();
+			if (p == sm)
+				this.applyUniforms(m, sm, (GLSLProgramRecord)sm.getStateRecord(this.context.getRenderManager()), this.context.getGL());
+			else
+				this.resetUniformRecord(p);
+		}
+	}
+	
+	private void resetUniformRecord(GLSLShaderProgram prog) {
+		GLSLProgramRecord r = (GLSLProgramRecord)prog.getStateRecord(this.context.getRenderManager());
+		Set<GLSLUniform> vars = prog.getAvailableUniforms();
+		for (GLSLUniform ur : vars) {
+			this.uniforms[r.id].get(ur).lastValue = null;
+		}
+	}
+	
+	public void disableManager(StateManager manager) {
+		this.lastManager = null;
+		this.resetUniformRecord(((GLSLShaderProgramManager)manager).getStateAtom());
+	}
+	
 	public void updateStateAtom(StateAtom a, StateRecord record) {
 		GLSLShaderProgram atom = (GLSLShaderProgram)a;
 		GLSLProgramRecord r = (GLSLProgramRecord)record;
 
 		if (r.compiled && atom.isFixedFunction()) {
 			this.deleteProgram(r, gl);
+			this.cleanupUniforms(r);
 			atom.setCompiled(false, "Fixed Function");
 		} else if (!atom.isFixedFunction()) {
 			if (r.id <= 0)
 			if (temp[0] == GL.GL_FALSE) {
 				r.infoLog = "Program failed to link, error msg:\n" + r.infoLog;
 				r.compiled = false;
+				this.cleanupUniforms(r);
 			} else {
 				r.infoLog = "Program linked successfully";
 				r.compiled = true;
+				this.detectUniforms(prog, r, gl);
 			}
-			prog.setCompiled(r.compiled, r.infoLog);
 		} else {
 			r.compiled = false;
-			r.infoLog = "Uncompiled shaders";
-			prog.setCompiled(false, "GLSL shaders didn't compile, can't link program");
+			r.infoLog = "GLSL shaders didn't compile, can't link program";
+		}
+		prog.setCompiled(r.compiled, r.infoLog);
+	}
+	
+	private void detectAttrs(GLSLShaderProgram prog, GLSLProgramRecord r, GL gl) {
+		int[] totalA = new int[1];
+		gl.glGetProgramiv(r.id, GL.GL_ACTIVE_ATTRIBUTES, totalA, 0);
+		if (r.id >= this.attrs.length) {
+			HashMap[] temp = new HashMap[r.id + 1];
+			System.arraycopy(this.attrs, 0, temp, 0, this.attrs.length);
+			this.attrs = temp;
+		}
+		
+		HashMap<GLSLAttribute, AttributeRecord> map = new HashMap<GLSLAttribute, AttributeRecord>();
+		this.attrs[r.id] = map;
+		int[] maxName = new int[1];
+		gl.glGetProgramiv(r.id, GL.GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, maxName, 0);
+		byte[] chars = new byte[maxName[0]];
+		int[] type = new int[1];
+		int[] size = new int[1];
+		int[] nameL = new int[1];
+		
+		Set<GLSLAttribute> allAtt = prog.getAvailableVertexAttributes();
+		Set<Integer> usedBindings = new HashSet<Integer>();
+		
+		for (int i = 0; i < totalA[0]; i++) {
+			GLSLAttribute u;
+			gl.glGetActiveAttrib(r.id, i, maxName[0], nameL, 0, size, 0, type, 0, chars, 0);
+			StringBuffer buff = new StringBuffer();
+			for (int c = 0; c < nameL[0]; c++) {
+				buff.append((char)chars[c]);
+			}
+			
+			AttributeType t = getAttributeType(type[0]);
+			String name = buff.toString().trim();
+			if (t != null && name.indexOf("gl") != 0) {
+				u = new GLSLAttribute(name, t);
+				AttributeRecord ar = new AttributeRecord();
+				ar.location = gl.glGetAttribLocation(r.id, u.getName());
+				u.setBinding(-1);
+				if (allAtt != null && allAtt.contains(u)) {
+					u = prog.getVertexAttributeByName(u.getName());
+					usedBindings.add(u.getBinding());
+				}
+				map.put(u, ar);
+			} else {
+				System.out.println("Ignored attribute " + name + " " + t + " " + type[0]);
+			}
+		}
+		
+		int bindingCounter = 0;
+		allAtt = map.keySet();
+		ArrayList<GLSLAttribute> alphaSorted = new ArrayList<GLSLAttribute>();
+		for (GLSLAttribute a : allAtt) {
+			if (a.getBinding() < 0)
+				alphaSorted.add(a);
+		}
+		
+		Collections.sort(alphaSorted, new Comparator<GLSLAttribute>() {
+			public int compare(GLSLAttribute a1, GLSLAttribute a2) {
+				return a1.getName().compareTo(a2.getName());
+			}
+		});
+		for (GLSLAttribute a : alphaSorted) {
+			while(usedBindings.contains(bindingCounter))
+				bindingCounter++;
+			a.setBinding(bindingCounter);
+			usedBindings.add(bindingCounter);
+		}
+		
+		prog.setAvailableVertexAttributes(allAtt);
+		this.updateAttributeBindings(prog, r, gl);
+	}
+	
+	private void updateAttributeBindings(GLSLShaderProgram prog, GLSLProgramRecord r, GL gl) {
+		Set<GLSLAttribute> allAtt = prog.getAvailableVertexAttributes();
+		if (allAtt == null)
+			this.detectAttrs(prog, r, gl);
+		else {
+			AttributeRecord ar;
+			for (GLSLAttribute a : allAtt) {
+				ar = this.attrs[r.id].get(a);
+				if (ar != null)
+					ar.updateAttribute(a, r.id, gl);
+			}
+		}
+	}
+	
+	private AttributeType getAttributeType(int gl) {
+		switch(gl) {
+		case GL.GL_FLOAT: return AttributeType.FLOAT;
+		case GL.GL_FLOAT_VEC2: return AttributeType.VEC2F;
+		case GL.GL_FLOAT_VEC3: return AttributeType.VEC3F;
+		case GL.GL_FLOAT_VEC4: return AttributeType.VEC4F;
+		case GL.GL_FLOAT_MAT2: return AttributeType.MAT2F;
+		case GL.GL_FLOAT_MAT3: return AttributeType.MAT3F;
+		case GL.GL_FLOAT_MAT4: return AttributeType.MAT4F;
+		}
+		return null;
+	}
+	
+	private void detectUniforms(GLSLShaderProgram prog, GLSLProgramRecord r, GL gl) {
+		int[] totalU = new int[1];
+		gl.glGetProgramiv(r.id, GL.GL_ACTIVE_UNIFORMS, totalU, 0);
+		if (r.id >= this.uniforms.length) {
+			HashMap[] temp = new HashMap[r.id + 1];
+			System.arraycopy(this.uniforms, 0, temp, 0, this.uniforms.length);
+			this.uniforms = temp;
+		}
+		
+		HashMap<GLSLUniform, UniformRecord> map = new HashMap<GLSLUniform, UniformRecord>();
+		this.uniforms[r.id] = map;
+		int[] maxName = new int[1];
+		gl.glGetProgramiv(r.id, GL.GL_ACTIVE_UNIFORM_MAX_LENGTH, maxName, 0);
+		byte[] chars = new byte[maxName[0]];
+		int[] type = new int[1];
+		int[] size = new int[1];
+		int[] nameL = new int[1];
+		
+		for (int i = 0; i < totalU[0]; i++) {
+			GLSLUniform u;
+			gl.glGetActiveUniform(r.id, i, maxName[0], nameL, 0, size, 0, type, 0, chars, 0);
+			StringBuffer buff = new StringBuffer();
+			for (int c = 0; c < nameL[0]; c++) {
+				buff.append((char)chars[c]);
+			}
+			UniformType t = getArrayType(getUniformType(type[0]), size[0]);
+			String name = buff.toString().trim();
+			if (t != null && name.indexOf("gl") != 0) {
+				u = new GLSLUniform(name, t, size[0]);
+				UniformRecord ur = new UniformRecord();
+				ur.lastValue = null;
+				ur.location = gl.glGetUniformLocation(r.id, u.getName());
+				ur.variable = u;
+				map.put(u, ur);
+			} else {
+				System.out.println("Ignored uniform " + name + " " + t + " " + type[0]);
+			}
+		}
+		
+		prog.setAvailableUniforms(map.keySet());
+	}
+	
+	private static UniformType getArrayType(UniformType base, int size) {
+		if (size <= 1)
+			return base;
+		switch(base) {
+		case FLOAT: return UniformType.FLOAT_ARRAY;
+		case INT: return UniformType.INT_ARRAY;
+		case BOOLEAN: return UniformType.BOOLEAN_ARRAY;
+		case VEC2F: return UniformType.VEC2F_ARRAY;
+		case VEC3F: return UniformType.VEC3F_ARRAY;
+		case VEC4F: return UniformType.VEC4F_ARRAY;
+		case VEC2I: return UniformType.VEC2I_ARRAY;
+		case MAT3F: return UniformType.MAT3F_ARRAY;
+		case MAT4F: return UniformType.MAT4F_ARRAY;
+		default:
+			return null;
+		}
+	}
+	
+	private static UniformType getUniformType(int gl) {
+		switch(gl) {
+		case GL.GL_FLOAT: return UniformType.FLOAT;
+		case GL.GL_INT: case GL.GL_SAMPLER_2D:
+		case GL.GL_SAMPLER_3D: case GL.GL_SAMPLER_CUBE: case GL.GL_SAMPLER_2D_SHADOW:
+			return UniformType.INT;
+		case GL.GL_BOOL: return UniformType.BOOLEAN;
+		case GL.GL_FLOAT_VEC2: return UniformType.VEC2F;
+		case GL.GL_FLOAT_VEC3: return UniformType.VEC3F;
+		case GL.GL_FLOAT_VEC4: return UniformType.VEC4F;
+		case GL.GL_INT_VEC2: return UniformType.VEC2I;
+		case GL.GL_FLOAT_MAT3: return UniformType.MAT3F;
+		case GL.GL_FLOAT_MAT4: return UniformType.MAT4F;
+		default:
+			return null;
+		}
+	}
+	
+	private void cleanupUniforms(GLSLProgramRecord r) {
+		if (this.uniforms == null || r.id >= this.uniforms.length)
+			return;
+		this.uniforms[r.id] = null;
+	}
+	
+	private void applyUniforms(GLSLShaderProgramManager prog, GLSLShaderProgram p, GLSLProgramRecord r, GL gl) {
+		List<GLSLValuePair> vars = prog.getUniforms();
+		Set<GLSLUniform> all = p.getAvailableUniforms();
+		
+		int size = vars.size();
+		GLSLValuePair variable;
+		for (int i = 0; i < size; i++) {
+			variable = vars.get(i);
+			if (all.contains(variable.getVariable())) 
+				this.uniforms[r.id].get(variable.getVariable()).setValue(variable.getValue(), gl);
 		}
 	}
 }

src/com/ferox/test/GLSLTest.java

+package com.ferox.test;
+
+import java.awt.Component;
+import java.awt.event.KeyEvent;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.util.Set;
+
+import javax.swing.JFrame;
+
+import org.openmali.vecmath.Matrix3f;
+import org.openmali.vecmath.Vector3f;
+import org.openmali.vecmath.Vector4f;
+
+import com.ferox.core.renderer.FrameListener;
+import com.ferox.core.renderer.FrameStatistics;
+import com.ferox.core.renderer.InitializationListener;
+import com.ferox.core.renderer.RenderManager;
+import com.ferox.core.scene.InfluenceLeaf;
+import com.ferox.core.scene.SpatialBranch;
+import com.ferox.core.scene.SpatialLeaf;
+import com.ferox.core.scene.SpatialNode;
+import com.ferox.core.scene.SpatialTree;
+import com.ferox.core.scene.Transform;
+import com.ferox.core.scene.View;
+import com.ferox.core.scene.ViewNode;
+import com.ferox.core.scene.bounds.AxisAlignedBox;
+import com.ferox.core.scene.bounds.BoundingSphere;
+import com.ferox.core.scene.states.DirectionLight;
+import com.ferox.core.scene.states.SpotLight;
+import com.ferox.core.states.StateBranch;
+import com.ferox.core.states.StateLeaf;
+import com.ferox.core.states.StateTree;
+import com.ferox.core.states.atoms.BufferData;
+import com.ferox.core.states.atoms.GLSLShaderObject;
+import com.ferox.core.states.atoms.GLSLShaderProgram;
+import com.ferox.core.states.atoms.GLSLUniform;
+import com.ferox.core.states.atoms.Material;
+import com.ferox.core.states.atoms.VertexArray;
+import com.ferox.core.states.atoms.GLSLShaderObject.GLSLType;
+import com.ferox.core.states.atoms.GLSLUniform.UniformType;
+import com.ferox.core.states.manager.GLSLShaderProgramManager;
+import com.ferox.core.states.manager.Geometry;
+import com.ferox.core.states.manager.LightManager;
+import com.ferox.core.states.manager.MaterialManager;
+import com.ferox.core.system.DisplayOptions;
+import com.ferox.core.system.OnscreenRenderSurface;
+import com.ferox.core.util.InputManager;
+import com.ferox.core.util.TimeSmoother;
+import com.ferox.impl.jsr231.JOGLPassiveRenderContext;
+import com.sun.opengl.util.BufferUtil;
+
+public class GLSLTest implements FrameListener, InitializationListener {
+	private RenderManager manager;
+	private JFrame window;
+	private SpatialTree scene;
+	private View view;
+	private Transform transform;
+	
+	private InputManager input;
+	private boolean quit;
+	private TimeSmoother timer;
+	private FrameStatistics stats;
+	private GLSLShaderProgramManager program;
+	private GLSLShaderProgramManager program2;
+	
+	private long elapsedTime;
+	
+	public static void main(String[] args) {
+		new GLSLTest().run();
+	}
+	
+	private GLSLTest() {
+		this.manager = new RenderManager(new JOGLPassiveRenderContext(new DisplayOptions()));
+		
+		this.window = new JFrame("GLSLTest");
+		this.window.add((Component)((OnscreenRenderSurface)this.manager.getRenderingSurface()).getRenderSurface());
+		this.window.setSize(800, 600);
+		
+		this.view = new View();
+		this.view.setPerspective(60, 4f/3f, 1, 200);
+		ViewNode node = new ViewNode(this.view);
+		node.getLocalTransform().setTranslation(0f, 0f, -10f);
+		
+		this.scene = this.buildScene(this.manager);
+		this.transform = this.scene.getRootNode().getChild(0).getLocalTransform();
+		this.scene.getRootNode().add(node);
+		
+		this.manager.setSpatialTree(this.scene);
+		this.manager.setView(this.view);
+		
+		this.manager.getRenderPass(0).setClearedColor(new float[] {.5f, .5f, .5f, 1f});
+		this.manager.addFrameListener(this);
+		this.manager.addInitializationListener(this);
+		
+		this.timer = new TimeSmoother();
+		this.input = new InputManager(this.window);
+		this.input.setKeyBehavior(KeyEvent.VK_ESCAPE, InputManager.INITIAL_PRESS);
+		this.input.setKeyBehavior(KeyEvent.VK_R, InputManager.INITIAL_PRESS);
+	}
+	
+	private SpatialTree buildScene(RenderManager manager) {
+		SpatialBranch top = new SpatialBranch(null, 1);
+		
+		SpatialBranch root = new SpatialBranch(top, 4);
+		StateBranch sRoot = new StateBranch(null, 4);
+		
+		Geometry cube = this.buildCube(2f);
+		Geometry cube2 = this.buildCube(.5f);
+		
+		// Basic cube
+		StateLeaf sl1 = new StateLeaf(sRoot);
+		sl1.addStateManager(cube);
+		SpatialLeaf l1 = new SpatialLeaf(root, sl1);
+		l1.getLocalTransform().getTranslation().set(3, 0, 0);
+		l1.setModelBounds(new AxisAlignedBox());
+		
+		StateLeaf hl1 = new StateLeaf(sRoot);
+		hl1.addStateManager(cube2);
+		SpatialLeaf h1 = new SpatialLeaf(root, hl1);
+		h1.getLocalTransform().getTranslation().set(3, 2, 0);
+		h1.setModelBounds(new BoundingSphere());
+		
+		// GLSL cube
+		StateLeaf sl2 = new StateLeaf(sRoot);
+		sl2.addStateManager(cube);
+		SpatialLeaf l2 = new SpatialLeaf(root, sl2);
+		l2.getLocalTransform().getTranslation().set(-3, 0, 0);
+		l2.setModelBounds(new AxisAlignedBox());
+		
+		StateLeaf hl2 = new StateLeaf(sRoot);
+		hl2.addStateManager(cube2);
+		SpatialLeaf h2 = new SpatialLeaf(root, hl2);
+		h2.getLocalTransform().getTranslation().set(-3, 2, 0);
+		h2.setModelBounds(new AxisAlignedBox());
+		
+		buildProgram();
+		
+		MaterialManager white = new MaterialManager(new Material(new float[] {1f, 1f, 1f, 1f}, new float[] {1f, 0f, 0f, 1f}, 50));
+		MaterialManager red = new MaterialManager(new Material(new float[] {1f, 0f, 0f, 1f}));
+		MaterialManager green = new MaterialManager(new Material(new float[] {0f, 1f, 0f, 1f}));
+		
+		sl1.addStateManager(white);
+		sl1.addStateManager(this.program2);
+		sl2.addStateManager(white);
+		sl2.addStateManager(this.program);
+		hl1.addStateManager(green);
+		hl2.addStateManager(red);
+		
+		LightManager lm = new LightManager();
+		lm.setGlobalAmbientLight(new float[] {.5f, .5f, .5f, 1f});
+		lm.setLocalViewer(true);
+		sl1.addStateManager(lm);
+		sl2.addStateManager(lm);
+		
+		DirectionLight light = new DirectionLight(new Vector3f(0f, 1f, -1f), new float[] {1f, 1f, 1f, 1f});
+		//light.setSpotCutoffAngle(45);
+		InfluenceLeaf l = new InfluenceLeaf(root, light);
+		l.setCullMode(SpatialNode.CullMode.NEVER);
+		l.getLocalTransform().getTranslation().set(2f, 5f, 2f);
+		BoundingSphere s = new BoundingSphere();
+		s.setCenter(new Vector3f());
+		s.setRadius(15f);
+		l.setInfluence(s);
+		
+		DirectionLight light2 = new DirectionLight(new Vector3f(0f, -1f, 1f), new float[] {1f, 0f, 1f, 1f});
+		//light.setSpotCutoffAngle(45);
+		InfluenceLeaf l2Leaf = new InfluenceLeaf(root, light2);
+		l2Leaf.setCullMode(SpatialNode.CullMode.NEVER);
+		l2Leaf.getLocalTransform().getTranslation().set(3f, -5f, -2f);
+		BoundingSphere s2 = new BoundingSphere();
+		s2.setCenter(new Vector3f());
+		s2.setRadius(15f);
+		l2Leaf.setInfluence(s2);
+		
+		manager.enableUpdate(new StateTree(sRoot));
+		manager.setSpatialTree(new SpatialTree(top));
+		return manager.getSpatialTree();
+	}
+	
+	private GLSLShaderProgramManager buildProgram() {
+		String[] vSource = new String[] {
+				"uniform vec4 mat_diff;",
+				"uniform vec3 mat_amb;",
+				"void main() {",
+					"vec3 normal, lightDir;",
+					"vec4 diffuse;",
+					"float NdotL;",
+					"normal = normalize(gl_NormalMatrix * gl_Normal);",
+					"lightDir = normalize(vec3(gl_LightSource[0].position));",
+					"NdotL = max(dot(normal, lightDir), 0.0);",
+					"diffuse = mat_diff * gl_LightSource[0].diffuse;",
+					"gl_FrontColor = NdotL * diffuse + 0.4 * vec4(mat_amb, 1.0);",
+					"gl_Position = ftransform();",
+				"}"
+		};
+		String[] fSource = new String[] {
+				"void main() {",
+					"gl_FragColor = gl_Color;",
+				"}"
+		};
+		GLSLShaderObject vert = new GLSLShaderObject(vSource, GLSLType.VERTEX);
+		GLSLShaderObject frag = new GLSLShaderObject(fSource, GLSLType.FRAGMENT);
+		
+		GLSLShaderProgram program = new GLSLShaderProgram(new GLSLShaderObject[] {vert, frag});
+		GLSLShaderProgramManager man = new GLSLShaderProgramManager(program);
+		man.setUniform(new GLSLUniform("mat_diff", UniformType.VEC4F, 1), new Vector4f(1f, 0f, 0f, 1f));
+		man.setUniform(new GLSLUniform("mat_amb", UniformType.VEC3F, 1), new Vector3f(1f, 1f, 1f));
+		this.program = man;
+		
+		man = new GLSLShaderProgramManager(program);
+		man.setUniform(new GLSLUniform("mat_diff", UniformType.VEC4F, 1), new Vector4f(1f, 0f, 0f, 1f));
+		man.setUniform(new GLSLUniform("mat_amb", UniformType.VEC3F, 1), new Vector3f(1f, 0f, 0f));
+		this.program2 = man;
+		return man;
+	}
+	
+	public void run() {
+		this.window.setVisible(true);
+		
+		while (!quit) {
+			stats = this.manager.render();
+			timer.addSample(stats.getDuration() / 1000000);
+			/*try {
+				Thread.sleep(5);
+			} catch(Exception e) {}*/
+			
+			checkInput();
+		}
+	
+		this.manager.destroy();
+		System.exit(0);
+	}
+	
+	public void checkInput() {
+		if (input.isKeyPressed(KeyEvent.VK_ESCAPE)) 
+			quit = true;
+		if (input.isKeyPressed(KeyEvent.VK_R)) {
+			System.out.println(stats);
+			System.out.println(timer.getFrameRate());
+		}
+		if (input.isKeyPressed(KeyEvent.VK_LEFT))
+			this.transform.getTranslation().x += -.1f;
+		if (input.isKeyPressed(KeyEvent.VK_RIGHT))
+			this.transform.getTranslation().x += .1f;
+		if (input.isKeyPressed(KeyEvent.VK_S))
+			this.transform.getTranslation().y += -.1f;
+		if (input.isKeyPressed(KeyEvent.VK_W))
+			this.transform.getTranslation().y += .1f;
+		if (input.isKeyPressed(KeyEvent.VK_UP))
+			this.transform.getTranslation().z += .1;
+		if (input.isKeyPressed(KeyEvent.VK_DOWN))
+			this.transform.getTranslation().z += -.1;
+	}
+	
+	public Geometry buildCube() {
+		return this.buildCube(1f);
+	}
+	
+	public Geometry buildCube(float side) {
+		float[] v = new float[72];
+		float[] n = new float[72];
+		
+		// front
+		v[0] = 1f; v[1] = 1f; v[2] = 1f; n[0] = 0f; n[1] = 0f; n[2] = 1f;
+		v[3] = -1f; v[4] = 1f; v[5] = 1f; n[3] = 0f; n[4] = 0f; n[5] = 1f;
+		v[6] = -1f; v[7] = -1f; v[8] = 1f; n[6] = 0f; n[7] = 0f; n[8] = 1f;
+		v[9] = 1f; v[10] = -1f; v[11] = 1f; n[9] = 0f; n[10] = 0f; n[11] = 1f;
+		//back
+		v[12] = -1f; v[13] = -1f; v[14] = -1f; n[12] = 0f; n[13] = 0f; n[14] = -1f;
+		v[21] = 1f; v[22] = -1f; v[23] = -1f; n[21] = 0f; n[22] = 0f; n[23] = -1f;
+		v[18] = 1f; v[19] = 1f; v[20] = -1f; n[18] = 0f; n[19] = 0f; n[20] = -1f;
+		v[15] = -1f; v[16] = 1f; v[17] = -1f; n[15] = 0f; n[16] = 0f; n[17] = -1f;
+		//right
+		v[24] = 1f; v[25] = 1f; v[26] = -1f; n[24] = 1f; n[25] = 0f; n[26] = 0f;
+		v[27] = 1f; v[28] = 1f; v[29] = 1f; n[27] = 1f; n[28] = 0f; n[29] = 0f;
+		v[30] = 1f; v[31] = -1f; v[32] = 1f; n[30] = 1f; n[31] = 0f; n[32] = 0f;
+		v[33] = 1f; v[34] = -1f; v[35] = -1f; n[33] = 1f; n[34] = 0f; n[35] = 0f;
+		//left
+		v[36] = -1f; v[37] = -1f; v[38] = 1f; n[36] = -1f; n[37] = 0f; n[38] = 0f;
+		v[45] = -1f; v[46] = -1f; v[47] = -1f; n[45] = -1f; n[46] = 0f; n[47] = 0f;
+		v[42] = -1f; v[43] = 1f; v[44] = -1f; n[42] = -1f; n[43] = 0f; n[44] = 0f;
+		v[39] = -1f; v[40] = 1f; v[41] = 1f; n[39] = -1f; n[40] = 0f; n[41] = 0f;
+		//top
+		v[48] = -1f; v[49] = 1f; v[50] = -1f; n[48] = 0f; n[49] = 1f; n[50] = 0f;
+		v[57] = 1f; v[58] = 1f; v[59] = -1f; n[57] = 0f; n[58] = 1f; n[59] = 0f;
+		v[54] = 1f; v[55] = 1f; v[56] = 1f; n[54] = 0f; n[55] = 1f; n[56] = 0f;
+		v[51] = -1f; v[52] = 1f; v[53] = 1f; n[51] = 0f; n[52] = 1f; n[53] = 0f;
+		//bottom
+		v[60] = 1f; v[61] = -1f; v[62] = 1f; n[60] = 0f; n[61] = -1f; n[62] = 0f;
+		v[63] = -1f; v[64] = -1f; v[65] = 1f; n[63] = 0f; n[64] = -1f; n[65] = 0f;
+		v[66] = -1f; v[67] = -1f; v[68] = -1f; n[66] = 0f; n[67] = -1f; n[68] = 0f;
+		v[69] = 1f; v[70] = -1f; v[71] = -1f; n[69] = 0f; n[70] = -1f; n[71] = 0f;
+		
+		for (int i = 0; i < v.length; i++)
+			v[i] = v[i] * side / 2f;
+		
+		int[] i = new int[24];
+		for (int u = 0; u < 24; u++)
+			i[u] = u;
+		
+		FloatBuffer vb = BufferUtil.newFloatBuffer(v.length);
+		vb.put(v).rewind();
+		FloatBuffer nb = BufferUtil.newFloatBuffer(n.length);
+		nb.put(n).rewind();
+		
+		IntBuffer ib = BufferUtil.newIntBuffer(i.length);
+		ib.put(i).rewind();
+		
+		BufferData vbd = new BufferData(vb, BufferData.DataType.FLOAT, vb.capacity(), BufferData.BufferTarget.ARRAY_BUFFER);
+		BufferData nbd = new BufferData(nb, BufferData.DataType.FLOAT, nb.capacity(), BufferData.BufferTarget.ARRAY_BUFFER);
+		BufferData ibd = new BufferData(ib, BufferData.DataType.UNSIGNED_INT, ib.capacity(), BufferData.BufferTarget.ELEMENT_BUFFER);
+		
+		VertexArray iva = new VertexArray(ibd, 1);
+		
+		Geometry geom = new Geometry(new VertexArray(vbd, 3), new VertexArray(nbd, 3), iva, Geometry.PolygonType.QUADS);
+		return geom;
+	}
+
+	public void endFrame(RenderManager manager) {
+		// do nothing
+	}
+
+	public void startFrame(RenderManager renderManager) {
+		long now = System.currentTimeMillis();
+		if (now - this.elapsedTime > 12) {
+			Matrix3f rx = new Matrix3f();
+			Matrix3f ry = new Matrix3f();
+			rx.rotX((float)Math.PI / 512);
+			ry.rotY((float)Math.PI / 512);
+			rx.mul(ry);
+			this.transform.getRotation().mul(ry);
+			
+			Vector4f v = (Vector4f)this.program.getUniform(this.program.getStateAtom().getUniformByName("mat_diff"));
+			v.x = (float)Math.sin(this.elapsedTime * Math.PI / 512);
+			v.y = (float)Math.cos(this.elapsedTime * Math.PI / 1024);
+			v.z = (float)Math.tan(this.elapsedTime * Math.PI / 768 + Math.PI / 2);
+			
+			Vector3f m = (Vector3f)this.program2.getUniform(this.program.getStateAtom().getUniformByName("mat_amb"));
+			m.x = (float)Math.sin(this.elapsedTime * Math.PI / 512);
+			m.y = (float)Math.cos(this.elapsedTime * Math.PI / 1024);
+			m.z = (float)Math.tan(this.elapsedTime * Math.PI / 768 + Math.PI / 2);
+			
+			this.elapsedTime = now;
+		}
+	}
+
+	public void onInit(RenderManager manager) {
+		GLSLShaderProgram program = this.program.getStateAtom();
+		program.update(manager);
+		System.out.println(program.isCompiled() + " " + program.getInfoLog());
+		for (int i = 0; i < program.getShaders().length; i++) {
+			GLSLShaderObject e = program.getShaders()[i];
+			System.out.println(e.getShaderType() + " " + e.isCompiled() + " " + e.getInfoLog());
+		}
+		Set<GLSLUniform> au = program.getAvailableUniforms();
+		if (au != null) {
+			System.out.println("Uniforms: " + au.size());
+			for (GLSLUniform o: au) {
+				System.out.println(o.getName() + " " + o.getType() + " " + o.getSize());
+			}
+		}
+		
+		
+	}
+}