Commits

Michael Ludwig committed 4fac977

Remove old state management system, and replace with new from ffp2 package.

Comments (0)

Files changed (38)

ferox-scene/src/main/java/com/ferox/scene/controller/ffp/CameraGroupFactory.java

-/*
- * Ferox, a graphics and game library in Java
- *
- * Copyright (c) 2012, Michael Ludwig
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without modification,
- * are permitted provided that the following conditions are met:
- *
- *     Redistributions of source code must retain the above copyright notice,
- *         this list of conditions and the following disclaimer.
- *     Redistributions in binary form must reproduce the above copyright notice,
- *         this list of conditions and the following disclaimer in the
- *         documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
- * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
- * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.ferox.scene.controller.ffp;
-
-import java.util.Collections;
-import java.util.List;
-
-import com.ferox.math.bounds.Frustum;
-import com.ferox.renderer.FixedFunctionRenderer;
-import com.ferox.renderer.HardwareAccessLayer;
-import com.lhkbob.entreri.Entity;
-
-public class CameraGroupFactory implements StateGroupFactory {
-    private final StateGroupFactory childFactory;
-
-    private final Frustum camera;
-
-    public CameraGroupFactory(Frustum camera, StateGroupFactory childFactory) {
-        this.camera = camera;
-        this.childFactory = childFactory;
-    }
-
-    @Override
-    public StateGroup newGroup() {
-        return new CameraGroup();
-    }
-
-    private class CameraGroup implements StateGroup {
-        private final StateNode node;
-
-        public CameraGroup() {
-            node = new StateNode((childFactory == null ? null : childFactory.newGroup()),
-                                 new CameraState());
-        }
-
-        @Override
-        public StateNode getNode(Entity e) {
-            return node;
-        }
-
-        @Override
-        public List<StateNode> getNodes() {
-            return Collections.singletonList(node);
-        }
-
-        @Override
-        public AppliedEffects applyGroupState(HardwareAccessLayer access,
-                                              AppliedEffects effects) {
-            return effects;
-        }
-
-        @Override
-        public void unapplyGroupState(HardwareAccessLayer access, AppliedEffects effects) {}
-
-    }
-
-    private class CameraState implements State {
-        @Override
-        public void add(Entity e) {}
-
-        @Override
-        public AppliedEffects applyState(HardwareAccessLayer access,
-                                         AppliedEffects effects, int index) {
-            FixedFunctionRenderer r = access.getCurrentContext()
-                                            .getFixedFunctionRenderer();
-            r.setProjectionMatrix(camera.getProjectionMatrix());
-            r.setModelViewMatrix(camera.getViewMatrix());
-            return effects.applyViewMatrix(camera.getViewMatrix());
-        }
-
-        @Override
-        public void unapplyState(HardwareAccessLayer access, AppliedEffects effects,
-                                 int index) {}
-    }
-}

ferox-scene/src/main/java/com/ferox/scene/controller/ffp/CameraState.java

+package com.ferox.scene.controller.ffp;
+
+import com.ferox.math.bounds.Frustum;
+import com.ferox.renderer.FixedFunctionRenderer;
+import com.ferox.renderer.HardwareAccessLayer;
+
+public class CameraState implements State {
+    private final Frustum camera;
+
+    public CameraState(Frustum camera) {
+        this.camera = camera;
+    }
+
+    public Frustum getFrustum() {
+        return camera;
+    }
+
+    @Override
+    public void visitNode(StateNode currentNode, AppliedEffects effects,
+                          HardwareAccessLayer access) {
+        FixedFunctionRenderer r = access.getCurrentContext().getFixedFunctionRenderer();
+        r.setProjectionMatrix(camera.getProjectionMatrix());
+        r.setModelViewMatrix(camera.getViewMatrix());
+
+        AppliedEffects childEffects = effects.applyViewMatrix(camera.getViewMatrix());
+        currentNode.visitChildren(childEffects, access);
+    }
+}

ferox-scene/src/main/java/com/ferox/scene/controller/ffp/ColorState.java

+package com.ferox.scene.controller.ffp;
+
+import com.ferox.math.ColorRGB;
+import com.ferox.math.Const;
+import com.ferox.math.Vector4;
+import com.ferox.renderer.FixedFunctionRenderer;
+import com.ferox.renderer.HardwareAccessLayer;
+
+public class ColorState implements State {
+    public static final Vector4 DEFAULT_DIFFUSE = new Vector4(0.8, 0.8, 0.8, 1.0);
+    public static final Vector4 DEFAULT_SPECULAR = new Vector4(0.0, 0.0, 0.0, 1.0);
+    public static final Vector4 DEFAULT_EMITTED = new Vector4(0.0, 0.0, 0.0, 1.0);
+    public static final Vector4 DEFAULT_AMBIENT = new Vector4(0.2, 0.2, 0.2, 1.0);
+
+    private final Vector4 diffuse = new Vector4();
+    private final Vector4 specular = new Vector4();
+    private final Vector4 emitted = new Vector4();
+
+    private double shininess;
+
+    public void set(@Const ColorRGB diffuse, @Const ColorRGB specular,
+                    @Const ColorRGB emitted, double alpha, double shininess) {
+        if (diffuse == null) {
+            this.diffuse.set(DEFAULT_DIFFUSE).w = alpha;
+        } else {
+            this.diffuse.set(diffuse.red(), diffuse.green(), diffuse.blue(), alpha);
+        }
+
+        if (specular == null) {
+            this.specular.set(DEFAULT_SPECULAR).w = alpha;
+        } else {
+            this.specular.set(specular.red(), specular.green(), specular.blue(), alpha);
+        }
+
+        if (emitted == null) {
+            this.emitted.set(DEFAULT_EMITTED).w = alpha;
+        } else {
+            this.emitted.set(emitted.red(), emitted.green(), emitted.blue(), alpha);
+        }
+
+        this.shininess = shininess;
+    }
+
+    public double getShininess() {
+        return shininess;
+    }
+
+    @Const
+    public Vector4 getDiffuse() {
+        return diffuse;
+    }
+
+    @Const
+    public Vector4 getSpecular() {
+        return specular;
+    }
+
+    @Const
+    public Vector4 getEmitted() {
+        return emitted;
+    }
+
+    public double getAlpha() {
+        return diffuse.w;
+    }
+
+    @Override
+    public void visitNode(StateNode currentNode, AppliedEffects effects,
+                          HardwareAccessLayer access) {
+        FixedFunctionRenderer r = access.getCurrentContext().getFixedFunctionRenderer();
+
+        r.setMaterial(DEFAULT_AMBIENT, diffuse, specular, emitted);
+        r.setMaterialShininess(shininess);
+
+        currentNode.visitChildren(effects, access);
+    }
+
+    @Override
+    public int hashCode() {
+        long bits = Double.doubleToLongBits(shininess);
+
+        int hash = 17 * (int) (bits ^ (bits >>> 32));
+        hash = 31 * hash + diffuse.hashCode();
+        hash = 31 * hash + emitted.hashCode();
+        hash = 31 * hash + specular.hashCode();
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof ColorState)) {
+            return false;
+        }
+
+        ColorState ts = (ColorState) o;
+        return ts.diffuse.equals(diffuse) && ts.emitted.equals(emitted) && ts.specular.equals(specular) && shininess == shininess;
+    }
+}

ferox-scene/src/main/java/com/ferox/scene/controller/ffp/FixedFunctionRenderController.java

-/*
- * Ferox, a graphics and game library in Java
- *
- * Copyright (c) 2012, Michael Ludwig
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without modification,
- * are permitted provided that the following conditions are met:
- *
- *     Redistributions of source code must retain the above copyright notice,
- *         this list of conditions and the following disclaimer.
- *     Redistributions in binary form must reproduce the above copyright notice,
- *         this list of conditions and the following disclaimer in the
- *         documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
- * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
- * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.ferox.scene.controller.ffp;
-
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Queue;
-import java.util.Set;
-import java.util.concurrent.Future;
-
-import com.ferox.math.Vector4;
-import com.ferox.math.bounds.Frustum;
-import com.ferox.renderer.Context;
-import com.ferox.renderer.FixedFunctionRenderer;
-import com.ferox.renderer.Framework;
-import com.ferox.renderer.HardwareAccessLayer;
-import com.ferox.renderer.RenderCapabilities;
-import com.ferox.renderer.Renderer.DrawStyle;
-import com.ferox.renderer.Surface;
-import com.ferox.scene.AmbientLight;
-import com.ferox.scene.AtmosphericFog;
-import com.ferox.scene.BlinnPhongMaterial;
-import com.ferox.scene.Camera;
-import com.ferox.scene.DecalColorMap;
-import com.ferox.scene.DiffuseColor;
-import com.ferox.scene.DiffuseColorMap;
-import com.ferox.scene.DirectionLight;
-import com.ferox.scene.EmittedColor;
-import com.ferox.scene.EmittedColorMap;
-import com.ferox.scene.Light;
-import com.ferox.scene.PointLight;
-import com.ferox.scene.Renderable;
-import com.ferox.scene.SpecularColor;
-import com.ferox.scene.SpecularColorMap;
-import com.ferox.scene.SpotLight;
-import com.ferox.scene.Transform;
-import com.ferox.scene.Transparent;
-import com.ferox.scene.controller.PVSResult;
-import com.ferox.scene.controller.light.LightGroupResult;
-import com.ferox.util.Bag;
-import com.ferox.util.profile.Profiler;
-import com.lhkbob.entreri.Component;
-import com.lhkbob.entreri.ComponentData;
-import com.lhkbob.entreri.Entity;
-import com.lhkbob.entreri.EntitySystem;
-import com.lhkbob.entreri.task.Job;
-import com.lhkbob.entreri.task.ParallelAware;
-import com.lhkbob.entreri.task.Task;
-
-public class FixedFunctionRenderController implements Task, ParallelAware {
-    private static final Set<Class<? extends ComponentData<?>>> COMPONENTS;
-    static {
-        Set<Class<? extends ComponentData<?>>> types = new HashSet<Class<? extends ComponentData<?>>>();
-        types.add(AmbientLight.class);
-        types.add(DirectionLight.class);
-        types.add(SpotLight.class);
-        types.add(PointLight.class);
-        types.add(Renderable.class);
-        types.add(Transform.class);
-        types.add(BlinnPhongMaterial.class);
-        types.add(DiffuseColor.class);
-        types.add(EmittedColor.class);
-        types.add(SpecularColor.class);
-        types.add(DiffuseColorMap.class);
-        types.add(EmittedColorMap.class);
-        types.add(SpecularColorMap.class);
-        types.add(DecalColorMap.class);
-        types.add(Transparent.class);
-        types.add(Camera.class);
-        types.add(AtmosphericFog.class);
-        COMPONENTS = Collections.unmodifiableSet(types);
-    }
-    private final Framework framework;
-    private final ShadowMapCache shadowMapA;
-    private final ShadowMapCache shadowMapB;
-
-    private final int shadowmapTextureUnit;
-    private final int diffuseTextureUnit;
-    private final int emissiveTextureUnit;
-    private final int decalTextureUnit;
-
-    private List<PVSResult> pvs;
-    private LightGroupResult lightGroups;
-
-    private ShadowMapCache inuseCache;
-    private final Queue<Future<Void>> previousFrame;
-
-    public FixedFunctionRenderController(Framework framework) {
-        this(framework, 1024);
-    }
-
-    public FixedFunctionRenderController(Framework framework, int shadowMapSize) {
-        if (framework == null) {
-            throw new NullPointerException("Framework cannot be null");
-        }
-
-        RenderCapabilities caps = framework.getCapabilities();
-        if (!caps.hasFixedFunctionRenderer()) {
-            throw new IllegalArgumentException("Framework must support a FixedFunctionRenderer");
-        }
-
-        this.framework = framework;
-
-        previousFrame = new ArrayDeque<Future<Void>>();
-
-        int numTex = caps.getMaxFixedPipelineTextures();
-        boolean shadowsRequested = shadowMapSize > 0; // size is positive
-        boolean shadowSupport = ((caps.getFboSupport() || caps.getPbufferSupport()) && numTex > 1 && caps.getDepthTextureSupport());
-
-        if (shadowsRequested && shadowSupport) {
-            // convert size to a power of two
-            int sz = 1;
-            while (sz < shadowMapSize) {
-                sz = sz << 1;
-            }
-            shadowMapA = new ShadowMapCache(framework, sz, sz);
-            shadowMapB = new ShadowMapCache(framework, sz, sz);
-
-            // use the 4th unit if available, or the last unit if we're under
-            shadowmapTextureUnit = Math.max(numTex, 3) - 1;
-            // reserve one unit for the shadow map
-            numTex--;
-        } else {
-            shadowMapA = null;
-            shadowMapB = null;
-            shadowmapTextureUnit = -1;
-        }
-
-        if (numTex >= 3) {
-            diffuseTextureUnit = 0;
-            emissiveTextureUnit = 2;
-            decalTextureUnit = 1;
-        } else if (numTex == 2) {
-            diffuseTextureUnit = 0;
-            // merge emissive and decal units
-            emissiveTextureUnit = 1;
-            decalTextureUnit = 1;
-        } else if (numTex == 1) {
-            // merge all units
-            diffuseTextureUnit = 0;
-            emissiveTextureUnit = 0;
-            decalTextureUnit = 0;
-        } else {
-            // disable texturing
-            diffuseTextureUnit = -1;
-            emissiveTextureUnit = -1;
-            decalTextureUnit = -1;
-        }
-    }
-
-    @Override
-    @SuppressWarnings("unchecked")
-    public Task process(EntitySystem system, Job job) {
-        Profiler.push("render");
-
-        Camera camera = system.createDataInstance(Camera.class);
-
-        // first cache all shadow map frustums so any view can easily prepare a texture
-        Profiler.push("generate-shadow-map-scene");
-        ShadowMapCache shadowMap = (inuseCache == shadowMapA ? shadowMapB : shadowMapA);
-
-        shadowMap.reset();
-        for (PVSResult visible : pvs) {
-            // cacheShadowScene properly ignores PVSResults that aren't for lights
-            shadowMap.cacheShadowScene(visible);
-        }
-        Profiler.pop();
-
-        // go through all results and render all camera frustums
-        List<com.ferox.renderer.Task<Void>> thisFrame = new ArrayList<com.ferox.renderer.Task<Void>>();
-        for (PVSResult visible : pvs) {
-            if (visible.getSource().getType().equals(Camera.class)) {
-                Profiler.push("build-render-tree");
-                camera.set((Component<Camera>) visible.getSource());
-                thisFrame.add(render(camera.getSurface(), visible.getFrustum(),
-                                     visible.getPotentiallyVisibleSet(), system,
-                                     shadowMap));
-                Profiler.pop();
-            }
-        }
-
-        Profiler.push("block-opengl");
-        while (!previousFrame.isEmpty()) {
-            Future<Void> f = previousFrame.poll();
-            try {
-                f.get();
-            } catch (Exception e) {
-                throw new RuntimeException("Previous frame failed", e);
-            }
-        }
-        Profiler.pop();
-
-        inuseCache = shadowMap;
-        for (com.ferox.renderer.Task<Void> rf : thisFrame) {
-            previousFrame.add(framework.queue(rf));
-        }
-
-        Profiler.pop();
-        // FIXME return a flush task
-        return null;
-    }
-
-    private com.ferox.renderer.Task<Void> render(final Surface surface, Frustum view,
-                                                 Bag<Entity> pvs, EntitySystem system,
-                                                 ShadowMapCache shadowMap) {
-        // FIXME can we somehow preserve this tree across frames instead of continuing
-        // to build it over and over again?
-        GeometryGroupFactory geomGroup = new GeometryGroupFactory(system);
-        TextureGroupFactory textureGroup = new TextureGroupFactory(system,
-                                                                   diffuseTextureUnit,
-                                                                   emissiveTextureUnit,
-                                                                   decalTextureUnit,
-                                                                   geomGroup);
-        MaterialGroupFactory materialGroup = new MaterialGroupFactory(system,
-                                                                      (diffuseTextureUnit >= 0 ? textureGroup : geomGroup));
-
-        LightGroupFactory lightGroup = new LightGroupFactory(system,
-                                                             lightGroups,
-                                                             (shadowMap != null ? shadowMap.getShadowCastingLights() : Collections.<Component<? extends Light<?>>> emptySet()),
-                                                             framework.getCapabilities()
-                                                                      .getMaxActiveLights(),
-                                                             materialGroup);
-
-        ShadowMapGroupFactory shadowGroup = (shadowMap != null ? new ShadowMapGroupFactory(shadowMap,
-                                                                                           shadowmapTextureUnit,
-                                                                                           lightGroup) : null);
-
-        SolidColorGroupFactory solidColorGroup = new SolidColorGroupFactory(system,
-                                                                            textureGroup);
-
-        LightingGroupFactory lightingGroup = new LightingGroupFactory(solidColorGroup,
-                                                                      (shadowMap != null ? shadowGroup : lightGroup));
-        CameraGroupFactory cameraGroup = new CameraGroupFactory(view, lightingGroup);
-
-        final StateNode rootNode = new StateNode(cameraGroup.newGroup());
-        for (Entity e : pvs) {
-            rootNode.add(e);
-        }
-
-        // FIXME add profiling within rendering thread too
-        return new com.ferox.renderer.Task<Void>() {
-            @Override
-            public Void run(HardwareAccessLayer access) {
-                Context ctx = access.setActiveSurface(surface);
-                if (ctx != null) {
-                    FixedFunctionRenderer ffp = ctx.getFixedFunctionRenderer();
-                    // FIXME clear color should be configurable somehow
-                    ffp.clear(true, true, true, new Vector4(0, 0, 0, 1.0), 1, 0);
-                    ffp.setDrawStyle(DrawStyle.SOLID, DrawStyle.SOLID);
-                    rootNode.render(access, new AppliedEffects());
-
-                    //                    Frustum twoD = new Frustum(true,
-                    //                                               0,
-                    //                                               surface.getWidth(),
-                    //                                               0,
-                    //                                               surface.getHeight(),
-                    //                                               -1,
-                    //                                               1);
-                    //                    ffp.setProjectionMatrix(twoD.getProjectionMatrix());
-                    //                    ffp.setModelViewMatrix(twoD.getViewMatrix());
-                    //                    ffp.setDepthTest(Comparison.ALWAYS);
-                    //
-                    //                    shadowMap.getShadowMap().setDepthCompareEnabled(false);
-                    //                    access.update(shadowMap.getShadowMap());
-                    //                    ffp.setTexture(0, shadowMap.getShadowMap());
-                    //                    ffp.setTextureCombineRGB(0, CombineFunction.REPLACE,
-                    //                                             CombineSource.CURR_TEX,
-                    //                                             CombineOperand.COLOR,
-                    //                                             CombineSource.CURR_TEX,
-                    //                                             CombineOperand.COLOR,
-                    //                                             CombineSource.CURR_TEX, CombineOperand.COLOR);
-                    //
-                    //                    Geometry g = Rectangle.create(0, 250, 0, 250);
-                    //                    ffp.setVertices(g.getVertices());
-                    //                    ffp.setTextureCoordinates(0, g.getTextureCoordinates());
-                    //                    if (g.getIndices() == null) {
-                    //                        ffp.render(g.getPolygonType(), g.getIndexOffset(),
-                    //                                   g.getIndexCount());
-                    //                    } else {
-                    //                        ffp.render(g.getPolygonType(), g.getIndices(),
-                    //                                   g.getIndexOffset(), g.getIndexCount());
-                    //                    }
-                    //
-                    //                    ffp.setTexture(0, null);
-                    //                    shadowMap.getShadowMap().setDepthCompareEnabled(true);
-                    //                    access.update(shadowMap.getShadowMap());
-                }
-                return null;
-            }
-        };
-    }
-
-    @Override
-    public void reset(EntitySystem system) {
-        pvs = new ArrayList<PVSResult>();
-        lightGroups = null;
-    }
-
-    public void report(PVSResult pvs) {
-        this.pvs.add(pvs);
-    }
-
-    public void report(LightGroupResult r) {
-        lightGroups = r;
-    }
-
-    @Override
-    public Set<Class<? extends ComponentData<?>>> getAccessedComponents() {
-        return COMPONENTS;
-    }
-
-    @Override
-    public boolean isEntitySetModified() {
-        return false;
-    }
-}

ferox-scene/src/main/java/com/ferox/scene/controller/ffp/FixedFunctionRenderTask.java

+package com.ferox.scene.controller.ffp;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.Future;
+
+import com.ferox.math.ColorRGB;
+import com.ferox.math.Vector4;
+import com.ferox.math.bounds.Frustum;
+import com.ferox.renderer.Context;
+import com.ferox.renderer.FixedFunctionRenderer;
+import com.ferox.renderer.Framework;
+import com.ferox.renderer.HardwareAccessLayer;
+import com.ferox.renderer.RenderCapabilities;
+import com.ferox.renderer.Renderer.DrawStyle;
+import com.ferox.renderer.Surface;
+import com.ferox.resource.Texture;
+import com.ferox.resource.VertexAttribute;
+import com.ferox.scene.AmbientLight;
+import com.ferox.scene.AtmosphericFog;
+import com.ferox.scene.BlinnPhongMaterial;
+import com.ferox.scene.Camera;
+import com.ferox.scene.DecalColorMap;
+import com.ferox.scene.DiffuseColor;
+import com.ferox.scene.DiffuseColorMap;
+import com.ferox.scene.DirectionLight;
+import com.ferox.scene.EmittedColor;
+import com.ferox.scene.EmittedColorMap;
+import com.ferox.scene.Light;
+import com.ferox.scene.PointLight;
+import com.ferox.scene.Renderable;
+import com.ferox.scene.SpecularColor;
+import com.ferox.scene.SpecularColorMap;
+import com.ferox.scene.SpotLight;
+import com.ferox.scene.Transform;
+import com.ferox.scene.Transparent;
+import com.ferox.scene.controller.PVSResult;
+import com.ferox.scene.controller.light.LightGroupResult;
+import com.ferox.util.Bag;
+import com.ferox.util.profile.Profiler;
+import com.lhkbob.entreri.ComponentData;
+import com.lhkbob.entreri.Entity;
+import com.lhkbob.entreri.EntitySystem;
+import com.lhkbob.entreri.property.IntProperty;
+import com.lhkbob.entreri.property.ObjectProperty;
+import com.lhkbob.entreri.task.Job;
+import com.lhkbob.entreri.task.ParallelAware;
+import com.lhkbob.entreri.task.Task;
+
+public class FixedFunctionRenderTask implements Task, ParallelAware {
+    private static final Set<Class<? extends ComponentData<?>>> COMPONENTS;
+    static {
+        Set<Class<? extends ComponentData<?>>> types = new HashSet<Class<? extends ComponentData<?>>>();
+        types.add(AmbientLight.class);
+        types.add(DirectionLight.class);
+        types.add(SpotLight.class);
+        types.add(PointLight.class);
+        types.add(Renderable.class);
+        types.add(Transform.class);
+        types.add(BlinnPhongMaterial.class);
+        types.add(DiffuseColor.class);
+        types.add(EmittedColor.class);
+        types.add(SpecularColor.class);
+        types.add(DiffuseColorMap.class);
+        types.add(EmittedColorMap.class);
+        types.add(SpecularColorMap.class);
+        types.add(DecalColorMap.class);
+        types.add(Transparent.class);
+        types.add(Camera.class);
+        types.add(AtmosphericFog.class);
+        COMPONENTS = Collections.unmodifiableSet(types);
+    }
+
+    private final Framework framework;
+    private final boolean flush;
+
+    // alternating frame storage so one frame can be prepared while the
+    // other is being rendering
+    private final Frame frameA;
+    private final Frame frameB;
+
+    private final int shadowmapTextureUnit;
+
+    // per-frame data
+    private List<PVSResult> cameraPVS;
+    private List<PVSResult> lightPVS;
+    private LightGroupResult lightGroups;
+
+    private Frame inuseFrame;
+    private final Queue<Future<Void>> previousFrame;
+
+    // cached local instances
+    private Camera camera;
+    private Transform transform;
+
+    private AmbientLight ambientLight;
+    private DirectionLight directionLight;
+    private SpotLight spotLight;
+    private PointLight pointLight;
+
+    private Renderable renderable;
+    private BlinnPhongMaterial blinnPhong;
+    private DiffuseColor diffuseColor;
+    private SpecularColor specularColor;
+    private EmittedColor emittedColor;
+    private DiffuseColorMap diffuseTexture;
+    private DecalColorMap decalTexture;
+    private EmittedColorMap emittedTexture;
+    private Transparent transparent; // FIXME not fully implemented either
+
+    // FIXME not implemented yet
+    //    private AtmosphericFog fog;
+
+    public FixedFunctionRenderTask(Framework framework) {
+        this(framework, 1024, true);
+    }
+
+    public FixedFunctionRenderTask(Framework framework, int shadowMapSize, boolean flush) {
+        if (framework == null) {
+            throw new NullPointerException("Framework cannot be null");
+        }
+
+        RenderCapabilities caps = framework.getCapabilities();
+        if (!caps.hasFixedFunctionRenderer()) {
+            throw new IllegalArgumentException("Framework must support a FixedFunctionRenderer");
+        }
+
+        this.framework = framework;
+        this.flush = flush;
+
+        previousFrame = new ArrayDeque<Future<Void>>();
+
+        ShadowMapCache shadowMapA;
+        ShadowMapCache shadowMapB;
+
+        int numTex = caps.getMaxFixedPipelineTextures();
+        boolean shadowsRequested = shadowMapSize > 0; // size is positive
+        boolean shadowSupport = ((caps.getFboSupport() || caps.getPbufferSupport()) && numTex > 1 && caps.getDepthTextureSupport());
+
+        if (shadowsRequested && shadowSupport) {
+            // convert size to a power of two
+            int sz = 1;
+            while (sz < shadowMapSize) {
+                sz = sz << 1;
+            }
+            shadowMapA = new ShadowMapCache(framework, sz, sz);
+            shadowMapB = new ShadowMapCache(framework, sz, sz);
+
+            // use the 4th unit if available, or the last unit if we're under
+            shadowmapTextureUnit = Math.max(numTex, 3) - 1;
+            // reserve one unit for the shadow map
+            numTex--;
+        } else {
+            shadowMapA = null;
+            shadowMapB = null;
+            shadowmapTextureUnit = -1;
+        }
+
+        int diffuseTextureUnit, emissiveTextureUnit, decalTextureUnit;
+        if (numTex >= 3) {
+            diffuseTextureUnit = 0;
+            emissiveTextureUnit = 2;
+            decalTextureUnit = 1;
+        } else if (numTex == 2) {
+            diffuseTextureUnit = 0;
+            // merge emissive and decal units
+            emissiveTextureUnit = 1;
+            decalTextureUnit = 1;
+        } else if (numTex == 1) {
+            // merge all units
+            diffuseTextureUnit = 0;
+            emissiveTextureUnit = 0;
+            decalTextureUnit = 0;
+        } else {
+            // disable texturing
+            diffuseTextureUnit = -1;
+            emissiveTextureUnit = -1;
+            decalTextureUnit = -1;
+        }
+
+        frameA = new Frame(shadowMapA,
+                           diffuseTextureUnit,
+                           decalTextureUnit,
+                           emissiveTextureUnit);
+        frameB = new Frame(shadowMapB,
+                           diffuseTextureUnit,
+                           decalTextureUnit,
+                           emissiveTextureUnit);
+    }
+
+    @Override
+    public void reset(EntitySystem system) {
+        if (transform == null) {
+            transform = system.createDataInstance(Transform.class);
+            camera = system.createDataInstance(Camera.class);
+
+            ambientLight = system.createDataInstance(AmbientLight.class);
+            directionLight = system.createDataInstance(DirectionLight.class);
+            spotLight = system.createDataInstance(SpotLight.class);
+            pointLight = system.createDataInstance(PointLight.class);
+
+            renderable = system.createDataInstance(Renderable.class);
+            blinnPhong = system.createDataInstance(BlinnPhongMaterial.class);
+            diffuseColor = system.createDataInstance(DiffuseColor.class);
+            specularColor = system.createDataInstance(SpecularColor.class);
+            emittedColor = system.createDataInstance(EmittedColor.class);
+            diffuseTexture = system.createDataInstance(DiffuseColorMap.class);
+            decalTexture = system.createDataInstance(DecalColorMap.class);
+            emittedTexture = system.createDataInstance(EmittedColorMap.class);
+            transparent = system.createDataInstance(Transparent.class);
+
+            frameA.decorate(system);
+            frameB.decorate(system);
+        }
+
+        lightGroups = null;
+        cameraPVS = new ArrayList<PVSResult>();
+        lightPVS = new ArrayList<PVSResult>();
+    }
+
+    public void report(PVSResult pvs) {
+        if (pvs.getSource().getType().equals(Camera.class)) {
+            cameraPVS.add(pvs);
+        } else if (Light.class.isAssignableFrom(pvs.getSource().getType())) {
+            lightPVS.add(pvs);
+        }
+    }
+
+    public void report(LightGroupResult r) {
+        lightGroups = r;
+    }
+
+    @Override
+    public Set<Class<? extends ComponentData<?>>> getAccessedComponents() {
+        return COMPONENTS;
+    }
+
+    @Override
+    public boolean isEntitySetModified() {
+        return false;
+    }
+
+    @Override
+    public Task process(EntitySystem system, Job job) {
+        Profiler.push("render");
+        Frame currentFrame = (inuseFrame == frameA ? frameB : frameA);
+
+        Profiler.push("shadow-map-scene");
+        currentFrame.shadowMap.reset();
+        for (PVSResult light : lightPVS) {
+            currentFrame.shadowMap.cacheShadowScene(light);
+        }
+        Profiler.pop();
+
+        // synchronize render atoms for all visible entities
+        Profiler.push("state-sync");
+        if (currentFrame.needsReset()) {
+            currentFrame.resetStates();
+        }
+
+        RenderAtom atom;
+        for (PVSResult visible : cameraPVS) {
+            for (Entity e : visible.getPotentiallyVisibleSet()) {
+                e.get(renderable); // guaranteed that this one is present
+                atom = currentFrame.atoms.get(renderable.getIndex());
+
+                if (atom == null) {
+                    atom = new RenderAtom();
+                    currentFrame.atoms.set(atom, renderable.getIndex());
+                }
+
+                syncEntityState(e, atom, currentFrame);
+            }
+        }
+        Profiler.pop();
+
+        Profiler.push("build-state-tree");
+        // all visible atoms have valid states now, so we build a 
+        // postman sorted state node tree used for rendering
+        List<com.ferox.renderer.Task<Void>> thisFrame = new ArrayList<com.ferox.renderer.Task<Void>>();
+        for (PVSResult visible : cameraPVS) {
+            visible.getSource().getEntity().get(camera);
+            thisFrame.add(buildTree(visible.getPotentiallyVisibleSet(),
+                                    visible.getFrustum(), currentFrame,
+                                    camera.getSurface()));
+        }
+        Profiler.pop();
+
+        // block until the previous frame has completed, so we no its data
+        // structures are no longer in use and we can swap which frame is active
+        Profiler.push("block-opengl");
+        while (!previousFrame.isEmpty()) {
+            Future<Void> f = previousFrame.poll();
+            try {
+                f.get();
+            } catch (Exception e) {
+                throw new RuntimeException("Previous frame failed", e);
+            }
+        }
+        Profiler.pop();
+
+        // activate frame and queue all tasks
+        inuseFrame = currentFrame;
+        for (com.ferox.renderer.Task<Void> rf : thisFrame) {
+            previousFrame.add(framework.queue(rf));
+        }
+
+        Profiler.pop();
+        return null;
+    }
+
+    private com.ferox.renderer.Task<Void> buildTree(Bag<Entity> pvs, Frustum camera,
+                                                    Frame frame, final Surface surface) {
+        // static tree construction that doesn't depend on entities
+        final StateNode root = new StateNode(new CameraState(camera)); // children = lit, unlit
+        StateNode litNode = new StateNode(frame.litState); // child = shadowmap state
+        StateNode unlitNode = new StateNode(frame.unlitState); // child = texture states
+        StateNode smNode = new StateNode(new ShadowMapState(frame.shadowMap,
+                                                            shadowmapTextureUnit)); // children = light groups
+
+        root.setChild(0, litNode);
+        root.setChild(1, unlitNode);
+        litNode.setChild(0, smNode);
+
+        // insert light group nodes
+        for (int i = 0; i < lightGroups.getGroupCount(); i++) {
+            smNode.setChild(i,
+                            new StateNode(new LightGroupState(lightGroups.getGroup(i),
+                                                              frame.shadowMap.getShadowCastingLights(),
+                                                              framework.getCapabilities()
+                                                                       .getMaxActiveLights(),
+                                                              directionLight,
+                                                              spotLight,
+                                                              pointLight,
+                                                              ambientLight,
+                                                              transform),
+                                          frame.textureState.length));
+        }
+
+        IntProperty groupAssgn = lightGroups.getAssignmentProperty();
+
+        RenderAtom atom;
+        for (Entity e : pvs) {
+            e.get(renderable);
+            atom = frame.atoms.get(renderable.getIndex());
+
+            StateNode firstNode;
+            try {
+                firstNode = (atom.lit ? smNode.getChild(groupAssgn.get(renderable.getIndex())) : unlitNode);
+            } catch (NullPointerException e1) {
+                System.out.println(atom + " " + groupAssgn);
+                throw e1;
+            }
+
+            // texture state
+            StateNode texNode = firstNode.getChild(atom.textureStateIndex);
+            if (texNode == null) {
+                texNode = new StateNode(frame.textureState[atom.textureStateIndex],
+                                        frame.geometryState.length);
+                firstNode.setChild(atom.textureStateIndex, texNode);
+            }
+
+            // geometry state
+            StateNode geomNode = texNode.getChild(atom.geometryStateIndex);
+            if (geomNode == null) {
+                geomNode = new StateNode(frame.geometryState[atom.geometryStateIndex],
+                                         frame.colorState.length);
+                texNode.setChild(atom.geometryStateIndex, geomNode);
+            }
+
+            // color state
+            StateNode colorNode = geomNode.getChild(atom.colorStateIndex);
+            if (colorNode == null) {
+                colorNode = new StateNode(frame.colorState[atom.colorStateIndex],
+                                          frame.renderState.length);
+                geomNode.setChild(atom.colorStateIndex, colorNode);
+            }
+
+            // render state
+            StateNode renderNode = colorNode.getChild(atom.renderStateIndex);
+            if (renderNode == null) {
+                // must clone the geometry since each node accumulates its own
+                // packed transforms that must be rendered
+                renderNode = new StateNode(frame.renderState[atom.renderStateIndex].cloneGeometry());
+                colorNode.setChild(atom.renderStateIndex, renderNode);
+            }
+
+            // now record the transform into the render node's state
+            e.get(transform);
+            ((RenderState) renderNode.getState()).add(transform.getMatrix());
+        }
+
+        // every entity in the PVS has been put into the tree, which is automatically
+        // clustered by the defined state hierarchy
+        return new com.ferox.renderer.Task<Void>() {
+            @Override
+            public Void run(HardwareAccessLayer access) {
+                Context ctx = access.setActiveSurface(surface);
+                if (ctx != null) {
+                    Profiler.push("render-tree");
+                    FixedFunctionRenderer ffp = ctx.getFixedFunctionRenderer();
+                    // FIXME clear color should be configurable somehow
+                    ffp.clear(true, true, true, new Vector4(0, 0, 0, 1.0), 1, 0);
+                    ffp.setDrawStyle(DrawStyle.SOLID, DrawStyle.SOLID);
+                    root.visit(new AppliedEffects(), access);
+                    Profiler.pop();
+
+                    count++;
+                    if (count > 100) {
+                        Profiler.getDataSnapshot().print(System.out);
+                        count = 0;
+                    }
+                    if (flush) {
+                        ctx.flush();
+                    }
+                }
+                return null;
+            }
+        };
+    }
+
+    static int count = 0;
+
+    private void syncEntityState(Entity e, RenderAtom atom, Frame frame) {
+        // sync render state
+        e.get(renderable);
+        boolean renderableChanged = atom.renderableVersion != renderable.getVersion();
+        if (renderableChanged || atom.renderStateIndex < 0) {
+            atom.renderableVersion = renderable.getVersion();
+            if (atom.renderStateIndex >= 0) {
+                frame.renderUsage[atom.renderStateIndex]--;
+            }
+            atom.renderStateIndex = frame.getRenderState(renderable);
+            frame.renderUsage[atom.renderStateIndex]++;
+        }
+
+        // sync geometry state
+        e.get(blinnPhong);
+        boolean blinnChanged = (blinnPhong.isEnabled() ? atom.blinnPhongVersion != blinnPhong.getVersion() : atom.blinnPhongVersion >= 0);
+        if (renderableChanged || blinnChanged || atom.geometryStateIndex < 0) {
+            // renderable version already synced above
+            atom.blinnPhongVersion = (blinnPhong.isEnabled() ? blinnPhong.getVersion() : -1);
+            if (atom.geometryStateIndex >= 0) {
+                frame.geometryUsage[atom.geometryStateIndex]--;
+            }
+            atom.geometryStateIndex = frame.getGeometryState(renderable, blinnPhong);
+            frame.geometryUsage[atom.geometryStateIndex]++;
+        }
+
+        // sync texture state
+        e.get(diffuseTexture);
+        e.get(decalTexture);
+        e.get(emittedTexture);
+        boolean dftChanged = (diffuseTexture.isEnabled() ? atom.diffuseTextureVersion != diffuseTexture.getVersion() : atom.diffuseTextureVersion >= 0);
+        boolean dctChanged = (decalTexture.isEnabled() ? atom.decalTextureVersion != decalTexture.getVersion() : atom.decalTextureVersion >= 0);
+        boolean emtChanged = (emittedTexture.isEnabled() ? atom.emittedTextureVersion != emittedTexture.getVersion() : atom.emittedTextureVersion >= 0);
+        if (dftChanged || dctChanged || emtChanged || atom.textureStateIndex < 0) {
+            atom.diffuseTextureVersion = (diffuseTexture.isEnabled() ? diffuseTexture.getVersion() : -1);
+            atom.decalTextureVersion = (decalTexture.isEnabled() ? decalTexture.getVersion() : -1);
+            atom.emittedTextureVersion = (emittedTexture.isEnabled() ? emittedTexture.getVersion() : -1);
+
+            if (atom.textureStateIndex >= 0) {
+                frame.textureUsage[atom.textureStateIndex]--;
+            }
+            atom.textureStateIndex = frame.getTextureState(diffuseTexture, decalTexture,
+                                                           emittedTexture);
+            frame.textureUsage[atom.textureStateIndex]++;
+        }
+
+        // sync color state
+        e.get(diffuseColor);
+        e.get(specularColor);
+        e.get(emittedColor);
+        e.get(transparent);
+        boolean dfcChanged = (diffuseColor.isEnabled() ? atom.diffuseColorVersion != diffuseColor.getVersion() : atom.diffuseColorVersion >= 0);
+        boolean spcChanged = (specularColor.isEnabled() ? atom.specularColorVersion != specularColor.getVersion() : atom.specularColorVersion >= 0);
+        boolean emcChanged = (emittedColor.isEnabled() ? atom.emittedColorVersion != emittedColor.getVersion() : atom.emittedColorVersion >= 0);
+        boolean transparentChanged = (transparent.isEnabled() ? atom.transparentVersion != transparent.getVersion() : atom.transparentVersion >= 0);
+        if (dfcChanged || spcChanged || emcChanged || blinnChanged || transparentChanged || atom.colorStateIndex < 0) {
+            // blinn phong version already synced
+            atom.diffuseColorVersion = (diffuseColor.isEnabled() ? diffuseColor.getVersion() : -1);
+            atom.specularColorVersion = (specularColor.isEnabled() ? specularColor.getVersion() : -1);
+            atom.emittedColorVersion = (emittedColor.isEnabled() ? emittedColor.getVersion() : -1);
+
+            if (atom.colorStateIndex >= 0) {
+                frame.colorUsage[atom.colorStateIndex]--;
+            }
+            atom.colorStateIndex = frame.getColorState(diffuseColor, specularColor,
+                                                       emittedColor, transparent,
+                                                       blinnPhong);
+            frame.colorUsage[atom.colorStateIndex]++;
+        }
+
+        // lit state
+        atom.lit = blinnPhong.isEnabled();
+    }
+
+    private static class Frame {
+        final ShadowMapCache shadowMap;
+
+        //FIXME        TransparentState[] transparentStates;
+        final LightingState litState;
+        final LightingState unlitState;
+
+        final int diffuseTextureUnit;
+        final int emissiveTextureUnit;
+        final int decalTextureUnit;
+
+        TextureState[] textureState;
+        GeometryState[] geometryState;
+        ColorState[] colorState;
+        RenderState[] renderState;
+
+        Map<TextureState, Integer> textureLookup;
+        Map<GeometryState, Integer> geometryLookup;
+        Map<ColorState, Integer> colorLookup;
+        Map<RenderState, Integer> renderLookup;
+
+        int[] textureUsage;
+        int[] geometryUsage;
+        int[] colorUsage;
+        int[] renderUsage;
+
+        // per-entity tracking
+        ObjectProperty<RenderAtom> atoms;
+
+        Frame(ShadowMapCache map, int diffuseTextureUnit, int decalTextureUnit,
+              int emissiveTextureUnit) {
+            shadowMap = map;
+
+            this.diffuseTextureUnit = diffuseTextureUnit;
+            this.decalTextureUnit = decalTextureUnit;
+            this.emissiveTextureUnit = emissiveTextureUnit;
+
+            litState = new LightingState(true);
+            unlitState = new LightingState(false);
+
+            textureState = new TextureState[0];
+            geometryState = new GeometryState[0];
+            colorState = new ColorState[0];
+            renderState = new RenderState[0];
+
+            textureUsage = new int[0];
+            geometryUsage = new int[0];
+            colorUsage = new int[0];
+            renderUsage = new int[0];
+
+            textureLookup = new HashMap<TextureState, Integer>();
+            geometryLookup = new HashMap<GeometryState, Integer>();
+            colorLookup = new HashMap<ColorState, Integer>();
+            renderLookup = new HashMap<RenderState, Integer>();
+        }
+
+        int getTextureState(DiffuseColorMap diffuse, DecalColorMap decal,
+                            EmittedColorMap emitted) {
+            Texture diffuseTex = (diffuse.isEnabled() ? diffuse.getTexture() : null);
+            VertexAttribute diffuseCoord = (diffuse.isEnabled() ? diffuse.getTextureCoordinates() : null);
+            Texture decalTex = (decal.isEnabled() ? decal.getTexture() : null);
+            VertexAttribute decalCoord = (decal.isEnabled() ? decal.getTextureCoordinates() : null);
+            Texture emittedTex = (emitted.isEnabled() ? emitted.getTexture() : null);
+            VertexAttribute emittedCoord = (emitted.isEnabled() ? emitted.getTextureCoordinates() : null);
+
+            TextureState state = new TextureState(diffuseTextureUnit,
+                                                  decalTextureUnit,
+                                                  emissiveTextureUnit);
+            state.set(diffuseTex, diffuseCoord, decalTex, decalCoord, emittedTex,
+                      emittedCoord);
+
+            Integer index = textureLookup.get(state);
+            if (index == null) {
+                // must create a new state
+                index = textureState.length;
+                textureState = Arrays.copyOf(textureState, textureState.length + 1);
+                textureUsage = Arrays.copyOf(textureUsage, textureUsage.length + 1);
+                textureState[index] = state;
+                textureLookup.put(state, index);
+            }
+
+            return index;
+        }
+
+        int getGeometryState(Renderable renderable, BlinnPhongMaterial blinnPhong) {
+            VertexAttribute verts = renderable.getVertices();
+            VertexAttribute norms = (blinnPhong.isEnabled() ? blinnPhong.getNormals() : null);
+
+            GeometryState state = new GeometryState();
+            state.set(verts, norms);
+
+            Integer index = geometryLookup.get(state);
+            if (index == null) {
+                // needs a new state
+                index = geometryState.length;
+                geometryState = Arrays.copyOf(geometryState, geometryState.length + 1);
+                geometryUsage = Arrays.copyOf(geometryUsage, geometryUsage.length + 1);
+                geometryState[index] = state;
+                geometryLookup.put(state, index);
+            }
+
+            return index;
+        }
+
+        int getColorState(DiffuseColor diffuse, SpecularColor specular,
+                          EmittedColor emitted, Transparent transparent,
+                          BlinnPhongMaterial blinnPhong) {
+            double alpha = (transparent.isEnabled() ? transparent.getOpacity() : 1.0);
+            double shininess = (blinnPhong.isEnabled() ? blinnPhong.getShininess() : 0.0);
+            ColorRGB d = (diffuse.isEnabled() ? diffuse.getColor() : null);
+            ColorRGB s = (specular.isEnabled() ? specular.getColor() : null);
+            ColorRGB e = (emitted.isEnabled() ? emitted.getColor() : null);
+
+            ColorState state = new ColorState();
+            state.set(d, s, e, alpha, shininess);
+
+            Integer index = colorLookup.get(state);
+            if (index == null) {
+                // must form a new state
+                index = colorState.length;
+                colorState = Arrays.copyOf(colorState, colorState.length + 1);
+                colorUsage = Arrays.copyOf(colorUsage, colorUsage.length + 1);
+                colorState[index] = state;
+                colorLookup.put(state, index);
+            }
+
+            return index;
+        }
+
+        int getRenderState(Renderable renderable) {
+            // we can assume that the renderable is always valid, since
+            // we're processing renderable entities
+            RenderState state = new RenderState();
+            state.set(renderable.getPolygonType(), renderable.getIndices(),
+                      renderable.getIndexOffset(), renderable.getIndexCount());
+
+            //            System.out.println("getting render state");
+            Integer index = renderLookup.get(state);
+            if (index == null) {
+                //                System.out.println("new render state");
+                // must form a new state
+                index = renderState.length;
+                renderState = Arrays.copyOf(renderState, renderState.length + 1);
+                renderUsage = Arrays.copyOf(renderUsage, renderUsage.length + 1);
+                renderState[index] = state;
+                renderLookup.put(state, index);
+            }
+
+            return index;
+        }
+
+        void resetStates() {
+            System.out.println("RESET");
+            textureState = new TextureState[0];
+            geometryState = new GeometryState[0];
+            colorState = new ColorState[0];
+            renderState = new RenderState[0];
+
+            textureUsage = new int[0];
+            geometryUsage = new int[0];
+            colorUsage = new int[0];
+            renderUsage = new int[0];
+
+            textureLookup = new HashMap<TextureState, Integer>();
+            geometryLookup = new HashMap<GeometryState, Integer>();
+            colorLookup = new HashMap<ColorState, Integer>();
+            renderLookup = new HashMap<RenderState, Integer>();
+
+            // clearing the render atoms effectively invalidates all of the
+            // version tracking we do as well
+            Arrays.fill(atoms.getIndexedData(), null);
+        }
+
+        boolean needsReset() {
+            int empty = 0;
+            int total = textureUsage.length + geometryUsage.length + colorUsage.length + renderUsage.length;
+
+            for (int i = 0; i < textureUsage.length; i++) {
+                if (textureUsage[i] == 0) {
+                    empty++;
+                }
+            }
+
+            for (int i = 0; i < geometryUsage.length; i++) {
+                if (geometryUsage[i] == 0) {
+                    empty++;
+                }
+            }
+
+            for (int i = 0; i < colorUsage.length; i++) {
+                if (colorUsage[i] == 0) {
+                    empty++;
+                }
+            }
+
+            for (int i = 0; i < renderUsage.length; i++) {
+                if (renderUsage[i] == 0) {
+                    empty++;
+                }
+            }
+
+            return empty / (double) total > .5;
+        }
+
+        @SuppressWarnings("unchecked")
+        void decorate(EntitySystem system) {
+            atoms = system.decorate(Renderable.class, new ObjectProperty.Factory(null));
+        }
+    }
+
+    private static class RenderAtom {
+        // state indices
+        int textureStateIndex = -1; // depends on the 3 texture versions
+        int colorStateIndex = -1; // depends on blinnphong-material and 3 color versions
+        int geometryStateIndex = -1; // depends on renderable, blinnphong-material
+        int renderStateIndex = -1; // depends on indices within renderable
+        boolean lit = false;
+
+        // component versions
+        int renderableVersion = -1;
+        int diffuseColorVersion = -1;
+        int emittedColorVersion = -1;
+        int specularColorVersion = -1;
+        int diffuseTextureVersion = -1;
+        int emittedTextureVersion = -1;
+        int decalTextureVersion = -1;
+        int blinnPhongVersion = -1;
+        int transparentVersion = -1;
+    }
+}

ferox-scene/src/main/java/com/ferox/scene/controller/ffp/GeometryGroupFactory.java

-/*
- * Ferox, a graphics and game library in Java
- *
- * Copyright (c) 2012, Michael Ludwig
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without modification,
- * are permitted provided that the following conditions are met:
- *
- *     Redistributions of source code must retain the above copyright notice,
- *         this list of conditions and the following disclaimer.
- *     Redistributions in binary form must reproduce the above copyright notice,
- *         this list of conditions and the following disclaimer in the
- *         documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
- * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
- * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.ferox.scene.controller.ffp;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import com.ferox.math.Matrix4;
-import com.ferox.renderer.FixedFunctionRenderer;
-import com.ferox.renderer.HardwareAccessLayer;
-import com.ferox.renderer.Renderer.PolygonType;
-import com.ferox.resource.VertexAttribute;
-import com.ferox.resource.VertexBufferObject;
-import com.ferox.scene.BlinnPhongMaterial;
-import com.ferox.scene.Renderable;
-import com.ferox.scene.Transform;
-import com.lhkbob.entreri.Entity;
-import com.lhkbob.entreri.EntitySystem;
-
-public final class GeometryGroupFactory implements StateGroupFactory {
-    // all groups created by the same GeometryGroupFactory object share these instances,
-    // this is safe only in a single-threaded context
-    private final Matrix4 modelMatrix;
-
-    private final Renderable geometry;
-    private final Transform transform;
-    private final BlinnPhongMaterial material;
-
-    private final Geometry access;
-
-    public GeometryGroupFactory(EntitySystem system) {
-        if (system == null) {
-            throw new NullPointerException("System cannot be null");
-        }
-
-        access = new Geometry();
-        modelMatrix = new Matrix4();
-        geometry = system.createDataInstance(Renderable.class);
-        transform = system.createDataInstance(Transform.class);
-        material = system.createDataInstance(BlinnPhongMaterial.class);
-    }
-
-    @Override
-    public StateGroup newGroup() {
-        return new GeometryGroup();
-    }
-
-    private class GeometryGroup implements StateGroup {
-        private final List<StateNode> allNodes;
-        private final Map<Geometry, StateNode> index;
-
-        public GeometryGroup() {
-            allNodes = new ArrayList<StateNode>();
-            index = new HashMap<Geometry, StateNode>();
-        }
-
-        @Override
-        public StateNode getNode(Entity e) {
-            if (!e.get(geometry)) {
-                return null;
-            }
-
-            VertexAttribute normals = (e.get(material) ? material.getNormals() : null);
-
-            access.set(geometry, normals);
-            StateNode node = index.get(access);
-            if (node == null) {
-                // no child group, this will be the leaf node
-                GeometryState state = new GeometryState(geometry, normals);
-                node = new StateNode(null, state);
-                allNodes.add(node);
-                index.put(state.geometry, node);
-            }
-
-            return node;
-        }
-
-        @Override
-        public List<StateNode> getNodes() {
-            return allNodes;
-        }
-
-        @Override
-        public AppliedEffects applyGroupState(HardwareAccessLayer access,
-                                              AppliedEffects effects) {
-            return effects;
-        }
-
-        @Override
-        public void unapplyGroupState(HardwareAccessLayer access, AppliedEffects effects) {
-            FixedFunctionRenderer r = access.getCurrentContext()
-                                            .getFixedFunctionRenderer();
-            // set attributes to null
-            r.setVertices(null);
-            r.setNormals(null);
-            // restore matrix to camera only
-            r.setModelViewMatrix(effects.getViewMatrix());
-        }
-    }
-
-    private class GeometryState implements State {
-        private final Geometry geometry; // immutable, do not call set()
-
-        // packed objects to render
-        private float[] matrices;
-        private int count;
-
-        public GeometryState(Renderable r, VertexAttribute n) {
-            geometry = new Geometry(r, n);
-
-            matrices = new float[32]; // 2 instances FIXME go to 1?
-            count = 0;
-        }
-
-        @Override
-        public void add(Entity e) {
-            if (count + 16 > matrices.length) {
-                // grow array
-                matrices = Arrays.copyOf(matrices, matrices.length * 2);
-            }
-
-            if (e.get(transform)) {
-                // use provided matrix
-                transform.getMatrix().get(matrices, count, false);
-            } else {
-                // use identity
-                modelMatrix.setIdentity().get(matrices, count, false);
-            }
-
-            count += 16;
-        }
-
-        @Override
-        public AppliedEffects applyState(HardwareAccessLayer access,
-                                         AppliedEffects effects, int index) {
-            FixedFunctionRenderer r = access.getCurrentContext()
-                                            .getFixedFunctionRenderer();
-
-            r.setVertices(geometry.vertices);
-            r.setNormals(geometry.normals);
-
-            if (geometry.indices == null) {
-                for (int i = 0; i < count; i += 16) {
-                    // load and multiply the model with the view
-                    modelMatrix.set(matrices, i, false);
-                    modelMatrix.mul(effects.getViewMatrix(), modelMatrix);
-
-                    r.setModelViewMatrix(modelMatrix);
-                    r.render(geometry.polyType, geometry.indexOffset, geometry.indexCount);
-                }
-            } else {
-                for (int i = 0; i < count; i += 16) {
-                    // load and multiply the model with the view
-                    modelMatrix.set(matrices, i, false);
-                    modelMatrix.mul(effects.getViewMatrix(), modelMatrix);
-
-                    r.setModelViewMatrix(modelMatrix);
-                    r.render(geometry.polyType, geometry.indices, geometry.indexOffset,
-                             geometry.indexCount);
-                }
-            }
-
-            return effects;
-        }
-
-        @Override
-        public void unapplyState(HardwareAccessLayer access, AppliedEffects effects,
-                                 int index) {
-            // do nothing
-        }
-    }
-
-    /*
-     * Internal POJO to store the geometry data used for clustering, of which
-     * there is a surprisingly large amount.
-     */
-    private static class Geometry {
-        private VertexAttribute vertices;
-        private VertexAttribute normals;
-        private VertexBufferObject indices;
-        private PolygonType polyType;
-        private int indexOffset;
-        private int indexCount;
-
-        public Geometry() {
-            // invalid state until set() is called
-        }
-
-        public Geometry(Renderable r, VertexAttribute n) {
-            set(r, n);
-        }
-
-        public void set(Renderable r, VertexAttribute n) {
-            vertices = r.getVertices();
-            indices = r.getIndices();
-            polyType = r.getPolygonType();
-            indexOffset = r.getIndexOffset();
-            indexCount = r.getIndexCount();
-
-            normals = n;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (!(o instanceof Geometry)) {
-                return false;
-            }
-            Geometry g = (Geometry) o;
-
-            // vertices
-            if (vertices != g.vertices) {
-                // if ref's aren't equal they might still use the same data
-                if (vertices.getData() != g.vertices.getData() || vertices.getElementSize() != g.vertices.getElementSize() || vertices.getOffset() != g.vertices.getOffset() || vertices.getStride() != g.vertices.getStride()) {
-                    return false;
-                }
-            }
-
-            // normals
-            if (normals != g.normals) {
-                // if ref's aren't equal they might still use the same data,
-                // but we also have to control for nullability
-                if (normals != null && g.normals != null) {
-                    // check access pattern
-                    if (normals.getData() != g.normals.getData() || normals.getElementSize() != g.normals.getElementSize() || normals.getOffset() != g.normals.getOffset() || normals.getStride() != g.normals.getStride()) {
-                        return false;
-                    }
-                }
-            }
-
-            // indices
-            if (indices == g.indices) {
-                if (indexCount != g.indexCount || indexOffset != g.indexOffset || polyType != g.polyType) {
-                    return false;
-                }
-            } else {
-                return false;
-            }
-
-            // everything is equal
-            return true;
-        }
-
-        @Override
-        public int hashCode() {
-            int result = 17;
-            result += 31 * indexCount;
-            result += 31 * indexOffset;
-            result += 31 * polyType.hashCode();
-            result += 31 * vertices.getData().getId();
-            if (indices != null) {
-                result += 31 * indices.getId();
-            }
-            if (normals != null) {
-                result += 31 * normals.getData().getId();
-            }
-            return result;
-        }
-    }
-}

ferox-scene/src/main/java/com/ferox/scene/controller/ffp/GeometryState.java

+package com.ferox.scene.controller.ffp;
+
+import com.ferox.renderer.FixedFunctionRenderer;
+import com.ferox.renderer.HardwareAccessLayer;
+import com.ferox.resource.VertexAttribute;
+
+public class GeometryState implements State {
+    private VertexAttribute vertices;
+    private VertexAttribute normals;
+
+    public void set(VertexAttribute vertices, VertexAttribute normals) {
+        this.vertices = vertices;
+        this.normals = normals;
+    }
+
+    public VertexAttribute getVertices() {
+        return vertices;
+    }
+
+    public VertexAttribute getNormals() {
+        return normals;
+    }
+
+    @Override
+    public void visitNode(StateNode currentNode, AppliedEffects effects,
+                          HardwareAccessLayer access) {
+        FixedFunctionRenderer r = access.getCurrentContext().getFixedFunctionRenderer();
+
+        r.setVertices(vertices);
+        r.setNormals(normals);
+
+        currentNode.visitChildren(effects, access);
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 17;
+        hash = 31 * hash + (vertices == null ? 0 : vertices.hashCode());
+        hash = 31 * hash + (normals == null ? 0 : normals.hashCode());
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof GeometryState)) {
+            return false;
+        }
+
+        GeometryState ts = (GeometryState) o;
+        return nullEquals(ts.normals, normals) && nullEquals(ts.vertices, vertices);
+    }
+
+    private static boolean nullEquals(Object a, Object b) {
+        return (a == null ? b == null : a.equals(b));
+    }
+}

ferox-scene/src/main/java/com/ferox/scene/controller/ffp/LightGroupFactory.java

-/*
- * Ferox, a graphics and game library in Java
- *
- * Copyright (c) 2012, Michael Ludwig
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without modification,
- * are permitted provided that the following conditions are met:
- *
- *     Redistributions of source code must retain the above copyright notice,
- *         this list of conditions and the following disclaimer.
- *     Redistributions in binary form must reproduce the above copyright notice,
- *         this list of conditions and the following disclaimer in the