Commits

Michael Ludwig  committed 1972a4e

Initial upload of already existing code.

  • Participants
  • Parent commits 5188104

Comments (0)

Files changed (145)

+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="lib" path="lib/gluegen-rt.jar">
+		<attributes>
+			<attribute name="org.eclipse.jdt.launching.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY" value="Ferox-release/lib"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="lib" path="lib/jogl.jar">
+		<attributes>
+			<attribute name="org.eclipse.jdt.launching.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY" value="Ferox-release/lib"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="lib" path="lib/openmali.jar"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>Ferox-release</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>

File lib/gluegen-rt.dll

Binary file added.

File lib/gluegen-rt.jar

Binary file added.

File lib/jogl.dll

Binary file added.

File lib/jogl.jar

Binary file added.

File lib/jogl_awt.dll

Binary file added.

File lib/jogl_cg.dll

Binary file added.

File lib/libgluegen-rt.jnilib

Binary file added.

File lib/libjogl.jnilib

Binary file added.

File lib/libjogl_awt.jnilib

Binary file added.

File lib/libjogl_cg.jnilib

Binary file added.

File lib/openmali.jar

Binary file added.

File src/com/ferox/core/renderer/Bin.java

+ package com.ferox.core.renderer;
+
+
+/**
+ * A simple interface for accumulating items, used by RenderAtomBin and StateBin.
+ * 
+ * @author Michael Ludwig
+ *
+ */
+public interface Bin<T> {
+	public int capacity();
+	public int itemCount();
+	public void clear();
+	public void ensureCapacity(int size);
+	public void add(T item);
+	public void optimize();
+}

File src/com/ferox/core/renderer/FrameListener.java

+package com.ferox.core.renderer;
+
+
+/**
+ * When added to a RenderManager instance, a FrameListener will have its startFrame() method called at the
+ * start of each frame, and its endFrame() method called at the end of each frame.  You can use to modify
+ * scene and state elements before they are updated, and if you're an advanced user to interact with the 
+ * RenderContext (or possibly lower).  If manipulating openGL state directly, unexpected results could occur
+ * if those changes conflict with programmed state management.
+ * @author Michael Ludwig
+ *
+ */
+public interface FrameListener {
+	/**
+	 * Called when the given manager is ending a frame. Called after all rendering, but before any end frame
+	 * tasks are executed.
+	 */
+	public void endFrame(RenderManager manager);
+	
+	/**
+	 * Called when the given manager is starting a frame. Called before any State or SpatialTree's are updated
+	 * and before any passes are rendered. Called after any InitializationListeners just added and after any
+	 * before frame tasks.
+	 */
+	public void startFrame(RenderManager renderManager);
+}

File src/com/ferox/core/renderer/FrameStatistics.java

+package com.ferox.core.renderer;
+
+/**
+ * Holds statistics for frames and the simulation. 
+ * @author Michael Ludwig
+ *
+ */
+public class FrameStatistics {
+	private int totalSpatialAtom;
+	private int totalVertices;
+	private int totalPolygons;
+	private long duration;
+	
+	public FrameStatistics() {
+		this.reset();
+	}
+	
+	/**
+	 * The total polygons, spatial atoms, and vertices from stat to this.
+	 */
+	public void add(FrameStatistics stat) {
+		this.totalPolygons = stat.totalPolygons;
+		this.totalSpatialAtom = stat.totalSpatialAtom;
+		this.totalVertices = stat.totalVertices;
+	}
+	
+	/**
+	 * Add the given counts of atoms, vertices, and polygons to this's totals.
+	 */
+	public void add(int atomCount, int vertCount, int polyCount) {
+		this.totalPolygons += polyCount;
+		this.totalSpatialAtom += atomCount;
+		this.totalVertices += vertCount;
+	}
+	
+	/**
+	 * The duration in ns of the last frame.
+	 */
+	public long getDuration() {
+		return this.duration;
+	}
+
+	/**
+	 * The FPS of the last frame. 
+	 */
+	public float getFPS() {
+		return 1e9f / this.getDuration();
+	}
+
+	/**
+	 * The PPS of the last frame. 
+	 */
+	public float getPolygonPerSecond() {
+		return this.getFPS() * this.totalPolygons;
+	}
+
+	/**
+	 * The total polygons rendered in the last scene.
+	 */
+	public int getTotalPolygons() {
+		return this.totalPolygons;
+	}
+
+	/**
+	 * The number of objects rendered in the last scene.
+	 */
+	public int getTotalSpatialAtom() {
+		return this.totalSpatialAtom;
+	}
+
+	/**
+	 * The number of vertices rendered in the last scene.
+	 */
+	public int getTotalVertices() {
+		return this.totalVertices;
+	}
+
+	/**
+	 * Resets the values in FrameStatistics back to 0.
+	 */
+	public void reset() {
+		this.totalPolygons = 0;
+		this.totalVertices = 0;
+		this.totalSpatialAtom = 0;
+		this.duration = 0;
+	}
+
+	/**
+	 * Set the duration (in ns) of the frame.
+	 */
+	public void setDuration(long duration) {
+		this.duration = duration;
+	}
+
+	/**
+	 * Set the total number of polygons rendered.
+	 */
+	public void setTotalPolygons(int totalIndices) {
+		this.totalPolygons = totalIndices;
+	}
+	
+	/**
+	 * Set the total number of spatial atoms rendered.
+	 */
+	public void setTotalSpatialAtom(int totalSpatialAtom) {
+		this.totalSpatialAtom = totalSpatialAtom;
+	}
+	
+	/**
+	 * Set the total number of vertices rendered.
+	 */
+	public void setTotalVertices(int totalVertices) {
+		this.totalVertices = totalVertices;
+	}
+	
+	@Override
+	public String toString() {
+		return "Frame Statistics (Duration: " + (this.getDuration() / 1e9f) + " s, Polygons / sec: " + this.getPolygonPerSecond()
+			   + "\nMeshes: " + this.getTotalSpatialAtom() + ", Vertices: " + this.getTotalVertices() + ", Polygons: " + this.getTotalPolygons() + ")";
+	}
+}

File src/com/ferox/core/renderer/InitializationListener.java

+package com.ferox.core.renderer;
+
+
+/**
+ * When added to a RenderManager instance, InitializationListeners are called once each time the underlying
+ * RenderContext must re-initialize itself.  They can be useful to perform texture loads and creation, especially
+ * if the texture data isn't kept in memory.  A listener added to a RenderManager which has already been initialized 
+ * will be called once at the next frame, right after the before frame tasks are executed.
+ * @author Michael Ludwig
+ *
+ */
+public interface InitializationListener {
+	/**
+	 * Called once by a RenderManager instance once per context life cycle.  Generally, if a canvas or component is
+	 * removed from screen, that will start another context life cycle, and this method will be called again.
+	 */
+	public void onInit(RenderManager manager);
+}

File src/com/ferox/core/renderer/RenderAtom.java

+package com.ferox.core.renderer;
+
+import com.ferox.core.scene.SpatialLeaf;
+import com.ferox.core.states.StateLeaf;
+import com.ferox.core.states.StateManager;
+import com.ferox.core.states.atoms.BlendState;
+import com.ferox.core.states.atoms.VertexArray;
+import com.ferox.core.states.manager.BlendStateManager;
+import com.ferox.core.states.manager.Geometry;
+
+/**
+ * Represents something to be rendered onto the screen, given a location (SpatialLeaf) and a visual representation
+ * (StateLeaf).  Any geometry must be stored in the StateLeaf.  If there is no geometry found after the StateLeaf
+ * has merged states from its parent, this RenderAtom will not be rendered.  It is not recommended to instanciate
+ * these yourself, but instead let a SpatialLeaf manage them.
+ * 
+ * @author Michael Ludwig
+ *
+ */
+public class RenderAtom  {
+	private static int gDT = -1;
+	private static int bDT = -1;
+	
+	StateManager[] states;
+	private final SpatialLeaf spatialLink;
+	private StateLeaf stateLink;
+	int stateSortedIndex;
+	
+	/**
+	 * Constructs a RenderAtom with the given Spatial and State leaves.  A RenderAtom can't have a null
+	 * SpatialLeaf, and its linked SpatialLeaf is final.
+	 * @param link
+	 * @param stateLink
+	 */
+	public RenderAtom(SpatialLeaf link, StateLeaf stateLink) {
+		if (link == null)
+			throw new NullPointerException("Can't have a null spatial link");
+		
+		this.spatialLink = link;
+		this.stateLink = stateLink;
+	}
+	
+	/**
+	 * Return the SpatialLeaf that represents this atom's location.
+	 */
+	public SpatialLeaf getSpatialLink() {
+		return this.spatialLink;
+	}
+	
+	/**
+	 * Return the StateLeaf that represents this atom's visual appearance, it can be null.
+	 */
+	public StateLeaf getStateLink() {
+		return this.stateLink;
+	}
+	
+	/**
+	 * Set this atom's visual appearance.  If link is null, or if link doesn't have a Geometry instance 
+	 * in its merged states, then this atom will not be rendered.
+	 */
+	public void setStateLink(StateLeaf link) {
+		this.stateLink = link;
+	}
+	
+	/**
+	 * Get a cached representation of this atom's StateLeaf's merged states.  This may be null or invalid
+	 * if accessed outside of a RenderAtomBin rendering operation.  You should not modify the contents
+	 * of this array directly, instead use StateAtom and StateManager masks.
+	 */
+	public StateManager[] getCachedStates() {
+		return this.states;
+	}
+	
+	/**
+	 * Return the Geometry instance in this atom's linked StateLeaf.  Null is returned if it can't be 
+	 * accessed for any reason.
+	 */
+	public Geometry getGeometry() {
+		if (this.stateLink == null)
+			return null;
+		if (gDT < 0)
+			gDT = RenderContext.registerStateAtomType(VertexArray.class);
+		StateManager[] s = this.stateLink.getMergedStates();
+		if (s == null || gDT >= s.length)
+			return null;
+		return (Geometry)s[gDT];
+	}
+	
+	/**
+	 * Whether or not this atom has a non-null BlendState present in its StateLeaf that has blending enabled.
+	 * If this is true, then this atom is subject to a more rigorous and expensive rendering algorithm to 
+	 * increase the chances of a visually correct rendering (see TransparentAtomBin).
+	 */
+	public boolean isTransparent() {
+		if (this.stateLink == null)
+			return false;
+		if (bDT < 0)
+			bDT = RenderContext.registerStateAtomType(BlendState.class);
+		StateManager[] sm = this.stateLink.getMergedStates();
+		if (bDT >= sm.length || sm[bDT] == null)
+			return false;
+		BlendState s = ((BlendStateManager)sm[bDT]).getStateAtom();
+		return s != null && s.isBlendEnabled();
+	}
+	
+	/**
+	 * Don't call directly, should be used by RenderAtomBin (and subclasses) to correctly apply and restore
+	 * states given the previously rendered atom and the upcoming atom.  This method obeys the state manager
+	 * masking in pass.
+	 */
+	public static void applyStates(RenderAtom prev, RenderAtom next, RenderManager manager, RenderPass pass) {
+		StateManager[] p = (prev != null ? prev.states : null);
+		StateManager[] n = (next != null ? next.states : null);
+		
+		if (n != null) {
+			for (int i = 0; i < n.length; i++) {
+				if (n[i] != null && !pass.isStateManagerMasked(i)) {
+					n[i].apply(manager, pass);
+				}
+			}
+		}
+		if (p != null) {
+			for (int i = 0; i < p.length; i++) {
+				if (p[i] != null && (n == null || i >= n.length || n[i] == null) && !pass.isStateManagerMasked(i)) {
+					p[i].restore(manager, pass);
+				}
+			}
+		}
+	}
+}

File src/com/ferox/core/renderer/RenderAtomBin.java

+package com.ferox.core.renderer;
+
+import java.util.ArrayList;
+
+import com.ferox.core.scene.InfluenceLeaf;
+import com.ferox.core.states.*;
+import com.ferox.core.states.atoms.AlphaState;
+import com.ferox.core.states.atoms.BlendState;
+import com.ferox.core.states.atoms.DrawMode;
+import com.ferox.core.states.atoms.ZBuffer;
+import com.ferox.core.states.atoms.BlendState.BlendFactor;
+import com.ferox.core.states.atoms.BlendState.BlendFunction;
+import com.ferox.core.states.atoms.DrawMode.DrawFace;
+import com.ferox.core.states.atoms.DrawMode.DrawStyle;
+import com.ferox.core.states.atoms.DrawMode.Winding;
+import com.ferox.core.states.manager.Geometry;
+
+/**
+ * A Bin implementation that is used to render all RenderAtoms present in a given view of a scene.  It is recommended
+ * to only subclass this class when necessary or to use a custom transparent queue.  It is backed by an array.
+ * For subclasses, the array is always 2 greater than the capacity of the bin because there is a null element in the
+ * first index and the index after the last valid RenderAtom.  This is to make iterating and apply states easier
+ * since you can use array access to always get an appropriate previous or next atom.
+ * 
+ * @author Michael Ludwig
+ *
+ */
+public class RenderAtomBin implements Bin<RenderAtom> {	
+	private static ZBuffer defaultZ = new ZBuffer();
+	private static DrawMode defaultDraw = new DrawMode();
+	private static AlphaState defaultAlpha = new AlphaState();
+	private static BlendState defaultBlend = new BlendState();
+	
+	private static final int DEFAULT_INCREMENT = 10;
+	
+	private RenderAtomBin transparent;
+	private ArrayList<InfluenceLeaf> influences;
+	protected RenderAtom[] bin;
+	protected int count;
+		
+	/**
+	 * Creates a queue with the given capacity.  You shouldn't need to instantiate these
+	 * directly, instead let SpatialTree take care of it.
+	 */
+	public RenderAtomBin(int startingSize) {
+		this(startingSize, false);
+	}
+	
+	/**
+	 * transparent should be true if this bin is intended for use as a transparent RenderAtom bin
+	 */
+	protected RenderAtomBin(int startingSize, boolean transparent) {
+		this.count = 0;
+		this.bin = new RenderAtom[startingSize + 2];
+		this.influences = new ArrayList<InfluenceLeaf>();
+		if (!transparent)
+			this.transparent = new TransparentAtomBin(startingSize);
+	}
+	
+	/**
+	 * The capacity of the RenderQueue, if more atoms are added beyond this, the bin's backing array will grow.
+	 */
+	public int capacity() {
+		return this.bin.length - 2;
+	}
+	
+	/**
+	 * Current number of atoms in the bin.
+	 */
+	public int itemCount() {
+		return this.count;
+	}
+	
+	/**
+	 * Must be called between renderings of a scene if the view has changed.
+	 */
+	public void clear() {
+		this.count = 0;
+		this.influences.clear();
+		if (this.transparent != null)
+			this.transparent.clear();
+	}
+	
+	public void ensureCapacity(int size) {
+		if (this.transparent != null) {
+			this.transparent.ensureCapacity(size);					                			         
+		}
+		if (size + 2 > this.bin.length) {
+			RenderAtom[] temp = new RenderAtom[size + 2];
+			if (this.bin != null)
+				System.arraycopy(this.bin, 1, temp, 1, Math.min(this.bin.length - 2, size));
+			this.bin = temp;
+		} 
+	}
+	
+	/**
+	 * Don't call directly, instead use addRenderAtom(), which handles statistics tracking and 
+	 * transparent render atoms.
+	 */
+	public void add(RenderAtom item) {
+		if (this.count == this.bin.length - 2) {
+			this.ensureCapacity(this.count + DEFAULT_INCREMENT);
+		}
+
+		this.bin[1 + this.count] = item;
+		this.count++;
+	}
+	
+	/**
+	 * Adds the atom to the queue, growing the queue if needed and updates the FrameStatistics for
+	 * the total scene.  It doesn't check for duplicate atoms, if the same atom is added (won't happen
+	 * if the bin is properly cleared between renderings of a scene and if you allow the SpatialTree to
+	 * handle everything), results are undefined (but most likely result in a double render).
+	 */
+	public void addRenderAtom(RenderAtom atom, RenderManager manager) {
+		if (atom.getStateLink() == null)
+			return;
+		Geometry g = atom.getGeometry();
+		if (g == null)
+			return;
+		
+		if (this.transparent != null && atom.isTransparent()) {
+			this.transparent.addRenderAtom(atom, manager);
+		} else {
+			this.add(atom);
+			atom.stateSortedIndex = atom.getStateLink().getSortIndex();
+			atom.states = atom.getStateLink().getMergedStates();
+			
+			this.addStatistics(g, manager);
+		}
+	}
+	
+	/**
+	 * Utility method for subclasses to update the given RenderManager's statistics.
+	 */
+	protected final void addStatistics(Geometry g, RenderManager manager) {
+		manager.getFrameStatistics().add(1, g.getVertices().getNumElements(), g.getPolygonCount());
+	}
+	
+	/**
+	 * Add an InfluenceLeaf.  Should not be called directly, it is called by InfluenceLeaves that intersect
+	 * the viewing frustum.  An added InfluenceLeaf will apply its attached state to all influenced atoms that
+	 * will be rendered.
+	 */
+	public void addInfluenceLeaf(InfluenceLeaf leaf) {
+		if (leaf.getState() == null)
+			return;
+		
+		this.influences.add(leaf);
+		if (this.transparent != null)
+			this.transparent.addInfluenceLeaf(leaf);
+	}
+	
+	/**
+	 * Renders the bin with all of the atoms currently inside it, this should not be called
+	 * directly by the program, but instead by RenderPass.
+	 */
+	public void renderAtoms(RenderManager manager, RenderPass pass) {
+		if (this.count > 0) {
+			this.bin[0] = null;
+			this.bin[1 + this.count] = null;
+			
+			defaultZ.setZBufferWriteEnabled(true);
+			defaultZ.setDepthTest(FragmentTest.LEQUAL);
+			
+			defaultDraw.setWinding(Winding.COUNTER_CLOCKWISE);
+			defaultDraw.setBackMode(DrawStyle.FILLED);
+			defaultDraw.setFrontMode(DrawStyle.FILLED);
+			defaultDraw.setDrawFace(DrawFace.FRONT);
+			
+			defaultAlpha.setAlphaEnabled(false);
+			defaultAlpha.setAlphaRefValue(1f);
+			defaultAlpha.setAlphaTest(FragmentTest.GEQUAL);
+			
+			defaultBlend.setBlendEnabled(false);
+			defaultBlend.setBlendFunction(BlendFunction.ADD);
+			defaultBlend.setSourceBlendFactor(BlendFactor.SRC_ALPHA);
+			defaultBlend.setDestBlendFactor(BlendFactor.ONE_MINUS_SRC_ALPHA);
+			
+			this.renderAll(manager, pass, pass.getRenderAtomMask(), defaultZ, defaultDraw, defaultAlpha, defaultBlend);
+		}
+		
+		if (this.transparent != null) {
+			this.transparent.optimize();
+			this.transparent.renderAtoms(manager, pass);
+		}
+	}
+	
+	/**
+	 * Subclasses can override this method to provide more unique rendering of atoms.  They must obey pass's 
+	 * RenderAtomMask and they must apply and restore the default states passed in.  They are allowed to modify
+	 * the values of the default states to get desired affects (each frame the defaults are restored).
+	 */
+	protected void renderAll(RenderManager manager, RenderPass pass, RenderAtomMask mask, ZBuffer zbuff, DrawMode draw, AlphaState alpha, BlendState blend) {
+		RenderAtom prev = null;
+		
+		zbuff.applyState(manager, NullUnit.get());
+		draw.applyState(manager, NullUnit.get());
+		alpha.applyState(manager, NullUnit.get());
+		blend.applyState(manager, NullUnit.get());
+		RenderAtom curr;
+		RenderContext context = manager.getRenderContext();
+		
+		for (int i = 1; i < this.count + 1; i++) {
+			curr = this.bin[i];
+			if (mask == null || mask.isValidForRender(curr, manager, pass)) {
+				this.renderAtom(prev, curr, context, manager, pass);
+				prev = curr;
+			}
+		}
+		context.clearSpatialStates();
+		RenderAtom.applyStates(prev, null, manager, pass);
+		
+		zbuff.restoreState(manager, NullUnit.get());
+		draw.restoreState(manager, NullUnit.get());
+		alpha.restoreState(manager, NullUnit.get());
+		blend.restoreState(manager, NullUnit.get());
+	}
+	
+	/**
+	 * Utility method to render curr, given that prev was the last rendered atom.  This method computes the 
+	 * correct influences and applies any states and renders the geomtry to the context.
+	 */
+	protected final void renderAtom(RenderAtom prev, RenderAtom curr, RenderContext context, RenderManager manager, RenderPass pass) {
+		context.beginAtom(curr);
+		RenderAtom.applyStates(prev, curr, manager, pass);
+		
+		int s = this.influences.size();
+		InfluenceLeaf l;
+		for (int u = 0; u < s; u++) {
+			l = this.influences.get(u);
+			if (l.influencesSpatialLeaf(curr.getSpatialLink()))
+				context.addSpatialState(l.getState());
+		}
+		
+		context.endAtom(curr);
+	}
+	
+	/**
+	 * Re-orders the bin using the stateSortedIndex of the atom's attached StateLeaf.  You will get inefficient results
+	 * if the atoms in the bin have state leaves from multiply trees or if the tree's update() method isn't called
+	 * each frame.
+	 */
+	public void optimize() {
+		if (this.count > 1) {
+			quickSort(this.bin, 1, this.count);
+		}
+	}
+	
+	private static void quickSort(RenderAtom[] x, int off, int len) {
+		int m = off + (len >> 1);
+		if (len > 7) {
+			int l = off;
+			int n = off + len - 1;
+			if (len > 40) {
+				int s = len / 8;
+				l = med3(x, l, l+s, l+2*s);
+				m = med3(x, m-s, m, m+s);
+				n = med3(x, n-2*s, n-s, n);
+			}
+			m = med3(x, l, m, n);
+		}
+		int v = x[m].stateSortedIndex ;
+		
+		int a = off, b = a, c = off+len-1, d = c;
+		while(true) {
+			while (b <= c && x[b].stateSortedIndex <= v) {
+				if (x[b].stateSortedIndex == v)
+					swap(x, a++, b);
+				b++;
+			}
+			while (c >= b&& x[c].stateSortedIndex >=v) {
+				if (x[c].stateSortedIndex  == v)
+					swap(x, c, d--);
+				c--;
+			}
+			if (b > c)
+				break;
+			swap(x, b++, c--);
+		}
+		
+		int s, n = off + len;
+		s = Math.min(a-off, b-a); vecswap(x, off, b-s, s);
+		s = Math.min(d-c, n-d-1); vecswap(x, b, n-s,s);
+		
+		if ((s = b-a) > 1)
+			quickSort(x, off, s);
+		if ((s = d-c) > 1)
+			quickSort(x, n-s, s);
+	}
+	
+	private static void vecswap(RenderAtom x[], int a, int b, int n) {
+		for (int i = 0; i < n; i++, a++, b++)
+			swap(x, a, b);
+	}
+	
+	private static void swap(RenderAtom[] x, int a, int b) {
+		RenderAtom t = x[a];
+		x[a] = x[b];
+		x[b] = t;
+	}
+	
+	private static int med3(RenderAtom[] x, int a, int b, int c) {
+		return (x[a].stateSortedIndex < x[b].stateSortedIndex  ? (x[b].stateSortedIndex  < x[c].stateSortedIndex  ? b: x[a].stateSortedIndex  < x[c].stateSortedIndex  ? c: a) : (x[b].stateSortedIndex  > x[c].stateSortedIndex  ? b: x[a].stateSortedIndex  > x[c].stateSortedIndex  ? c : a));
+	}
+}

File src/com/ferox/core/renderer/RenderAtomMask.java

+package com.ferox.core.renderer;
+
+/**
+ * A callback mechanism that can determine if at the last minute a RenderAtom should be rendered or not.
+ * If a non-null mask is set on a RenderPass then that mask will have its method called once for each atom that
+ * was added to the scene's render atom bin for the pass rendering.  For visually appealing results, don't alternate
+ * return values for the same atom across subsequent frames.
+ * @author Michael Ludwig
+ *
+ */
+public interface RenderAtomMask {
+	/**
+	 * Return true if the given atom should be rendered, false otherwise.
+	 */
+	public boolean isValidForRender(RenderAtom atom, RenderManager manager, RenderPass pass);
+}

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

+package com.ferox.core.renderer;
+
+import java.nio.*;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+
+import com.ferox.core.scene.Plane;
+import com.ferox.core.scene.SpatialState;
+import com.ferox.core.scene.Transform;
+import com.ferox.core.scene.View;
+import com.ferox.core.states.*;
+import com.ferox.core.states.atoms.*;
+import com.ferox.core.states.atoms.BufferData.BufferTarget;
+import com.ferox.core.states.atoms.BufferData.DataType;
+import com.ferox.core.states.atoms.TextureCubeMap.Face;
+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.system.RenderSurface;
+import com.ferox.core.system.SystemCapabilities;
+import com.ferox.core.util.FeroxException;
+import com.ferox.core.util.DataTransfer.Block;
+import com.ferox.core.util.DataTransfer.Slice;
+import com.ferox.core.system.DisplayOptions;
+
+/**
+ * RenderContext is an abstract class that provides opengl specific code for rendering a scene.  A new type
+ * of RenderContext (and set of StateAtomPeers) must be implemented for a desired GL implementation, such as
+ * JOGL or LWJGL.  RenderContext provides a number of state management and record keeping utilities that should
+ * not be overridden (unless absolutely necessary).  Besides record keeping, it also provides methods to
+ * fetch and set data resident on the graphics card.
+ * 
+ * @author Michael Ludwig
+ *
+ */
+public abstract class RenderContext {	
+	private static class StateAtomTracker {
+		private StateAtom[] stateRecord;
+	}
+	
+	private static class DynamicState {
+		SpatialState state;
+		float influence;
+	}
+	
+	private static class DynamicUnit {
+		SpatialState next;
+		SpatialState previous;
+		StateUnit unit;
+	}
+	
+	private static class DynamicStateTracker {
+		private static Comparator<DynamicState> influenceSorter = new Comparator<DynamicState>() {
+			public int compare(DynamicState ds1, DynamicState ds2) {
+				float diff = ds2.influence - ds1.influence;
+				if (diff < 0)
+					return -1;
+				else if (diff > 0)
+					return 1;
+				else
+					return 0;
+			}
+		};
+		DynamicState[] states;
+		DynamicUnit[] units;
+		int count;
+		
+		public DynamicStateTracker(StateUnit[] units) {
+			this.states = new DynamicState[units.length];
+			this.units = new DynamicUnit[units.length];
+			for (int i = 0; i < this.units.length; i++) {
+				this.units[i] = new DynamicUnit();
+				this.units[i].unit = units[i];
+				
+				this.states[i] = new DynamicState();
+			}
+		}
+		
+		public void add(SpatialState state, float influence) {
+			if (this.count + 1 > this.states.length) {
+				DynamicState[] temp = new DynamicState[this.states.length + 1];
+				System.arraycopy(this.states, 0, temp, 0, this.states.length);
+				temp[temp.length - 1] = new DynamicState();
+				this.states = temp;
+			}
+			this.states[this.count].state = state;
+			this.states[this.count].influence = influence;
+			this.count++;
+		}
+		
+		public void apply(RenderManager manager) {
+			if (this.count > this.units.length) 
+				Arrays.sort(this.states, 0, this.count, influenceSorter);
+			for (int i = 0; i < this.count; i++) {
+				for (int u = 0; u < this.units.length; u++) {
+					if (this.units[u].previous == this.states[i].state) {
+						this.units[u].next = this.states[i].state;
+						this.states[i].state = null;
+						break;
+					}
+				}
+			}
+			for (int i = 0; i < this.count; i++) {
+				if (this.states[i].state != null) {
+					for (int u = 0; u < this.units.length; u++) {
+						if (this.units[u].next == null) {
+							this.units[u].next = this.states[i].state;
+							this.states[i].state = null;
+							break;
+						}
+					}
+				}
+			}
+			for (int u = 0; u < this.units.length; u++) {
+				if (this.units[u].next != null && this.units[u].previous != this.units[u].next)
+					this.units[u].next.applyState(manager, this.units[u].unit);
+				else if (this.units[u].next == null && this.units[u].previous != null)
+					this.units[u].previous.restoreState(manager, this.units[u].unit);
+				this.units[u].previous = this.units[u].next;
+				this.units[u].next = null;
+			}
+		}
+		
+		public void restore(RenderManager manager) {
+			for (int u = 0; u < this.units.length; u++) {
+				if (this.units[u].previous != null)
+					this.units[u].previous.restoreState(manager, this.units[u].unit);
+				this.units[u].previous = null;
+				this.units[u].next = null;
+			}
+		}
+	}
+	
+	private static HashMap<Class<? extends StateAtom>, Integer> atomTypes = new HashMap<Class<? extends StateAtom>, Integer>();
+	private static int aTypeCounter = 0;
+	
+	RenderManager manager;
+	
+	private RenderPassPeer<RenderPass> defaultPass; 
+	// TODO: add render to texture render passes
+	
+	private StateManager[] stateRecord;
+	private StateAtomTracker[] atomRecord;
+	
+	private DynamicStateTracker[] dynamicRecord;
+	private RenderAtom currAtom;
+	
+	public static int registerStateAtomType(Class<? extends StateAtom> m) {
+		if (atomTypes.containsKey(m))
+			return atomTypes.get(m);
+		atomTypes.put(m, aTypeCounter);
+		aTypeCounter++;
+		return aTypeCounter - 1;
+	}
+	
+	/**
+	 * Create a RenderContext with the requested DisplayOptions.  This constructor doesn't actually use options,
+	 * it is here to force subclasses to take DisplayOptions as arguments.  Subclasses must do there best to 
+	 * satisfy the requested options.  They are also responsible for instantiating the correct implementation of 
+	 * a RenderSurface.
+	 * @param options
+	 */
+	public RenderContext(DisplayOptions options) {
+		this.defaultPass = this.createDefaultRenderPassPeer();
+	}
+	
+	/**
+	 * Get the RenderSurface that rendering goes to
+	 */
+	public abstract RenderSurface getRenderSurface();
+	
+	/**
+	 * Set the projection matrix and the view matrix so that when pushModelTransform is used, it will
+	 * correctly place and project objects onto the screen.
+	 */
+	public abstract void setProjectionViewTransform(View view);
+	/**
+	 * Set the viewport to the relative locations, left right top and bottom have the same definition as the
+	 * values in View.
+	 */
+	public abstract void setViewport(float left, float right, float top, float bottom);
+	
+	/**
+	 * Push a world space model transform onto the matrix stack.
+	 */
+	public abstract void pushModelTransform(Transform trans);
+	/**
+	 * Pop the last pushed model transform off the matrix stack.  Must leave the projection and view matrices intact.
+	 */
+	public abstract void popModelTransform();
+	
+	/**
+	 * Render the given geometry with any previously applied state and transform.  It can be assumed that the
+	 * Geometry will have any necessary state set and enabled (such as vbos) before the call to this method.
+	 */
+	public abstract void renderGeometry(Geometry geom);
+	
+	/**
+	 * Set the given custom clip plane, i must be greater than or equal to 0.  It is not recommended to use these
+	 * because TransparentAtomBin reserves the right to use planes 0 and 1.  The clip plane will have no effect until
+	 * it is enabled.
+	 */
+	public abstract void setUserClipPlane(Plane plane, int i);
+	/**
+	 * Enable the given custom clip plane.
+	 */
+	public abstract void enableUserClipPlane(int i);
+	/**
+	 * Disable the given custom clip plane.
+	 */
+	public abstract void disableUserClipPlane(int i);
+	
+	/**
+	 * Get the width, in pixels, of the rendering surface or context/canvas.
+	 */
+	public abstract int getContextWidth();
+	/**
+	 * Get the height, in pixels, of the rendering surface or context/canvas.
+	 */
+	public abstract int getContextHeight();
+	
+	/**
+	 * Clear the buffers of the currently enabled draw buffers.  Only clear a buffer if its boolean is true, if 
+	 * cleared the buffer should be cleared to the given value.  If an FBO is attached, it should clear those 
+	 * buffers instead of the standard back buffer.
+	 */
+	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.
+	 */
+	public void beginAtom(RenderAtom atom) {
+		if (this.currAtom != null)
+			throw new FeroxException("Can't call beginAtom() after a previous beginAtom() call if endAtom() hasn't been called");
+		this.currAtom = atom;
+		if (this.dynamicRecord != null)
+			for (int i = 0; i < this.dynamicRecord.length; i++)
+				if (this.dynamicRecord[i] != null)
+					this.dynamicRecord[i].count = 0;
+	}
+	
+	/**
+	 * Method to signal the end of a RenderAtom.  At the moment, it applies any influenced spatial states,
+	 * pushes the atom's world transform, renders its geometry, and pops the world transform back off.  
+	 * The rest of the atom's states should have already been applied by the RenderAtomBin.
+	 */
+	public void endAtom(RenderAtom atom) {
+		if (this.dynamicRecord != null)
+			for (int i = 0; i < this.dynamicRecord.length; i++)
+				if (this.dynamicRecord[i] != null)
+					this.dynamicRecord[i].apply(this.manager);
+		this.pushModelTransform(atom.getSpatialLink().getWorldTransform());
+		this.renderGeometry(atom.getGeometry());
+		this.popModelTransform();
+	}
+	
+	/**
+	 * Clear the spatial state record, should be called once at the end of a RenderAtomBin's rendering to reset
+	 * it for subsequent passes or frames.
+	 */
+	public void clearSpatialStates() {
+		if (this.dynamicRecord != null)
+			for (int i = 0; i < this.dynamicRecord.length; i++)
+				if (this.dynamicRecord[i] != null)
+					this.dynamicRecord[i].restore(this.manager);
+	}
+	
+	/**
+	 * Add the given spatial state to the current RenderAtom.  When rendered, the atom will be under
+	 * its influence.
+	 */
+	public void addSpatialState(SpatialState state) {
+		int type = state.getDynamicType();
+		if (this.dynamicRecord == null || type >= this.dynamicRecord.length) {
+			DynamicStateTracker[] temp = new DynamicStateTracker[type + 1];
+			if (this.dynamicRecord != null)
+				System.arraycopy(this.dynamicRecord, 0, temp, 0, this.dynamicRecord.length);
+			this.dynamicRecord = temp;
+		}
+		
+		DynamicStateTracker record = this.dynamicRecord[type];
+		if (record == null) {
+			record = new DynamicStateTracker(state.availableUnits());
+			this.dynamicRecord[type] = record;
+		}
+		
+		record.add(state, state.getInfluence(this.currAtom.getSpatialLink()));
+	}
+	
+	/**
+	 * Get the RenderPassPeer impl for the default render pass.
+	 */
+	public abstract RenderPassPeer<RenderPass> createDefaultRenderPassPeer();
+	//public RenderPassImpl<T> getRenderToTextureImpl();
+	
+	/**
+	 * Get the StateAtomPeer implementation instance for the given class of StateAtom and this context
+	 */
+	public abstract StateAtomPeer getStateAtomPeer(Class<? extends StateAtom> type);
+	/**
+	 * Get the StateAtomPeer implementation instance for the given StateAtom, should return the same object
+	 * as getStateAtomPeer(atom.getAtomType()).
+	 */
+	public abstract StateAtomPeer getStateAtomPeer(StateAtom atom);
+
+	public void setTextureRegion(Texture2D data, Block region, int level, Buffer in, Slice slice) {
+		validateAll(data.getDataType(), data.getDataFormat(), data.getNumMipmaps(),
+					data.getWidth(level), data.getHeight(level), 1, 
+					region.getXOffset(), region.getYOffset(), 0, region.getWidth(), region.getHeight(), 1, level,
+					in.capacity(), getBufferDataType(in), slice);
+		TextureData prev = (TextureData)this.push(data, NumericUnit.get(0), TextureData.class);
+		this.setTextureData(data, region, null, level, in, slice);
+		this.pop(data, prev, NumericUnit.get(0));
+	}
+	public void setTextureRegion(Texture2D data, Block region, int level, BufferData in, Slice slice) {
+		validateAll(data.getDataType(), data.getDataFormat(), data.getNumMipmaps(),
+					data.getWidth(level), data.getHeight(level), 1, 
+					region.getXOffset(), region.getYOffset(), 0, region.getWidth(), region.getHeight(), 1, level,
+					in.getCapacity(), in.getDataType(), slice);
+		TextureData prev = (TextureData)this.push(data, NumericUnit.get(0), TextureData.class);
+		BufferData pBD = (BufferData)this.push(in, BufferTarget.PIXEL_WRITE_BUFFER, BufferData.class);
+		if (in.isVBO() && RenderManager.getSystemCapabilities().arePixelBuffersSupported())
+			this.setTextureData(data, region, null, level, null, slice);
+		else if (in.getData() != null)
+			this.setTextureData(data, region, null, level, in.getData(), slice);
+		this.pop(in, pBD, BufferTarget.PIXEL_WRITE_BUFFER);
+		this.pop(data, prev, NumericUnit.get(0));
+	}
+	
+	public void setTextureRegion(Texture3D data, Block region, int level, Buffer in, Slice slice) {
+		validateAll(data.getDataType(), data.getDataFormat(), data.getNumMipmaps(),
+					data.getWidth(level), data.getHeight(level), data.getDepth(level), 
+					region.getXOffset(), region.getYOffset(), region.getZOffset(), region.getWidth(), region.getHeight(), region.getDepth(), level,
+					in.capacity(), getBufferDataType(in), slice);
+		TextureData prev = (TextureData)this.push(data, NumericUnit.get(0), TextureData.class);
+		this.setTextureData(data, region, null, level, in, slice);
+		this.pop(data, prev, NumericUnit.get(0));
+	}
+	public void setTextureRegion(Texture3D data, Block region, int level, BufferData in, Slice slice) {
+		validateAll(data.getDataType(), data.getDataFormat(), data.getNumMipmaps(),
+					data.getWidth(level), data.getHeight(level), data.getDepth(level), 
+					region.getXOffset(), region.getYOffset(), region.getZOffset(), region.getWidth(), region.getHeight(), region.getDepth(), level,
+					in.getCapacity(), in.getDataType(), slice);
+		TextureData prev = (TextureData)this.push(data, NumericUnit.get(0), TextureData.class);
+		BufferData pBD = (BufferData)this.push(in, BufferTarget.PIXEL_WRITE_BUFFER, BufferData.class);
+		if (in.isVBO() && RenderManager.getSystemCapabilities().arePixelBuffersSupported())
+			this.setTextureData(data, region, null, level, null, slice);
+		else
+			throw new NullPointerException("Can't set data from a non-vbo buffer data that has a null backing buffer");
+		this.pop(in, pBD, BufferTarget.PIXEL_WRITE_BUFFER);
+		this.pop(data, prev, NumericUnit.get(0));
+	}
+	
+	public void setTextureRegion(TextureCubeMap data, Block region, Face face, int level, Buffer in, Slice slice) {
+		validateAll(data.getDataType(), data.getDataFormat(), data.getNumMipmaps(),
+					data.getSideLength(level), data.getSideLength(level), 1, 
+					region.getXOffset(), region.getYOffset(), 0, region.getWidth(), region.getHeight(), 1, level,
+					in.capacity(), getBufferDataType(in), slice);
+		TextureData prev = (TextureData)this.push(data, NumericUnit.get(0), TextureData.class);
+		this.setTextureData(data, region, face, level, in, slice);
+		this.pop(data, prev, NumericUnit.get(0));
+	}
+	public void setTextureRegion(TextureCubeMap data, Block region, Face face, int level, BufferData in, Slice slice) {
+		validateAll(data.getDataType(), data.getDataFormat(), data.getNumMipmaps(),
+					data.getSideLength(level), data.getSideLength(level), 1, 
+					region.getXOffset(), region.getYOffset(), 0, region.getWidth(), region.getHeight(), 1, level,
+					in.getCapacity(), in.getDataType(), slice);
+		TextureData prev = (TextureData)this.push(data, NumericUnit.get(0), TextureData.class);
+		BufferData pBD = (BufferData)this.push(in, BufferTarget.PIXEL_WRITE_BUFFER, BufferData.class);
+		if (in.isVBO() && RenderManager.getSystemCapabilities().arePixelBuffersSupported())
+			this.setTextureData(data, region, face, level, null, slice);
+		else if (in.getData() != null)
+			this.setTextureData(data, region, face, level, in.getData(), slice);
+		this.pop(in, pBD, BufferTarget.PIXEL_WRITE_BUFFER);
+		this.pop(data, prev, NumericUnit.get(0));
+	}
+	
+	protected abstract void setTextureData(TextureData data, Block region, Face face, int level, Buffer in, Slice slice);
+	
+	public void getTexture(Texture2D data, int level, Buffer out, Slice slice) {
+		TextureFormat f = getServerCompressedFormat(data.getDataFormat());
+		TextureType t = (f.isServerCompressed() ? TextureType.UNSIGNED_BYTE : data.getDataType());
+		validateAll(t, f, data.getNumMipmaps(),
+					data.getWidth(level), data.getHeight(level), 1,
+					0, 0, 0, data.getWidth(level), data.getHeight(level), 1, level,
+					out.capacity(), getBufferDataType(out), slice);
+		TextureData prev = (TextureData)this.push(data, NumericUnit.get(0), TextureData.class);
+		this.getTextureData(data, null, level, out, slice);
+		this.pop(data, prev, NumericUnit.get(0));
+	}
+	public void getTexture(Texture2D data, int level, BufferData out, Slice slice) {
+		TextureFormat f = getServerCompressedFormat(data.getDataFormat());
+		TextureType t = (f.isServerCompressed() ? TextureType.UNSIGNED_BYTE : data.getDataType());
+		validateAll(t, f, data.getNumMipmaps(),
+					data.getWidth(level), data.getHeight(level), 1,
+					0, 0, 0, data.getWidth(level), data.getHeight(level), 1, level,
+					out.getCapacity(), out.getDataType(), slice);
+		TextureData prev = (TextureData)this.push(data, NumericUnit.get(0), TextureData.class);
+		BufferData pBD = (BufferData)this.push(out, BufferTarget.PIXEL_READ_BUFFER, BufferData.class);
+		if (out.isVBO() && RenderManager.getSystemCapabilities().arePixelBuffersSupported())
+			this.getTextureData(data, null, level, null, slice);
+		else if (out.getData() != null)
+			this.getTextureData(data, null, level, out.getData(), slice);
+		this.pop(out, pBD, BufferTarget.PIXEL_READ_BUFFER);
+		this.pop(data, prev, NumericUnit.get(0));
+	}
+	
+	public void getTexture(Texture3D data, int level, Buffer out, Slice slice) {
+		TextureFormat f = getServerCompressedFormat(data.getDataFormat());
+		TextureType t = (f.isServerCompressed() ? TextureType.UNSIGNED_BYTE : data.getDataType());
+		validateAll(t, f, data.getNumMipmaps(),
+					data.getWidth(level), data.getHeight(level), data.getDepth(level),
+					0, 0, 0, data.getWidth(level), data.getHeight(level), data.getDepth(level), level,
+					out.capacity(), getBufferDataType(out), slice);
+		TextureData prev = (TextureData)this.push(data, NumericUnit.get(0), TextureData.class);
+		this.getTextureData(data, null, level, out, slice);
+		this.pop(data, prev, NumericUnit.get(0));
+	}
+	public void getTexture(Texture3D data, int level, BufferData out, Slice slice) {
+		TextureFormat f = getServerCompressedFormat(data.getDataFormat());
+		TextureType t = (f.isServerCompressed() ? TextureType.UNSIGNED_BYTE : data.getDataType());
+		validateAll(t, f, data.getNumMipmaps(),
+					data.getWidth(level), data.getHeight(level), data.getDepth(level),
+					0, 0, 0, data.getWidth(level), data.getHeight(level), data.getDepth(level), level,
+					out.getCapacity(), out.getDataType(), slice);
+		TextureData prev = (TextureData)this.push(data, NumericUnit.get(0), TextureData.class);
+		BufferData pBD = (BufferData)this.push(out, BufferTarget.PIXEL_READ_BUFFER, BufferData.class);
+		if (out.isVBO() && RenderManager.getSystemCapabilities().arePixelBuffersSupported())
+			this.getTextureData(data, null, level, null, slice);
+		else if (out.getData() != null)
+			this.getTextureData(data, null, level, out.getData(), slice);
+		this.pop(out, pBD, BufferTarget.PIXEL_READ_BUFFER);
+		this.pop(data, prev, NumericUnit.get(0));
+	}
+	public void getTexture(TextureCubeMap data, Face face, int level, Buffer out, Slice slice) {
+		TextureFormat f = getServerCompressedFormat(data.getDataFormat());
+		TextureType t = (f.isServerCompressed() ? TextureType.UNSIGNED_BYTE : data.getDataType());
+		validateAll(t, f, data.getNumMipmaps(),
+					data.getSideLength(level), data.getSideLength(level), 1,
+					0, 0, 0, data.getSideLength(level), data.getSideLength(level), 1, level,
+					out.capacity(), getBufferDataType(out), slice);
+		TextureData prev = (TextureData)this.push(data, NumericUnit.get(0), TextureData.class);
+		this.getTextureData(data, face, level, out, slice);
+		this.pop(data, prev, NumericUnit.get(0));
+	}
+	public void getTexture(TextureCubeMap data, Face face, int level, BufferData out, Slice slice) {
+		TextureFormat f = getServerCompressedFormat(data.getDataFormat());
+		TextureType t = (f.isServerCompressed() ? TextureType.UNSIGNED_BYTE : data.getDataType());
+		validateAll(t, f, data.getNumMipmaps(),
+					data.getSideLength(level), data.getSideLength(level), 1,
+					0, 0, 0, data.getSideLength(level), data.getSideLength(level), 1, level,
+					out.getCapacity(), out.getDataType(), slice);
+		TextureData prev = (TextureData)this.push(data, NumericUnit.get(0), TextureData.class);
+		BufferData pBD = (BufferData)this.push(out, BufferTarget.PIXEL_READ_BUFFER, BufferData.class);
+		if (out.isVBO() && RenderManager.getSystemCapabilities().arePixelBuffersSupported())
+			this.getTextureData(data, face, level, null, slice);
+		else if (out.getData() != null)
+			this.getTextureData(data, face, level, out.getData(), slice);
+		this.pop(out, pBD, BufferTarget.PIXEL_READ_BUFFER);
+		this.pop(data, prev, NumericUnit.get(0));
+	}
+	
+	protected abstract void getTextureData(TextureData data, Face face, int level, Buffer out, Slice slice);
+	
+	public void copyFramePixels(Texture2D data, Block region, int level, int sx, int sy) {
+		if (data.getDataFormat().isServerCompressed())
+			throw new IllegalArgumentException("copyFramePixels doesn't support compressed textures");
+		validateMipmap(level, data.getNumMipmaps());
+		validateRegion(region.getXOffset(), region.getYOffset(), 0, region.getWidth(), region.getHeight(), 1, data.getWidth(level), data.getHeight(level), 1);
+		validateRegion(sx, sy, 0, region.getWidth(), region.getHeight(), 1, this.getContextWidth(), this.getContextHeight(), 1);
+		TextureData prev = (TextureData)this.push(data, NumericUnit.get(0), TextureData.class);
+		this.copyTextureData(data, region, null, level, sx, sy);
+		this.pop(data, prev, NumericUnit.get(0));
+	}
+	
+	public void copyFramePixels(Texture3D data, Block region, int level, int sx, int sy) {
+		if (data.getDataFormat().isServerCompressed())
+			throw new IllegalArgumentException("copyFramePixels doesn't support compressed textures");
+		validateMipmap(level, data.getNumMipmaps());
+		validateRegion(region.getXOffset(), region.getYOffset(), region.getZOffset(), region.getWidth(), region.getHeight(), 1, data.getWidth(level), data.getHeight(level), data.getDepth(level));
+		validateRegion(sx, sy, 0, region.getWidth(), region.getHeight(), 1, this.getContextWidth(), this.getContextHeight(), 1);
+		TextureData prev = (TextureData)this.push(data, NumericUnit.get(0), TextureData.class);
+		this.copyTextureData(data, region, null, level, sx, sy);
+		this.pop(data, prev, NumericUnit.get(0));
+	}
+	
+	public void copyFramePixels(TextureCubeMap data, Block region, int level, int sx, int sy) {
+		if (data.getDataFormat().isServerCompressed())
+			throw new IllegalArgumentException("copyFramePixels doesn't support compressed textures");
+		validateMipmap(level, data.getNumMipmaps());
+		validateRegion(region.getXOffset(), region.getYOffset(), 0, region.getWidth(), region.getHeight(), 1, data.getSideLength(level), data.getSideLength(level), 0);
+		validateRegion(sx, sy, 0, region.getWidth(), region.getHeight(), 1, this.getContextWidth(), this.getContextHeight(), 1);
+		TextureData prev = (TextureData)this.push(data, NumericUnit.get(0), TextureData.class);
+		this.copyTextureData(data, region, null, level, sx, sy);
+		this.pop(data, prev, NumericUnit.get(0));
+	}
+	
+	protected abstract void copyTextureData(TextureData data, Block region, Face face, int level, int sx, int sy);
+	
+	public void readFramePixels(Buffer in, Slice slice, TextureType type, TextureFormat format, Block region) {
+		if (!format.isTypeCompatible(type) || format.isServerCompressed())
+			throw new IllegalArgumentException("Invalid texture type and format");
+		validateTypePrimitive(type, getBufferDataType(in));
+		validateRegion(region.getXOffset(), region.getYOffset(), 0, region.getWidth(), region.getHeight(), 1, this.getContextWidth(), this.getContextHeight(), 1);
+		validateBuffer(format.getBufferSize(type, region.getWidth(), region.getHeight()), in.capacity(), slice);
+		
+		this.readPixels(in, slice, type, format, region);
+	}
+	public void readFramePixels(BufferData in, Slice slice, TextureType type, TextureFormat format, Block region) {
+		if (!format.isTypeCompatible(type) || format.isServerCompressed())
+			throw new IllegalArgumentException("Invalid texture type and format");
+		validateTypePrimitive(type, in.getDataType());
+		validateRegion(region.getXOffset(), region.getYOffset(), 0, region.getWidth(), region.getHeight(), 1, this.getContextWidth(), this.getContextHeight(), 1);
+		validateBuffer(format.getBufferSize(type, region.getWidth(), region.getHeight()), in.getCapacity(), slice);
+		
+		BufferData pBD = (BufferData)this.push(in, BufferTarget.PIXEL_READ_BUFFER, BufferData.class);
+		if (in.isVBO() && RenderManager.getSystemCapabilities().arePixelBuffersSupported())
+			this.readPixels(null, slice, type, format, region);
+		else if (in.getData() != null)
+			this.readPixels(in.getData(), slice, type, format, region);
+		this.pop(in, pBD, BufferTarget.PIXEL_READ_BUFFER);
+	}
+	
+	protected abstract void readPixels(Buffer in, Slice slice, TextureType type, TextureFormat format, Block region);
+	
+	private static TextureFormat getServerCompressedFormat(TextureFormat f) {
+		switch(f) {
+		case RGB_DXT1:
+			return TextureFormat.COMPRESSED_RGB_DXT1;
+		case RGBA_DXT1: case BGRA_DXT1:
+			return TextureFormat.COMPRESSED_RGBA_DXT1;
+		case RGBA_DXT3:
+			return TextureFormat.COMPRESSED_RGBA_DXT3;
+		case RGBA_DXT5: case BGRA_DXT5:
+			return TextureFormat.COMPRESSED_RGBA_DXT5;
+		}
+		return f;
+	}
+	
+	private StateAtom push(StateAtom atom, StateUnit unit, Class<? extends StateAtom> type) {
+		StateAtom prev = this.getActiveStateAtom(type, unit);
+		atom.applyState(this.getRenderManager(), NumericUnit.get(0));
+		return prev;
+	}
+	
+	private void pop(StateAtom atom, StateAtom prev, StateUnit unit) {
+		if (prev != null)
+			prev.applyState(this.getRenderManager(), unit);
+		else
+			atom.restoreState(this.getRenderManager(), unit);
+	}
+	
+	private static void validateAll(TextureType texType, TextureFormat format, int numMips, int width, int height, int depth, 
+								    int sx, int sy, int sz, int sw, int sh, int sd, int level, 
+								    int capacity, DataType bType, Slice slice) {
+		validateTypePrimitive(texType, bType);
+		validateMipmap(level, numMips);
+		validateRegion(sx, sy, sz, sw, sh, sd, width, height, depth);
+		validateBuffer(format.getBufferSize(texType, width, height, depth), capacity, slice);
+	}
+	
+	private static void validateRegion(int sx, int sy, int sz, int sw, int sh, int sd, int width, int height, int depth) {
+		if (sx < 0 || sy < 0 || sz < 0)
+			throw new IllegalArgumentException("region must have positive offsets");
+		if (sw < 0 || sh < 0 || sd < 0 || width < 0 || height < 0 || depth < 0)
+			throw new IllegalArgumentException("region must have positive dimensions");
+		if ((sx + sw > width) || (sy + sh) > height || (sz + sd) > depth)
+			throw new IllegalArgumentException("region extends beyond texture dimensions");
+	}
+	
+	private static void validateBuffer(int correctSize, int capacity, Slice slice) {
+		if (slice.getOffset() < 0 || slice.getOffset() + slice.getLength() > capacity)
+			throw new IllegalArgumentException("Slice extends beyond the buffer limits: (" + slice.getOffset() + " - " + (slice.getOffset() + slice.getLength()) + ")");
+		if (slice.getLength() != correctSize)
+			throw new IllegalArgumentException("Buffer slice doesn't have the correct capacity. Provided " + slice.getLength() + ", required " + correctSize);
+	}
+	
+	private static DataType getBufferDataType(Buffer in) {
+		if (in instanceof FloatBuffer)
+			return DataType.FLOAT;
+		else if (in instanceof DoubleBuffer)
+			return DataType.DOUBLE;
+		else if (in instanceof ByteBuffer)
+			return DataType.BYTE;
+		else if (in instanceof IntBuffer)
+			return DataType.INT;
+		else if (in instanceof ShortBuffer)
+			return DataType.SHORT;
+		throw new IllegalArgumentException("Invalid buffer primitive type");
+	}
+	
+	private static void validateTypePrimitive(TextureType type, DataType in) {
+		boolean valid;
+		switch(in) {
+		case FLOAT:
+			valid = (type == TextureType.FLOAT);
+			break;
+		case BYTE: case UNSIGNED_BYTE:
+			valid = (type == TextureType.UNSIGNED_BYTE);
+			break;
+		case INT: case UNSIGNED_INT:
+			valid = (type == TextureType.UNSIGNED_INT || type == TextureType.PACKED_INT_8888);
+			break;
+		case SHORT: case UNSIGNED_SHORT:
+			valid = (type == TextureType.UNSIGNED_SHORT || type == TextureType.PACKED_SHORT_4444
+					 || type == TextureType.PACKED_SHORT_5551 || type == TextureType.PACKED_SHORT_565);
+			break;
+		default:
+			valid = false;
+			break;
+		}
+		
+		if (!valid)
+			throw new IllegalArgumentException("Incompatible buffer primitive type");
+	}
+	
+	private static void validateMipmap(int level, int provided) {
+		if (level < 0 || level >= provided)
+			throw new IllegalArgumentException("Invalid mipmap level for the given texture");
+	}
+	
+	/**
+	 * Get the current version of this context.  This number must be >= 0, and increase each time the context
+	 * is internally re-initialized.
+	 */
+	public abstract int getContextVersion();
+	
+	/**
+	 * Get the SystemCapabilities of this context, can't return null if isInitialized() returns true.
+	 */
+	public abstract SystemCapabilities getCapabilities();
+	/**
+	 * Destroy the internal context resources for this context.
+	 */
+	public abstract void destroyContext();
+	/**
+	 * Called by RenderManager to tell the context to begin the process of rendering.  When appropriate, the context
+	 * must then call notifyRenderFrame() on this context's manager.
+	 */
+	public abstract void render();
+	/**
+	 * Whether or not this RenderContext is current on the calling thread, i.e. that its appropriate to call internal
+	 * resources or gl functions.
+	 */
+	public abstract boolean isCurrent();
+	public abstract boolean isInitialized();
+	
+	public void setActiveStateManager(StateManager man, Class<? extends StateAtom> type) {
+		if (type == null)
+			throw new NullPointerException("Can't have a null type");
+		int index = (man != null ? man.getDynamicType() : registerStateAtomType(type));
+		this.setActiveStateManager(man, index);
+	}
+	
+	public void setActiveStateManager(StateManager man, int type) {
+		if (this.stateRecord == null)
+			this.stateRecord = new StateManager[StateManager.NUM_CORE_STATES];
+		if (this.stateRecord.length <= type) {
+			StateManager[] temp = new StateManager[type + 1];
+			System.arraycopy(this.stateRecord, 0, temp, 0, this.stateRecord.length);
+			this.stateRecord = temp;
+		}
+		this.stateRecord[type] = man;
+	}
+	
+	public StateManager getActiveStateManager(Class<? extends StateAtom> type) {
+		if (type == null)
+			throw new NullPointerException("Can't have a null type");
+		int index = registerStateAtomType(type);
+		return this.getActiveStateManager(index);
+	}
+	
+	public StateManager getActiveStateManager(int dynamicType) {
+		if (this.stateRecord == null || dynamicType >= this.stateRecord.length)
+			return null;
+		return this.stateRecord[dynamicType];
+	}
+	
+	public void setActiveStateAtom(StateAtom atom, Class<? extends StateAtom> type, StateUnit unit) {
+		if (type == null)
+			throw new NullPointerException("Can't have a null type");
+		if (unit == null)
+			throw new NullPointerException("Can't have a null unit");
+		
+		this.setActiveStateAtom(atom, registerStateAtomType(type), unit.ordinal());
+	}
+	
+	public void setActiveStateAtom(StateAtom atom, int type, int ordinal) {
+		if (this.atomRecord == null)
+			this.atomRecord = new StateAtomTracker[StateManager.NUM_CORE_STATES];
+		if (this.atomRecord.length <= type) {
+			StateAtomTracker[] temp = new StateAtomTracker[type + 1];
+			System.arraycopy(this.atomRecord, 0, temp, 0, this.atomRecord.length);
+			this.atomRecord = temp;
+		}
+		
+		StateAtomTracker st = this.atomRecord[type];
+		if (st == null) {
+			st = new StateAtomTracker();
+			this.atomRecord[type] = st;
+		}
+		if (st.stateRecord == null || st.stateRecord.length <= ordinal) {
+			StateAtom[] temp = new StateAtom[ordinal + 1];
+			if (st.stateRecord != null)
+				System.arraycopy(st.stateRecord, 0, temp, 0, st.stateRecord.length);
+			st.stateRecord = temp;
+		}
+		st.stateRecord[ordinal] = atom;
+	}
+	
+	public StateAtom getActiveStateAtom(Class<? extends StateAtom> type, StateUnit unit) {
+		if (type == null)
+			throw new NullPointerException("Can't have a null type");
+		if (unit == null)
+			throw new NullPointerException("Can't have a null unit");
+			
+		return this.getActiveStateAtom(registerStateAtomType(type), unit.ordinal());
+	}
+	
+	public StateAtom getActiveStateAtom(int dynamicType, int ordinal) {
+		if (this.atomRecord == null || this.atomRecord.length <= dynamicType || this.atomRecord[dynamicType] == null || this.atomRecord[dynamicType].stateRecord.length <= ordinal)
+			return null;
+		return this.atomRecord[dynamicType].stateRecord[ordinal];
+	}
+	
+	public RenderManager getRenderManager() {
+		return this.manager;
+	}
+
+	public RenderPassPeer<RenderPass> getDefaultRenderPassPeer() {
+		return this.defaultPass;
+	}
+	
+	protected void callValidate() {
+		if (!this.isCurrent())
+			throw new FeroxException("Method unavailable when RenderContext isn't current");
+	}
+}

File src/com/ferox/core/renderer/RenderManager.java

+package com.ferox.core.renderer;
+
+import java.util.ArrayList;
+
+import com.ferox.core.scene.SpatialTree;
+import com.ferox.core.scene.View;
+import com.ferox.core.system.RenderSurface;
+import com.ferox.core.system.SystemCapabilities;
+import com.ferox.core.tasks.Task;
+import com.ferox.core.tasks.TaskCompleteListener;
+import com.ferox.core.tasks.TaskExecutor;
+import com.ferox.core.util.FeroxException;
+import com.ferox.core.util.TimeSmoother;
+
+/**
+ * RenderManager is the glue that manages rendering a number of passes onto a context.  A RenderManager is
+ * assigned a RenderContext at construction, and afterwards no other manager can use that context.
+ * The RenderManager provides ways listen to various events: initialization, frame changes, and resizing of the
+ * context.  It also lets you register Updatable's to be updated each frame.
+ * 
+ * Generally you will only need one RenderManager for an application, but if you need other windows, you will
+ * have to create multiple RenderManagers and contexts.  However, you are able to uses scenes and states in
+ * multiple RenderManagers (you might just run out of more graphics card memory if you have a large number of 
+ * vbos and textures because each context needs a copy).
+ * 
+ * @author Michael Ludwig
+ *
+ */
+public class RenderManager implements TaskExecutor {
+	public static final int AP_START_FRAME = 0;
+	public static final int AP_END_FRAME = 1;
+	
+	private static final int[] aPs = new int[] {AP_START_FRAME, AP_END_FRAME};
+	
+	private static int managerCounter = 0;
+	private static SystemCapabilities caps;
+	
+	private int id;
+	private ArrayList<RenderPass> passes;
+	
+	private ArrayList<Updatable> updates;
+	
+	private RenderContext renderContext;
+	private int oldWidth, oldHeight;
+	
+	private ArrayList<FrameListener> frameListeners;
+	private ArrayList<InitializationListener> initListeners;
+	private ArrayList<InitializationListener> tempInitListeners;
+	private ArrayList<ReshapeListener> reshapeListeners;
+	
+	private boolean isRendering;
+	private boolean isDestroyed;
+	
+	private TimeSmoother timer;
+	private long lastTime;
+	private FrameStatistics stats;
+	
+	private ArrayList<Task>[] taskStore;
+	private ArrayList<Task>[] tasks;
+	private ArrayList<TaskCompleteListener> listeners;
+
+	
+	/**
+	 * Creates a RenderManager with the given RenderContext.  An exception is thrown if the given
+	 * context has been assigned to another render manager (ie its getRenderManager() method returns a non-null
+	 * value).  The context can't be null, either.
+	 */
+	@SuppressWarnings("unchecked")
+	public RenderManager(RenderContext context) {
+		this.id = managerCounter++;
+		this.passes = new ArrayList<RenderPass>();
+		this.updates = new ArrayList<Updatable>();
+		
+		this.frameListeners = new ArrayList<FrameListener>();
+		this.initListeners = new ArrayList<InitializationListener>();
+		this.tempInitListeners = new ArrayList<InitializationListener>();
+		this.reshapeListeners = new ArrayList<ReshapeListener>();
+		
+		this.oldHeight = -1;
+		this.oldWidth = -1;
+		
+		this.isDestroyed = false;
+		this.isRendering = false;
+		this.timer = new TimeSmoother();
+		
+		this.tasks = new ArrayList[2];
+		this.tasks[0] = new ArrayList<Task>();
+		this.tasks[1] = new ArrayList<Task>();
+		
+		this.taskStore = new ArrayList[2];
+		this.taskStore[0] = new ArrayList<Task>();
+		this.taskStore[1] = new ArrayList<Task>();
+		
+		if (context == null)
+			throw new NullPointerException("Can't create a manager with a null context");
+		if (context.manager != null)
+			throw new IllegalArgumentException("Can't reuse a RenderContext that is already attached to a RenderManager");
+		this.renderContext = context;
+		this.renderContext.manager = this;
+		
+		if (caps == null)
+			caps = this.renderContext.getCapabilities();
+	}
+	
+	/**
+	 * Convenience method to get the spatial tree of the first pass.
+	 */
+	public SpatialTree getSpatialTree() {
+		if (this.passes.size() > 0)
+			return this.passes.get(0).getSpatialTree();
+		return null;
+	}
+	
+	/**
+	 * Convenience method to set the spatial tree of the first pass.  If there is no
+	 * pass, a new one is created with the given scene and a default view.  Disables 
+	 * the previously set scene from being updated, and enables auto updating on the passed
+	 * scene if its non-null.
+	 */
+	public void setSpatialTree(SpatialTree scene) {
+		SpatialTree pt = null;
+		if (this.passes.size() == 0)
+			this.addRenderPass(new RenderPass(scene, new View()));
+		else {
+			pt = this.passes.get(0).getSpatialTree();
+			this.passes.get(0).setSpatialTree(scene);
+		}
+		
+		if (pt != null)
+			this.disableUpdate(pt);
+		if (scene != null)
+			this.enableUpdate(scene);
+	}
+	
+	/**
+	 * Convenience method to get the View of the first pass.
+	 */
+	public View getView() {
+		if (this.passes.size() > 0)
+			return this.passes.get(0).getView();
+		return null;
+	}
+	
+	/**
+	 * Convenience method to set the view of the first pass.  If no pass exists, creates a new
+	 * pass with the view and a null spatial tree.
+	 */
+	public void setView(View view) {
+		if (this.passes.size() == 0)
+			this.addRenderPass(new RenderPass(null, view));
+		else
+			this.passes.get(0).setView(view);
+	}
+	
+	/**
+	 * Registers the Updatable to be updated right before passes are rendered, but after frame listeners
+	 * have been notified.
+	 */
+	public void enableUpdate(Updatable up) {
+		if (!this.updates.contains(up)) {
+			this.updates.add(up);
+		}
+	}
+	
+	/**
+	 * Removes the Updatable, so that it is no longer updated each frame.
+	 */
+	public boolean disableUpdate(Updatable up) {
+		return this.updates.remove(up);
+	}
+	
+	/**
+	 * Whether or not up is updated.
+	 */
+	public boolean isUpdated(Updatable up) {
+		return this.updates.contains(up);
+	}
+	
+	/**
+	 * Remove all registered Updatables.
+	 */
+	public void clearUpdates() {
+		this.updates.clear();
+	}
+	
+	/**
+	 * Whether or not this RenderManager is actively rendering on its owning thread.
+	 */
+	public boolean isRendering() {
+		return this.isRendering;
+	}
+	
+	/**
+	 * Whether or not this RenderManager has had it contents destroyed, it is illegal to attempt to
+	 * render a destroyed RenderManager.
+	 */
+	public boolean isDestroyed() {
+		return this.isDestroyed;
+	}
+	
+	/**
+	 * Destroy the contents of this RenderManager.  Clears listeners and passes and calls
+	 * destroyContext() on its RenderContext.
+	 */
+	public void destroy() {
+		if (!this.isDestroyed) {
+			this.isDestroyed = true;
+			
+			this.renderContext.destroyContext();
+			this.renderContext = null;
+			
+			this.reshapeListeners = null;
+			this.frameListeners = null;
+			this.initListeners = null;
+			this.passes = null;
+		}
+	}
+	
+	/**
+	 * Get the RenderContext used by this RenderManager.  If the RenderContext's isCurrent() method returns
+	 * true, then it is valid to use the internal resources of the the context.  Any listener, atom or pass
+	 * implementation will only have their worker methods called when the context is valid, so they don't
+	 * need to waste time checking.
+	 */
+	public RenderContext getRenderContext() {
+		return this.renderContext;
+	}
+	
+	/**
+	 * Get the frame rate of the simulation
+	 */
+	public float getFrameRate() {
+		return this.timer.getFrameRate();
+	}
+	
+	/**
+	 * Get the RenderSurface that this manager renders to.
+	 */
+	public RenderSurface getRenderingSurface() {
+		return this.renderContext.getRenderSurface();
+	}
+	
+	/**
+	 * Get the unique id for this manager.
+	 */
+	public int getManagerID() {
+		return this.id;
+	}
+	
+	/**
+	 * Add a FrameListener to this RenderManager.  Returns true if it hasn't already been added.
+	 */
+	public boolean addFrameListener(FrameListener frame) {
+		if (!this.frameListeners.contains(frame)) {
+			this.frameListeners.add(frame);
+			return true;
+		}
+		return false;
+	}
+	
+	/**
+	 * Add an InitializationListener to this RenderManager.  Returns true if it hasn't already
+	 * been added.
+	 */
+	public boolean addInitializationListener(InitializationListener init) {
+		if (!this.initListeners.contains(init)) {