Переглянути джерело

MPO: implement overrides on uniforms and add test

Kirill Vainer 9 роки тому
батько
коміт
59614e177c

+ 29 - 4
jme3-core/src/main/java/com/jme3/material/Material.java

@@ -785,12 +785,36 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
         sortingId = -1;
     }
 
-    private void updateShaderMaterialParameters(Renderer renderer, Shader shader) {
+    private void updateShaderMaterialParameters(Renderer renderer, Shader shader, ArrayList<MatParamOverride> overrides) {
         int unit = 0;
+
+        for (MatParamOverride override : overrides) {
+            VarType type = override.getVarType();
+
+            MatParam paramDef = def.getMaterialParam(override.getName());
+            if (paramDef == null || paramDef.getVarType() != type) {
+                continue;
+            }
+
+            Uniform uniform = shader.getUniform(override.getPrefixedName());
+            if (type.isTextureType()) {
+                renderer.setTexture(unit, (Texture) override.getValue());
+                uniform.setValue(VarType.Int, unit);
+                unit++;
+            } else {
+                uniform.setValue(type, override.getValue());
+            }
+        }
+
         for (int i = 0; i < paramValues.size(); i++) {
             MatParam param = paramValues.getValue(i);
             VarType type = param.getVarType();
             Uniform uniform = shader.getUniform(param.getPrefixedName());
+
+            if (uniform.isSetByCurrentMaterial()) {
+                continue;
+            }
+
             if (type.isTextureType()) {
                 renderer.setTexture(unit, (Texture) param.getValue());
                 uniform.setValue(VarType.Int, unit);
@@ -799,8 +823,9 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
                 uniform.setValue(type, param.getValue());
             }
         }
+
     }
-    
+
     private void updateRenderState(RenderManager renderManager, Renderer renderer, TechniqueDef techniqueDef) {
         if (renderManager.getForcedRenderState() != null) {
             renderer.applyRenderState(renderManager.getForcedRenderState());
@@ -835,7 +860,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
         }
 
         Shader shader = technique.makeCurrent(renderManager, null, null, rendererCaps);
-        updateShaderMaterialParameters(renderer, shader);
+        updateShaderMaterialParameters(renderer, shader, null);
         renderManager.getRenderer().setShader(shader);
     }
 
@@ -951,7 +976,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
         renderManager.updateUniformBindings(shader);
         
         // Set material parameters
-        updateShaderMaterialParameters(renderer, shader);
+        updateShaderMaterialParameters(renderer, shader, geometry.getWorldOverrides());
         
         // Clear any uniforms not changed by material.
         resetUniformsNotSetByCurrent(shader);

+ 17 - 12
jme3-core/src/main/java/com/jme3/material/Technique.java

@@ -44,7 +44,6 @@ import com.jme3.shader.VarType;
 import com.jme3.util.ListMap;
 import java.util.ArrayList;
 import java.util.EnumSet;
-import java.util.List;
 
 /**
  * Represents a technique instance.
@@ -53,6 +52,7 @@ public final class Technique {
 
     private final TechniqueDef def;
     private final Material owner;
+    private final DefineList paramDefines;
     private final DefineList dynamicDefines;
 
     /**
@@ -65,6 +65,7 @@ public final class Technique {
     public Technique(Material owner, TechniqueDef def) {
         this.owner = owner;
         this.def = def;
+        this.paramDefines = def.createDefineList();
         this.dynamicDefines = def.createDefineList();
     }
 
@@ -83,13 +84,14 @@ public final class Technique {
      * Called by the material to tell the technique a parameter was modified.
      * Specify <code>null</code> for value if the param is to be cleared.
      */
-    void notifyParamChanged(String paramName, VarType type, Object value) {
+    final void notifyParamChanged(String paramName, VarType type, Object value) {
         Integer defineId = def.getShaderParamDefineId(paramName);
+
         if (defineId == null) {
             return;
         }
 
-        dynamicDefines.set(defineId, type, value);
+        paramDefines.set(defineId, type, value);
     }
     
     /**
@@ -98,9 +100,9 @@ public final class Technique {
      * The technique updates dynamic defines based on the
      * currently set material parameters.
      */
-    void notifyTechniqueSwitched() {
+    final void notifyTechniqueSwitched() {
         ListMap<String, MatParam> paramMap = owner.getParamsMap();
-        dynamicDefines.clear();
+        paramDefines.clear();
         for (int i = 0; i < paramMap.size(); i++) {
             MatParam param = paramMap.getValue(i);
             notifyParamChanged(param.getName(), param.getVarType(), param.getValue());
@@ -122,18 +124,19 @@ public final class Technique {
         TechniqueDefLogic logic = def.getLogic();
         AssetManager assetManager = owner.getMaterialDef().getAssetManager();
 
-        // TODO: remove allocation
-        DefineList combinedDefines = def.createDefineList();
-        combinedDefines.setAll(dynamicDefines);
+        dynamicDefines.clear();
+        dynamicDefines.setAll(paramDefines);
 
         for (MatParamOverride override : overrides) {
             Integer defineId = def.getShaderParamDefineId(override.name);
             if (defineId != null) {
-                combinedDefines.set(defineId, override.type, override.value);
+                if (def.getDefineIdType(defineId) == override.type) {
+                    dynamicDefines.set(defineId, override.type, override.value);
+                }
             }
         }
 
-        return logic.makeCurrent(assetManager, renderManager, rendererCaps, lights, combinedDefines);
+        return logic.makeCurrent(assetManager, renderManager, rendererCaps, lights, dynamicDefines);
     }
     
     /**
@@ -163,8 +166,10 @@ public final class Technique {
     }
     
     /**
-     * @deprecated Preset defines are precompiled into 
-     * {@link TechniqueDef#getShaderPrologue()}, whereas
+     * @return nothing.
+     *
+     * @deprecated Preset defines are precompiled into
+       * {@link TechniqueDef#getShaderPrologue()}, whereas
      * dynamic defines are available via {@link #getParamDefines()}.
      */
     @Deprecated

+ 31 - 0
jme3-core/src/main/java/com/jme3/material/TechniqueDef.java

@@ -340,6 +340,15 @@ public class TechniqueDef implements Savable {
         return paramToDefineId.get(paramName);
     }
 
+    /**
+     * Get the type of a particular define.
+     *
+     * @param defineId The define ID to lookup.
+     * @return The type of the define, or null if not found.
+     */
+    public VarType getDefineIdType(int defineId) {
+        return defineId < defineTypes.size() ? defineTypes.get(defineId) : null;
+    }
     
     /**
      * Adds a define linked to a material parameter.
@@ -388,6 +397,28 @@ public class TechniqueDef implements Savable {
         defineTypes.add(defineType);
         return defineId;
     }
+
+    /**
+     * Get the names of all defines declared on this technique definition.
+     *
+     * The defines are returned in order of declaration.
+     *
+     * @return the names of all defines declared.
+     */
+    public String[] getDefineNames() {
+        return defineNames.toArray(new String[0]);
+    }
+
+    /**
+     * Get the types of all defines declared on this technique definition.
+     *
+     * The types are returned in order of declaration.
+     *
+     * @return the types of all defines declared.
+     */
+    public VarType[] getDefineTypes() {
+        return defineTypes.toArray(new VarType[0]);
+    }
     
     /**
      * Create a define list with the size matching the number

+ 24 - 0
jme3-core/src/main/java/com/jme3/shader/Uniform.java

@@ -70,6 +70,30 @@ public class Uniform extends ShaderVariable {
      */
     protected boolean setByCurrentMaterial = false;
 
+    @Override
+    public int hashCode() {
+        int hash = 5;
+        hash = 31 * hash + (this.value != null ? this.value.hashCode() : 0);
+        hash = 31 * hash + (this.varType != null ? this.varType.hashCode() : 0);
+        hash = 31 * hash + (this.binding != null ? this.binding.hashCode() : 0);
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        final Uniform other = (Uniform) obj;
+        if (this.value != other.value && (this.value == null || !this.value.equals(other.value))) {
+            return false;
+        }
+        return this.binding == other.binding && this.varType == other.varType;
+    }
+
     @Override
     public String toString(){
         StringBuilder sb = new StringBuilder();

+ 509 - 0
jme3-core/src/test/java/com/jme3/material/TechniqueDefMatParamOverrideTest.java

@@ -0,0 +1,509 @@
+/*
+ * Copyright (c) 2009-2016 jMonkeyEngine
+ * 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.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * 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 OWNER 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.jme3.material;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.light.LightList;
+import com.jme3.math.Matrix4f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+import com.jme3.shader.Shader;
+import com.jme3.shader.Uniform;
+import com.jme3.shader.VarType;
+import java.util.Arrays;
+import java.util.HashSet;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static com.jme3.scene.MPOTestUtils.*;
+import com.jme3.shader.DefineList;
+import com.jme3.system.NullRenderer;
+import com.jme3.system.TestUtil;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture2D;
+import java.util.HashMap;
+import java.util.Map;
+
+public class TechniqueDefMatParamOverrideTest {
+
+    private static final HashSet<String> IGNORED_UNIFORMS = new HashSet<String>(
+            Arrays.asList(new String[]{"m_ParallaxHeight", "m_Shininess"}));
+
+    @Test
+    public void testBoolMpoOnly() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        inputMpo(mpoBool("UseMaterialColors", true));
+        outDefines(def("MATERIAL_COLORS", VarType.Boolean, true));
+        outUniforms(uniform("UseMaterialColors", VarType.Boolean, true));
+    }
+
+    @Test
+    public void testBoolMpOnly() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        inputMp(mpoBool("UseMaterialColors", true));
+        outDefines(def("MATERIAL_COLORS", VarType.Boolean, true));
+        outUniforms(uniform("UseMaterialColors", VarType.Boolean, true));
+    }
+
+    @Test
+    public void testBoolOverrideFalse() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        inputMp(mpoBool("UseMaterialColors", true));
+        inputMpo(mpoBool("UseMaterialColors", false));
+        outDefines();
+        outUniforms(uniform("UseMaterialColors", VarType.Boolean, false));
+    }
+
+    @Test
+    public void testBoolOverrideTrue() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        inputMp(mpoBool("UseMaterialColors", false));
+        inputMpo(mpoBool("UseMaterialColors", true));
+        outDefines(def("MATERIAL_COLORS", VarType.Boolean, true));
+        outUniforms(uniform("UseMaterialColors", VarType.Boolean, true));
+    }
+
+    @Test
+    public void testFloatMpoOnly() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        inputMpo(mpoFloat("AlphaDiscardThreshold", 3.12f));
+        outDefines(def("DISCARD_ALPHA", VarType.Float, 3.12f));
+        outUniforms(uniform("AlphaDiscardThreshold", VarType.Float, 3.12f));
+    }
+
+    @Test
+    public void testFloatMpOnly() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        inputMp(mpoFloat("AlphaDiscardThreshold", 3.12f));
+        outDefines(def("DISCARD_ALPHA", VarType.Float, 3.12f));
+        outUniforms(uniform("AlphaDiscardThreshold", VarType.Float, 3.12f));
+    }
+
+    @Test
+    public void testFloatOverride() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        inputMp(mpoFloat("AlphaDiscardThreshold", 3.12f));
+        inputMpo(mpoFloat("AlphaDiscardThreshold", 2.79f));
+        outDefines(def("DISCARD_ALPHA", VarType.Float, 2.79f));
+        outUniforms(uniform("AlphaDiscardThreshold", VarType.Float, 2.79f));
+    }
+
+    @Test
+    public void testIntMpoOnly() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        inputMpo(mpoInt("NumberOfBones", 1234));
+        outDefines(def("NUM_BONES", VarType.Int, 1234));
+        outUniforms(uniform("NumberOfBones", VarType.Int, 1234));
+    }
+
+    @Test
+    public void testIntMpOnly() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        inputMp(mpoInt("NumberOfBones", 1234));
+        outDefines(def("NUM_BONES", VarType.Int, 1234));
+        outUniforms(uniform("NumberOfBones", VarType.Int, 1234));
+    }
+
+    @Test
+    public void testIntOverride() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        inputMp(mpoInt("NumberOfBones", 1234));
+        inputMpo(mpoInt("NumberOfBones", 4321));
+        outDefines(def("NUM_BONES", VarType.Int, 4321));
+        outUniforms(uniform("NumberOfBones", VarType.Int, 4321));
+    }
+
+    @Test
+    public void testMatrixArray() {
+        Matrix4f[] matrices = new Matrix4f[]{
+            new Matrix4f()
+        };
+
+        material("Common/MatDefs/Light/Lighting.j3md");
+        inputMpo(mpoMatrix4Array("BoneMatrices", matrices));
+        outDefines();
+        outUniforms(uniform("BoneMatrices", VarType.Matrix4Array, matrices));
+    }
+
+    @Test
+    public void testNonExistentParameter() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        inputMpo(mpoInt("NonExistent", 4321));
+        outDefines();
+        outUniforms();
+    }
+
+    @Test
+    public void testWrongType() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        inputMpo(mpoInt("UseMaterialColors", 4321));
+        outDefines();
+        outUniforms();
+    }
+
+    @Test
+    public void testParamOnly() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        inputMpo(mpoFloat("ShadowMapSize", 3.12f));
+        outDefines();
+        outUniforms(uniform("ShadowMapSize", VarType.Float, 3.12f));
+    }
+
+    @Test
+    public void testRemove() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+
+        reset();
+        inputMp(mpoInt("NumberOfBones", 1234));
+        outDefines(def("NUM_BONES", VarType.Int, 1234));
+        outUniforms(uniform("NumberOfBones", VarType.Int, 1234));
+
+        reset();
+        inputMpo(mpoInt("NumberOfBones", 4321));
+        outDefines(def("NUM_BONES", VarType.Int, 4321));
+        outUniforms(uniform("NumberOfBones", VarType.Int, 4321));
+
+        reset();
+        geometry.clearMatParamOverrides();
+        outDefines(def("NUM_BONES", VarType.Int, 1234));
+        outUniforms(uniform("NumberOfBones", VarType.Int, 1234));
+
+        reset();
+        geometry.getMaterial().clearParam("NumberOfBones");
+        outDefines();
+        outUniforms();
+
+        reset();
+        inputMpo(mpoInt("NumberOfBones", 4321));
+        outDefines(def("NUM_BONES", VarType.Int, 4321));
+        outUniforms(uniform("NumberOfBones", VarType.Int, 4321));
+
+        reset();
+        inputMp(mpoInt("NumberOfBones", 1234));
+        outDefines(def("NUM_BONES", VarType.Int, 4321));
+        outUniforms(uniform("NumberOfBones", VarType.Int, 4321));
+    }
+
+    public void testRemoveOverride() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+
+        reset();
+        inputMp(mpoInt("NumberOfBones", 1234));
+        outDefines(def("NUM_BONES", VarType.Int, 1234));
+        outUniforms(uniform("NumberOfBones", VarType.Int, 1234));
+
+        reset();
+        inputMpo(mpoInt("NumberOfBones", 4321));
+        outDefines(def("NUM_BONES", VarType.Int, 4321));
+        outUniforms(uniform("NumberOfBones", VarType.Int, 4321));
+
+        reset();
+        geometry.clearMatParamOverrides();
+        outDefines(def("NUM_BONES", VarType.Int, 1234));
+        outUniforms(uniform("NumberOfBones", VarType.Int, 1234));
+    }
+
+    @Test
+    public void testRemoveMpoOnly() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+
+        reset();
+        inputMpo(mpoInt("NumberOfBones", 4321));
+        outDefines(def("NUM_BONES", VarType.Int, 4321));
+        outUniforms(uniform("NumberOfBones", VarType.Int, 4321));
+
+        reset();
+        geometry.clearMatParamOverrides();
+        outDefines();
+        outUniforms();
+    }
+
+    @Test
+    public void testTextureMpoOnly() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        Texture2D tex = new Texture2D(128, 128, Format.RGBA8);
+
+        inputMpo(mpoTexture2D("DiffuseMap", tex));
+        outDefines(def("DIFFUSEMAP", VarType.Texture2D, tex));
+        outUniforms(uniform("DiffuseMap", VarType.Int, 0));
+        outTextures(tex);
+    }
+
+    @Test
+    public void testTextureOverride() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        Texture2D tex1 = new Texture2D(128, 128, Format.RGBA8);
+        Texture2D tex2 = new Texture2D(128, 128, Format.RGBA8);
+
+        inputMp(mpoTexture2D("DiffuseMap", tex1));
+        inputMpo(mpoTexture2D("DiffuseMap", tex2));
+
+        outDefines(def("DIFFUSEMAP", VarType.Texture2D, tex2));
+        outUniforms(uniform("DiffuseMap", VarType.Int, 0));
+        outTextures(tex2);
+    }
+
+    @Test
+    public void testRemoveTexture() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        Texture2D tex = new Texture2D(128, 128, Format.RGBA8);
+
+        reset();
+        inputMpo(mpoTexture2D("DiffuseMap", tex));
+        outDefines(def("DIFFUSEMAP", VarType.Texture2D, tex));
+        outUniforms(uniform("DiffuseMap", VarType.Int, 0));
+        outTextures(tex);
+
+        reset();
+        geometry.clearMatParamOverrides();
+        outDefines();
+        outUniforms();
+        outTextures();
+    }
+
+    @Test
+    public void testRemoveTextureOverride() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        Texture2D tex1 = new Texture2D(128, 128, Format.RGBA8);
+        Texture2D tex2 = new Texture2D(128, 128, Format.RGBA8);
+
+        reset();
+        inputMp(mpoTexture2D("DiffuseMap", tex1));
+        outDefines(def("DIFFUSEMAP", VarType.Texture2D, tex1));
+        outUniforms(uniform("DiffuseMap", VarType.Int, 0));
+        outTextures(tex1);
+
+        reset();
+        inputMpo(mpoTexture2D("DiffuseMap", tex2));
+        outDefines(def("DIFFUSEMAP", VarType.Texture2D, tex2));
+        outUniforms(uniform("DiffuseMap", VarType.Int, 0));
+        outTextures(tex2);
+
+        reset();
+        geometry.clearMatParamOverrides();
+        outDefines(def("DIFFUSEMAP", VarType.Texture2D, tex1));
+        outUniforms(uniform("DiffuseMap", VarType.Int, 0));
+        outTextures(tex1);
+    }
+
+    private static final class Define {
+
+        public String name;
+        public VarType type;
+        public Object value;
+
+        @Override
+        public int hashCode() {
+            int hash = 3;
+            hash = 89 * hash + this.name.hashCode();
+            hash = 89 * hash + this.type.hashCode();
+            hash = 89 * hash + this.value.hashCode();
+            return hash;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            final Define other = (Define) obj;
+            return this.name.equals(other.name) && this.type.equals(other.type) && this.value.equals(other.value);
+        }
+    }
+
+    private final Geometry geometry = new Geometry("geometry", new Box(1, 1, 1));
+    private final LightList lightList = new LightList(geometry);
+
+    private final NullRenderer renderer = new NullRenderer() {
+        @Override
+        public void setShader(Shader shader) {
+            TechniqueDefMatParamOverrideTest.this.usedShader = shader;
+            evaluated = true;
+        }
+
+        @Override
+        public void setTexture(int unit, Texture texture) {
+            TechniqueDefMatParamOverrideTest.this.usedTextures[unit] = texture;
+        }
+    };
+    private final RenderManager renderManager = new RenderManager(renderer);
+
+    private boolean evaluated = false;
+    private Shader usedShader = null;
+    private final Texture[] usedTextures = new Texture[32];
+
+    private void inputMp(MatParam... params) {
+        if (evaluated) {
+            throw new IllegalStateException();
+        }
+        Material mat = geometry.getMaterial();
+        for (MatParam param : params) {
+            mat.setParam(param.getName(), param.getVarType(), param.getValue());
+        }
+    }
+
+    private void inputMpo(MatParamOverride... overrides) {
+        if (evaluated) {
+            throw new IllegalStateException();
+        }
+        for (MatParamOverride override : overrides) {
+            geometry.addMatParamOverride(override);
+        }
+        geometry.updateGeometricState();
+    }
+
+    private void reset() {
+        evaluated = false;
+        usedShader = null;
+        Arrays.fill(usedTextures, null);
+    }
+
+    private Define def(String name, VarType type, Object value) {
+        Define d = new Define();
+        d.name = name;
+        d.type = type;
+        d.value = value;
+        return d;
+    }
+
+    private Uniform uniform(String name, VarType type, Object value) {
+        Uniform u = new Uniform();
+        u.setName("m_" + name);
+        u.setValue(type, value);
+        return u;
+    }
+
+    private void material(String path) {
+        AssetManager assetManager = TestUtil.createAssetManager();
+        geometry.setMaterial(new Material(assetManager, path));
+    }
+
+    private void evaluateTechniqueDef() {
+        Assert.assertFalse(evaluated);
+        Material mat = geometry.getMaterial();
+        mat.render(geometry, lightList, renderManager);
+        Assert.assertTrue(evaluated);
+    }
+
+    private void outTextures(Texture... textures) {
+        for (int i = 0; i < usedTextures.length; i++) {
+            if (i < textures.length) {
+                Assert.assertSame(textures[i], usedTextures[i]);
+            } else {
+                Assert.assertNull(usedTextures[i]);
+            }
+        }
+    }
+
+    private void outDefines(Define... expectedDefinesArray) {
+        Map<String, Define> nameToDefineMap = new HashMap<String, Define>();
+        for (Define define : expectedDefinesArray) {
+            nameToDefineMap.put(define.name, define);
+        }
+
+        if (!evaluated) {
+            evaluateTechniqueDef();
+        }
+
+        Material mat = geometry.getMaterial();
+        Technique tech = mat.getActiveTechnique();
+        TechniqueDef def = tech.getDef();
+        DefineList actualDefines = tech.getDynamicDefines();
+
+        String[] defineNames = def.getDefineNames();
+        VarType[] defineTypes = def.getDefineTypes();
+
+        Assert.assertEquals(defineNames.length, defineTypes.length);
+
+        for (int index = 0; index < defineNames.length; index++) {
+            String name = defineNames[index];
+            VarType type = defineTypes[index];
+            Define expectedDefine = nameToDefineMap.remove(name);
+            Object expectedValue = null;
+
+            if (expectedDefine != null) {
+                Assert.assertEquals(expectedDefine.type, type);
+                expectedValue = expectedDefine.value;
+            }
+
+            switch (type) {
+                case Boolean:
+                    if (expectedValue != null) {
+                        Assert.assertEquals((boolean) (Boolean) expectedValue, actualDefines.getBoolean(index));
+                    } else {
+                        Assert.assertEquals(false, actualDefines.getBoolean(index));
+                    }
+                    break;
+                case Int:
+                    if (expectedValue != null) {
+                        Assert.assertEquals((int) (Integer) expectedValue, actualDefines.getInt(index));
+                    } else {
+                        Assert.assertEquals(0, actualDefines.getInt(index));
+                    }
+                    break;
+                case Float:
+                    if (expectedValue != null) {
+                        Assert.assertEquals((float) (Float) expectedValue, actualDefines.getFloat(index), 0f);
+                    } else {
+                        Assert.assertEquals(0f, actualDefines.getFloat(index), 0f);
+                    }
+                    break;
+                default:
+                    if (expectedValue != null) {
+                        Assert.assertEquals(1, actualDefines.getInt(index));
+                    } else {
+                        Assert.assertEquals(0, actualDefines.getInt(index));
+                    }
+                    break;
+            }
+        }
+
+        Assert.assertTrue(nameToDefineMap.isEmpty());
+    }
+
+    private void outUniforms(Uniform... uniforms) {
+        HashSet<Uniform> actualUniforms = new HashSet<Uniform>();
+
+        for (Uniform uniform : usedShader.getUniformMap().values()) {
+            if (uniform.getName().startsWith("m_")
+                    && !IGNORED_UNIFORMS.contains(uniform.getName())) {
+                actualUniforms.add(uniform);
+            }
+        }
+
+        HashSet<Uniform> expectedUniforms = new HashSet<Uniform>(Arrays.asList(uniforms));
+
+        if (!expectedUniforms.equals(actualUniforms)) {
+            System.out.println(expectedUniforms + " != " + actualUniforms);
+            Assert.fail("Uniform lists must match");
+        }
+    }
+}