Commits

Michael Ludwig committed b0fc96d

Add ability to save and restore current state configuration for renderers in public API, and implement support for FFP renderer.

Comments (0)

Files changed (14)

ferox-renderer/ferox-renderer-api/src/main/java/com/ferox/renderer/ContextState.java

+package com.ferox.renderer;
+
+/**
+ * <p>
+ * ContextState is a tag-interface for objects that hold a complete
+ * representation of a Renderer's state for a context. ContextState instances
+ * are not tied to a specific context or renderer, and can be used to duplicate
+ * state configuration across multiple renderers, or to restore state after a
+ * surface activation.
+ * <p>
+ * ContextState implementations are tied to a particular Framework. A state
+ * representation for one Framework's FixedFunctionRenderer will not work with
+ * another's FixedFunctionRenderer.
+ * 
+ * @author Michael Ludwig
+ * 
+ */
+public interface ContextState<R extends Renderer> {
+
+}

ferox-renderer/ferox-renderer-api/src/main/java/com/ferox/renderer/FixedFunctionRenderer.java

 
     /**
      * <p>
+     * Get the current state configuration for this FixedFunctionRenderer. The
+     * returned instance can be used in {@link #setCurrentState(ContextState)}
+     * with any FixedFunctionRenderer created by the same Framework as this
+     * renderer.
+     * <p>
+     * Because the fixed-function pipeline maintains a large amount of state,
+     * getting and setting the entire state should be used infrequently.
+     * 
+     * @return The current state
+     */
+    public ContextState<FixedFunctionRenderer> getCurrentState();
+
+    /**
+     * <p>
+     * Set the current state of this renderer to equal the given state snapshot.
+     * <tt>state</tt> must have been returned by a prior call to
+     * {@link #getCurrentState()} from a FixedFunctionRenderer created by this
+     * renderer's Framework or behavior is undefined.
+     * <p>
+     * Because the fixed-function pipeline maintains a large amount of state,
+     * getting and setting the entire state should be used infrequently.
+     * 
+     * @param state The state snapshot to update this renderer
+     * @throws NullPointerException if state is null
+     */
+    public void setCurrentState(ContextState<FixedFunctionRenderer> state);
+
+    /**
+     * <p>
      * Set whether or not eye-space fogging is enabled. If this is enabled, each
      * rendered pixel's color value is blended with the configured fog color (at
      * the time of the rendering) based on the fog equation. The fog equation

ferox-renderer/ferox-renderer-api/src/main/java/com/ferox/renderer/GlslRenderer.java

 import com.ferox.resource.VertexAttribute;
 
 public interface GlslRenderer extends Renderer {
+    /**
+     * <p>
+     * Get the current state configuration for this GlslRenderer. The returned
+     * instance can be used in {@link #setCurrentState(ContextState)} with any
+     * GlslRenderer created by the same Framework as this renderer. The returned
+     * snapshot must preserve all of the current uniform and attribute values or
+     * bindings.
+     * <p>
+     * Because the shader pipeline maintains a large amount of state, getting
+     * and setting the entire state should be used infrequently.
+     * 
+     * @return The current state
+     */
+    public ContextState<GlslRenderer> getCurrentState();
+
+    /**
+     * <p>
+     * Set the current state of this renderer to equal the given state snapshot.
+     * <tt>state</tt> must have been returned by a prior call to
+     * {@link #getCurrentState()} from a GlslRenderer created by this renderer's
+     * Framework or behavior is undefined.
+     * <p>
+     * Because the shader pipeline maintains a large amount of state, getting
+     * and setting the entire state should be used infrequently.
+     * 
+     * @param state The state snapshot to update this renderer
+     * @throws NullPointerException if state is null
+     */
+    public void setCurrentState(ContextState<GlslRenderer> state);
+
     // FIXME: for advanced shaders, this is the fragment variable to GL_COLOR_ATTACHMENT0+target
     //    and is configured with glBindFragDataLocation
     // for older shaders, they have to write to glFragData[target], so maybe switch
     public void setUniform(String name, @Const Matrix4 val);
 
     // FIXME should I get rid of the array versions?
+    // I am inclined to say yes, especially now that state snapshots require me
+    // to track everything and that's not feasible for entire arrays.
+    // - Somehow I must incorporate UniformBuffers as resource types too
     public void setUniform(String name, float[] vals);
 
     public void setUniform(String name, int val);

ferox-renderer/ferox-renderer-api/src/main/java/com/ferox/renderer/Renderer.java

  * Renderers are linked to the {@link Context} that produced them because each
  * Context has a unique set of state that is controlled by the renderer. Use the
  * {@link HardwareAccessLayer#setActiveSurface(Surface)} to get the context of a
- * surface, and then use the context's renderer to render into the just
- * activated surface.
+ * surface, and then use the context's renderer to render into the activated
+ * surface.
  * <p>
  * There are two sub-implementations of Renderer that provide access to the old
  * fixed-function pipeline in OpenGL, and a shader based Renderer that is

ferox-renderer/ferox-renderer-impl/src/main/java/com/ferox/renderer/impl/AbstractFixedFunctionRenderer.java

 import com.ferox.math.Matrix4;
 import com.ferox.math.Vector3;
 import com.ferox.math.Vector4;
+import com.ferox.renderer.ContextState;
 import com.ferox.renderer.FixedFunctionRenderer;
+import com.ferox.renderer.RenderCapabilities;
 import com.ferox.renderer.Renderer;
+import com.ferox.renderer.impl.FixedFunctionState.FogMode;
+import com.ferox.renderer.impl.FixedFunctionState.LightColor;
+import com.ferox.renderer.impl.FixedFunctionState.LightState;
+import com.ferox.renderer.impl.FixedFunctionState.MatrixMode;
+import com.ferox.renderer.impl.FixedFunctionState.TextureState;
+import com.ferox.renderer.impl.FixedFunctionState.VertexState;
+import com.ferox.renderer.impl.FixedFunctionState.VertexTarget;
 import com.ferox.renderer.impl.drivers.TextureHandle;
 import com.ferox.renderer.impl.drivers.VertexBufferObjectHandle;
 import com.ferox.resource.BufferData.DataType;
  * @author Michael Ludwig
  */
 public abstract class AbstractFixedFunctionRenderer extends AbstractRenderer implements FixedFunctionRenderer {
-    /**
-     * FogMode represents the three different eye fog modes that are available
-     * in OpenGL.
-     */
-    protected static enum FogMode {
-        LINEAR, EXP, EXP_SQUARED
-    }
-
-    /**
-     * When configuring lighting and material colors, OpenGL uses the same
-     * functions to control the different types of color. For light colors, the
-     * EMISSIVE enum is unused, since it's only available for material colors.
-     */
-    protected static enum LightColor {
-        AMBIENT, DIFFUSE, SPECULAR, EMISSIVE
-    }
-
-    /**
-     * OpenGL provides only one way to update matrices, and to switch between
-     * matrix types, you must set the current mode.
-     */
-    protected static enum MatrixMode {
-        MODELVIEW, PROJECTION, TEXTURE
-    }
-
-    protected static enum VertexTarget {
-        VERTICES, NORMALS, TEXCOORDS, COLORS
-    }
-
-    // cached defaults
     private static final Matrix4 IDENTITY = new Matrix4();
-
-    private static final Vector4 DEFAULT_MAT_A_COLOR = new Vector4(.2, .2, .2, 1);
     private static final Vector4 DEFAULT_MAT_D_COLOR = new Vector4(.8, .8, .8, 1);
 
-    private static final Vector4 ZERO = new Vector4(0, 0, 0, 0);
-    private static final Vector4 BLACK = new Vector4(0, 0, 0, 1);
-    private static final Vector4 WHITE = new Vector4(1, 1, 1, 1);
+    protected FixedFunctionState state;
+    protected FixedFunctionState defaultState;
 
-    private static final Vector4 DEFAULT_LIGHT_POS = new Vector4(0, 0, 1, 0);
-    private static final Vector3 DEFAULT_SPOT_DIR = new Vector3(0, 0, -1);
+    // private handles tracking the actual resource locks of current state
+    private VertexBufferObjectHandle verticesHandle;
+    private VertexBufferObjectHandle normalsHandle;
+    private VertexBufferObjectHandle colorsHandle;
+    private VertexBufferObjectHandle[] texCoordsHandles;
 
-    private static final Vector4 DEFAULT_S_PLANE = new Vector4(1, 0, 0, 0);
-    private static final Vector4 DEFAULT_T_PLANE = new Vector4(0, 1, 0, 0);
-    private static final Vector4 DEFAULT_RQ_PLANE = new Vector4(0, 0, 0, 0);
+    private TextureHandle[] textureHandles;
 
-    /**
-     * An inner class that contains per-light state. Although it's accessible to
-     * sub-classes, it should be considered read-only because the
-     * AbstractFixedFunctionRenderer manages the updates to its variables.
-     */
-    protected class LightState {
-        // LightState does not track position or direction since
-        // they're stored by OpenGL after being modified by the current MV matrix
-        private boolean modifiedSinceReset = false;
+    // hidden state that is used to minimize opengl calls
+    private VertexBufferObject arrayVboBinding;
+    private int activeArrayVbos;
+    private int activeClientTex;
+    private int activeTex;
 
-        public final Vector4 ambient = new Vector4(BLACK);
-        public final Vector4 specular = new Vector4(BLACK);
-        public final Vector4 diffuse = new Vector4(BLACK);
-
-        public double constAtt = 1;
-        public double linAtt = 0;
-        public double quadAtt = 0;
-
-        public double spotAngle = 180;
-
-        public boolean enabled = false;
-    }
-
-    /**
-     * An inner class that contains per-texture unit state. Although it's
-     * accessible to sub-classes, it should be considered read-only because the
-     * AbstractFixedFunctionRenderer manages the updates to its variables.
-     */
-    protected class TextureState {
-        public final int unit;
-
-        // TextureState does not track texture transforms, or eye planes
-        // since these are difficult to track
-        public boolean transformModifiedSinceReset = false;
-
-        public Texture texture;
-        public TextureHandle handle;
-
-        public Vector4 color = new Vector4(0, 0, 0, 0);
-
-        public TexCoordSource tcS = TexCoordSource.ATTRIBUTE;
-        public TexCoordSource tcT = TexCoordSource.ATTRIBUTE;
-        public TexCoordSource tcR = TexCoordSource.ATTRIBUTE;
-        public TexCoordSource tcQ = TexCoordSource.ATTRIBUTE;
-
-        public final Vector4 objPlaneS = new Vector4(DEFAULT_S_PLANE);
-        public final Vector4 objPlaneT = new Vector4(DEFAULT_T_PLANE);
-        public final Vector4 objPlaneR = new Vector4(DEFAULT_RQ_PLANE);
-        public final Vector4 objPlaneQ = new Vector4(DEFAULT_RQ_PLANE);
-
-        public CombineFunction rgbFunc = CombineFunction.MODULATE;
-        public CombineFunction alphaFunc = CombineFunction.MODULATE;
-
-        public final CombineOperand[] opRgb = {CombineOperand.COLOR,
-                                               CombineOperand.COLOR, CombineOperand.ALPHA};
-        public final CombineOperand[] opAlpha = {CombineOperand.ALPHA,
-                                                 CombineOperand.ALPHA,
-                                                 CombineOperand.ALPHA};
-
-        public final CombineSource[] srcRgb = {CombineSource.CURR_TEX,
-                                               CombineSource.PREV_TEX,
-                                               CombineSource.CONST_COLOR};
-        public final CombineSource[] srcAlpha = {CombineSource.CURR_TEX,
-                                                 CombineSource.PREV_TEX,
-                                                 CombineSource.CONST_COLOR};
-
-        public TextureState(int unit) {
-            this.unit = unit;
-        }
-    }
-
-    protected class VertexState {
-        // Used to handle relocking/unlocking
-        public final VertexTarget target;
-        public final int slot;
-
-        public VertexBufferObject vbo;
-        public VertexBufferObjectHandle handle;
-
-        public int offset;
-        public int stride;
-        public int elementSize;
-
-        private VertexState(VertexTarget target, int slot) {
-            this.target = target;
-            this.slot = slot;
-        }
-
-        public void activateSlot() {
-            if (target == VertexTarget.TEXCOORDS && slot != activeClientTex) {
-                // Special case slot handling for texture coordinates (other targets ignore slot)
-                activeClientTex = slot;
-                glActiveClientTexture(slot);
-            }
-        }
-    }
-
-    // alpha test
-    protected Comparison alphaTest = Comparison.ALWAYS;
-    protected double alphaRefValue = 1;
-
-    // fog
-    protected final Vector4 fogColor = new Vector4(ZERO);
-
-    protected double fogStart = 0;
-    protected double fogEnd = 1;
-    protected double fogDensity = 1;
-
-    protected FogMode fogMode = FogMode.EXP;
-    protected boolean fogEnabled = false;
-
-    // global lighting
-    protected final Vector4 globalAmbient = new Vector4(DEFAULT_MAT_A_COLOR);
-    protected boolean lightingEnabled = false;
-    protected boolean lightingTwoSided = false;
-    protected boolean lightingSmoothed = true;
-
-    // lights
-    protected LightState[] lights; // "final"
-
-    // material
-    protected final Vector4 matDiffuse = new Vector4(DEFAULT_MAT_D_COLOR);
-    protected final Vector4 matAmbient = new Vector4(DEFAULT_MAT_A_COLOR);
-    protected final Vector4 matSpecular = new Vector4(BLACK);
-    protected final Vector4 matEmmissive = new Vector4(BLACK);
-
-    protected double matShininess = 0;
-
-    // primitive size/aa
-    protected boolean lineAAEnabled = false;
-    protected boolean pointAAEnabled = false;
-    protected boolean polyAAEnabled = false;
-
-    protected double lineWidth = 1;
-    protected double pointWidth = 1;
-
-    // texturing
-    protected int activeTex = 0;
-    protected TextureState[] textures = null; // "final"
-
-    // bindings for vbos and rendering
-    protected final VertexState vertexBinding = new VertexState(VertexTarget.VERTICES, 0);
-    protected final VertexState normalBinding = new VertexState(VertexTarget.NORMALS, 0);
-    protected final VertexState colorBinding = new VertexState(VertexTarget.COLORS, 0);
-    protected VertexState[] texBindings = null; // "final"
-
-    protected VertexBufferObject arrayVboBinding = null;
-    protected int activeArrayVbos = 0;
-    protected int activeClientTex = 0;
-
-    // matrix
-    protected MatrixMode matrixMode = MatrixMode.MODELVIEW;
-    private final Matrix4 dirtyModelView = new Matrix4(); // last set model view that hasn't been sent yet
-    private boolean isModelViewDirty = true;
+    private boolean isModelViewDirty;
+    private final Matrix4 inverseModelView; // needed for eye-plane texture coordinates
+    private boolean isModelInverseDirty;
 
     /**
      * Create an AbstractFixedFunctionRenderer that will use the given
      */
     public AbstractFixedFunctionRenderer(RendererDelegate delegate) {
         super(delegate);
+        inverseModelView = new Matrix4();
+        isModelInverseDirty = true;
     }
 
     @Override
                          ResourceManager resourceManager) {
         super.activate(surface, context, resourceManager);
 
-        // Complete initialization the first time we're activated. We can't do this
-        // in the constructor because we didn't have a framework at that point.
-        if (lights == null) {
-            int numLights = surface.getFramework().getCapabilities().getMaxActiveLights();
+        if (state == null) {
+            // init state
+            RenderCapabilities caps = surface.getFramework().getCapabilities();
+            state = new FixedFunctionState(caps.getMaxActiveLights(),
+                                           caps.getMaxFixedPipelineTextures(),
+                                           caps.getMaxTextureCoordinates());
+            defaultState = new FixedFunctionState(delegate.defaultState, state);
 
-            lights = new LightState[numLights];
-            for (int i = 0; i < lights.length; i++) {
-                lights[i] = new LightState();
-            }
-            // modify 0th light's colors
-            lights[0].specular.set(WHITE);
-            lights[0].diffuse.set(WHITE);
-        }
-
-        if (textures == null) {
-            int numTextures = surface.getFramework().getCapabilities()
-                                     .getMaxFixedPipelineTextures();
-
-            textures = new TextureState[numTextures];
-            for (int i = 0; i < textures.length; i++) {
-                textures[i] = new TextureState(i);
-            }
-        }
-
-        if (texBindings == null) {
-            int numBindings = surface.getFramework().getCapabilities()
-                                     .getMaxTextureCoordinates();
-
-            texBindings = new VertexState[numBindings];
-            for (int i = 0; i < texBindings.length; i++) {
-                texBindings[i] = new VertexState(VertexTarget.TEXCOORDS, i);
-            }
+            textureHandles = new TextureHandle[caps.getMaxFixedPipelineTextures()];
+            texCoordsHandles = new VertexBufferObjectHandle[caps.getMaxTextureCoordinates()];
         }
     }
 
     @Override
     public void reset() {
-        super.reset();
+        // this also takes care of setting the delegate's default state
+        setCurrentState(defaultState);
+    }
 
+    @Override
+    public ContextState<FixedFunctionRenderer> getCurrentState() {
+        return new FixedFunctionState(delegate.getCurrentState(), state);
+    }
+
+    @Override
+    public void setCurrentState(ContextState<FixedFunctionRenderer> state) {
+        FixedFunctionState f = (FixedFunctionState) state;
+
+        setAlphaTest(f.alphaTest, f.alphaRefValue);
+        setFogColor(f.fogColor);
+        switch (f.fogMode) {
+        case EXP:
+            setFogExponential(f.fogDensity, false);
+            break;
+        case EXP_SQUARED:
+            setFogExponential(f.fogDensity, true);
+            break;
+        case LINEAR:
+            setFogLinear(f.fogStart, f.fogEnd);
+            break;
+        default:
+            throw new UnsupportedOperationException("Unsupported FogMode value: " + f.fogMode);
+        }
+        setFogEnabled(f.fogEnabled);
+
+        setGlobalAmbientLight(f.globalAmbient);
+        setLightingEnabled(f.lightingEnabled);
+        setSmoothedLightingEnabled(f.lightingSmoothed);
+        setTwoSidedLightingEnabled(f.lightingTwoSided);
+
+        setMaterial(f.matAmbient, f.matDiffuse, f.matSpecular, f.matEmmissive);
+        setMaterialShininess(f.matShininess);
+
+        setLineAntiAliasingEnabled(f.lineAAEnabled);
+        setPointAntiAliasingEnabled(f.pointAAEnabled);
+        setPolygonAntiAliasingEnabled(f.pointAAEnabled);
+
+        setLineSize(f.lineWidth);
+        setPointSize(f.pointWidth);
+
+        setProjectionMatrix(f.projection);
+
+        // set the modelview to the identity matrix, since the subsequent state
+        // is modified by the current modelview, but we store the post-transform
         setModelViewMatrix(IDENTITY);
-        setProjectionMatrix(IDENTITY);
-
-        setAlphaTest(Comparison.ALWAYS, 1f);
-
-        setFogColor(ZERO);
-        setFogExponential(1f, false);
-        setFogEnabled(false);
-
-        setGlobalAmbientLight(DEFAULT_MAT_A_COLOR);
-        setLightingEnabled(false);
-        setSmoothedLightingEnabled(true);
-        setTwoSidedLightingEnabled(false);
-
-        setMaterial(DEFAULT_MAT_A_COLOR, DEFAULT_MAT_D_COLOR, BLACK, BLACK);
-        setMaterialShininess(0f);
-
-        setLineAntiAliasingEnabled(false);
-        setPointAntiAliasingEnabled(false);
-        setPolygonAntiAliasingEnabled(false);
-
-        setLineSize(1f);
-        setPointSize(1f);
-
-        // reset all lights
-        for (int i = 0; i < lights.length; i++) {
-            setLightEnabled(i, false);
-            if (lights[i].modifiedSinceReset) {
-                // special check to avoid repeated no-point calls
-                setLightPosition(i, DEFAULT_LIGHT_POS);
-                setSpotlight(i, DEFAULT_SPOT_DIR, 180.0);
-                lights[i].modifiedSinceReset = false;
-            }
-
-            if (i == 0) {
-                setLightColor(i, BLACK, WHITE, WHITE);
-            } else {
-                setLightColor(i, BLACK, BLACK, BLACK);
-            }
-
-            setLightAttenuation(i, 1.0, 0.0, 0.0);
+        for (int i = 0; i < f.lights.length; i++) {
+            LightState fl = f.lights[i];
+            setLightEnabled(i, fl.enabled);
+            setLightPosition(i, fl.position);
+            setSpotlight(i, fl.spotlightDirection, fl.spotAngle);
+            setLightColor(i, fl.ambient, fl.diffuse, fl.specular);
+            setLightAttenuation(i, fl.constAtt, fl.linAtt, fl.quadAtt);
         }
 
-        // reset all textures
-        for (int i = 0; i < textures.length; i++) {
-            setTexture(i, null);
+        for (int i = 0; i < f.textures.length; i++) {
+            TextureState tf = f.textures[i];
+            setTexture(i, tf.texture);
 
-            setTextureColor(i, ZERO);
+            setTextureColor(i, tf.color);
 
-            setTextureCombineRGB(i, CombineFunction.MODULATE, CombineSource.CURR_TEX,
-                                 CombineOperand.COLOR, CombineSource.PREV_TEX,
-                                 CombineOperand.COLOR, CombineSource.CONST_COLOR,
-                                 CombineOperand.ALPHA);
-            setTextureCombineAlpha(i, CombineFunction.MODULATE, CombineSource.CURR_TEX,
-                                   CombineOperand.ALPHA, CombineSource.PREV_TEX,
-                                   CombineOperand.ALPHA, CombineSource.CONST_COLOR,
-                                   CombineOperand.ALPHA);
+            setTextureCombineRGB(i, tf.rgbFunc, tf.srcRgb[0], tf.opRgb[0], tf.srcRgb[1],
+                                 tf.opRgb[1], tf.srcRgb[2], tf.opRgb[2]);
+            setTextureCombineAlpha(i, tf.alphaFunc, tf.srcAlpha[0], tf.opAlpha[0],
+                                   tf.srcAlpha[1], tf.opAlpha[1], tf.srcAlpha[2],
+                                   tf.opAlpha[2]);
 
-            setTextureCoordGeneration(i, TexCoordSource.ATTRIBUTE);
+            setTextureCoordGeneration(i, TexCoord.S, tf.tcS);
+            setTextureCoordGeneration(i, TexCoord.T, tf.tcT);
+            setTextureCoordGeneration(i, TexCoord.R, tf.tcR);
+            setTextureCoordGeneration(i, TexCoord.Q, tf.tcQ);
 
-            if (textures[i].transformModifiedSinceReset) {
-                // special check to only do it if something was modified
-                setTextureTransform(i, IDENTITY);
-                setTextureEyePlane(i, TexCoord.S, DEFAULT_S_PLANE);
-                setTextureEyePlane(i, TexCoord.T, DEFAULT_T_PLANE);
-                setTextureEyePlane(i, TexCoord.R, DEFAULT_RQ_PLANE);
-                setTextureEyePlane(i, TexCoord.Q, DEFAULT_RQ_PLANE);
-                textures[i].transformModifiedSinceReset = false;
-            }
+            setTextureObjectPlane(i, TexCoord.S, tf.objPlaneS);
+            setTextureObjectPlane(i, TexCoord.T, tf.objPlaneT);
+            setTextureObjectPlane(i, TexCoord.R, tf.objPlaneR);
+            setTextureObjectPlane(i, TexCoord.Q, tf.objPlaneQ);
 
-            setTextureObjectPlane(i, TexCoord.S, DEFAULT_S_PLANE);
-            setTextureObjectPlane(i, TexCoord.T, DEFAULT_T_PLANE);
-            setTextureObjectPlane(i, TexCoord.R, DEFAULT_RQ_PLANE);
-            setTextureObjectPlane(i, TexCoord.Q, DEFAULT_RQ_PLANE);
+            setTextureObjectPlane(i, TexCoord.S, tf.eyePlaneS);
+            setTextureObjectPlane(i, TexCoord.T, tf.eyePlaneT);
+            setTextureObjectPlane(i, TexCoord.R, tf.eyePlaneR);
+            setTextureObjectPlane(i, TexCoord.Q, tf.eyePlaneQ);
+
+            setTextureTransform(i, tf.textureMatrix);
         }
 
-        // reset attribute binding
-        setVertices(null);
-        setNormals(null);
-        setColors(null);
-        for (int i = 0; i < texBindings.length; i++) {
-            setTextureCoordinates(i, null);
+        if (f.vertexBinding.vbo == null) {
+            setVertices(null);
+        } else {
+            setVertices(new VertexAttribute(f.vertexBinding.vbo,
+                                            f.vertexBinding.elementSize,
+                                            f.vertexBinding.offset,
+                                            f.vertexBinding.stride));
         }
+        if (f.normalBinding.vbo == null) {
+            setNormals(null);
+        } else {
+            setNormals(new VertexAttribute(f.normalBinding.vbo,
+                                           f.normalBinding.elementSize,
+                                           f.normalBinding.offset,
+                                           f.normalBinding.stride));
+        }
+        if (f.colorBinding.vbo == null) {
+            setColors(null);
+        } else {
+            setColors(new VertexAttribute(f.colorBinding.vbo,
+                                          f.colorBinding.elementSize,
+                                          f.colorBinding.offset,
+                                          f.colorBinding.stride));
+        }
+
+        for (int i = 0; i < f.texBindings.length; i++) {
+            VertexState fv = f.texBindings[i];
+            if (fv.vbo == null) {
+                setTextureCoordinates(i, null);
+            } else {
+                setTextureCoordinates(i, new VertexAttribute(fv.vbo,
+                                                             fv.elementSize,
+                                                             fv.offset,
+                                                             fv.stride));
+            }
+        }
+
+        // set true modelview matrix
+        setModelViewMatrix(f.modelView);
+
+        // set shared state
+        delegate.setCurrentState(f.sharedState);
     }
 
     @Override
             throw new NullPointerException("Matrix cannot be null");
         }
 
-        dirtyModelView.set(matrix);
+        state.modelView.set(matrix);
         isModelViewDirty = true;
+        isModelInverseDirty = true;
     }
 
     /**
     protected abstract void glSetMatrix(@Const Matrix4 matrix);
 
     private void setMatrixMode(MatrixMode mode) {
-        if (matrixMode != mode) {
-            matrixMode = mode;
+        if (state.matrixMode != mode) {
+            state.matrixMode = mode;
             glMatrixMode(mode);
         }
     }
     private void flushModelView() {
         if (isModelViewDirty) {
             setMatrixMode(MatrixMode.MODELVIEW);
-            glSetMatrix(dirtyModelView);
+            glSetMatrix(state.modelView);
             isModelViewDirty = false;
         }
     }
             throw new NullPointerException("Matrix cannot be null");
         }
 
+        state.projection.set(matrix);
         setMatrixMode(MatrixMode.PROJECTION);
         glSetMatrix(matrix);
     }
         if (test == null) {
             throw new NullPointerException("Null comparison");
         }
-        if (alphaTest != test || alphaRefValue != refValue) {
-            alphaTest = test;
-            alphaRefValue = refValue;
+        if (state.alphaTest != test || state.alphaRefValue != refValue) {
+            state.alphaTest = test;
+            state.alphaRefValue = refValue;
             glAlphaTest(test, refValue);
         }
     }
         if (color == null) {
             throw new NullPointerException("Null fog color");
         }
-        if (!fogColor.equals(color)) {
-            fogColor.set(color);
+        if (!state.fogColor.equals(color)) {
+            state.fogColor.set(color);
             glFogColor(color);
         }
     }
 
     @Override
     public void setFogEnabled(boolean enable) {
-        if (fogEnabled != enable) {
-            fogEnabled = enable;
+        if (state.fogEnabled != enable) {
+            state.fogEnabled = enable;
             glEnableFog(enable);
         }
     }
             throw new IllegalArgumentException("Density must be >= 0, not: " + density);
         }
 
-        if (fogDensity != density) {
-            fogDensity = density;
+        if (state.fogDensity != density) {
+            state.fogDensity = density;
             glFogDensity(density);
         }
 
-        if (squared && fogMode != FogMode.EXP_SQUARED) {
-            fogMode = FogMode.EXP_SQUARED;
+        if (squared && state.fogMode != FogMode.EXP_SQUARED) {
+            state.fogMode = FogMode.EXP_SQUARED;
             glFogMode(FogMode.EXP_SQUARED);
-        } else if (fogMode != FogMode.EXP) {
-            fogMode = FogMode.EXP;
+        } else if (state.fogMode != FogMode.EXP) {
+            state.fogMode = FogMode.EXP;
             glFogMode(FogMode.EXP);
         }
     }
             throw new IllegalArgumentException("Illegal start/end range: " + start + ", " + end);
         }
 
-        if (fogStart != start || fogEnd != end) {
-            fogStart = start;
-            fogEnd = end;
+        if (state.fogStart != start || state.fogEnd != end) {
+            state.fogStart = start;
+            state.fogEnd = end;
             glFogRange(start, end);
         }
 
-        if (fogMode != FogMode.LINEAR) {
-            fogMode = FogMode.LINEAR;
+        if (state.fogMode != FogMode.LINEAR) {
+            state.fogMode = FogMode.LINEAR;
             glFogMode(FogMode.LINEAR);
         }
     }
         if (ambient == null) {
             throw new NullPointerException("Null global ambient color");
         }
-        if (!globalAmbient.equals(ambient)) {
-            clamp(ambient, 0, Float.MAX_VALUE, globalAmbient);
-            glGlobalLighting(globalAmbient);
+        if (!state.globalAmbient.equals(ambient)) {
+            clamp(ambient, 0, Float.MAX_VALUE, state.globalAmbient);
+            glGlobalLighting(state.globalAmbient);
         }
     }
 
             throw new NullPointerException("Colors cannot be null");
         }
 
-        LightState l = lights[light];
+        LightState l = state.lights[light];
         if (!l.ambient.equals(amb)) {
             clamp(amb, 0, Float.MAX_VALUE, l.ambient);
             glLightColor(light, LightColor.AMBIENT, l.ambient);
 
     @Override
     public void setLightEnabled(int light, boolean enable) {
-        LightState l = lights[light];
+        LightState l = state.lights[light];
         if (l.enabled != enable) {
             l.enabled = enable;
             glEnableLight(light, enable);
             throw new NullPointerException("pos.w must be 0 or 1, not: " + pos.w);
         }
 
-        // always set the light position since pos will be transformed by
-        // the current matrix
-        lights[light].modifiedSinceReset = true;
+        // compute the eye-space light position
+        state.lights[light].position.mul(state.lights[light].position, state.modelView);
         flushModelView();
         glLightPosition(light, pos);
     }
             throw new IllegalArgumentException("Spotlight angle must be in [0, 90] or be 180, not: " + angle);
         }
 
-        LightState l = lights[light];
+        LightState l = state.lights[light];
         if (l.spotAngle != angle) {
             l.spotAngle = angle;
             glLightAngle(light, angle);
         }
 
-        // always set the spotlight direction since it will be transformed
-        // by the current matrix
-        l.modifiedSinceReset = true;
+        // compute eye-space spotlight direction
+        l.spotlightDirection.transform(state.modelView, l.spotlightDirection, 0);
         flushModelView();
         glLightDirection(light, dir);
     }
             throw new IllegalArgumentException("Quadratic factor must be positive: " + quadratic);
         }
 
-        LightState l = lights[light];
+        LightState l = state.lights[light];
         if (l.constAtt != constant || l.linAtt != linear || l.quadAtt != quadratic) {
             l.constAtt = constant;
             l.linAtt = linear;
 
     @Override
     public void setLightingEnabled(boolean enable) {
-        if (lightingEnabled != enable) {
-            lightingEnabled = enable;
+        if (state.lightingEnabled != enable) {
+            state.lightingEnabled = enable;
             glEnableLighting(enable);
         }
     }
 
     @Override
     public void setSmoothedLightingEnabled(boolean smoothed) {
-        if (lightingSmoothed != smoothed) {
-            lightingSmoothed = smoothed;
+        if (state.lightingSmoothed != smoothed) {
+            state.lightingSmoothed = smoothed;
             glEnableSmoothShading(smoothed);
         }
     }
 
     @Override
     public void setTwoSidedLightingEnabled(boolean enable) {
-        if (lightingTwoSided != enable) {
-            lightingTwoSided = enable;
+        if (state.lightingTwoSided != enable) {
+            state.lightingTwoSided = enable;
             glEnableTwoSidedLighting(enable);
         }
     }
 
     @Override
     public void setLineAntiAliasingEnabled(boolean enable) {
-        if (lineAAEnabled != enable) {
-            lineAAEnabled = enable;
+        if (state.lineAAEnabled != enable) {
+            state.lineAAEnabled = enable;
             glEnableLineAntiAliasing(enable);
         }
     }
         if (width < 1f) {
             throw new IllegalArgumentException("Line width must be at least 1, not: " + width);
         }
-        if (lineWidth != width) {
-            lineWidth = width;
+        if (state.lineWidth != width) {
+            state.lineWidth = width;
             glLineWidth(width);
         }
     }
         if (amb == null || diff == null || spec == null || emm == null) {
             throw new NullPointerException("Material colors can't be null: " + amb + ", " + diff + ", " + spec + ", " + emm);
         }
-        if (!matAmbient.equals(amb)) {
-            clamp(amb, 0, 1, matAmbient);
-            glMaterialColor(LightColor.AMBIENT, matAmbient);
+        if (!state.matAmbient.equals(amb)) {
+            clamp(amb, 0, 1, state.matAmbient);
+            glMaterialColor(LightColor.AMBIENT, state.matAmbient);
         }
 
-        if (!matDiffuse.equals(diff)) {
-            clamp(diff, 0, 1, matDiffuse);
-            glMaterialColor(LightColor.DIFFUSE, matDiffuse);
+        if (!state.matDiffuse.equals(diff)) {
+            clamp(diff, 0, 1, state.matDiffuse);
+            glMaterialColor(LightColor.DIFFUSE, state.matDiffuse);
         }
 
-        if (!matSpecular.equals(spec)) {
-            matSpecular.set(spec);
-            clamp(spec, 0, 1, matSpecular);
-            glMaterialColor(LightColor.SPECULAR, matSpecular);
+        if (!state.matSpecular.equals(spec)) {
+            state.matSpecular.set(spec);
+            clamp(spec, 0, 1, state.matSpecular);
+            glMaterialColor(LightColor.SPECULAR, state.matSpecular);
         }
 
-        if (!matEmmissive.equals(emm)) {
-            clamp(emm, 0, Float.MAX_VALUE, matEmmissive);
-            glMaterialColor(LightColor.EMISSIVE, matEmmissive);
+        if (!state.matEmmissive.equals(emm)) {
+            clamp(emm, 0, Float.MAX_VALUE, state.matEmmissive);
+            glMaterialColor(LightColor.EMISSIVE, state.matEmmissive);
         }
     }
 
         if (shininess < 0.0 || shininess > 128.0) {
             throw new IllegalArgumentException("Shininess must be in [0, 128], not: " + shininess);
         }
-        if (matShininess != shininess) {
-            matShininess = shininess;
+        if (state.matShininess != shininess) {
+            state.matShininess = shininess;
             glMaterialShininess(shininess);
         }
     }
 
     @Override
     public void setPointAntiAliasingEnabled(boolean enable) {
-        if (pointAAEnabled != enable) {
-            pointAAEnabled = enable;
+        if (state.pointAAEnabled != enable) {
+            state.pointAAEnabled = enable;
             glEnablePointAntiAliasing(enable);
         }
     }
         if (width < 1.0) {
             throw new IllegalArgumentException("Point width must be at least 1, not: " + width);
         }
-        if (pointWidth != width) {
-            pointWidth = width;
+        if (state.pointWidth != width) {
+            state.pointWidth = width;
             glPointWidth(width);
         }
     }
 
     @Override
     public void setPolygonAntiAliasingEnabled(boolean enable) {
-        if (polyAAEnabled != enable) {
-            polyAAEnabled = enable;
+        if (state.polyAAEnabled != enable) {
+            state.polyAAEnabled = enable;
             glEnablePolyAntiAliasing(enable);
         }
     }
 
     @Override
     public void setTexture(int tex, Texture image) {
-        TextureState t = textures[tex];
+        TextureState t = state.textures[tex];
         if (t.texture != image) {
             // Release current texture if need-be
             Target oldTarget = null;
             if (t.texture != null) {
                 resourceManager.unlock(t.texture);
-                oldTarget = t.handle.target;
+                oldTarget = textureHandles[tex].target;
                 t.texture = null;
-                t.handle = null;
+                textureHandles[tex] = null;
             }
 
             // Lock new texture if needed
                     newTarget = newHandle.target;
 
                     t.texture = image;
-                    t.handle = newHandle;
+                    textureHandles[tex] = newHandle;
                 }
             }
 
             throw new NullPointerException("Texture color can't be null");
         }
 
-        TextureState t = textures[tex];
+        TextureState t = state.textures[tex];
         if (!t.color.equals(color)) {
             t.color.set(color);
             setTextureUnit(tex);
             throw new NullPointerException("Arguments cannot be null");
         }
 
-        TextureState t = textures[tex];
+        TextureState t = state.textures[tex];
         setTextureUnit(tex);
         if (t.rgbFunc != function) {
             t.rgbFunc = function;
         checkAlphaCombineOperand(op1);
         checkAlphaCombineOperand(op2);
 
-        TextureState t = textures[tex];
+        TextureState t = state.textures[tex];
         setTextureUnit(tex);
         if (t.alphaFunc != function) {
             t.alphaFunc = function;
         if (gen == null) {
             throw new NullPointerException("TexCoordSource can't be null");
         }
-        if (tex < 0) {
-            throw new IllegalArgumentException("Texture unit must be at least 0, not: " + tex);
-        }
-        if (tex >= textures.length) {
-            return; // Ignore it
-        }
 
-        TextureState t = textures[tex];
+        TextureState t = state.textures[tex];
         switch (coord) {
         case S:
             if (t.tcS != gen) {
         if (coord == null) {
             throw new NullPointerException("TexCoord cannot be null");
         }
-        if (tex < 0) {
-            throw new IllegalArgumentException("Texture unit must be at least 0, not: " + tex);
-        }
-        if (tex >= textures.length) {
-            return; // Ignore it
+
+        TextureState t = state.textures[tex];
+
+        if (isModelInverseDirty) {
+            // update inverse matrix
+            inverseModelView.inverse(state.modelView);
+            isModelInverseDirty = false;
         }
 
-        // always send plane
+        switch (coord) {
+        case S:
+            t.eyePlaneS.mul(t.eyePlaneS, inverseModelView);
+            break;
+        case T:
+            t.eyePlaneT.mul(t.eyePlaneT, inverseModelView);
+            break;
+        case R:
+            t.eyePlaneR.mul(t.eyePlaneR, inverseModelView);
+            break;
+        case Q:
+            t.eyePlaneQ.mul(t.eyePlaneQ, inverseModelView);
+            break;
+        }
+
         flushModelView();
-        textures[tex].transformModifiedSinceReset = true;
-
         setTextureUnit(tex);
         glTexEyePlane(coord, plane);
     }
         if (coord == null) {
             throw new NullPointerException("TexCoord cannot be null");
         }
-        if (tex < 0) {
-            throw new IllegalArgumentException("Texture unit must be at least 0, not: " + tex);
-        }
-        if (tex >= textures.length) {
-            return; // Ignore it
-        }
 
-        TextureState t = textures[tex];
+        TextureState t = state.textures[tex];
         switch (coord) {
         case S:
             if (!t.objPlaneS.equals(plane)) {
         if (matrix == null) {
             throw new NullPointerException("Matrix cannot be null");
         }
-        if (tex < 0) {
-            throw new IllegalArgumentException("Texture unit must be at least 0, not: " + tex);
-        }
-        if (tex >= textures.length) {
-            return; // Ignore it
-        }
 
-        // always send texture matrix
-        textures[tex].transformModifiedSinceReset = true;
+        state.textures[tex].textureMatrix.set(matrix);
 
         setTextureUnit(tex);
         setMatrixMode(MatrixMode.TEXTURE);
         if (vertices != null && vertices.getElementSize() == 1) {
             throw new IllegalArgumentException("Vertices element size cannot be 1");
         }
-        setAttribute(vertexBinding, vertices);
+        verticesHandle = setAttribute(state.vertexBinding, verticesHandle, vertices);
     }
 
     @Override
         if (normals != null && normals.getElementSize() != 3) {
             throw new IllegalArgumentException("Normals element size must be 3");
         }
-        setAttribute(normalBinding, normals);
+        normalsHandle = setAttribute(state.normalBinding, normalsHandle, normals);
     }
 
     @Override
         if (colors != null && colors.getElementSize() != 3 && colors.getElementSize() != 4) {
             throw new IllegalArgumentException("Colors element size must be 3 or 4");
         }
-        setAttribute(colorBinding, colors);
-        if (colorBinding.handle == null) {
+        colorsHandle = setAttribute(state.colorBinding, colorsHandle, colors);
+        if (colorsHandle == null) {
             // per-vertex coloring is disabled, so make sure we have a predictable diffuse color
             glMaterialColor(LightColor.DIFFUSE, DEFAULT_MAT_D_COLOR);
-            matDiffuse.set(DEFAULT_MAT_D_COLOR);
+            state.matDiffuse.set(DEFAULT_MAT_D_COLOR);
         }
     }
 
     @Override
     public void setTextureCoordinates(int tex, VertexAttribute texCoords) {
-        if (tex < 0) {
-            throw new IllegalArgumentException("Texture unit must be at least 0");
-        }
-        if (tex >= texBindings.length) {
-            return; // ignore it
-        }
-        setAttribute(texBindings[tex], texCoords);
+        texCoordsHandles[tex] = setAttribute(state.texBindings[tex],
+                                             texCoordsHandles[tex], texCoords);
     }
 
-    private void setAttribute(VertexState state, VertexAttribute attr) {
+    private VertexBufferObjectHandle setAttribute(VertexState state,
+                                                  VertexBufferObjectHandle currentHandle,
+                                                  VertexAttribute attr) {
+        VertexBufferObjectHandle handle = currentHandle;
+
         if (attr != null) {
             // We are setting a new vertex attribute
             boolean accessDiffers = (state.offset != attr.getOffset() || state.stride != attr.getStride() || state.elementSize != attr.getElementSize());
                     // Unlock the old one
                     resourceManager.unlock(oldVbo);
                     state.vbo = null;
-                    state.handle = null;
+                    handle = null;
                 }
 
                 if (state.vbo == null) {
                         failTypeCheck = true;
 
                         state.vbo = null;
-                        state.handle = null;
+                        handle = null;
                     } else {
                         // VBO is ready
                         state.vbo = attr.getData();
-                        state.handle = newHandle;
+                        handle = newHandle;
                     }
                 }
 
                 // Make sure OpenGL is operating on the correct unit for subsequent commands
-                state.activateSlot();
+                if (state.target == VertexTarget.TEXCOORDS && state.slot != activeClientTex) {
+                    // Special case slot handling for texture coordinates (other targets ignore slot)
+                    activeClientTex = state.slot;
+                    glActiveClientTexture(state.slot);
+                }
+
                 if (state.vbo != null) {
                     // At this point, state.vbo is the new VBO (or possibly old VBO)
                     state.elementSize = attr.getElementSize();
                     state.offset = attr.getOffset();
                     state.stride = attr.getStride();
 
-                    bindArrayVbo(attr.getData(), state.handle, oldVbo);
+                    bindArrayVbo(attr.getData(), handle, oldVbo);
 
                     if (oldVbo == null) {
                         glEnableAttribute(state.target, true);
                     }
-                    glAttributePointer(state.target, state.handle, state.offset,
-                                       state.stride, state.elementSize);
+                    glAttributePointer(state.target, handle, state.offset, state.stride,
+                                       state.elementSize);
                 } else if (oldVbo != null) {
                     // Since there was an old vbo we need to clean some things up
                     // which weren't cleaned up when we unlocked the old vbo
         } else {
             // The attribute is meant to be unbound
             if (state.vbo != null) {
-                // Change the slot to support multiple texture coordinates
-                state.activateSlot();
+                // Make sure OpenGL is operating on the correct unit for subsequent commands
+                if (state.target == VertexTarget.TEXCOORDS && state.slot != activeClientTex) {
+                    // Special case slot handling for texture coordinates (other targets ignore slot)
+                    activeClientTex = state.slot;
+                    glActiveClientTexture(state.slot);
+                }
 
                 // Disable the attribute
                 glEnableAttribute(state.target, false);
                 // Unlock it
                 resourceManager.unlock(state.vbo);
                 state.vbo = null;
-                state.handle = null;
+                handle = null;
             }
         }
+
+        return handle;
     }
 
     private void bindArrayVbo(VertexBufferObject vbo, VertexBufferObjectHandle handle,

ferox-renderer/ferox-renderer-impl/src/main/java/com/ferox/renderer/impl/AbstractGlslRenderer.java

 import com.ferox.math.Matrix4;
 import com.ferox.math.Vector3;
 import com.ferox.math.Vector4;
+import com.ferox.renderer.ContextState;
 import com.ferox.renderer.GlslRenderer;
 import com.ferox.renderer.RenderCapabilities;
 import com.ferox.renderer.Renderer;
         }
     }
 
+    // FIXME implement this at the same time I do the fixes to GlslRenderer,
+    // like adding int attrs, cleaning up arbitrary array uniforms, etc.
+    // FIXME should update the resource driver to have warnings, errors if
+    // there are uniforms/attrs of unsupported types
+    @Override
+    public ContextState<GlslRenderer> getCurrentState() {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    @Override
+    public void setCurrentState(ContextState<GlslRenderer> state) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
     @Override
     public void reset() {
-        super.reset();
+        // FIXME not fully correct yet
+        delegate.setCurrentState(delegate.defaultState);
 
         // This unbinds the shader handle, all textures and vertex attributes.
         // It clears the uniform and attribute cached values, but does not

ferox-renderer/ferox-renderer-impl/src/main/java/com/ferox/renderer/impl/AbstractRenderer.java

 public abstract class AbstractRenderer implements Renderer {
     private static final Vector4 BLACK = new Vector4(0, 0, 0, 0);
 
-    private final RendererDelegate delegate;
+    protected final RendererDelegate delegate;
 
     protected OpenGLContext context;
     protected ResourceManager resourceManager;
     }
 
     @Override
-    public void reset() {
-        delegate.reset();
-    }
-
-    @Override
     public void setBlendColor(@Const Vector4 color) {
         delegate.setBlendColor(color);
     }

ferox-renderer/ferox-renderer-impl/src/main/java/com/ferox/renderer/impl/AbstractTextureSurface.java

         case T_CUBEMAP:
             options = options.setHeight(options.getWidth()).setDepth(1);
             break;
+        default:
+            // no validation needed for T_3D
+            break;
         }
 
         if (!caps.getNpotTextureSupport()) {

ferox-renderer/ferox-renderer-impl/src/main/java/com/ferox/renderer/impl/FixedFunctionState.java

+package com.ferox.renderer.impl;
+
+import java.util.Arrays;
+
+import com.ferox.math.Matrix4;
+import com.ferox.math.Vector3;
+import com.ferox.math.Vector4;
+import com.ferox.renderer.ContextState;
+import com.ferox.renderer.FixedFunctionRenderer;
+import com.ferox.renderer.FixedFunctionRenderer.CombineFunction;
+import com.ferox.renderer.FixedFunctionRenderer.CombineOperand;
+import com.ferox.renderer.FixedFunctionRenderer.CombineSource;
+import com.ferox.renderer.FixedFunctionRenderer.TexCoordSource;
+import com.ferox.renderer.Renderer.Comparison;
+import com.ferox.resource.Texture;
+import com.ferox.resource.VertexBufferObject;
+
+public class FixedFunctionState implements ContextState<FixedFunctionRenderer> {
+    /**
+     * FogMode represents the three different eye fog modes that are available
+     * in OpenGL.
+     */
+    public static enum FogMode {
+        LINEAR, EXP, EXP_SQUARED
+    }
+
+    /**
+     * When configuring lighting and material colors, OpenGL uses the same
+     * functions to control the different types of color. For light colors, the
+     * EMISSIVE enum is unused, since it's only available for material colors.
+     */
+    public static enum LightColor {
+        AMBIENT, DIFFUSE, SPECULAR, EMISSIVE
+    }
+
+    /**
+     * OpenGL provides only one way to update matrices, and to switch between
+     * matrix types, you must set the current mode.
+     */
+    public static enum MatrixMode {
+        MODELVIEW, PROJECTION, TEXTURE
+    }
+
+    public static enum VertexTarget {
+        VERTICES, NORMALS, TEXCOORDS, COLORS
+    }
+
+    // cached defaults
+    private static final Vector4 DEFAULT_MAT_A_COLOR = new Vector4(.2, .2, .2, 1);
+    private static final Vector4 DEFAULT_MAT_D_COLOR = new Vector4(.8, .8, .8, 1);
+
+    private static final Vector4 ZERO = new Vector4(0, 0, 0, 0);
+    private static final Vector4 BLACK = new Vector4(0, 0, 0, 1);
+    private static final Vector4 WHITE = new Vector4(1, 1, 1, 1);
+
+    private static final Vector4 DEFAULT_LIGHT_POS = new Vector4(0, 0, 1, 0);
+    private static final Vector3 DEFAULT_SPOT_DIR = new Vector3(0, 0, -1);
+
+    private static final Vector4 DEFAULT_S_PLANE = new Vector4(1, 0, 0, 0);
+    private static final Vector4 DEFAULT_T_PLANE = new Vector4(0, 1, 0, 0);
+    private static final Vector4 DEFAULT_RQ_PLANE = new Vector4(0, 0, 0, 0);
+
+    /**
+     * An inner class that contains per-light state. Although it's accessible to
+     * sub-classes, it should be considered read-only because the
+     * AbstractFixedFunctionRenderer manages the updates to its variables.
+     */
+    public class LightState {
+        // post-transform by current modelview matrix
+        public final Vector4 position;
+        public final Vector3 spotlightDirection;
+
+        public final Vector4 ambient;
+        public final Vector4 specular;
+        public final Vector4 diffuse;
+
+        public double constAtt;
+        public double linAtt;
+        public double quadAtt;
+
+        public double spotAngle;
+
+        public boolean enabled;
+
+        public LightState(boolean firstLight) {
+            // default state is to have the identity modelview, so this is correct
+            position = new Vector4(DEFAULT_LIGHT_POS);
+            spotlightDirection = new Vector3(DEFAULT_SPOT_DIR);
+
+            ambient = new Vector4(BLACK);
+            specular = new Vector4(firstLight ? WHITE : BLACK);
+            diffuse = new Vector4(firstLight ? WHITE : BLACK);
+
+            constAtt = 1;
+            linAtt = 0;
+            quadAtt = 0;
+
+            spotAngle = 180;
+            enabled = false;
+        }
+
+        public LightState(LightState state) {
+            position = new Vector4(state.position);
+            spotlightDirection = new Vector3(state.spotlightDirection);
+            ambient = new Vector4(state.ambient);
+            specular = new Vector4(state.specular);
+            diffuse = new Vector4(state.diffuse);
+            constAtt = state.constAtt;
+            linAtt = state.linAtt;
+            quadAtt = state.quadAtt;
+            spotAngle = state.spotAngle;
+            enabled = state.enabled;
+        }
+    }
+
+    /**
+     * An inner class that contains per-texture unit state. Although it's
+     * accessible to sub-classes, it should be considered read-only because the
+     * AbstractFixedFunctionRenderer manages the updates to its variables.
+     */
+    public class TextureState {
+        public final int unit;
+
+        public Texture texture;
+
+        public TexCoordSource tcS;
+        public TexCoordSource tcT;
+        public TexCoordSource tcR;
+        public TexCoordSource tcQ;
+
+        public final Vector4 objPlaneS;
+        public final Vector4 objPlaneT;
+        public final Vector4 objPlaneR;
+        public final Vector4 objPlaneQ;
+
+        // post-transform by current modelview matrix
+        public final Vector4 eyePlaneS;
+        public final Vector4 eyePlaneT;
+        public final Vector4 eyePlaneR;
+        public final Vector4 eyePlaneQ;
+
+        public final Matrix4 textureMatrix;
+
+        public final Vector4 color;
+
+        public CombineFunction rgbFunc;
+        public CombineFunction alphaFunc;
+
+        public final CombineOperand[] opRgb;
+        public final CombineOperand[] opAlpha;
+
+        public final CombineSource[] srcRgb;
+        public final CombineSource[] srcAlpha;
+
+        public TextureState(int unit) {
+            this.unit = unit;
+
+            texture = null;
+
+            tcS = tcT = tcR = tcQ = TexCoordSource.ATTRIBUTE;
+
+            objPlaneS = new Vector4(DEFAULT_S_PLANE);
+            objPlaneT = new Vector4(DEFAULT_T_PLANE);
+            objPlaneR = new Vector4(DEFAULT_RQ_PLANE);
+            objPlaneQ = new Vector4(DEFAULT_RQ_PLANE);
+
+            eyePlaneS = new Vector4(DEFAULT_S_PLANE);
+            eyePlaneT = new Vector4(DEFAULT_T_PLANE);
+            eyePlaneR = new Vector4(DEFAULT_RQ_PLANE);
+            eyePlaneQ = new Vector4(DEFAULT_RQ_PLANE);
+
+            textureMatrix = new Matrix4().setIdentity();
+
+            color = new Vector4();
+
+            rgbFunc = CombineFunction.MODULATE;
+            alphaFunc = CombineFunction.MODULATE;
+
+            opRgb = new CombineOperand[] {CombineOperand.COLOR, CombineOperand.COLOR,
+                                          CombineOperand.ALPHA};
+            opAlpha = new CombineOperand[] {CombineOperand.ALPHA, CombineOperand.ALPHA,
+                                            CombineOperand.ALPHA};
+            srcRgb = new CombineSource[] {CombineSource.CURR_TEX, CombineSource.PREV_TEX,
+                                          CombineSource.CONST_COLOR};
+            srcAlpha = new CombineSource[] {CombineSource.CURR_TEX,
+                                            CombineSource.PREV_TEX,
+                                            CombineSource.CONST_COLOR};
+        }
+
+        public TextureState(TextureState state) {
+            unit = state.unit;
+            texture = state.texture;
+            tcS = state.tcS;
+            tcT = state.tcT;
+            tcR = state.tcR;
+            tcQ = state.tcQ;
+            objPlaneS = new Vector4(state.objPlaneS);
+            objPlaneT = new Vector4(state.objPlaneT);
+            objPlaneR = new Vector4(state.objPlaneR);
+            objPlaneQ = new Vector4(state.objPlaneQ);
+            eyePlaneS = new Vector4(state.eyePlaneS);
+            eyePlaneT = new Vector4(state.eyePlaneT);
+            eyePlaneR = new Vector4(state.eyePlaneR);
+            eyePlaneQ = new Vector4(state.eyePlaneQ);
+            textureMatrix = new Matrix4(state.textureMatrix);
+            color = new Vector4(state.color);
+            rgbFunc = state.rgbFunc;
+            alphaFunc = state.alphaFunc;
+            opRgb = Arrays.copyOf(state.opRgb, state.opRgb.length);
+            opAlpha = Arrays.copyOf(state.opAlpha, state.opAlpha.length);
+            srcRgb = Arrays.copyOf(state.srcRgb, state.srcRgb.length);
+            srcAlpha = Arrays.copyOf(state.srcAlpha, state.srcAlpha.length);
+        }
+    }
+
+    public class VertexState {
+        // Used to handle relocking/unlocking
+        public final VertexTarget target;
+        public final int slot;
+
+        public VertexBufferObject vbo;
+
+        public int offset;
+        public int stride;
+        public int elementSize;
+
+        public VertexState(VertexTarget target, int slot) {
+            this.target = target;
+            this.slot = slot;
+
+            vbo = null;
+            offset = 0;
+            stride = 0;
+            elementSize = 0;
+        }
+
+        public VertexState(VertexState state) {
+            target = state.target;
+            slot = state.slot;
+            vbo = state.vbo;
+            offset = state.offset;
+            stride = state.stride;
+            elementSize = state.elementSize;
+        }
+    }
+
+    // alpha test
+    public Comparison alphaTest;
+    public double alphaRefValue;
+
+    // fog
+    public final Vector4 fogColor;
+
+    public double fogStart;
+    public double fogEnd;
+    public double fogDensity;
+
+    public FogMode fogMode;
+    public boolean fogEnabled;
+
+    // global lighting
+    public final Vector4 globalAmbient;
+    public boolean lightingEnabled;
+    public boolean lightingTwoSided;
+    public boolean lightingSmoothed;
+
+    // lights
+    public LightState[] lights; // "final"
+
+    // material
+    public final Vector4 matDiffuse;
+    public final Vector4 matAmbient;
+    public final Vector4 matSpecular;
+    public final Vector4 matEmmissive;
+
+    public double matShininess;
+
+    // primitive size/aa
+    public boolean lineAAEnabled;
+    public boolean pointAAEnabled;
+    public boolean polyAAEnabled;
+
+    public double lineWidth;
+    public double pointWidth;
+
+    // texturing
+    public final TextureState[] textures;
+
+    // bindings for vbos and rendering
+    public final VertexState vertexBinding;
+    public final VertexState normalBinding;
+    public final VertexState colorBinding;
+    public final VertexState[] texBindings;
+
+    // matrix
+    public MatrixMode matrixMode = MatrixMode.MODELVIEW;
+    public final Matrix4 modelView;
+    public final Matrix4 projection;
+
+    public final RendererState sharedState;
+
+    public FixedFunctionState(int numLights, int numTextures, int numTextureUnits) {