Commits

Michael Ludwig committed dffee0d

Improve the Geometry interface, add compile(), and fix problems with fullsceen surfaces.

  • Participants
  • Parent commits c9d5e4d

Comments (0)

Files changed (20)

File src/com/ferox/renderer/Renderer.java

 package com.ferox.renderer;
 
+import java.util.List;
+
+import com.ferox.resource.Geometry;
 import com.ferox.resource.Resource;
 import com.ferox.resource.ResourceManager;
 import com.ferox.resource.Resource.Status;
 	 * idle, if it's destroyed, or if it can't create the surface for any other reason. */
 	public TextureSurface createTextureSurface(TextureSurface share, int layer) throws RenderException;
 	
+	/** Return a single Geometry instance that represents the list of render atoms.  If
+	 * submitted with renderAtom(), this rendered Geometry will represent the what would
+	 * be rendered if the list of atoms were submitted to renderAtom() in order (accounting
+	 * for the current viewport and transform).
+	 * 
+	 * Because the returned Geometry uses the Appearances stored in atoms, it must return
+	 * true from isAppearanceIgnored().
+	 * If the specified list is empty, return null.
+	 * 
+	 * The returned Geometry can usually be rendered much faster than if the list were
+	 * rendered.  It is intended for static atom's, and because of this, updating the returned
+	 * geometry has no effect (it must still be cleaned-up, but subsequent updates will then
+	 * fail).  
+	 * 
+	 * Implementations do not need to provide a useful implementation of getVertex(), so
+	 * programmers should use getBounds() instead of bounds.enclose() to get the BoundVolume
+	 * for this Geometry.
+	 * 
+	 * Throw an exception if atoms is null, any atom would cause an exception to be thrown
+	 * if it were rendered normally, if the renderer isn't idle, or if it's been destroyed. */
+	public Geometry compile(List<RenderAtom> atoms) throws RenderException;
+	
 	/** Destroy the given RenderSurface.  After a call to this method, the
 	 * surface can no longer be used for calls to queueRender() and its
 	 * isDestroyed() method will return true.  The behavior of other methods
 	 * many low-level context switches.  In the worst case, each TextureSurface will require
 	 * a context switch, just like each OnscreenSurface.
 	 * 
-	 * If a surface is queued multiple times, the subsequent queues will be ignored.
+	 * If a surface is queued multiple times or has been destroyed, it will be ignored.
 	 * Return this Renderer object so queue requests can be chained together.
 	 * 
 	 * Throw an exception if the renderer is destroyed, the renderer isn't idle,
-	 * the surface is destroyed, or the surface wasn't created by this renderer. */
+	 * or the surface wasn't created by this renderer. */
 	public Renderer queueRender(RenderSurface surface) throws RenderException;
 	
 	/** Render a single frame.  The renderer must invoke manage() on
 	 * its default ResourceManager and then any custom managers.  After the 
 	 * managers are processed, the renderer should, for each queued surface,
 	 * clear the surface based on its settings and render each attached 
-	 * render pass.
+	 * render pass.  Queued surfaces that have been destroyed should be ignored.
 	 * 
 	 * The resource managers must be processed even if there are no queued
 	 * render surfaces.
 	 * 
 	 * Return store (or the new instance if store is null).
 	 * 
-	 * Throw an exception if the renderer is destroyed, it's not idle, if any
-	 * queued surfaces have been destroyed after being enqueued, if render passes
+	 * Throw an exception if the renderer is destroyed, it's not idle, or  if render passes
 	 * return a null RenderQueue.  Throw a RenderException that wraps any
 	 * exception that happened to occur while rendering. 
 	 * 
 	 *  	as created by those state's default constructors.
 	 *  2. If any other state isn't present, that state type should not effect the rendering of the atom,
 	 *  	unless there is a special contract with an implementation of the renderer.
+	 *  
+	 * If the Geometry to be rendered returns true from isAppearanceIgnored(), only the default Appearance
+	 * should be used.
 	 * 
 	 * This method should not be called directly, instead it is to be used by RenderQueue implementations.
 	 * The render atom will not be rendered if it has a null geometry or null transform.  However, influences

File src/com/ferox/renderer/impl/AbstractRenderer.java

 package com.ferox.renderer.impl;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Stack;
 import com.ferox.renderer.View;
 import com.ferox.renderer.WindowSurface;
 import com.ferox.renderer.impl.ResourceData.Handle;
+import com.ferox.renderer.util.BasicRenderPass;
 import com.ferox.renderer.util.DefaultResourceManager;
 import com.ferox.resource.Geometry;
 import com.ferox.resource.Resource;
  * @author Michael Ludwig
  *
  */
+// TODO: remove currentPass == null/frameStates == null checks and when necessary just
+// assign them dummy values so that they still function.
+// TODO: in compile() make sure there is better default state before calling the driver
 public class AbstractRenderer implements Renderer {
 	/** Represents all possible states of an AbstractRenderer. */
 	public static enum RenderState {
 		WAITING_INIT, /** State of the renderer before init() is called */
 		IDLE,		  /** State of the renderer when it's not doing anything */
-		PIPELINE,
-		RESOURCE,
 		RENDERING,    /** State of the renderer when it's in flushRenderer(). */
 		DESTROYED 	  /** State of the renderer after destroy() is called */
 	}
 		int typeId;
 	}
 	
+	/** List to be passed into renderFrame() by methods needing custom resource actions executed. */
+	public static final List<ContextRecordSurface> EMPTY_LIST = Collections.unmodifiableList(new LinkedList<ContextRecordSurface>());
+	
 	private static AbstractRenderer instance = null;
 	
 	/* AbstractRenderer misc variables */
 	private Map<Class<? extends Resource>, ResourceDriver> resourceDrivers;
 	private IdentityHashMap<Resource, Integer> resourceLocks;
 	
-	private DefaultResourceManager dfltManager; // dfltManager is 0th child of resourceManagers
+	private DefaultResourceManager dfltManager; // dfltManager is 0th element in resourceManagers
 	private List<ResourceManager> resourceManagers;
 	private Stack<Resource> resourceProcessStack;
 	
 	private IdentityHashMap<RenderPass, View> preparedPasses;
 	private List<ContextRecordSurface> queuedSurfaces;
 	private TransformDriver transform;
+	private CompiledGeometryDriver compiler;
 	
 	/** Create a new AbstractRenderer.  This constructor does not completely configure
 	 * the renderer for use.  Subclasses must also invoke init() before their constructor
 	public final Renderer queueRender(RenderSurface surface) throws RenderException {
 		this.ensure(RenderState.IDLE);
 		ContextRecordSurface s = this.validateSurface(surface);
-		if (s == null)
+		if (s == null) {
+			if (surface != null && surface.isDestroyed() && surface.getRenderer() == this)
+				return this; // just ignore it
 			throw new RenderException("Surface is invalid and cannot be queued: " + surface);
+		}
 		
 		if (!this.queuedSurfaces.contains(s)) // avoid duplicates
 			this.queuedSurfaces.add(s);
 	/* Class that performs only the managing of resources during a 
 	 * frame.  This is passed in as the 2nd argument to renderFrame()
 	 * of the surface factory. */
-	private class ManageResourcesAction implements Runnable {
-		
+	private class ManageResourcesAction implements Runnable {	
 		@Override
 		public void run() {
-			manageResources();
+			int numManagers = resourceManagers.size();
+			for (int i = 0; i < numManagers; i++)
+				resourceManagers.get(i).manage(AbstractRenderer.this);
 		}
 	}
 	
 		
 		try {
 			long prepareStart = now;
+			this.renderState = RenderState.RENDERING;
+
 			// make sure all surfaces are valid
+			ContextRecordSurface s;
+			for (int i = this.queuedSurfaces.size() - 1; i >= 0; i--) {
+				s = this.validateSurface(this.queuedSurfaces.get(i));
+				if (s == null) {
+					if (s != null && s.isDestroyed() && s.getRenderer() == this) {
+						// remove it from the list
+						this.queuedSurfaces.remove(i);
+					} else
+						throw new RenderException("Cannot render an invalid surface: " + this.queuedSurfaces.get(i));
+				}
+			}
 			int numSurfaces = this.queuedSurfaces.size();
 
 			if (numSurfaces > 0) {
 				// we're rendering an entire frame now, resource managers will be done on 1st surface
-				for (int i = 0; i < numSurfaces; i++) {
-					if (this.validateSurface(this.queuedSurfaces.get(i)) == null)
-						throw new RenderException("Cannot render an invalid surface: " + this.queuedSurfaces.get(i));
-				}
-
 				int numPasses;
 				List<RenderPass> passes;
 				RenderPass pass;
 			}
 
 			long renderStart = now;
-			this.renderState = RenderState.PIPELINE;
-
 			// if numSurfaces == 0, the resource action is still executed
 			this.factory.renderFrame(this.queuedSurfaces, this.manageResourceAction);
 
 	
 	@Override
 	public final Status update(Resource resource, boolean forceFullUpdate) throws RenderException {
-		this.ensure(RenderState.RESOURCE);
+		this.ensure(RenderState.RENDERING);
 		return this.doUpdate(resource, forceFullUpdate, this.factory);
 	}
 	
 	@Override
 	public final void cleanUp(Resource resource) throws RenderException {
-		this.ensure(RenderState.RESOURCE);
+		this.ensure(RenderState.RENDERING);
 		this.doCleanUp(resource, this.factory);
 	}
 	
+	/* Used to run the compile() task on the graphics thread. */
+	private class CompileGeometryResourceAction implements Runnable {
+		private Geometry compiledGeom;
+		private final List<RenderAtom> atoms;
+		
+		public CompileGeometryResourceAction(List<RenderAtom> atoms) {
+			this.atoms = atoms;
+		}
+		
+		@Override
+		public void run() {
+			ResourceData data = new ResourceData(AbstractRenderer.this, compiler.getDriver());
+			// setup the default state
+			transform.setView(null, 0, 0);
+			setAppearance(null);
+			this.compiledGeom = compiler.compile(this.atoms, data);
+			this.compiledGeom.setResourceData(data);
+		}
+	}
+	
+	@Override
+	public final Geometry compile(List<RenderAtom> atoms) throws RenderException {
+		this.ensure(RenderState.IDLE);
+		if (atoms == null)
+			throw new RenderException("Atoms list cannot be null");
+		
+		if (atoms.size() > 0) {
+			try {
+				this.renderState = RenderState.RENDERING;
+				
+				// set some dummy values
+				this.frameStats = new FrameStatistics();
+				this.currentPass = new BasicRenderPass(null, null);
+				
+				CompileGeometryResourceAction compile = new CompileGeometryResourceAction(atoms);
+				this.factory.renderFrame(EMPTY_LIST, compile);
+				return compile.compiledGeom;
+			} catch(Exception e) {
+				throw new RenderException("Exception occurred while compiling geometry", e);
+			} finally {
+				this.renderState = RenderState.IDLE;
+				this.frameStats = null;
+				this.currentPass = null;
+			}
+		} else
+			return null;
+	}
+	
 	/* Rendering operations. */
 	
 	@Override
 	public final void applyInfluence(InfluenceAtom atom, float influence) throws RenderException {
-		this.ensure(RenderState.PIPELINE);
+		this.ensure(RenderState.RENDERING);
 		if (atom == null)
 			throw new RenderException("Cannot call applyInfluence with a null InfluenceAtom");
 		
 	
 	@Override
 	public final void renderAtom(RenderAtom atom) throws RenderException {
-		this.ensure(RenderState.PIPELINE);
+		this.ensure(RenderState.RENDERING);
 		if (atom == null)
 			throw new RenderException("Cannot call renderAtom with a null RenderAtom");
+		if (!this.factory.isGraphicsThread())
+			throw new RenderException("renderAtom() cannot be invoked on this thread");
 		
 		Transform model = atom.getTransform();
 		Geometry geom = atom.getGeometry();
 
 			this.queueAppearance(this.dfltAppearance);
 			Appearance app = atom.getAppearance();
-			if (app != null) {
+			if (app != null && !geom.isAppearanceIgnored()) {
 				this.queueAppearance(app);
 			} // else... no queuing implies use default state
 
 	 * object from its detect() method. */
 	protected final void init(SurfaceFactory surfaceFactory,
 							  TransformDriver transformDriver,
+							  CompiledGeometryDriver compiledGeomDriver,
 							  DriverFactory<Class<? extends Geometry>, GeometryDriver> geomDrivers,
 							  DriverFactory<Class<? extends Resource>, ResourceDriver> resourceDrivers,
 							  DriverFactory<Role, StateDriver> stateDrivers,
 		
 		if (transformDriver == null)
 			throw new RenderException("Must pass in a non-null TransformDriver");
+		if (compiledGeomDriver == null)
+			throw new RenderException("Must pass in a non-null CompiledGeometryDriver");
 		if (geomDrivers == null)
 			throw new RenderException("Must pass in a non-null geometry driver factory");
 		if (resourceDrivers == null)
 		
 		this.factory = surfaceFactory;
 		this.transform = transformDriver;
+		this.compiler = compiledGeomDriver;
 		this.geomFactory = geomDrivers;
 		this.resourceFactory = resourceDrivers;
 		this.stateFactory = stateDrivers;
 	 * about to be rendered to or have resources managed). */
 	public final Status doUpdate(Resource resource, boolean forceFullUpdate, SurfaceFactory key) throws RenderException {
 		this.ensure(null); // must make sure it's still okay to call
+		if (!this.factory.isGraphicsThread())
+			throw new RenderException("doUpdate() cannot be invoked on this thread");
 		
 		if (key != this.factory)
 			throw new RenderException("Illegal to call this method without the correct surface factory: " + key);
 	/** As doUpdate(), but mirroring the cleanUp() method. */
 	public final void doCleanUp(Resource resource, SurfaceFactory key) throws RenderException {
 		this.ensure(null); // must make sure it's still okay to call
+		if (!this.factory.isGraphicsThread())
+			throw new RenderException("doCleanUp() cannot be invoked on this thread");
 		
 		if (key != this.factory)
 			throw new RenderException("Illegal to call this method without the correct surface factory: " + key);
 	 * the surface has already had its references cleaned.  It is imperative that this method
 	 * is only called at the appropriate times because it causes the given surface to become
 	 * unusable as an argument to any of the renderer's methods. */
-	public void notifyOnscreenSurfaceClosed(OnscreenSurface surface) {
+	public final void notifyOnscreenSurfaceClosed(OnscreenSurface surface) {
 		if (surface != null && surface.getRenderer() == this) {
 			ContextRecordSurface target = (ContextRecordSurface) surface;
 			if (this.surfaces[target.getSurfaceId()] == surface)
 		}
 	}
 	
+	/** Adjust the current record so that the given Appearance
+	 * is active.  If a is null, the record is set to be the
+	 * default appearance used when rendering atoms.
+	 * 
+	 * This will ignore any queued influence atoms.
+	 * 
+	 * This can only be called when the renderer is in the
+	 * RENDERING state and from the gl thread. */
+	public final void setAppearance(Appearance a) {
+		this.ensure(RenderState.RENDERING);
+		if (!this.factory.isGraphicsThread())
+			throw new RenderException("renderAtom() cannot be invoked on this thread");
+		
+		// reset, so we're on a clean slate
+		for (int i = 0; i < this.stateTypeCounter; i++)
+			this.stateDrivers[i].reset();
+		
+		this.queueAppearance(this.dfltAppearance);
+		if (a != null)
+			this.queueAppearance(a);
+
+		// apply the queued appearances
+		for (int i = 0; i < this.stateTypeCounter; i++)
+			this.stateDrivers[i].doApply();
+	}
+	
+	/** To be used by CompiledGeometryDrivers to forcibly reset the
+	 * geometry driver when they're done compiling.  All other
+	 * methods are ok to call outside of flushRenderer() for a pass,
+	 * but it is necessary to call this so that geometry state is
+	 * reset. */
+	public final void resetGeometryDriver() {
+		if (this.lastDriver != null) {
+			this.lastDriver.reset();
+			this.lastDriver = null;
+		}
+	}
+	
 	/* Internal operations. */
 	
 	private void queueAppearance(Appearance app) {
 		}
 	}
 	
-	// Expects there to be a current context when this is called
-	// (either a surface or the shadow context).  Otherwise, this method
-	// properly sets the render state and processes all resource managers.
-	private void manageResources() {
-		RenderState old = this.renderState;
-		try {
-			this.renderState = RenderState.RESOURCE;
-			int numManagers = this.resourceManagers.size();
-			for (int i = 0; i < numManagers; i++)
-				this.resourceManagers.get(i).manage(this);
-		} finally {
-			this.renderState = old;
-		}
-	}
-	
 	// Reset the state and geometry drivers for the next surface
 	private void resetForNextSurface() {
-		if (this.lastDriver != null)
-			this.lastDriver.reset();
-		this.lastDriver = null;
+		this.resetGeometryDriver();
 		for (int i = 0; i < this.stateTypeCounter; i++) {
 			this.stateDrivers[i].reset();
 			this.stateDrivers[i].doApply(); // now make sure everything is the "default"

File src/com/ferox/renderer/impl/CompiledGeometryDriver.java

+package com.ferox.renderer.impl;
+
+import java.util.List;
+
+import com.ferox.renderer.RenderAtom;
+import com.ferox.resource.Geometry;
+
+/** Driver class that provides support for the compile() method
+ * in the Renderer interface.
+ * 
+ * @author Michael Ludwig
+ *
+ */
+public interface CompiledGeometryDriver {
+	/** Perform the compile() functionality as described
+	 * in Renderer.  It can be assumed that atoms is not null.
+	 * The given ResourceData is the data that will be associated
+	 * with the returned Geometry. 
+	 * 
+	 * This method should effectively perform an update() if it
+	 * were a GeometryDriver, since update() for the GeometryDriver
+	 * should do nothing for the compiled geometries.
+	 * 
+	 * It can be assumed that low-level operations can be performed
+	 * (just like the methods in ResourceDriver). 
+	 * 
+	 * The low-level state will be having no active View (e.g.
+	 * the modelview starts out as the identity matrix, instead of the
+	 * view position), and the state record has been set to the
+	 * defaults. */
+	public Geometry compile(List<RenderAtom> atoms, ResourceData data);
+	
+	/** Return a GeometryDriver to use for the returned Geometries
+	 * from compile(). */
+	public GeometryDriver getDriver();
+}

File src/com/ferox/renderer/impl/SurfaceFactory.java

 	public void renderFrame(List<ContextRecordSurface> queuedSurfaces, Runnable resourceAction);
 	
 	
-	/** Get the surface that is current.  Return null if no surface is current. */
-	//public ContextRecordSurface getCurrentSurface();
+	/** Return true if the calling thread can have low-level graphics operations invoked on it. */
+	public boolean isGraphicsThread();
 }

File src/com/ferox/renderer/impl/TransformDriver.java

 public interface TransformDriver {
 	/** Set the current view on the low-level graphics hardware.
 	 * For OpenGL like systems, this involves setting the projection
-	 * matrix and the view matrix.  It can be assumed that the view isn't null. */
+	 * matrix and the view matrix.  
+	 * 
+	 * If the view is null, set the modelview matrix to the identity,
+	 * and ignore width and height. */
 	public void setView(View view, int width, int height);
 	
 	/** Set the model transform to use.  This must preserve the

File src/com/ferox/renderer/impl/jogl/BasicJoglRenderer.java

 
 import com.ferox.renderer.RenderException;
 import com.ferox.renderer.impl.AbstractRenderer;
+import com.ferox.renderer.impl.jogl.drivers.JoglDisplayListGeometryDriver;
 
 /** Provides a full implementation of Renderer that is based on
  * the functionality provided by AbstractRenderer.
 
 		JoglSurfaceFactory factory = new JoglSurfaceFactory(this, detector.detect(), debugGL);
 		
+		JoglDisplayListGeometryDriver compiler = new JoglDisplayListGeometryDriver(factory);
+		
 		BasicGeometryDriverFactory gdf = new BasicGeometryDriverFactory(factory);
 		BasicResourceDriverFactory rdf = new BasicResourceDriverFactory(factory);
 		BasicStateDriverFactory sdf = new BasicStateDriverFactory(factory);	
 		
-		this.init(factory, factory.getTransformDriver(), gdf, rdf, sdf, detector);
+		this.init(factory, factory.getTransformDriver(), compiler, gdf, rdf, sdf, detector);
 	}
 }

File src/com/ferox/renderer/impl/jogl/JoglFullscreenSurface.java

 package com.ferox.renderer.impl.jogl;
 
 import java.awt.DisplayMode;
-import java.awt.Frame;
 import java.awt.GraphicsDevice;
 import java.awt.GraphicsEnvironment;
 
 import com.ferox.renderer.FullscreenSurface;
 
 public class JoglFullscreenSurface extends JoglOnscreenSurface implements FullscreenSurface {
-	private Frame frame;
 	private DisplayMode mode;
 	private GraphicsDevice gDev;
 	
-	protected JoglFullscreenSurface(JoglSurfaceFactory factory,	int id, DisplayOptions optionsRequest, int width, int height) {
+	protected JoglFullscreenSurface(JoglSurfaceFactory factory,	int id, DisplayOptions optionsRequest, final int width, final int height) {
 		super(factory, id, optionsRequest, 0, 0, width, height, false, true);
-	
+		
 		// get target device parameters and set the display mode
 		this.gDev = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
 		this.gDev.setFullScreenWindow(this.frame);
-		
+
 		if (this.gDev.isFullScreenSupported()) {
 			if (this.gDev.isDisplayChangeSupported()) {
 				this.mode = chooseBestMode(this.gDev.getDisplayModes(), optionsRequest, width, height);
 			if (weight < bestWeight) {
 				bestWeight = weight;
 				best = modes[i];
-				System.out.println(weight + " " + best.getBitDepth() + " " + best.getWidth() + " " + best.getHeight() + " " + best.getRefreshRate());
 			}
 		}
 		return best;

File src/com/ferox/renderer/impl/jogl/JoglOnscreenSurface.java

  */
 public abstract class JoglOnscreenSurface extends JoglRenderSurface implements OnscreenSurface, WindowListener {
 	protected final GLCanvas canvas;
-	protected final Frame frame;
+	protected Frame frame; // final
 	
 	private final JoglStateRecord record;
 	
 	/** The given options is used to identify the GLCapabilities for the
 	 * constructed GLCanvas.  The GLCanvas shares with the given factory's 
 	 * shadow context. */
-	protected JoglOnscreenSurface(JoglSurfaceFactory factory, int id, DisplayOptions optionsRequest,
+	protected JoglOnscreenSurface(JoglSurfaceFactory factory, int id, DisplayOptions optionsRequest, 
 								  final int x, final int y, final int width, final int height, 
 								  final boolean resizable, final  boolean undecorated) {
 		super(factory, id);
 			optionsRequest = new DisplayOptions();
 		this.canvas = new GLCanvas(chooseCapabilities(optionsRequest), new DefaultGLCapabilitiesChooser(), factory.getShadowContext(), null);
 		this.frame = new Frame();
-		
+
 		try {
 			SwingUtilities.invokeAndWait(new Runnable() {
 				public void run() {
 					frame.setBounds(x, y, Math.max(width, 1), Math.max(height, 1));
 
 					frame.add(canvas);
-					
+
 					frame.setVisible(true);
 					canvas.requestFocusInWindow();
 				}
 		} catch (Exception e) {
 			throw new RenderException("Error creating JoglOnscreenSurface", e);
 		}
+
+		this.frame.addWindowListener(this);
 		
 		this.canvas.addGLEventListener(this);
-		this.frame.addWindowListener(this);
+		this.canvas.setIgnoreRepaint(true);
 		
 		this.record = new JoglStateRecord(factory.getRenderer().getCapabilities());
 		this.options = optionsRequest;

File src/com/ferox/renderer/impl/jogl/JoglSurfaceFactory.java

 package com.ferox.renderer.impl.jogl;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
 import javax.media.opengl.GL;
 import javax.media.opengl.GLAutoDrawable;
 import javax.media.opengl.GLContext;
+import javax.media.opengl.Threading;
 
 import com.ferox.math.Transform;
 import com.ferox.renderer.DisplayOptions;
  *
  */
 public class JoglSurfaceFactory implements SurfaceFactory {	
-	/** List to be passed into renderFrame() by methods needing custom resource actions executed. */
-	public static final List<ContextRecordSurface> EMPTY_LIST = Collections.unmodifiableList(new LinkedList<ContextRecordSurface>());
-	
 	/* Variables for the shadow context. */
 	private ShadowContext shadowContext;
 	
 		}
 	}
 	
+	@Override
+	public boolean isGraphicsThread() {
+		return !Threading.isSingleThreaded() || Threading.isOpenGLThread();
+	}
+	
 	/** Return the GLAutoDrawable that is currently executing its
 	 * GLEventListeners.  This is not necessarily associated with
 	 * the actual "current" surface, since fbos can be attached
 			ZombieFboCleanupAction cleanup;
 			for (Entry<GLAutoDrawable, Queue<JoglFbo>> e: this.zombieFbos.entrySet()) {
 				cleanup = new ZombieFboCleanupAction(e.getValue());
-				this.renderFrame(EMPTY_LIST, cleanup);
+				this.renderFrame(AbstractRenderer.EMPTY_LIST, cleanup);
 			}
 			this.zombieFbos.clear();
 		}

File src/com/ferox/renderer/impl/jogl/JoglTextureSurface.java

 import com.ferox.renderer.DisplayOptions.DepthFormat;
 import com.ferox.renderer.DisplayOptions.PixelFormat;
 import com.ferox.renderer.DisplayOptions.StencilFormat;
+import com.ferox.renderer.impl.AbstractRenderer;
 import com.ferox.renderer.impl.jogl.record.JoglStateRecord;
 import com.ferox.resource.BufferData.DataType;
 import com.ferox.resource.Resource.Status;
 		createDepthTexture(s, caps);
 		
 		// create texture images and delegates of gfx card
-		factory.renderFrame(JoglSurfaceFactory.EMPTY_LIST, new UpdateTextureImagesAction(s, factory));
-		factory.renderFrame(JoglSurfaceFactory.EMPTY_LIST, new ConstructDelegateAction(s, factory, this));
+		factory.renderFrame(AbstractRenderer.EMPTY_LIST, new UpdateTextureImagesAction(s, factory));
+		factory.renderFrame(AbstractRenderer.EMPTY_LIST, new ConstructDelegateAction(s, factory, this));
 		
 		// if we've gotten here, we're okay
 		this.layer = s.layer;

File src/com/ferox/renderer/impl/jogl/JoglWindowSurface.java

 package com.ferox.renderer.impl.jogl;
 
-import java.awt.Frame;
-
 import javax.swing.SwingUtilities;
 
 import com.ferox.renderer.DisplayOptions;
  *
  */
 public class JoglWindowSurface extends JoglOnscreenSurface implements WindowSurface {
-	private Frame frame;
-	
 	/** Expects as arguments, the factory that is currently handling a createWindowSurface()
 	 * call, as well as the identically named arguments to that call. 
 	 * 
 	protected JoglWindowSurface(JoglSurfaceFactory factory,	int id, DisplayOptions optionsRequest, 
 								int x, int y, int width, int height, boolean resizable, boolean undecorated) {
 		super(factory, id, optionsRequest, x, y, width, height, resizable, undecorated);
-		
 	}
 	
 	@Override

File src/com/ferox/renderer/impl/jogl/drivers/DisplayListGeometry.java

+package com.ferox.renderer.impl.jogl.drivers;
+
+import java.util.List;
+
+import com.ferox.math.AxisAlignedBox;
+import com.ferox.math.BoundSphere;
+import com.ferox.math.BoundVolume;
+import com.ferox.renderer.RenderAtom;
+import com.ferox.resource.Geometry;
+
+/** Geometry implementation used internally b JoglDisplayListGeometryDriver. 
+ * There is very little logic contained within this class. 
+ * 
+ * @author Michael Ludwig
+ *
+ */
+public class DisplayListGeometry implements Geometry {
+	private Object resourceData;
+	private final int vertexCount;
+
+	private final AxisAlignedBox box;
+	private final BoundSphere sphere;
+
+	public DisplayListGeometry(List<RenderAtom> atoms) {
+		int vCount = 0;
+		BoundVolume bounds = null;
+
+		int numAtoms = atoms.size();
+		RenderAtom a;
+		BoundVolume aBounds;
+		for (int i = 0; i < numAtoms; i++) {
+			a = atoms.get(i);
+			aBounds = a.getBounds();
+			vCount += a.getGeometry().getVertexCount();
+
+			if (aBounds != null) {
+				if (bounds == null) 
+					bounds = aBounds.clone(bounds);
+				else
+					bounds.enclose(aBounds);
+			}
+		}
+
+		if (bounds instanceof AxisAlignedBox) {
+			this.box = (AxisAlignedBox) bounds;
+			this.sphere = new BoundSphere();
+
+			this.sphere.setCenter(this.box.getCenter(null));
+			this.sphere.setRadius(0f);
+			this.sphere.enclose(this.box);
+		} else if (bounds instanceof BoundSphere) {
+			this.sphere = (BoundSphere) bounds;
+			this.box = new AxisAlignedBox();
+
+			this.box.setMax(Float.MIN_VALUE, Float.MIN_VALUE, Float.MIN_VALUE);
+			this.box.setMin(Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE);
+			this.box.enclose(this.sphere);
+		} else {
+			// use default bounds
+			this.sphere = new BoundSphere();
+			this.box = new AxisAlignedBox();
+		}
+
+		this.vertexCount = vCount;
+	}
+
+	@Override
+	public boolean isAppearanceIgnored() {
+		return true;
+	}
+
+	@Override
+	public void getBounds(BoundVolume result) {
+		if (result != null) {
+			if (result instanceof AxisAlignedBox) {
+				this.box.clone(result);
+			} else if (result instanceof BoundSphere) {
+				this.sphere.clone(result);
+			}
+		}
+	}
+
+	@Override
+	public float getVertex(int index, int coord) throws IllegalArgumentException {
+		return 0f;
+	}
+
+	@Override
+	public int getVertexCount() {
+		return this.vertexCount;
+	}
+
+	@Override
+	public void clearDirtyDescriptor() {
+		// do nothing
+	}
+
+	@Override
+	public Object getDirtyDescriptor() {
+		return null;
+	}
+
+	@Override
+	public Object getResourceData() {
+		return this.resourceData;
+	}
+
+	@Override
+	public void setResourceData(Object data) {
+		this.resourceData = data;
+	}
+}

File src/com/ferox/renderer/impl/jogl/drivers/JoglDisplayListGeometryDriver.java

+package com.ferox.renderer.impl.jogl.drivers;
+
+import java.util.List;
+
+import javax.media.opengl.GL;
+
+import com.ferox.renderer.RenderAtom;
+import com.ferox.renderer.impl.AbstractRenderer;
+import com.ferox.renderer.impl.CompiledGeometryDriver;
+import com.ferox.renderer.impl.GeometryDriver;
+import com.ferox.renderer.impl.ResourceData;
+import com.ferox.renderer.impl.ResourceData.Handle;
+import com.ferox.renderer.impl.jogl.JoglSurfaceFactory;
+import com.ferox.resource.Geometry;
+import com.ferox.resource.Resource;
+import com.ferox.resource.Resource.Status;
+
+/** Uses display lists to compile a list of render atoms into one Geometry.
+ * 
+ * @author Michael Ludwig
+ *
+ */
+public class JoglDisplayListGeometryDriver implements CompiledGeometryDriver, GeometryDriver {
+	/* Represents a display list in jogl. */
+	private static class DisplayListHandle implements Handle {
+		private static int displayListIdCounter = 1;
+
+		private int id = displayListIdCounter++;
+		private int polyCount;
+		
+		public int getId() { return this.id; }
+	}
+	
+	private final JoglSurfaceFactory factory;
+	
+	public JoglDisplayListGeometryDriver(JoglSurfaceFactory factory) {
+		this.factory = factory;
+	}
+	
+	@Override
+	public Geometry compile(List<RenderAtom> atoms, ResourceData data) {
+		GL gl = this.factory.getGL();
+		AbstractRenderer renderer = this.factory.getRenderer();
+
+		
+		DisplayListGeometry geom = new DisplayListGeometry(atoms);
+		DisplayListHandle handle = new DisplayListHandle();
+		data.setHandle(handle);
+		
+		// we have a clean slate, so just make the list
+		gl.glNewList(handle.id, GL.GL_COMPILE);
+			int numAtoms = atoms.size();
+			for (int i = 0; i < numAtoms; i++) {
+				renderer.renderAtom(atoms.get(i));
+			}
+			// reset the record in the dl, so that we have a predictable record when rendering
+			renderer.resetGeometryDriver();
+			renderer.setAppearance(null);
+		gl.glEndList();
+		return geom;
+	}
+
+	@Override
+	public GeometryDriver getDriver() {
+		return this;
+	}
+
+	@Override
+	public int render(Geometry geom, ResourceData data) {
+		GL gl = this.factory.getGL();
+		DisplayListHandle h = (DisplayListHandle) data.getHandle();
+		
+		// we know the current record is the default appearance
+		gl.glCallList(h.id);
+		
+		return h.polyCount;
+	}
+
+	@Override
+	public void reset() {
+		// do nothing
+	}
+
+	@Override
+	public void cleanUp(Resource resource, ResourceData data) {
+		GL gl = this.factory.getGL();
+		DisplayListHandle h = (DisplayListHandle) data.getHandle();
+		
+		if (h != null)
+			gl.glDeleteLists(h.getId(), 1);
+	}
+
+	@Override
+	public void update(Resource resource, ResourceData data, boolean fullUpdate) {
+		if (data.getHandle() == null) {
+			// we've been cleaned up
+			data.setStatus(Status.ERROR);
+			data.setStatusMessage("Cannot update a compiled geometry after its been cleaned-up");
+		} // else do nothing (effectively updated in compile())
+	}
+}

File src/com/ferox/renderer/impl/jogl/drivers/JoglLightingStateDriver.java

 public class JoglLightingStateDriver extends MultiStateDriver<Light> {
 	private static final int MAX_LIGHTS = 8;
 	
-	private final Transform cache;
 	private final Light[] appliedLights;
 	private Vector3f p;
 	
 	public JoglLightingStateDriver(JoglSurfaceFactory factory) {
 		super(null, Light.class, Math.min(MAX_LIGHTS, factory.getRenderer().getCapabilities().getMaxActiveLights()), factory);
-		this.cache = new Transform();
+		new Transform();
 		this.appliedLights = new Light[Math.min(MAX_LIGHTS, factory.getRenderer().getCapabilities().getMaxActiveLights())];
 	}
 
 		
 		lr.spotDirection[0] = this.p.x; 
 		lr.spotDirection[1] = this.p.y; 
-		lr.spotDirection[2] = this.p.z; 
+		lr.spotDirection[2] = this.p.z;
 		
-		this.cache.mul(this.factory.getViewTransform(), light.getWorldTransform());
-		this.factory.getTransformDriver().loadMatrix(gl, this.cache);
+		this.factory.getTransformDriver().pushMatrix(gl, light.getWorldTransform());
 		
 		// pos and dir
 		gl.glLightfv(glUnit, GL.GL_POSITION, lr.position, 0);
 			lr.quadraticAttenuation = light.getQuadraticAttenuation();
 			gl.glLightf(glUnit, GL.GL_QUADRATIC_ATTENUATION, lr.quadraticAttenuation);
 		}
+		
+		gl.glPopMatrix();
 	}
 	
 	private void setupDirectionLight(GL gl, LightRecord lr, int glUnit, DirectionLight light) {
 		lr.position[3] = 0f;
 		
 		// setup of the direction
-		this.cache.mul(this.factory.getViewTransform(), light.getWorldTransform());		
-		this.factory.getTransformDriver().loadMatrix(gl, this.cache);
-		
-		gl.glLightfv(glUnit, GL.GL_POSITION, lr.position, 0);		
+		this.factory.getTransformDriver().pushMatrix(gl, light.getWorldTransform());
+		gl.glLightfv(glUnit, GL.GL_POSITION, lr.position, 0);
+		gl.glPopMatrix();
 	}
 }

File src/com/ferox/renderer/impl/jogl/drivers/JoglTransformDriver.java

 	private final FloatBuffer matrix;
 	
 	private final Transform currentView;
-	private final Transform modelView;
-	
+	private boolean modelPushed;
 	private JoglSurfaceFactory factory;
 	
 	public JoglTransformDriver(JoglSurfaceFactory factory) {
 		this.matrix = BufferUtil.newFloatBuffer(16);
 		
 		this.currentView = new Transform();
-		this.modelView = new Transform();
+		this.modelPushed = false;
 	}
 	
 	/** Returns the current "view" portion of the modelview
 		gl.glLoadMatrixf(this.matrix);
 	}
 	
+	/** Push and then multiply the current stack by t. 
+	 * Should be paired with gl.glPopMatrix(). */
+	public void pushMatrix(GL gl, Transform t) {
+		gl.glPushMatrix();
+		getOpenGLMatrix(t, this.matrix);
+		gl.glMultMatrixf(this.matrix);
+	}
+	
 	@Override
 	public void setModelTransform(Transform transform) {
-		GL gl = this.factory.getGL();
-			
-		this.modelView.mul(this.currentView, transform);
-		this.loadMatrix(gl, this.modelView);
+		if (!this.modelPushed) {
+			this.pushMatrix(this.factory.getGL(), transform);
+			this.modelPushed = true;
+		} else 
+			this.loadMatrix(this.factory.getGL(), transform);
 	}
 	
 	@Override
 	public void resetModel() {
-		// do nothing
+		if (this.modelPushed) {
+			this.factory.getGL().glPopMatrix();
+			this.modelPushed = false;
+		}
 	}
 
 	@Override
 	public void setView(View view, int width, int height) {
 		GL gl = this.factory.getGL();
-		// setup the viewport
-		setViewport(gl, view.getViewLeft(), view.getViewRight(), view.getViewTop(), view.getViewBottom(), width, height);
 		
-		// set the projection matrix
-		gl.glMatrixMode(GL.GL_PROJECTION);
-		getOpenGLMatrix(view.getProjectionMatrix(), (FloatBuffer) this.matrix.rewind());
-		gl.glLoadMatrixf(this.matrix);
-		
-		// set the view portion of the modelview matrix
-		gl.glMatrixMode(GL.GL_MODELVIEW);
-		this.currentView.set(view.getViewTransform());
+		this.resetModel();
+		if (view != null) {
+			// setup the viewport
+			setViewport(gl, view.getViewLeft(), view.getViewRight(), view.getViewTop(), view.getViewBottom(), width, height);
+
+			// set the projection matrix
+			gl.glMatrixMode(GL.GL_PROJECTION);
+			getOpenGLMatrix(view.getProjectionMatrix(), (FloatBuffer) this.matrix.rewind());
+			gl.glLoadMatrixf(this.matrix);
+
+			// set the view portion of the modelview matrix
+			gl.glMatrixMode(GL.GL_MODELVIEW);
+			this.loadMatrix(gl, view.getViewTransform());
+			this.currentView.set(view.getViewTransform());
+		} else {
+			this.resetView();
+		}
 	}
 
 	@Override
 	public void resetView() {
+		this.factory.getGL().glLoadIdentity();
 		this.currentView.setIdentity();
 	}
 	

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

  *
  */
 public interface Geometry extends Boundable, Resource {
-	
+	/** Return true if the Appearance used in a RenderAtom that's
+	 * rendering this Geometry should be ignored.  This is useful
+	 * if the Geometry has a clearly defined, required Appearance
+	 * that Renderer must use. */
+	public boolean isAppearanceIgnored();
 }

File src/com/ferox/resource/geometry/BufferedGeometry.java

 	 * must be in consecutive units.
 	 * 
 	 * If these rules aren't met, then an exception is thrown. 
-	 * Also, if unit is < 0, then an exception is thrown. */
+	 * Also, if unit is < 1, then an exception is thrown. */
 	public void setVertexAttributes(int unit, T vas, VertexArray accessor) throws NullPointerException, IllegalArgumentException {
 		if (vas != null) {
 			if (accessor == null)
 			
 			if (accessor.getElementSize() != 1 && accessor.getElementSize() != 2 && accessor.getElementSize() != 3 && accessor.getElementSize() != 4)
 				throw new IllegalArgumentException("VertexArray can only have an element size of 1, 2, 3, or 4; not: " + accessor.getElementSize());
-
+			if (unit < 1)
+				throw new IllegalArgumentException("Vertex attributes are numbered from 1+, not: " + unit);
+			
 			this.vertexAttribs.setItem(unit, new GeometryArray<T>(vas, this.getData(vas).getType(), accessor, this.getNumElements(vas, accessor)));
 		} else {
 			this.vertexAttribs.setItem(unit, null);
 	 */
 	
 	@Override
+	public boolean isAppearanceIgnored() {
+		return false;
+	}
+	
+	@Override
 	public void getBounds(BoundVolume result) {
 		this.boundsCache.getBounds(result);
 	}

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

  * @author Michael Ludwig
  *
  */
-// FIXME: vertex attributes should start at 1, not 0
 public class GlslProgram implements Resource {
 	/** The dirty descriptor used by instances of GlslProgram. */
 	public static class GlslProgramDirtyDescriptor {

File src/com/ferox/resource/text/Text.java

 	public void getBounds(BoundVolume result) {
 		this.boundsCache.getBounds(result);
 	}
+	
+	@Override
+	public boolean isAppearanceIgnored() {
+		return false;
+	}
 
 	@Override
 	public float getVertex(int index, int coord) throws IllegalArgumentException {

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

+package com.ferox.scene;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.openmali.vecmath.Vector3f;
+
+import com.ferox.BasicApplication;
+import com.ferox.math.BoundSphere;
+import com.ferox.math.BoundVolume;
+import com.ferox.math.Color;
+import com.ferox.math.Transform;
+import com.ferox.renderer.RenderAtom;
+import com.ferox.renderer.RenderQueue;
+import com.ferox.renderer.Renderer;
+import com.ferox.renderer.util.RenderQueueDataCache;
+import com.ferox.resource.Geometry;
+import com.ferox.resource.geometry.Box;
+import com.ferox.resource.geometry.VertexArrayGeometry;
+import com.ferox.resource.geometry.VertexBufferGeometry;
+import com.ferox.resource.texture.TextureImage;
+import com.ferox.resource.texture.loader.TextureLoader;
+import com.ferox.scene.Fog.FogEquation;
+import com.ferox.state.Appearance;
+import com.ferox.state.FogReceiver;
+import com.ferox.state.LightReceiver;
+import com.ferox.state.Material;
+import com.ferox.state.PolygonStyle;
+import com.ferox.state.Texture;
+import com.ferox.state.FogReceiver.FogCoordSource;
+import com.ferox.state.PolygonStyle.DrawStyle;
+import com.ferox.state.State.Quality;
+import com.ferox.state.Texture.EnvMode;
+import com.ferox.state.Texture.TexCoordGen;
+
+public class DisplayListTest extends BasicApplication {
+	public static final boolean DEBUG = true;
+	public static final boolean USE_VBO = true;
+	public static final boolean RANDOM_PLACEMENT = true;
+	
+	public static final int NUM_CUBES = 10000;
+	public static final int BOUNDS = 100;
+	
+	public static final Color bgColor = new Color(0f, 0f, 0f);
+	
+	protected Geometry displayList;
+	
+	public static void main(String[] args) {
+		new DisplayListTest(DEBUG).run();
+	}
+	
+	public DisplayListTest(boolean debug) {
+		super(debug);
+	}
+
+	@Override
+	protected SceneElement buildScene(Renderer renderer, ViewNode view) {
+		view.getView().setPerspective(60f, this.window.getWidth() / (float) this.window.getHeight(), 1f, 300f);
+		view.getLocalTransform().getTranslation().z = 2f * BOUNDS;
+		
+		Group root = new Group();
+		root.add(view);
+		
+		Light spotLight = new SpotLight();
+		
+		spotLight.setLocalBounds(new BoundSphere(BOUNDS));
+		spotLight.getLocalTransform().getTranslation().set(0f, 0f, 0f);
+		view.add(spotLight);
+		
+		Light directionLight = new DirectionLight(new Vector3f(-1f, -1f, -1f));
+		directionLight.setLocalBounds(new BoundSphere(BOUNDS));
+		root.add(directionLight);
+		
+		Fog fog = new Fog(bgColor, 10f, 300f, 1f, FogEquation.LINEAR, Quality.BEST);
+		fog.setLocalBounds(new BoundSphere(BOUNDS));
+		root.add(fog);
+		
+		Appearance[] apps = this.createAppearances(renderer);
+		PolygonStyle ps = new PolygonStyle();
+		ps.setFrontStyle(DrawStyle.SOLID);
+		ps.setBackStyle(DrawStyle.NONE);
+		for (Appearance a: apps)
+			a.addState(ps);
+		
+		Geometry cube;
+		if (USE_VBO)
+			cube = new VertexBufferGeometry(new Box(2f).requestVboUpdate(renderer, true));
+		else
+			cube = new VertexArrayGeometry(new Box(2f));
+		renderer.requestUpdate(cube, true);
+		renderer.flushRenderer(null);
+		
+		// vars for regular gridding
+		int sideCubeCount = (int) (Math.ceil(Math.pow(NUM_CUBES, 1.0 / 3.0)));
+		float scale = BOUNDS / (float) sideCubeCount;
+		int x = 0;
+		int y = 0;
+		int z = 0;
+		
+		List<RenderAtom> compiledList = new ArrayList<RenderAtom>();
+		for (int i = 0; i < NUM_CUBES; i++) {
+			Vector3f pos = new Vector3f();
+			
+			if (RANDOM_PLACEMENT) {
+				if (i != 0) {
+					// randomly place all but the 1st cube
+					pos.x = (float) (Math.random() * BOUNDS - BOUNDS / 2f);
+					pos.y = (float) (Math.random() * BOUNDS - BOUNDS / 2f);
+					pos.z = (float) (Math.random() * BOUNDS - BOUNDS / 2f);
+				}
+			} else {
+				pos.x = scale * x - BOUNDS / 2f;
+				pos.y = scale * y - BOUNDS / 2f;
+				pos.z = scale * z - BOUNDS / 2f;
+				
+				x++;
+				if (x >= sideCubeCount) {
+					x = 0;
+					y++;
+					if (y >= sideCubeCount) {
+						y = 0;
+						z++;
+					}
+				}
+			}
+			compiledList.add(new DisplayListRenderAtom(cube, apps[(int) ((float) i / NUM_CUBES * apps.length)], pos));
+		}
+		this.displayList = renderer.compile(compiledList);
+		
+		root.add(new Shape(this.displayList, null));
+		
+		return root;
+	}
+	
+	private Appearance[] createAppearances(Renderer renderer) {
+		Appearance[] apps = new Appearance[4];
+		for (int i = 0; i < apps.length; i++)
+			apps[i] = this.createAppearance(renderer, i, apps.length);
+		return apps;
+	}
+	
+	private Appearance createAppearance(Renderer renderer, int i, int max) {
+		float percent = (float) i / max;
+		Material m;
+		
+		if (percent < .3333f) {
+			m = new Material(new Color(1f, percent, percent));
+		} else if (percent < .666667f) {
+			m = new Material(new Color(percent / 2f, 1f, percent / 2f));
+		} else {
+			m = new Material(new Color(percent / 3f, percent / 3f, 1f));
+		}
+
+		LightReceiver lr = null;
+		FogReceiver fr = null;
+		if (i % 2 == 0) {
+			lr = new LightReceiver();
+			m.setSmoothShaded(true);
+		} {
+			fr = new FogReceiver();
+			fr.setFogCoordinateSource(FogCoordSource.FRAGMENT_DEPTH);
+		}
+		
+		Texture t = null;
+		
+		if (i < max / 2) {
+			try {
+				TextureImage image = TextureLoader.readTexture(this.getClass().getClassLoader().getResource("data/textures/squiggles.tga"));
+				renderer.requestUpdate(image, false);
+				t = new Texture(image, EnvMode.MODULATE, new Color(), TexCoordGen.NONE);
+			} catch (IOException io) {
+				throw new RuntimeException(io);
+			}
+		} else {
+			try {
+				TextureImage image = TextureLoader.readTexture(this.getClass().getClassLoader().getResource("data/textures/grace_cube.dds"));
+				renderer.requestUpdate(image, false);
+				t = new Texture(image, EnvMode.MODULATE, new Color(), TexCoordGen.OBJECT);
+			} catch (IOException io) {
+				throw new RuntimeException(io);
+			}
+		}
+		
+		return new Appearance(m, lr, t, fr);
+	}
+	
+	private static class DisplayListRenderAtom implements RenderAtom {
+		private Appearance apperance;
+		private BoundVolume bounds;
+		private Geometry geometry;
+		private Transform transform;
+		private RenderQueueDataCache cache;
+		
+		public DisplayListRenderAtom(Geometry geom, Appearance app, Vector3f location) {
+			this.geometry = geom;
+			this.apperance = app;
+			
+			this.bounds = new BoundSphere();
+			geom.getBounds(this.bounds);
+			
+			this.transform = new Transform(location);
+			this.cache = new RenderQueueDataCache();
+		}
+		
+		@Override
+		public Appearance getAppearance() {
+			return this.apperance;
+		}
+
+		@Override
+		public BoundVolume getBounds() {
+			return this.bounds;
+		}
+
+		@Override
+		public Geometry getGeometry() {
+			return this.geometry;
+		}
+
+		@Override
+		public Object getRenderQueueData(RenderQueue pipe) {
+			return this.cache.getRenderQueueData(pipe);
+		}
+
+		@Override
+		public Transform getTransform() {
+			return this.transform;
+		}
+
+		@Override
+		public void setRenderQueueData(RenderQueue pipe, Object data) {
+			this.cache.setRenderQueueData(pipe, this);
+		}
+	}
+}