Commits

Michael Ludwig committed 9a08c71

Rudimentary RTT support (uses glCopyX) and fixed RenderPassPeer support.

Comments (0)

Files changed (23)

scratch/Sort.java

+import java.util.Arrays;
+
+
+public class Sort {
+	public static void main(String[] args) {
+		int numbers = 200000;
+		int[] array = new int[numbers];
+		System.out.println("randomizing");
+		for (int i = 0; i < array.length; i++) {
+			array[i] = (int)(Math.random() * numbers);
+		}
+		System.out.println("sorting");
+		sort(array);
+		verify(array);
+		System.out.println("finished");
+		Arrays.sort(array);
+		System.out.println("benchmark");
+	}
+	
+	public static void verify(int[] array) {
+		for (int i = 1; i < array.length; i++) {
+			if (array[i - 1] > array[i])
+				System.out.println("Sort failed at index: " + i + " | " + array[i - 1] + " " + array[i]);
+		}
+	}
+	
+	public static void sort(int[] array) {
+		int index = 0;
+		
+		
+		while(index < array.length) {
+			int min = array[index];
+			int minIndex = index;
+			
+			for (int i = index; i < array.length; i++) {
+				if (array[i] < min) {
+					min = array[i];
+					minIndex = i;
+				}
+			}
+			
+			int temp = array[index];
+			array[index] = min;
+			array[minIndex] = temp;
+			index += 1;
+		}
+	}
+}

src/com/ferox/core/renderer/RenderContext.java

 	
 	RenderManager manager;
 	
-	private RenderPassPeer<RenderPass> defaultPass;
-	// TODO: add render to texture render passes
-	
 	private StateManager[] stateRecord;
 	private StateAtomTracker[] atomRecord;
 	
 	 * satisfy the requested options.  They are also responsible for instantiating the correct implementation of 
 	 * a RenderSurface.
 	 */
-	public RenderContext(DisplayOptions options) {
-		this.defaultPass = this.createDefaultRenderPassPeer();
-	}
+	public RenderContext(DisplayOptions options) { }
 	
 	/**
 	 * Get the RenderSurface that rendering goes to
 	public abstract void clearBuffers(boolean color, float[] clearColor, boolean depth, float clearDepth, boolean stencil, int clearStencil);
 	
 	/**
-	 * Get the number of auxiliary buffers present (ie GL_AUXi)
-	 */
-	public abstract int getNumAuxiliaryBuffers();
-	/**
-	 * Get the maximum number of draw buffers that are present.
-	 */
-	public abstract int getMaxDrawBuffers();
-	
-	/**
 	 * Method to be called to signal the beginning of a RenderAtom.  At the moment, it resets the dynamic
 	 * spatial state record for use with this atom.  Throws an exception if called inside an existing
 	 * beginAtom() endAtom() pair of calls.
 	}
 	
 	/**
-	 * Get the RenderPassPeer impl for the default render pass.
+	 * Return a RenderPassPeer implementation suitable for the given type of RenderPass
+	 * (RenderPass or a subclass of it).
 	 */
-	public abstract RenderPassPeer<RenderPass> createDefaultRenderPassPeer();
-	//public RenderPassImpl<T> getRenderToTextureImpl();
+	public abstract RenderPassPeer getRenderPassPeer(Class<? extends RenderPass> type);
 	
 	/**
 	 * Get the StateAtomPeer implementation instance for the given class of StateAtom and this context
 	public RenderManager getRenderManager() {
 		return this.manager;
 	}
-
-	/**
-	 * Get the default render pass peer suitable for use in a standard double/single buffered
-	 * setup
-	 */
-	public RenderPassPeer<RenderPass> getDefaultRenderPassPeer() {
-		return this.defaultPass;
-	}
 	
 	/**
 	 * Convenience method that throws an exception if the RenderContext isn't current

src/com/ferox/core/renderer/RenderPass.java

 	/**
 	 * Returns false if the scene is null or the view is null or the view's view node is not within the scene.
 	 */
-	private boolean isValid() {
+	public boolean isValid() {
 		if (this.scene == null || this.view == null)
 			return false;
 		return true;
 	 * and submitting has begun.  Currently calls the default RenderPassPeer's prepare method.
 	 */
 	protected void prepareRenderPass(RenderManager manager) {
-		manager.getRenderContext().getDefaultRenderPassPeer().prepareRenderPass(this, manager.getRenderContext());
+		manager.getRenderContext().getRenderPassPeer(this.getClass()).prepareRenderPass(this);
 	}
 	
 	/**
 	 * Currently calls the default RenderPassPeer's finish method.
 	 */
 	protected void finalizeRenderPass(RenderManager manager) {
-		manager.getRenderContext().getDefaultRenderPassPeer().finishRenderPass(this, manager.getRenderContext());
+		manager.getRenderContext().getRenderPassPeer(this.getClass()).finishRenderPass(this);
 	}
 }

src/com/ferox/core/renderer/RenderPassPeer.java

 package com.ferox.core.renderer;
 
 
-public interface RenderPassPeer<R extends RenderPass> {
-	public void prepareRenderPass(R pass, RenderContext context);
-	public void finishRenderPass(R pass, RenderContext context);
+public interface RenderPassPeer {
+	public void prepareRenderPass(RenderPass pass);
+	public void finishRenderPass(RenderPass pass);
 }

src/com/ferox/core/renderer/RenderToTexturePass.java

+package com.ferox.core.renderer;
+
+import com.ferox.core.scene.SpatialTree;
+import com.ferox.core.scene.View;
+import com.ferox.core.states.atoms.Texture2D;
+import com.ferox.core.states.atoms.Texture3D;
+import com.ferox.core.states.atoms.TextureCubeMap;
+import com.ferox.core.states.atoms.TextureData;
+import com.ferox.core.states.atoms.TextureCubeMap.Face;
+import com.ferox.core.states.atoms.TextureData.TextureCompression;
+import com.ferox.core.states.atoms.TextureData.TextureFormat;
+import com.ferox.core.states.atoms.TextureData.TextureTarget;
+import com.ferox.core.states.atoms.TextureData.TextureType;
+import com.ferox.core.system.SystemCapabilities;
+
+public class RenderToTexturePass extends RenderPass {
+	private static int maxColorAttachments = -1;
+	private static boolean maxColorSet = false;
+	
+	public static int getMaxColorAttachments() {
+		if (!maxColorSet) {
+			SystemCapabilities caps = RenderManager.getSystemCapabilities();
+			if (caps != null) {
+				maxColorAttachments = caps.getMaxColorAttachments();
+				maxColorSet = true;
+			}
+		}
+		
+		return maxColorAttachments;
+	}
+	
+	private static class Attachment {
+		TextureData data;
+		Face face;
+		int slice;
+	}
+	
+	private Attachment[] colorArray;
+	private Attachment depthAttach;
+	
+	public RenderToTexturePass() {
+		this(null, null);
+	}
+	
+	public RenderToTexturePass(SpatialTree scene, View view) {
+		super(scene, view);
+		
+		this.colorArray = new Attachment[1];
+		this.depthAttach = new Attachment();
+	}
+	
+	public TextureData getDepthBinding() {
+		return this.depthAttach.data;
+	}
+	
+	public Face getDepthBindingFace() {
+		if (this.depthAttach.data != null && this.depthAttach.data.getTarget() == TextureTarget.CUBEMAP)
+			return this.depthAttach.face;
+		return null;
+	}
+	
+	public int getDepthBindingSlice() {
+		if (this.depthAttach.data != null && this.depthAttach.data.getTarget() == TextureTarget.TEX3D)
+			return this.depthAttach.slice;
+		return 0;
+	}
+	
+	public void setDepthBinding(Texture2D data) {
+		validateDepthFormat(data);
+		setAttachment(data, this.depthAttach);
+	}
+	
+	public void setDepthBinding(TextureCubeMap data, Face face) throws IllegalArgumentException {
+		validateDepthFormat(data);
+		setAttachment(data, face, this.depthAttach);
+	}
+	
+	public void setDepthBinding(Texture3D data, int slice) throws IllegalArgumentException {
+		validateDepthFormat(data);
+		setAttachment(data, slice, this.depthAttach);
+	}
+	
+	public TextureData getColorBinding(int drawBuffer) {
+		Attachment a = null;
+		if (drawBuffer >= 0 && drawBuffer < this.colorArray.length)
+			a = this.colorArray[drawBuffer];
+		
+		if (a != null)
+			return a.data;
+		return null;
+	}
+	
+	public Face getColorBindingFace(int drawBuffer) {
+		Attachment a = null;
+		if (drawBuffer >= 0 && drawBuffer < this.colorArray.length)
+			a = this.colorArray[drawBuffer];
+		
+		if (a != null && a.data != null && a.data.getTarget() == TextureTarget.CUBEMAP)
+			return a.face;
+		return null;
+	}
+	
+	public int getColorBindingSlice(int drawBuffer) {
+		Attachment a = null;
+		if (drawBuffer >= 0 && drawBuffer < this.colorArray.length)
+			a = this.colorArray[drawBuffer];
+		
+		if (a != null && a.data != null && a.data.getTarget() == TextureTarget.TEX3D)
+			return a.slice;
+		return 0;
+	}
+	
+	private static void validateColorFormat(TextureData data) throws IllegalArgumentException {
+		if (data != null && data.getDataFormat() == TextureFormat.DEPTH)
+			throw new IllegalArgumentException("Color attachment must have a non-depth format, not: " + data.getDataFormat());
+		if (data != null && (data.getDataFormat().isClientCompressed() || data.getDataCompression() != TextureCompression.NONE))
+			throw new IllegalArgumentException("Color attachment can't be compressed: " + data.getDataFormat() + " " + data.getDataCompression());
+	}
+	
+	public void setColorBinding(Texture2D data, int drawBuffer) throws IllegalArgumentException {
+		validateColorFormat(data);
+		this.ensureExistence(drawBuffer);
+		setAttachment(data, this.colorArray[drawBuffer]);
+	}
+	
+	public void setColorBinding(TextureCubeMap data, Face face, int drawBuffer) throws IllegalArgumentException {
+		validateColorFormat(data);
+		this.ensureExistence(drawBuffer);
+		setAttachment(data, face, this.colorArray[drawBuffer]);
+	}
+	
+	public void setColorBinding(Texture3D data, int slice, int drawBuffer) throws IllegalArgumentException {
+		validateColorFormat(data);
+		this.ensureExistence(drawBuffer);
+		setAttachment(data, slice, this.colorArray[drawBuffer]);
+	}
+	
+	public int getWidth() {
+		if (this.depthAttach.data != null) 
+			return getWidth(this.depthAttach.data);
+		for (int i = 0; i < this.colorArray.length; i++)
+			if (this.colorArray[i] != null && this.colorArray[i].data != null)
+				return getWidth(this.colorArray[i].data);
+		return 0;
+	}
+	
+	public int getHeight() {
+		if (this.depthAttach.data != null) 
+			return getHeight(this.depthAttach.data);
+		for (int i = 0; i < this.colorArray.length; i++)
+			if (this.colorArray[i] != null && this.colorArray[i].data != null)
+				return getHeight(this.colorArray[i].data);
+		return 0;
+	}
+	
+	private static int getWidth(TextureData data) {
+		if (data.getTarget() == TextureTarget.TEX2D)
+			return ((Texture2D)data).getWidth();
+		if (data.getTarget() == TextureTarget.TEX3D)
+			return ((Texture3D)data).getWidth();
+		if (data.getTarget() == TextureTarget.CUBEMAP)
+			return ((TextureCubeMap)data).getSideLength();
+		return 0;
+	}
+	
+	private static int getHeight(TextureData data) {
+		if (data.getTarget() == TextureTarget.TEX2D)
+			return ((Texture2D)data).getHeight();
+		if (data.getTarget() == TextureTarget.TEX3D)
+			return ((Texture3D)data).getHeight();
+		if (data.getTarget() == TextureTarget.CUBEMAP)
+			return ((TextureCubeMap)data).getSideLength();
+		return 0;
+	}
+	
+	public boolean isValid() {
+		if (super.isValid()) {
+			try {
+				validateSizes(this.getWidth(), this.getHeight());
+				validateColorFormats();
+				if (this.depthAttach.data != null)
+					validateDepthFormat(this.depthAttach.data);
+			} catch (Exception e) {
+				System.err.println("WARNING: Invalid RTT pass, because \n" + e.getMessage());
+				return false;
+			}
+			return true;
+		}
+		return false;
+	}
+	
+	private void validateSizes(int width, int height) throws IllegalArgumentException {
+		int w, h;
+		if (this.depthAttach.data != null) {
+			w = getWidth(this.depthAttach.data);
+			h = getHeight(this.depthAttach.data);
+			if (w != width || h != height)
+				throw new IllegalArgumentException("Dimensions don't match, was " + w + " X " + h + ", needs to be " + width + " X " + height);
+		}
+		for (int i = 0; i < this.colorArray.length; i++) {
+			if (this.colorArray[i] != null && this.colorArray[i].data != null) {
+				w = getWidth(this.colorArray[i].data);
+				h = getHeight(this.colorArray[i].data);
+				if (w != width || h != height)
+					throw new IllegalArgumentException("Dimensions don't match, was " + w + " X " + h + ", needs to be " + width + " X " + height);
+			}
+		}
+	}
+	
+	private void validateColorFormats() throws IllegalArgumentException {
+		TextureFormat f = null;
+		TextureType t = null;
+		TextureData d;
+		for (int i = 0; i < this.colorArray.length; i++) {
+			d = (this.colorArray[i] != null ? this.colorArray[i].data : null);
+			if (d != null) {
+				validateColorFormat(d);
+				if (t != null || f != null) {
+					if (d.getDataFormat() != f || d.getDataType() != t)
+						throw new IllegalArgumentException("Texture format and types don't match, was " + d.getDataFormat() + " | " + d.getDataType() + ", needs to be " + f + " | " + t);
+				} else {
+					t = d.getDataType();
+					f = d.getDataFormat();
+				}
+			}
+		}
+	}
+	
+	private static void validateDepthFormat(TextureData data) throws IllegalArgumentException {
+		if (data != null && data.getDataFormat() != TextureFormat.DEPTH)
+			throw new IllegalArgumentException("Depth attachment must have a depth format, not: " + data.getDataFormat());
+	}
+	
+	private void ensureExistence(int drawBuffer) throws IllegalArgumentException {
+		if (drawBuffer < 0 || (getMaxColorAttachments() >= 0 && drawBuffer >= getMaxColorAttachments()))
+			throw new IllegalArgumentException("Illegal draw buffer: " + drawBuffer);
+		if (drawBuffer >= this.colorArray.length) {
+			Attachment[] temp = new Attachment[drawBuffer + 1];
+			System.arraycopy(this.colorArray, 0, temp, 0, this.colorArray.length);
+			this.colorArray = temp;
+		}
+		if (this.colorArray[drawBuffer] == null)
+			this.colorArray[drawBuffer] = new Attachment();
+	}
+	
+	private static void setAttachment(Texture2D data, Attachment attach) throws IllegalArgumentException {
+		if (data != null && data.getNumMipmaps() > 1)
+			throw new IllegalArgumentException("Texture can't be mipmapped");
+		attach.data = data;
+		attach.slice = 0;
+		attach.face = null;
+	}
+	
+	private static void setAttachment(TextureCubeMap data, Face face, Attachment attach) throws IllegalArgumentException {
+		if (data != null && data.getNumMipmaps() > 1)
+			throw new IllegalArgumentException("Texture can't be mipmapped");
+		if (data == null)
+			face = null;
+		else if (data != null && face == null)
+			throw new IllegalArgumentException("Face can't be null when passing a non-null cube map");
+		
+		attach.data = data;
+		attach.slice = 0;
+		attach.face = face;
+	}
+	
+	private static void setAttachment(Texture3D data, int slice, Attachment attach) throws IllegalArgumentException {
+		if (data != null && data.getNumMipmaps() > 1)
+			throw new IllegalArgumentException("Texture can't be mipmapped");
+		if (data == null)
+			slice = 0;
+		else if (data != null && (slice < 0 || slice >= data.getDepth()))
+			throw new IllegalArgumentException("Slice must be in bounds for a non-null 3D texture");
+		
+		attach.data = data;
+		attach.slice = slice;
+		attach.face = null;
+	}
+}

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

 	private Buffer[] data;
 	private int width;
 	private int height;
+	private int numMips;
 	private boolean inited;
 	
 	public Texture2D(Buffer[] data, int width, int height, TextureType dataType, TextureFormat dataFormat, MinFilter min, MagFilter mag) {
 				this.data = null;
 			}
 		
+			this.numMips = (this.data == null ? 1 : this.data.length);
+			
 			if (this.isMipmapped() && (this.width != this.height)) 
 				throw new IllegalArgumentException("Can't use mipmaps for rectangular textures");
 			if (this.isMipmapped() && this.data.length != (int)(Math.log(this.width) / Math.log(2) + 1))
 	}
 	
 	public void clearClientMemory() {
+		int oldMips = this.numMips;
 		this.setTextureData(null, this.width, this.height);
+		this.numMips = oldMips;
 	}
 	
 	public Buffer getMipmapLevel(int level) {
 	}
 	
 	public int getNumMipmaps() {
-		return (this.data == null ? 1 : this.data.length);
+		return this.numMips;
 	}
 	
 	public int getWidth() {
 	
 	@Override
 	public boolean isMipmapped() {
-		return this.data != null && this.data.length > 1;
+		return this.numMips > 1;
 	}
 
 	@Override

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

 	private int width;
 	private int height;
 	private int depth;
+	private int numMips;
 	private boolean inited;
 	
 	public Texture3D(Buffer[] data, int width, int height, int depth, TextureType dataType, TextureFormat dataFormat, MinFilter min, MagFilter mag) {
 				this.data = null;
 			}
 		
+			this.numMips = (this.data == null ? 1 : this.data.length);
+			
 			if (this.width != this.height) 
 				throw new IllegalArgumentException("2D slices of texture data must be square");
 			if (this.isMipmapped() && this.data.length != (int)(Math.log(this.width) / Math.log(2) + 1))
 	}
 	
 	public void clearClientMemory() {
+		int oldMips = this.numMips;
 		this.setTextureData(null, this.width, this.height, this.depth);
+		this.numMips = oldMips;
 	}
 	
 	public Buffer getMipmapLevel(int level) {
 	}
 	
 	public int getNumMipmaps() {
-		return (this.data == null ? 1 : this.data.length);
+		return this.numMips;
 	}
 	
 	@Override	
 	public boolean isMipmapped() {
-		return this.data != null && this.data.length > 1;
+		return this.numMips > 1;
 	}
 	
 	public int getWidth() {

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

 	
 	private Buffer[] px, nx, py, ny, pz, nz;
 	private int side;
+	private int numMips;
 	private boolean inited;
 	
 	public TextureCubeMap(Buffer[] px, Buffer[] nx, Buffer[] py, Buffer[] ny, Buffer[] pz, Buffer[] nz, int side, TextureType dataType, TextureFormat dataFormat, MinFilter min, MagFilter mag) {
 				this.validateCubeFace(this.nz, this.side);
 			}
 		
+			this.numMips = (this.px == null ? 1 : this.px.length);
+			
 			if (this.isMipmapped() && this.px.length != (int)(Math.log(this.side) / Math.log(2) + 1))
 				throw new IllegalArgumentException("Can't specify mipmaps using too few mipmap buffers");
 		} catch (RuntimeException e) {
 	}
 	
 	public void clearClientMemory() {
+		int oldMips = this.numMips;
 		this.setTextureData(null, null, null, null, null, null, this.side);
+		this.numMips = oldMips;
 	}
 	
 	public int getNumMipmaps() {
-		return (this.px == null ? 1 : this.px.length);
+		return this.numMips;
 	}
 	
 	@Override
 	public boolean isMipmapped() {
-		return this.px != null && this.px.length > 1;
+		return this.numMips > 1;
 	}
 
 	@Override

src/com/ferox/core/system/SystemCapabilities.java

 	private int maxTextureCoordinates;
 	private int maxRecommendedIndices;
 	private int maxRecommendedVertices;
+	
+	private int maxColorAttachments;
+	
 	private float maxAnisoLevel;
 	
 	private String glVersion;
 			int maxFragmentShaderTextureUnits, int maxCombinedTextureUnits,
 			int maxFFPTextureUnits,
 			int maxLights, int maxVertexAttributes,
-			int maxTextureCoordinates, int maxVertices, int maxIndices, float maxAnisoLevel,
+			int maxTextureCoordinates, int maxVertices, int maxIndices, int maxColor, float maxAnisoLevel,
 			String glVersion, float versionNumber,
 			boolean fboSupported, boolean glslSupported, boolean vboSupported,
 			boolean pboSupported, boolean multiTexSupported,
 		this.maxTextureCoordinates = maxTextureCoordinates;
 		this.maxRecommendedIndices = maxIndices;
 		this.maxRecommendedVertices = maxVertices;
+		this.maxColorAttachments = maxColor;
 		this.pboSupported = pboSupported;
 		this.glVersion = glVersion;
 		this.versionNumber = versionNumber;
 		this.s3tcTextures = s3tcTextures;
 	}
 	
+	public int getMaxColorAttachments() {
+		return this.maxColorAttachments;
+	}
+	
 	public boolean arePixelBuffersSupported() {
 		return this.pboSupported;
 	}

src/com/ferox/impl/jsr231/JOGLCapabilitiesFetcher.java

 		boolean multiTexSupported = vNum >= 1.3f || gl.isExtensionAvailable("GL_ARB_multitexture");
 		boolean cubeMapSupport = vNum >= 1.3f || gl.isExtensionAvailable("GL_ARB_texture_cube_map");
 		boolean threeDSupport = vNum >= 1.2f;
-		boolean fboSupported = false;
+		boolean fboSupported = gl.isExtensionAvailable("GL_EXT_framebuffer_object");
 		boolean separateSpecularLightingSupported = vNum >= 1.2f;
 		
 		boolean npotTextures = vNum >= 2.0f || gl.isExtensionAvailable("GL_ARB_texture_non_power_of_two");
 		
 		boolean pboSupport = gl.isExtensionAvailable("GL_EXT_pixel_buffer");
 		
+		gl.glGetIntegerv(GL.GL_MAX_COLOR_ATTACHMENTS_EXT, store, 0);
+		int maxColor = store[0];
+		
 		this.caps = new SystemCapabilities(maxVertexShaderTextureUnits,
 				maxFragmentShaderTextureUnits, maxCombinedTextureUnits, maxFFPTextureUnits,
 				maxLights, maxVertexAttributes,maxTextureCoordinates, 
-				maxVertices, maxIndices, maxAniso, version, vNum,
+				maxVertices, maxIndices, maxColor, maxAniso, version, vNum,
 				fboSupported, glslSupported, vboSupported, pboSupport, multiTexSupported,
 				separateSpecularLightingSupported, cubeMapSupport, threeDSupport,
 				fpTextures, npotTextures, rectTextures, s3tcTex);

src/com/ferox/impl/jsr231/JOGLPassiveRenderContext.java

 import javax.media.opengl.*;
 import javax.media.opengl.glu.GLU;
 
+import com.ferox.core.renderer.RenderPass;
+import com.ferox.core.renderer.RenderPassPeer;
 import com.ferox.core.system.DisplayOptions;
 import com.ferox.core.system.OnscreenRenderSurface;
 import com.ferox.core.system.RenderSurface;
 	
 	private int width;
 	private int height;
-	
-	private int maxDrawBuffers;
-	private int numAuxBuffers;
 
 	private boolean initialized;
 	
 		
 		this.gl = null;
 		this.version = -1;
-		
-		this.maxDrawBuffers = -1;
-		this.numAuxBuffers = -1;
 
 		this.initialized = false;
 		
 	}
 
 	@Override
-	public int getMaxDrawBuffers() {
-		return this.maxDrawBuffers;
-	}
-
-	@Override
-	public int getNumAuxiliaryBuffers() {
-		return this.numAuxBuffers;
-	}
-
-	@Override
 	public int getContextHeight() {
 		return this.height;
 	}
 		boolean doubleBuffered, stereo;
 		
 		int[] t = new int[1];
-		this.gl.glGetIntegerv(GL.GL_MAX_DRAW_BUFFERS, t, 0);
-		this.maxDrawBuffers = t[0];
 		this.gl.glGetIntegerv(GL.GL_SAMPLES, t, 0);
 		numMultiSamples = t[0];
-		this.gl.glGetIntegerv(GL.GL_AUX_BUFFERS, t, 0);
-		this.numAuxBuffers = t[0];
 		
 		this.gl.glDrawBuffer(GL.GL_BACK_LEFT);
 		doubleBuffered = this.gl.glGetError() == 0;

src/com/ferox/impl/jsr231/JOGLRenderContext.java

 import java.nio.FloatBuffer;
 import java.util.HashMap;
 
-import javax.media.opengl.*;
+import javax.media.opengl.GL;
+import javax.media.opengl.GLCanvas;
+import javax.media.opengl.GLCapabilities;
+import javax.media.opengl.GLContext;
+import javax.media.opengl.GLDrawableFactory;
+import javax.media.opengl.GLJPanel;
+import javax.media.opengl.GLPbuffer;
 
 import org.openmali.vecmath.AxisAngle4f;
 import org.openmali.vecmath.Vector3f;
 import com.ferox.core.util.FeroxException;
 import com.ferox.core.util.DataTransfer.Block;
 import com.ferox.core.util.DataTransfer.Slice;
-import com.ferox.impl.jsr231.peers.JOGLDefaultRenderPassPeer;
 import com.ferox.impl.jsr231.peers.JOGLTextureDataPeer;
 import com.sun.opengl.util.GLUT;
 
 	private RenderSurface surface;
 	private double[] plane;
 	private HashMap<Class<? extends StateAtom>, StateAtomPeer> peerMap;
+	private HashMap<Class<? extends RenderPass>, RenderPassPeer> renderMap;
 	private StateAtomPeer[] peerCache;
 	
 	public JOGLRenderContext(DisplayOptions options) {
 		
 		this.peerCache = null;
 		this.peerMap = new HashMap<Class<? extends StateAtom>, StateAtomPeer>();
+		this.renderMap = new HashMap<Class<? extends RenderPass>, RenderPassPeer>();
 		
 		this.matrix = BufferUtil.newFloatBuffer(16);
 		this.plane = new double[4];
 		}
 		return joglCaps;
 	}
-
+	
 	@Override
-	public RenderPassPeer<RenderPass> createDefaultRenderPassPeer() {
-		return new JOGLDefaultRenderPassPeer();
+	public RenderPassPeer getRenderPassPeer(Class<? extends RenderPass> type) throws RuntimeException {
+		if (type == null)
+			throw new NullPointerException("Can't have a peer for a null type");
+		RenderPassPeer peer = this.renderMap.get(type);
+		if (peer != null)
+			return peer;
+		
+		String name = this.getClass().getPackage().getName() + ".peers.JOGL" + type.getSimpleName() + "Peer";
+		try {
+			peer = (RenderPassPeer)Class.forName(name).getDeclaredConstructor(JOGLRenderContext.class).newInstance(this);
+		} catch (RuntimeException e) {
+			throw e;
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		}
+		
+		this.renderMap.put(type, peer);
+		return peer;
 	}
 
 	@Override
 	public StateAtomPeer getStateAtomPeer(Class<? extends StateAtom> type) throws RuntimeException {
+		if (type == null)
+			throw new NullPointerException("Can't have a peer for null type");
 		StateAtomPeer peer = this.peerMap.get(type);
 		if (peer != null)
 			return peer;

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

+package com.ferox.impl.jsr231.peers;
+
+import com.ferox.core.renderer.RenderToTexturePass;
+import com.ferox.core.states.atoms.Texture2D;
+import com.ferox.core.states.atoms.Texture3D;
+import com.ferox.core.states.atoms.TextureCubeMap;
+import com.ferox.core.states.atoms.TextureData;
+import com.ferox.core.states.atoms.TextureCubeMap.Face;
+import com.ferox.core.states.atoms.TextureData.TextureTarget;
+import com.ferox.core.util.DataTransfer.Block;
+import com.ferox.impl.jsr231.JOGLRenderContext;
+import com.ferox.impl.jsr231.peers.JOGLRenderToTexturePassPeer.RTTPeer;
+
+public class CopyTexturePeer implements RTTPeer {
+	public void finish(RenderToTexturePass pass, JOGLRenderContext context) {
+		int width = pass.getWidth();
+		int height = pass.getHeight();
+		int maxColors = RenderToTexturePass.getMaxColorAttachments();
+		
+		TextureData attach = pass.getDepthBinding();
+		if (attach != null)
+			copyPixels(attach, pass.getDepthBindingSlice(), pass.getDepthBindingFace(), width, height, context);
+		
+		for (int i = 0; i < maxColors; i++) {
+			attach = pass.getColorBinding(i);
+			if (attach != null)
+				copyPixels(attach, pass.getColorBindingSlice(i), pass.getColorBindingFace(i), width, height, context);
+		}
+	}
+
+	private static void copyPixels(TextureData data, int slice, Face face, int width, int height, JOGLRenderContext context) {
+		//FIXME: center region onto the screen if the texture is smaller than the context dims.
+		Block region = new Block(0, 0, slice, Math.min(width, context.getContextWidth()), Math.min(height, context.getContextHeight()), 1);
+		if (data.getTarget() == TextureTarget.TEX2D)
+			context.copyFramePixels((Texture2D)data, region, 0, 0, 0);
+		else if (data.getTarget() == TextureTarget.TEX3D)
+			context.copyFramePixels((Texture3D)data, region, 0, 0, 0);
+		else
+			context.copyFramePixels((TextureCubeMap)data, region, face, 0, 0, 0);
+	}
+	
+	public void prepare(RenderToTexturePass pass, JOGLRenderContext context) {
+		// do nothing
+	}
+}

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

+package com.ferox.impl.jsr231.peers;
+
+import com.ferox.core.renderer.RenderToTexturePass;
+import com.ferox.impl.jsr231.JOGLRenderContext;
+import com.ferox.impl.jsr231.peers.JOGLRenderToTexturePassPeer.RTTPeer;
+
+public class FBOPeer implements RTTPeer {
+
+	public void finish(RenderToTexturePass pass, JOGLRenderContext context) {
+		// TODO Auto-generated method stub
+		
+	}
+
+	public void prepare(RenderToTexturePass pass, JOGLRenderContext context) {
+		// TODO Auto-generated method stub
+		
+	}
+
+}

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

-package com.ferox.impl.jsr231.peers;
-
-import com.ferox.core.renderer.RenderContext;
-import com.ferox.core.renderer.RenderPass;
-import com.ferox.core.renderer.RenderPassPeer;
-
-public class JOGLDefaultRenderPassPeer implements RenderPassPeer<RenderPass> {
-
-	public void finishRenderPass(RenderPass pass, RenderContext context) {
-		// do nothing
-	}
-
-	public void prepareRenderPass(RenderPass pass, RenderContext context) {
-		// do nothing
-	}
-}

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

+package com.ferox.impl.jsr231.peers;
+
+import com.ferox.core.renderer.RenderPass;
+import com.ferox.core.renderer.RenderPassPeer;
+import com.ferox.impl.jsr231.JOGLRenderContext;
+
+public class JOGLRenderPassPeer implements RenderPassPeer {
+	protected JOGLRenderContext context;
+	
+	public JOGLRenderPassPeer(JOGLRenderContext context) {
+		this.context = context;
+	}
+
+	public void finishRenderPass(RenderPass pass) {
+		// do nothing
+	}
+
+	public void prepareRenderPass(RenderPass pass) {
+		// do nothing
+	}
+}

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

+package com.ferox.impl.jsr231.peers;
+
+import com.ferox.core.renderer.RenderManager;
+import com.ferox.core.renderer.RenderPass;
+import com.ferox.core.renderer.RenderToTexturePass;
+import com.ferox.impl.jsr231.JOGLRenderContext;
+
+public class JOGLRenderToTexturePassPeer extends JOGLRenderPassPeer {
+	public static interface RTTPeer {
+		public void finish(RenderToTexturePass pass, JOGLRenderContext context);
+		public void prepare(RenderToTexturePass pass, JOGLRenderContext context);
+	}
+	
+	private RTTPeer peer;
+	
+	public JOGLRenderToTexturePassPeer(JOGLRenderContext context) {
+		super(context);
+		this.peer = null;
+	}
+	
+	public void finishRenderPass(RenderPass pass) {
+		this.peer.finish((RenderToTexturePass)pass, this.context);
+	}
+
+	public void prepareRenderPass(RenderPass pass) {
+		if (this.peer == null) {
+			if (RenderManager.getSystemCapabilities().isFBOSupported())
+				this.peer = new CopyTexturePeer();
+			else
+				this.peer = new CopyTexturePeer();
+		}
+		this.peer.prepare((RenderToTexturePass)pass, this.context);
+	}
+}

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

 		
 		for (int i = 0; i < texture.getNumMipmaps(); i++) {
 			if (!this.reallocateOnUpdate(width, height, texture.getDataFormat()) || texture.getMipmapLevel(i) == null)
-				this.setTexImage(true, gl, t.target, i, 0, 0, width, height, t.srcFormat, t.dstFormat, t.dataType, texture.getDataFormat(), texture.getMipmapLevel(i).clear());
+				this.setTexImage(true, gl, t.target, i, 0, 0, width, height, t.srcFormat, t.dstFormat, t.dataType, texture.getDataFormat(), texture.getMipmapLevel(i));
 			
 			width = Math.max(1, (width >> 1));
 			height = Math.max(1, (height >> 1));
 			data = texture.getMipmapLevel(i);
 			
 			if (data != null)
-				this.setTexImage(this.reallocateOnUpdate(width, height, texture.getDataFormat()), gl, t.target, i, 0, 0, width, height, t.srcFormat, t.dstFormat, t.dataType, texture.getDataFormat(), data.clear());
+				this.setTexImage(this.reallocateOnUpdate(width, height, texture.getDataFormat()), gl, t.target, i, 0, 0, width, height, t.srcFormat, t.dstFormat, t.dataType, texture.getDataFormat(), data);
 			
 			width = Math.max(1, (width >> 1));
 			height = Math.max(1, (height >> 1));
 	}
 	
 	private void setTexImage(boolean realloc, GL gl, int target, int level, int xOff, int yOff, int width, int height, int srcFormat, int dstFormat, int type, TextureFormat rFormat, Buffer data) {
+		if (data != null)
+			data.clear();
 		if (realloc) {
 			if (srcFormat < 0)
 				gl.glCompressedTexImage2D(target, level, dstFormat, width, height, 0, rFormat.getBufferSize(TextureType.UNSIGNED_BYTE, width, height), data);

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

 		int width = texture.getWidth();
 		int height = texture.getHeight();
 		int depth = texture.getDepth();
-		
+		Buffer data;
 		for (int i = 0; i < texture.getNumMipmaps(); i++) {	
-			gl.glTexImage3D(t.target, i, t.dstFormat, width, height, depth, 0, t.srcFormat, t.dataType, texture.getMipmapLevel(i).clear());
+			data = texture.getMipmapLevel(i);
+			if (data != null)
+				gl.glTexImage3D(t.target, i, t.dstFormat, width, height, depth, 0, t.srcFormat, t.dataType, data.clear());
+			else
+				gl.glTexImage3D(t.target, i, t.dstFormat, width, height, depth, 0, t.srcFormat, t.dataType, null);
 			width = Math.max(1, (width >> 1));
 			height = Math.max(1, (height >> 1));
 			depth = Math.max(1, (depth >> 1));

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

 		
 		for (int i = 0; i < texture.getNumMipmaps(); i++) {
 			if (!this.reallocateOnUpdate(side, side, texture.getNumMipmaps(), texture.getDataFormat()) || texture.getPositiveXMipmap(i) == null) {
-				this.setTexImage(true, gl, GL.GL_TEXTURE_CUBE_MAP_POSITIVE_X, i, 0, 0, side, side, t.srcFormat, t.dstFormat, t.dataType, texture.getDataFormat(), texture.getMipmap(i, Face.PX).clear());
-				this.setTexImage(true, gl, GL.GL_TEXTURE_CUBE_MAP_NEGATIVE_X, i, 0, 0, side, side, t.srcFormat, t.dstFormat, t.dataType, texture.getDataFormat(), texture.getMipmap(i, Face.NX).clear());
-				this.setTexImage(true, gl, GL.GL_TEXTURE_CUBE_MAP_POSITIVE_Y, i, 0, 0, side, side, t.srcFormat, t.dstFormat, t.dataType, texture.getDataFormat(), texture.getMipmap(i, Face.PY).clear());
-				this.setTexImage(true, gl, GL.GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, i, 0, 0, side, side, t.srcFormat, t.dstFormat, t.dataType, texture.getDataFormat(), texture.getMipmap(i, Face.NY).clear());
-				this.setTexImage(true, gl, GL.GL_TEXTURE_CUBE_MAP_POSITIVE_Z, i, 0, 0, side, side, t.srcFormat, t.dstFormat, t.dataType, texture.getDataFormat(), texture.getMipmap(i, Face.PZ).clear());
-				this.setTexImage(true, gl, GL.GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, i, 0, 0, side, side, t.srcFormat, t.dstFormat, t.dataType, texture.getDataFormat(), texture.getMipmap(i, Face.NZ).clear());
+				this.setTexImage(true, gl, GL.GL_TEXTURE_CUBE_MAP_POSITIVE_X, i, 0, 0, side, side, t.srcFormat, t.dstFormat, t.dataType, texture.getDataFormat(), texture.getMipmap(i, Face.PX));
+				this.setTexImage(true, gl, GL.GL_TEXTURE_CUBE_MAP_NEGATIVE_X, i, 0, 0, side, side, t.srcFormat, t.dstFormat, t.dataType, texture.getDataFormat(), texture.getMipmap(i, Face.NX));
+				this.setTexImage(true, gl, GL.GL_TEXTURE_CUBE_MAP_POSITIVE_Y, i, 0, 0, side, side, t.srcFormat, t.dstFormat, t.dataType, texture.getDataFormat(), texture.getMipmap(i, Face.PY));
+				this.setTexImage(true, gl, GL.GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, i, 0, 0, side, side, t.srcFormat, t.dstFormat, t.dataType, texture.getDataFormat(), texture.getMipmap(i, Face.NY));
+				this.setTexImage(true, gl, GL.GL_TEXTURE_CUBE_MAP_POSITIVE_Z, i, 0, 0, side, side, t.srcFormat, t.dstFormat, t.dataType, texture.getDataFormat(), texture.getMipmap(i, Face.PZ));
+				this.setTexImage(true, gl, GL.GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, i, 0, 0, side, side, t.srcFormat, t.dstFormat, t.dataType, texture.getDataFormat(), texture.getMipmap(i, Face.NZ));
 			}
 			side = Math.max(1, (side >> 1));
 		}
 				break;
 			}
 			if (data != null) 
-				this.setTexImage(this.reallocateOnUpdate(side, side, texture.getNumMipmaps(), texture.getDataFormat()), gl, face, i, 0, 0, side, side, t.srcFormat, t.dstFormat, t.dataType, texture.getDataFormat(), data.clear());
+				this.setTexImage(this.reallocateOnUpdate(side, side, texture.getNumMipmaps(), texture.getDataFormat()), gl, face, i, 0, 0, side, side, t.srcFormat, t.dstFormat, t.dataType, texture.getDataFormat(), data);
 			side = Math.max(1, (side >> 1));
 		}
 	}
 	
 	private void setTexImage(boolean realloc, GL gl, int target, int level, int xOff, int yOff, int width, int height, int srcFormat, int dstFormat, int type, TextureFormat rFormat, Buffer data) {
+		if (data != null)
+			data.clear();
 		if (realloc) {
 			if (srcFormat < 0)
 				gl.glCompressedTexImage2D(target, level, dstFormat, width, height, 0, rFormat.getBufferSize(TextureType.UNSIGNED_BYTE, width, height), data);

src/com/ferox/test/GLSLTest.java

 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;

src/com/ferox/test/LightTest.java

 		a1.addStateManager(geom);
 		a2.addStateManager(geom2);
 		
-		for (int i = 0; i < 100; i++) {
+		for (int i = 0; i < 10000; i++) {
 			SpatialLeaf spat_atom;
 			StateLeaf stat_atom;
 			double r = Math.random();

src/com/ferox/test/RTTTest.java

+package com.ferox.test;
+
+import java.awt.Component;
+import java.awt.event.KeyEvent;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+
+import javax.swing.JFrame;
+
+import org.openmali.vecmath.Matrix3f;
+import org.openmali.vecmath.Vector3f;
+
+import com.ferox.core.renderer.FrameListener;
+import com.ferox.core.renderer.FrameStatistics;
+import com.ferox.core.renderer.RenderManager;
+import com.ferox.core.renderer.RenderPass;
+import com.ferox.core.renderer.RenderToTexturePass;
+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.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.Material;
+import com.ferox.core.states.atoms.Texture;
+import com.ferox.core.states.atoms.Texture2D;
+import com.ferox.core.states.atoms.VertexArray;
+import com.ferox.core.states.atoms.Texture.AutoTCGen;
+import com.ferox.core.states.atoms.Texture.EnvMode;
+import com.ferox.core.states.atoms.TextureData.MagFilter;
+import com.ferox.core.states.atoms.TextureData.MinFilter;
+import com.ferox.core.states.atoms.TextureData.TexClamp;
+import com.ferox.core.states.atoms.TextureData.TextureFormat;
+import com.ferox.core.states.atoms.TextureData.TextureType;
+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.states.manager.TextureManager;
+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 RTTTest implements FrameListener {
+	private RenderManager manager;
+	private JFrame window;
+	
+	private SpatialTree scene1;
+	private View view1;
+	private Transform transform1;
+	
+	private SpatialTree scene2;
+	private View view2;
+	private Transform transform2;
+	
+	private Texture2D rttStore;
+	
+	private InputManager input;
+	private boolean quit;
+	private TimeSmoother timer;
+	private FrameStatistics stats;
+	
+	private long elapsedTime;
+	
+	public static void main(String[] args) {
+		new RTTTest().run();
+	}
+	
+	private RTTTest() {
+		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(512, 512);
+		
+		// RTT part
+		this.view1 = new View();
+		this.view1.setPerspective(60, 4f/3f, 1, 200);
+		ViewNode node = new ViewNode(this.view1);
+		node.getLocalTransform().setTranslation(0f, 0f, -2f);
+		this.scene1 = this.buildRTTScene(this.manager);
+		this.transform1 = this.scene1.getRootNode().getChild(0).getLocalTransform();
+		this.scene1.getRootNode().add(node);
+		
+		// View of RTT
+		this.view2 = new View();
+		this.view2.setPerspective(60, 4f/3f, 1, 200);
+		node = new ViewNode(this.view2);
+		node.getLocalTransform().setTranslation(0f, 0f, -10f);
+		this.scene2 = this.buildScene(this.manager);
+		this.transform2 = this.scene2.getRootNode().getChild(0).getLocalTransform();
+		this.scene2.getRootNode().add(node);
+		
+		
+		RenderToTexturePass rtt = new RenderToTexturePass(this.scene1, this.view1);
+		rtt.setColorBinding(rttStore, 0);
+		rtt.setClearedColor(new float[] {.3f, .3f, .3f, 1f});
+		
+		RenderPass v = new RenderPass(this.scene2, this.view2);
+		v.setClearedColor(new float[] {.5f, .5f, .5f, 1f});
+		
+		this.manager.enableUpdate(this.scene1);
+		this.manager.enableUpdate(this.scene2);
+		
+		this.manager.addRenderPass(rtt);
+		this.manager.addRenderPass(v);
+		
+		this.manager.addFrameListener(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 buildRTTScene(RenderManager manager) {
+		Component c = (Component)((OnscreenRenderSurface)this.manager.getRenderingSurface()).getRenderSurface();
+		int min = Math.min(c.getWidth(), c.getHeight());
+		this.rttStore = new Texture2D(null, min, min, TextureType.UNSIGNED_BYTE, TextureFormat.RGB, TexClamp.CLAMP, MinFilter.LINEAR, MagFilter.LINEAR);
+		
+		SpatialBranch root = new SpatialBranch(null, 1);
+		Geometry cube = this.buildCube(1f);
+		
+		StateBranch sRoot = new StateBranch(null, 1);
+		StateLeaf s1 = new StateLeaf(sRoot);
+		s1.addStateManager(cube);
+		
+		MaterialManager green = new MaterialManager(new Material(new float[] {0f, 1f, 0f, 1f}));
+		s1.addStateManager(green);
+		LightManager lm = new LightManager();
+		lm.setSeperateSpecularHighlight(true);
+		s1.addStateManager(lm);
+	
+		
+		SpatialLeaf leaf = new SpatialLeaf(root, s1);
+		leaf.getLocalTransform().setTranslation(0f, 0f, 0f);
+		leaf.setModelBounds(new AxisAlignedBox());
+		
+		DirectionLight light = new DirectionLight(new Vector3f(0f, -1f, 1f), new float[] {1f, 1f, 1f, 1f});
+		InfluenceLeaf l = new InfluenceLeaf(root, light);
+		l.setCullMode(SpatialNode.CullMode.NEVER);
+		l.getLocalTransform().getTranslation().set(2f, 5f, 2f);
+		l.setInfluence(new BoundingSphere(new Vector3f(), 15f));
+		
+		manager.enableUpdate(new StateTree(sRoot));
+		return new SpatialTree(root);
+	}
+	
+	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());
+		
+		TextureManager tman = new TextureManager();
+		Texture t = new Texture(this.rttStore, EnvMode.MODULATE, new float[4], AutoTCGen.NONE);
+		tman.setTexture(0, t);
+		
+		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(tman);
+		sl2.addStateManager(white);
+		sl2.addStateManager(tman);
+		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));
+		return new SpatialTree(top);
+	}
+	
+	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.transform2.getTranslation().x += -.1f;
+		if (input.isKeyPressed(KeyEvent.VK_RIGHT))
+			this.transform2.getTranslation().x += .1f;
+		if (input.isKeyPressed(KeyEvent.VK_S))
+			this.transform2.getTranslation().y += -.1f;
+		if (input.isKeyPressed(KeyEvent.VK_W))
+			this.transform2.getTranslation().y += .1f;
+		if (input.isKeyPressed(KeyEvent.VK_UP))
+			this.transform2.getTranslation().z += .1;
+		if (input.isKeyPressed(KeyEvent.VK_DOWN))
+			this.transform2.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];
+		float[] t = new float[48];
+		
+		// front
+		v[0] = 1f; v[1] = 1f; v[2] = 1f; n[0] = 0f; n[1] = 0f; n[2] = 1f; t[0] = 1f; t[1] = 1f;
+		v[3] = -1f; v[4] = 1f; v[5] = 1f; n[3] = 0f; n[4] = 0f; n[5] = 1f; t[2] = 0f; t[3] = 1f;
+		v[6] = -1f; v[7] = -1f; v[8] = 1f; n[6] = 0f; n[7] = 0f; n[8] = 1f; t[4] = 0f; t[5] = 0f;
+		v[9] = 1f; v[10] = -1f; v[11] = 1f; n[9] = 0f; n[10] = 0f; n[11] = 1f; t[6] = 1f; t[7] = 0f;
+		//back
+		v[12] = -1f; v[13] = -1f; v[14] = -1f; n[12] = 0f; n[13] = 0f; n[14] = -1f; t[8] = 1f; t[9] = 1f;
+		v[21] = 1f; v[22] = -1f; v[23] = -1f; n[21] = 0f; n[22] = 0f; n[23] = -1f; t[10] = 0f; t[11] = 1f;
+		v[18] = 1f; v[19] = 1f; v[20] = -1f; n[18] = 0f; n[19] = 0f; n[20] = -1f; t[12] = 0f; t[13] = 0f;
+		v[15] = -1f; v[16] = 1f; v[17] = -1f; n[15] = 0f; n[16] = 0f; n[17] = -1f; t[14] = 1f; t[15] = 0f;
+		//right
+		v[24] = 1f; v[25] = 1f; v[26] = -1f; n[24] = 1f; n[25] = 0f; n[26] = 0f; t[16] = 1f; t[17] = 1f;
+		v[27] = 1f; v[28] = 1f; v[29] = 1f; n[27] = 1f; n[28] = 0f; n[29] = 0f; t[18] = 0f; t[19] = 1f;
+		v[30] = 1f; v[31] = -1f; v[32] = 1f; n[30] = 1f; n[31] = 0f; n[32] = 0f; t[20] = 0f; t[21] = 0f;
+		v[33] = 1f; v[34] = -1f; v[35] = -1f; n[33] = 1f; n[34] = 0f; n[35] = 0f; t[22] = 1f; t[23] = 0f;
+		//left
+		v[36] = -1f; v[37] = -1f; v[38] = 1f; n[36] = -1f; n[37] = 0f; n[38] = 0f; t[24] = 1f; t[25] = 1f;
+		v[45] = -1f; v[46] = -1f; v[47] = -1f; n[45] = -1f; n[46] = 0f; n[47] = 0f; t[26] = 0f; t[27] = 1f;
+		v[42] = -1f; v[43] = 1f; v[44] = -1f; n[42] = -1f; n[43] = 0f; n[44] = 0f; t[28] = 0f; t[29] = 0f;
+		v[39] = -1f; v[40] = 1f; v[41] = 1f; n[39] = -1f; n[40] = 0f; n[41] = 0f; t[30] = 1f; t[31] = 0f;
+		//top
+		v[48] = -1f; v[49] = 1f; v[50] = -1f; n[48] = 0f; n[49] = 1f; n[50] = 0f; t[32] = 1f; t[33] = 1f;
+		v[57] = 1f; v[58] = 1f; v[59] = -1f; n[57] = 0f; n[58] = 1f; n[59] = 0f; t[34] = 0f; t[35] = 1f;
+		v[54] = 1f; v[55] = 1f; v[56] = 1f; n[54] = 0f; n[55] = 1f; n[56] = 0f; t[36] = 0f; t[37] = 0f;
+		v[51] = -1f; v[52] = 1f; v[53] = 1f; n[51] = 0f; n[52] = 1f; n[53] = 0f; t[38] = 1f; t[39] = 0f;
+		//bottom
+		v[60] = 1f; v[61] = -1f; v[62] = 1f; n[60] = 0f; n[61] = -1f; n[62] = 0f; t[40] = 1f; t[41] = 1f;
+		v[63] = -1f; v[64] = -1f; v[65] = 1f; n[63] = 0f; n[64] = -1f; n[65] = 0f; t[42] = 0f; t[43] = 1f;
+		v[66] = -1f; v[67] = -1f; v[68] = -1f; n[66] = 0f; n[67] = -1f; n[68] = 0f; t[44] = 0f; t[45] = 0f;
+		v[69] = 1f; v[70] = -1f; v[71] = -1f; n[69] = 0f; n[70] = -1f; n[71] = 0f; t[46] = 1f; t[47] = 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();
+		FloatBuffer tb = BufferUtil.newFloatBuffer(t.length);
+		tb.put(t).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 tbd = new BufferData(tb, BufferData.DataType.FLOAT, tb.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);
+		geom.setTexCoords(new VertexArray(tbd, 2), 0);
+		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.transform2.getRotation().mul(ry);
+			this.transform1.getRotation().mul(rx);
+			this.elapsedTime = now;
+		}
+	}
+}