ソースを参照

Add the new material system

Also includes some unrelated tests
Kirill Vainer 9 年 前
コミット
18db26292f
33 ファイル変更2068 行追加1211 行削除
  1. 0 9
      jme3-core/src/main/java/com/jme3/asset/AssetManager.java
  2. 0 33
      jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java
  3. 96 0
      jme3-core/src/main/java/com/jme3/material/DefaultTechniqueDefLogic.java
  4. 0 3
      jme3-core/src/main/java/com/jme3/material/MatParam.java
  5. 0 6
      jme3-core/src/main/java/com/jme3/material/MatParamTexture.java
  6. 102 354
      jme3-core/src/main/java/com/jme3/material/Material.java
  7. 176 0
      jme3-core/src/main/java/com/jme3/material/MultiPassLightingLogic.java
  8. 218 0
      jme3-core/src/main/java/com/jme3/material/SinglePassLightingLogic.java
  9. 90 143
      jme3-core/src/main/java/com/jme3/material/Technique.java
  10. 187 76
      jme3-core/src/main/java/com/jme3/material/TechniqueDef.java
  11. 95 0
      jme3-core/src/main/java/com/jme3/material/TechniqueDefLogic.java
  12. 9 4
      jme3-core/src/main/java/com/jme3/renderer/RenderManager.java
  13. 26 15
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
  14. 10 0
      jme3-core/src/main/java/com/jme3/renderer/queue/GeometryList.java
  15. 3 2
      jme3-core/src/main/java/com/jme3/renderer/queue/OpaqueComparator.java
  16. 128 286
      jme3-core/src/main/java/com/jme3/shader/DefineList.java
  17. 56 23
      jme3-core/src/main/java/com/jme3/shader/Shader.java
  18. 30 17
      jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java
  19. 0 201
      jme3-core/src/main/java/com/jme3/shader/ShaderKey.java
  20. 3 2
      jme3-core/src/main/java/com/jme3/shader/UniformBindingManager.java
  21. 4 3
      jme3-core/src/main/java/com/jme3/system/NullContext.java
  22. 1 1
      jme3-core/src/main/java/com/jme3/system/NullRenderer.java
  23. 5 12
      jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md
  24. 58 5
      jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java
  25. 5 4
      jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java
  26. 52 0
      jme3-core/src/test/java/com/jme3/asset/LoadShaderSourceTest.java
  27. 34 0
      jme3-core/src/test/java/com/jme3/math/FastMathTest.java
  28. 342 0
      jme3-core/src/test/java/com/jme3/renderer/OpaqueComparatorTest.java
  29. 143 0
      jme3-core/src/test/java/com/jme3/shader/DefineListTest.java
  30. 78 0
      jme3-core/src/test/java/com/jme3/system/MockJmeSystemDelegate.java
  31. 55 0
      jme3-core/src/test/java/com/jme3/system/TestUtil.java
  32. 12 12
      jme3-core/src/tools/java/jme3tools/shadercheck/ShaderCheck.java
  33. 50 0
      jme3-desktop/src/test/java/LibraryLoaderTest.java

+ 0 - 9
jme3-core/src/main/java/com/jme3/asset/AssetManager.java

@@ -41,9 +41,7 @@ import com.jme3.post.FilterPostProcessor;
 import com.jme3.renderer.Caps;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.plugins.OBJLoader;
-import com.jme3.shader.Shader;
 import com.jme3.shader.ShaderGenerator;
-import com.jme3.shader.ShaderKey;
 import com.jme3.texture.Texture;
 import com.jme3.texture.plugins.TGALoader;
 import java.io.IOException;
@@ -320,13 +318,6 @@ public interface AssetManager {
      */
     public Material loadMaterial(String name);
 
-    /**
-     * Loads shader file(s), shouldn't be used by end-user in most cases.
-     *
-     * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
-     */
-    public Shader loadShader(ShaderKey key);
-
     /**
      * Load a font file. Font files are in AngelCode text format,
      * and are with the extension "fnt".

+ 0 - 33
jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java

@@ -32,7 +32,6 @@
 package com.jme3.asset;
 
 import com.jme3.asset.cache.AssetCache;
-import com.jme3.asset.cache.SimpleAssetCache;
 import com.jme3.audio.AudioData;
 import com.jme3.audio.AudioKey;
 import com.jme3.font.BitmapFont;
@@ -42,9 +41,7 @@ import com.jme3.renderer.Caps;
 import com.jme3.scene.Spatial;
 import com.jme3.shader.Glsl100ShaderGenerator;
 import com.jme3.shader.Glsl150ShaderGenerator;
-import com.jme3.shader.Shader;
 import com.jme3.shader.ShaderGenerator;
-import com.jme3.shader.ShaderKey;
 import com.jme3.system.JmeSystem;
 import com.jme3.texture.Texture;
 import java.io.IOException;
@@ -431,36 +428,6 @@ public class DesktopAssetManager implements AssetManager {
         return loadFilter(new FilterKey(name));
     }
 
-    /**
-     * Load a vertex/fragment shader combo.
-     *
-     * @param key
-     * @return the loaded {@link Shader}
-     */
-    public Shader loadShader(ShaderKey key){
-        // cache abuse in method
-        // that doesn't use loaders/locators
-        AssetCache cache = handler.getCache(SimpleAssetCache.class);
-        Shader shader = (Shader) cache.getFromCache(key);
-        if (shader == null){
-            if (key.isUsesShaderNodes()) {
-                if(shaderGenerator == null){
-                    throw new UnsupportedOperationException("ShaderGenerator was not initialized, make sure assetManager.getGenerator(caps) has been called");
-                }
-                shader = shaderGenerator.generateShader();
-            } else {
-                shader = new Shader();
-                shader.initialize();
-                for (Shader.ShaderType shaderType : key.getUsedShaderPrograms()) {
-                    shader.addSource(shaderType,key.getShaderProgramName(shaderType),(String) loadAsset(new AssetKey(key.getShaderProgramName(shaderType))),key.getDefines().getCompiled(),key.getShaderProgramLanguage(shaderType));
-                }
-            }
-
-            cache.addToCache(key, shader);
-        }
-        return shader;
-    }
-
     /**
      * {@inheritDoc}
      */

+ 96 - 0
jme3-core/src/main/java/com/jme3/material/DefaultTechniqueDefLogic.java

@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2009-2015 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.AmbientLight;
+import com.jme3.light.Light;
+import com.jme3.light.LightList;
+import com.jme3.math.ColorRGBA;
+import com.jme3.renderer.Caps;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.instancing.InstancedGeometry;
+import com.jme3.shader.DefineList;
+import com.jme3.shader.Shader;
+import java.util.EnumSet;
+
+public class DefaultTechniqueDefLogic implements TechniqueDefLogic {
+
+    protected final TechniqueDef techniqueDef;
+    
+    public DefaultTechniqueDefLogic(TechniqueDef techniqueDef) {
+        this.techniqueDef = techniqueDef;
+    }
+   
+    @Override
+    public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager, 
+                              EnumSet<Caps> rendererCaps, DefineList defines) {
+        return techniqueDef.getShader(assetManager, rendererCaps, defines);
+    }
+
+    public static void renderMeshFromGeometry(Renderer renderer, Geometry geom) {
+        Mesh mesh = geom.getMesh();
+        int lodLevel = geom.getLodLevel();
+        if (geom instanceof InstancedGeometry) {
+            InstancedGeometry instGeom = (InstancedGeometry) geom;
+            renderer.renderMesh(mesh, lodLevel, instGeom.getActualNumInstances(), 
+                                                instGeom.getAllInstanceData());
+        } else {
+            renderer.renderMesh(mesh, lodLevel, 1, null);
+        }
+    }
+    
+    public static ColorRGBA getAmbientColor(LightList lightList, boolean removeLights, ColorRGBA ambientLightColor) {
+        ambientLightColor.set(0, 0, 0, 1);
+        for (int j = 0; j < lightList.size(); j++) {
+            Light l = lightList.get(j);
+            if (l instanceof AmbientLight) {
+                ambientLightColor.addLocal(l.getColor());
+                if (removeLights) {
+                    lightList.remove(l);
+                }
+            }
+        }
+        ambientLightColor.a = 1.0f;
+        return ambientLightColor;
+    }
+    
+    @Override
+    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights) {
+        Renderer renderer = renderManager.getRenderer();
+        renderer.setShader(shader);
+        renderMeshFromGeometry(renderer, geometry);
+    }
+}

+ 0 - 3
jme3-core/src/main/java/com/jme3/material/MatParam.java

@@ -129,9 +129,6 @@ public class MatParam implements Savable, Cloneable {
         this.value = value;
     }
 
-    void apply(Renderer r, Technique technique) {
-        technique.updateUniformParam(getPrefixedName(), getVarType(), getValue());
-    }
 
     /**
      * Returns the material parameter value as it would appear in a J3M

+ 0 - 6
jme3-core/src/main/java/com/jme3/material/MatParamTexture.java

@@ -100,12 +100,6 @@ public class MatParamTexture extends MatParam {
         return unit;
     }
 
-    @Override
-    public void apply(Renderer r, Technique technique) {
-        TechniqueDef techDef = technique.getDef();
-        r.setTexture(getUnit(), getTextureValue());
-        technique.updateUniformParam(getPrefixedName(), getVarType(), getUnit());
-    }
 
     @Override
     public void write(JmeExporter ex) throws IOException {

+ 102 - 354
jme3-core/src/main/java/com/jme3/material/Material.java

@@ -44,18 +44,16 @@ import com.jme3.math.*;
 import com.jme3.renderer.Caps;
 import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.Renderer;
-import com.jme3.renderer.RendererException;
 import com.jme3.renderer.queue.RenderQueue.Bucket;
 import com.jme3.scene.Geometry;
-import com.jme3.scene.Mesh;
-import com.jme3.scene.instancing.InstancedGeometry;
 import com.jme3.shader.Shader;
 import com.jme3.shader.Uniform;
+import com.jme3.shader.UniformBindingManager;
 import com.jme3.shader.VarType;
+import com.jme3.texture.Image;
 import com.jme3.texture.Texture;
 import com.jme3.texture.image.ColorSpace;
 import com.jme3.util.ListMap;
-import com.jme3.util.TempVars;
 import java.io.IOException;
 import java.util.*;
 import java.util.logging.Level;
@@ -79,7 +77,6 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
     private static final Logger logger = Logger.getLogger(Material.class.getName());
     private static final RenderState additiveLight = new RenderState();
     private static final RenderState depthOnly = new RenderState();
-    private static final Quaternion nullDirLight = new Quaternion(0, -1, 0, -1);
 
     static {
         depthOnly.setDepthTest(true);
@@ -175,22 +172,29 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
      * @return The sorting ID used for sorting geometries for rendering.
      */
     public int getSortId() {
-        Technique t = getActiveTechnique();
-        if (sortingId == -1 && t != null && t.getShader() != null) {
-            int texId = -1;
+        if (sortingId == -1 && technique != null) {
+            sortingId = technique.getSortId() << 16;
+            int texturesSortId = 17;
             for (int i = 0; i < paramValues.size(); i++) {
                 MatParam param = paramValues.getValue(i);
-                if (param instanceof MatParamTexture) {
-                    MatParamTexture tex = (MatParamTexture) param;
-                    if (tex.getTextureValue() != null && tex.getTextureValue().getImage() != null) {
-                        if (texId == -1) {
-                            texId = 0;
-                        }
-                        texId += tex.getTextureValue().getImage().getId() % 0xff;
-                    }
+                if (!param.getVarType().isTextureType()) {
+                    continue;
+                }
+                Texture texture = (Texture) param.getValue();
+                if (texture == null) {
+                    continue;
                 }
+                Image image = texture.getImage();
+                if (image == null) {
+                    continue;
+                }
+                int textureId = image.getId();
+                if (textureId == -1) {
+                    textureId = 0;
+                }
+                texturesSortId = texturesSortId * 23 + textureId;
             }
-            sortingId = texId + t.getShader().getId() * 1000;
+            sortingId |= texturesSortId & 0xFFFF;
         }
         return sortingId;
     }
@@ -215,6 +219,8 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
                 mat.paramValues.put(entry.getKey(), entry.getValue().clone());
             }
 
+            mat.sortingId = -1;
+            
             return mat;
         } catch (CloneNotSupportedException ex) {
             throw new AssertionError(ex);
@@ -444,7 +450,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
      *
      * @see #setParam(java.lang.String, com.jme3.shader.VarType, java.lang.Object)
      */
-    public ListMap getParamsMap() {
+    public ListMap<String, MatParam> getParamsMap() {
         return paramValues;
     }
 
@@ -695,257 +701,6 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
         setParam(name, VarType.Vector4, value);
     }
 
-    private ColorRGBA getAmbientColor(LightList lightList, boolean removeLights) {
-        ambientLightColor.set(0, 0, 0, 1);
-        for (int j = 0; j < lightList.size(); j++) {
-            Light l = lightList.get(j);
-            if (l instanceof AmbientLight) {
-                ambientLightColor.addLocal(l.getColor());
-                if(removeLights){
-                    lightList.remove(l);
-                }
-            }
-        }
-        ambientLightColor.a = 1.0f;
-        return ambientLightColor;
-    }
-
-    private static void renderMeshFromGeometry(Renderer renderer, Geometry geom) {
-        Mesh mesh = geom.getMesh();
-        int lodLevel = geom.getLodLevel();
-        if (geom instanceof InstancedGeometry) {
-            InstancedGeometry instGeom = (InstancedGeometry) geom;
-            int numInstances = instGeom.getActualNumInstances();
-            if (numInstances == 0) {
-                return;
-            }
-            if (renderer.getCaps().contains(Caps.MeshInstancing)) {
-                renderer.renderMesh(mesh, lodLevel, numInstances, instGeom.getAllInstanceData());
-            } else {
-                throw new RendererException("Mesh instancing is not supported by the video hardware");
-            }
-        } else {
-            renderer.renderMesh(mesh, lodLevel, 1, null);
-        }
-    }
-
-    /**
-     * Uploads the lights in the light list as two uniform arrays.<br/><br/> *
-     * <p>
-     * <code>uniform vec4 g_LightColor[numLights];</code><br/> //
-     * g_LightColor.rgb is the diffuse/specular color of the light.<br/> //
-     * g_Lightcolor.a is the type of light, 0 = Directional, 1 = Point, <br/> //
-     * 2 = Spot. <br/> <br/>
-     * <code>uniform vec4 g_LightPosition[numLights];</code><br/> //
-     * g_LightPosition.xyz is the position of the light (for point lights)<br/>
-     * // or the direction of the light (for directional lights).<br/> //
-     * g_LightPosition.w is the inverse radius (1/r) of the light (for
-     * attenuation) <br/> </p>
-     */
-    protected int updateLightListUniforms(Shader shader, Geometry g, LightList lightList, int numLights, RenderManager rm, int startIndex) {
-        if (numLights == 0) { // this shader does not do lighting, ignore.
-            return 0;
-        }
-
-        Uniform lightData = shader.getUniform("g_LightData");
-        lightData.setVector4Length(numLights * 3);//8 lights * max 3
-        Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
-
-
-        if (startIndex != 0) {
-            // apply additive blending for 2nd and future passes
-            rm.getRenderer().applyRenderState(additiveLight);
-            ambientColor.setValue(VarType.Vector4, ColorRGBA.Black);
-        }else{
-            ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList,true));
-        }
-
-        int lightDataIndex = 0;
-        TempVars vars = TempVars.get();
-        Vector4f tmpVec = vars.vect4f1;
-        int curIndex;
-        int endIndex = numLights + startIndex;
-        for (curIndex = startIndex; curIndex < endIndex && curIndex < lightList.size(); curIndex++) {
-
-
-                Light l = lightList.get(curIndex);
-                if(l.getType() == Light.Type.Ambient){
-                    endIndex++;
-                    continue;
-                }
-                ColorRGBA color = l.getColor();
-                //Color
-                lightData.setVector4InArray(color.getRed(),
-                        color.getGreen(),
-                        color.getBlue(),
-                        l.getType().getId(),
-                        lightDataIndex);
-                lightDataIndex++;
-
-                switch (l.getType()) {
-                    case Directional:
-                        DirectionalLight dl = (DirectionalLight) l;
-                        Vector3f dir = dl.getDirection();
-                        //Data directly sent in view space to avoid a matrix mult for each pixel
-                        tmpVec.set(dir.getX(), dir.getY(), dir.getZ(), 0.0f);
-                        rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec);
-//                        tmpVec.divideLocal(tmpVec.w);
-//                        tmpVec.normalizeLocal();
-                        lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), -1, lightDataIndex);
-                        lightDataIndex++;
-                        //PADDING
-                        lightData.setVector4InArray(0,0,0,0, lightDataIndex);
-                        lightDataIndex++;
-                        break;
-                    case Point:
-                        PointLight pl = (PointLight) l;
-                        Vector3f pos = pl.getPosition();
-                        float invRadius = pl.getInvRadius();
-                        tmpVec.set(pos.getX(), pos.getY(), pos.getZ(), 1.0f);
-                        rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec);
-                        //tmpVec.divideLocal(tmpVec.w);
-                        lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRadius, lightDataIndex);
-                        lightDataIndex++;
-                        //PADDING
-                        lightData.setVector4InArray(0,0,0,0, lightDataIndex);
-                        lightDataIndex++;
-                        break;
-                    case Spot:
-                        SpotLight sl = (SpotLight) l;
-                        Vector3f pos2 = sl.getPosition();
-                        Vector3f dir2 = sl.getDirection();
-                        float invRange = sl.getInvSpotRange();
-                        float spotAngleCos = sl.getPackedAngleCos();
-                        tmpVec.set(pos2.getX(), pos2.getY(), pos2.getZ(),  1.0f);
-                        rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec);
-                       // tmpVec.divideLocal(tmpVec.w);
-                        lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRange, lightDataIndex);
-                        lightDataIndex++;
-
-                        //We transform the spot direction in view space here to save 5 varying later in the lighting shader
-                        //one vec4 less and a vec4 that becomes a vec3
-                        //the downside is that spotAngleCos decoding happens now in the frag shader.
-                        tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(),  0.0f);
-                        rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec);
-                        tmpVec.normalizeLocal();
-                        lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos, lightDataIndex);
-                        lightDataIndex++;
-                        break;
-                    default:
-                        throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
-                }
-        }
-        vars.release();
-        //Padding of unsued buffer space
-        while(lightDataIndex < numLights * 3) {
-            lightData.setVector4InArray(0f, 0f, 0f, 0f, lightDataIndex);
-            lightDataIndex++;
-        }
-        return curIndex;
-    }
-
-    protected void renderMultipassLighting(Shader shader, Geometry g, LightList lightList, RenderManager rm) {
-
-        Renderer r = rm.getRenderer();
-        Uniform lightDir = shader.getUniform("g_LightDirection");
-        Uniform lightColor = shader.getUniform("g_LightColor");
-        Uniform lightPos = shader.getUniform("g_LightPosition");
-        Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
-        boolean isFirstLight = true;
-        boolean isSecondLight = false;
-
-        for (int i = 0; i < lightList.size(); i++) {
-            Light l = lightList.get(i);
-            if (l instanceof AmbientLight) {
-                continue;
-            }
-
-            if (isFirstLight) {
-                // set ambient color for first light only
-                ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList, false));
-                isFirstLight = false;
-                isSecondLight = true;
-            } else if (isSecondLight) {
-                ambientColor.setValue(VarType.Vector4, ColorRGBA.Black);
-                // apply additive blending for 2nd and future lights
-                r.applyRenderState(additiveLight);
-                isSecondLight = false;
-            }
-
-            TempVars vars = TempVars.get();
-            Quaternion tmpLightDirection = vars.quat1;
-            Quaternion tmpLightPosition = vars.quat2;
-            ColorRGBA tmpLightColor = vars.color;
-            Vector4f tmpVec = vars.vect4f1;
-
-            ColorRGBA color = l.getColor();
-            tmpLightColor.set(color);
-            tmpLightColor.a = l.getType().getId();
-            lightColor.setValue(VarType.Vector4, tmpLightColor);
-
-            switch (l.getType()) {
-                case Directional:
-                    DirectionalLight dl = (DirectionalLight) l;
-                    Vector3f dir = dl.getDirection();
-                    //FIXME : there is an inconstency here due to backward
-                    //compatibility of the lighting shader.
-                    //The directional light direction is passed in the
-                    //LightPosition uniform. The lighting shader needs to be
-                    //reworked though in order to fix this.
-                    tmpLightPosition.set(dir.getX(), dir.getY(), dir.getZ(), -1);
-                    lightPos.setValue(VarType.Vector4, tmpLightPosition);
-                    tmpLightDirection.set(0, 0, 0, 0);
-                    lightDir.setValue(VarType.Vector4, tmpLightDirection);
-                    break;
-                case Point:
-                    PointLight pl = (PointLight) l;
-                    Vector3f pos = pl.getPosition();
-                    float invRadius = pl.getInvRadius();
-
-                    tmpLightPosition.set(pos.getX(), pos.getY(), pos.getZ(), invRadius);
-                    lightPos.setValue(VarType.Vector4, tmpLightPosition);
-                    tmpLightDirection.set(0, 0, 0, 0);
-                    lightDir.setValue(VarType.Vector4, tmpLightDirection);
-                    break;
-                case Spot:
-                    SpotLight sl = (SpotLight) l;
-                    Vector3f pos2 = sl.getPosition();
-                    Vector3f dir2 = sl.getDirection();
-                    float invRange = sl.getInvSpotRange();
-                    float spotAngleCos = sl.getPackedAngleCos();
-
-                    tmpLightPosition.set(pos2.getX(), pos2.getY(), pos2.getZ(), invRange);
-                    lightPos.setValue(VarType.Vector4, tmpLightPosition);
-
-                    //We transform the spot direction in view space here to save 5 varying later in the lighting shader
-                    //one vec4 less and a vec4 that becomes a vec3
-                    //the downside is that spotAngleCos decoding happens now in the frag shader.
-                    tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(), 0);
-                    rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec);
-                    tmpLightDirection.set(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos);
-
-                    lightDir.setValue(VarType.Vector4, tmpLightDirection);
-
-                    break;
-                default:
-                    throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
-            }
-            vars.release();
-            r.setShader(shader);
-            renderMeshFromGeometry(r, g);
-        }
-
-        if (isFirstLight) {
-            // Either there are no lights at all, or only ambient lights.
-            // Render a dummy "normal light" so we can see the ambient color.
-            ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList, false));
-            lightColor.setValue(VarType.Vector4, ColorRGBA.BlackNoAlpha);
-            lightPos.setValue(VarType.Vector4, nullDirLight);
-            r.setShader(shader);
-            renderMeshFromGeometry(r, g);
-        }
-    }
-
     /**
      * Select the technique to use for rendering this material.
      * <p>
@@ -974,9 +729,8 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
         Technique tech = techniques.get(name);
         // When choosing technique, we choose one that
         // supports all the caps.
-        EnumSet<Caps> rendererCaps = renderManager.getRenderer().getCaps();
         if (tech == null) {
-
+            EnumSet<Caps> rendererCaps = renderManager.getRenderer().getCaps();
             if (name.equals("Default")) {
                 List<TechniqueDef> techDefs = def.getDefaultTechniques();
                 if (techDefs == null || techDefs.isEmpty()) {
@@ -1025,20 +779,40 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
         }
 
         technique = tech;
-        tech.makeCurrent(def.getAssetManager(), true, rendererCaps, renderManager);
+        tech.notifyTechniqueSwitched();
 
         // shader was changed
         sortingId = -1;
     }
 
-    private void autoSelectTechnique(RenderManager rm) {
-        if (technique == null) {
-            selectTechnique("Default", rm);
+    private void updateShaderMaterialParameters(Renderer renderer, Shader shader) {
+        int unit = 0;
+        for (int i = 0; i < paramValues.size(); i++) {
+            MatParam param = paramValues.getValue(i);
+            VarType type = param.getVarType();
+            Uniform uniform = shader.getUniform(param.getPrefixedName());
+            if (type.isTextureType()) {
+                renderer.setTexture(unit, (Texture) param.getValue());
+                uniform.setValue(VarType.Int, unit);
+                unit++;
+            } else {
+                uniform.setValue(type, param.getValue());
+            }
+        }
+    }
+    
+    private void updateRenderState(RenderManager renderManager, Renderer renderer, TechniqueDef techniqueDef) {
+        if (renderManager.getForcedRenderState() != null) {
+            renderer.applyRenderState(renderManager.getForcedRenderState());
         } else {
-            technique.makeCurrent(def.getAssetManager(), false, rm.getRenderer().getCaps(), rm);
+            if (techniqueDef.getRenderState() != null) {
+                renderer.applyRenderState(techniqueDef.getRenderState().copyMergedTo(additionalState, mergedRenderState));
+            } else {
+                renderer.applyRenderState(RenderState.DEFAULT.copyMergedTo(additionalState, mergedRenderState));
+            }
         }
     }
-
+    
     /**
      * Preloads this material for the given render manager.
      * <p>
@@ -1048,18 +822,21 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
      *
      * @param rm The render manager to preload for
      */
-    public void preload(RenderManager rm) {
-        autoSelectTechnique(rm);
-
-        Renderer r = rm.getRenderer();
-        TechniqueDef techDef = technique.getDef();
+    public void preload(RenderManager renderManager) {
+        if (technique == null) {
+            selectTechnique("Default", renderManager);
+        }
+        TechniqueDef techniqueDef = technique.getDef();
+        Renderer renderer = renderManager.getRenderer();
+        EnumSet<Caps> rendererCaps = renderer.getCaps();
 
-        Collection<MatParam> params = paramValues.values();
-        for (MatParam param : params) {
-            param.apply(r, technique);
+        if (techniqueDef.isNoRender()) {
+            return;
         }
 
-        r.setShader(technique.getShader());
+        Shader shader = technique.makeCurrent(renderManager, rendererCaps);
+        updateShaderMaterialParameters(renderer, shader);
+        renderManager.getRenderer().setShader(shader);
     }
 
     private void clearUniformsSetByCurrent(Shader shader) {
@@ -1141,80 +918,43 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
      * </ul>
      * </ul>
      *
-     * @param geom The geometry to render
+     * @param geometry The geometry to render
      * @param lights Presorted and filtered light list to use for rendering
-     * @param rm The render manager requesting the rendering
+     * @param renderManager The render manager requesting the rendering
      */
-    public void render(Geometry geom, LightList lights, RenderManager rm) {
-        autoSelectTechnique(rm);
-        TechniqueDef techDef = technique.getDef();
-
-        if (techDef.isNoRender()) return;
-
-        Renderer r = rm.getRenderer();
-
-        if (rm.getForcedRenderState() != null) {
-            r.applyRenderState(rm.getForcedRenderState());
-        } else {
-            if (techDef.getRenderState() != null) {
-                r.applyRenderState(techDef.getRenderState().copyMergedTo(additionalState, mergedRenderState));
-            } else {
-                r.applyRenderState(RenderState.DEFAULT.copyMergedTo(additionalState, mergedRenderState));
-            }
-        }
-
-
-        // update camera and world matrices
-        // NOTE: setWorldTransform should have been called already
-
-        // reset unchanged uniform flag
-        clearUniformsSetByCurrent(technique.getShader());
-        rm.updateUniformBindings(technique.getWorldBindUniforms());
-
-
-        // setup textures and uniforms
-        for (int i = 0; i < paramValues.size(); i++) {
-            MatParam param = paramValues.getValue(i);
-            param.apply(r, technique);
+    public void render(Geometry geometry, LightList lights, RenderManager renderManager) {
+        if (technique == null) {
+            selectTechnique("Default", renderManager);
         }
-
-        Shader shader = technique.getShader();
-
-        // send lighting information, if needed
-        switch (techDef.getLightMode()) {
-            case Disable:
-                break;
-            case SinglePass:
-                int nbRenderedLights = 0;
-                resetUniformsNotSetByCurrent(shader);
-                if (lights.size() == 0) {
-                    nbRenderedLights = updateLightListUniforms(shader, geom, lights, rm.getSinglePassLightBatchSize(), rm, 0);
-                    r.setShader(shader);
-                    renderMeshFromGeometry(r, geom);
-                } else {
-                    while (nbRenderedLights < lights.size()) {
-                        nbRenderedLights = updateLightListUniforms(shader, geom, lights, rm.getSinglePassLightBatchSize(), rm, nbRenderedLights);
-                        r.setShader(shader);
-                        renderMeshFromGeometry(r, geom);
-                    }
-                }
-                return;
-            case FixedPipeline:
-                throw new IllegalArgumentException("OpenGL1 is not supported");
-            case MultiPass:
-                // NOTE: Special case!
-                resetUniformsNotSetByCurrent(shader);
-                renderMultipassLighting(shader, geom, lights, rm);
-                // very important, notice the return statement!
-                return;
+        
+        TechniqueDef techniqueDef = technique.getDef();
+        Renderer renderer = renderManager.getRenderer();
+        EnumSet<Caps> rendererCaps = renderer.getCaps();
+        
+        if (techniqueDef.isNoRender()) {
+            return;
         }
 
-        // upload and bind shader
-        // any unset uniforms will be set to 0
+        // Apply render state
+        updateRenderState(renderManager, renderer, techniqueDef);
+
+        // Select shader to use
+        Shader shader = technique.makeCurrent(renderManager, rendererCaps);
+        
+        // Begin tracking which uniforms were changed by material.
+        clearUniformsSetByCurrent(shader);
+        
+        // Set uniform bindings
+        renderManager.updateUniformBindings(shader);
+        
+        // Set material parameters
+        updateShaderMaterialParameters(renderer, shader);
+        
+        // Clear any uniforms not changed by material.
         resetUniformsNotSetByCurrent(shader);
-        r.setShader(shader);
-
-        renderMeshFromGeometry(r, geom);
+        
+        // Delegate rendering to the technique
+        technique.render(renderManager, shader, geometry, lights);
     }
 
     /**
@@ -1238,6 +978,14 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
         oc.write(transparent, "is_transparent", false);
         oc.writeStringSavableMap(paramValues, "parameters", null);
     }
+    
+    @Override
+    public String toString() {
+        return "Material[name=" + name + 
+                ", def=" + def.getName() + 
+                ", tech=" + technique.getDef().getName() + 
+                "]";
+    }
 
     public void read(JmeImporter im) throws IOException {
         InputCapsule ic = im.getCapsule(this);

+ 176 - 0
jme3-core/src/main/java/com/jme3/material/MultiPassLightingLogic.java

@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2009-2015 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.AmbientLight;
+import com.jme3.light.DirectionalLight;
+import com.jme3.light.Light;
+import com.jme3.light.LightList;
+import com.jme3.light.PointLight;
+import com.jme3.light.SpotLight;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.math.Vector4f;
+import com.jme3.renderer.Caps;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.scene.Geometry;
+import com.jme3.shader.DefineList;
+import com.jme3.shader.Shader;
+import com.jme3.shader.Uniform;
+import com.jme3.shader.VarType;
+import com.jme3.util.TempVars;
+import java.util.EnumSet;
+
+public final class MultiPassLightingLogic extends DefaultTechniqueDefLogic {
+
+    private static final RenderState ADDITIVE_LIGHT = new RenderState();
+    private static final Quaternion NULL_DIR_LIGHT = new Quaternion(0, -1, 0, -1);
+    
+    private final ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1);
+    
+    static {
+        ADDITIVE_LIGHT.setBlendMode(RenderState.BlendMode.AlphaAdditive);
+        ADDITIVE_LIGHT.setDepthWrite(false);
+    }
+    
+    public MultiPassLightingLogic(TechniqueDef techniqueDef) {
+        super(techniqueDef);
+    }
+
+    @Override
+    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights) {
+        Renderer r = renderManager.getRenderer();
+        Uniform lightDir = shader.getUniform("g_LightDirection");
+        Uniform lightColor = shader.getUniform("g_LightColor");
+        Uniform lightPos = shader.getUniform("g_LightPosition");
+        Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
+        boolean isFirstLight = true;
+        boolean isSecondLight = false;
+        
+        getAmbientColor(lights, false, ambientLightColor);
+
+        for (int i = 0; i < lights.size(); i++) {
+            Light l = lights.get(i);
+            if (l instanceof AmbientLight) {
+                continue;
+            }
+
+            if (isFirstLight) {
+                // set ambient color for first light only
+                ambientColor.setValue(VarType.Vector4, ambientLightColor);
+                isFirstLight = false;
+                isSecondLight = true;
+            } else if (isSecondLight) {
+                ambientColor.setValue(VarType.Vector4, ColorRGBA.Black);
+                // apply additive blending for 2nd and future lights
+                r.applyRenderState(ADDITIVE_LIGHT);
+                isSecondLight = false;
+            }
+
+            TempVars vars = TempVars.get();
+            Quaternion tmpLightDirection = vars.quat1;
+            Quaternion tmpLightPosition = vars.quat2;
+            ColorRGBA tmpLightColor = vars.color;
+            Vector4f tmpVec = vars.vect4f1;
+
+            ColorRGBA color = l.getColor();
+            tmpLightColor.set(color);
+            tmpLightColor.a = l.getType().getId();
+            lightColor.setValue(VarType.Vector4, tmpLightColor);
+
+            switch (l.getType()) {
+                case Directional:
+                    DirectionalLight dl = (DirectionalLight) l;
+                    Vector3f dir = dl.getDirection();
+                    //FIXME : there is an inconstency here due to backward
+                    //compatibility of the lighting shader.
+                    //The directional light direction is passed in the
+                    //LightPosition uniform. The lighting shader needs to be
+                    //reworked though in order to fix this.
+                    tmpLightPosition.set(dir.getX(), dir.getY(), dir.getZ(), -1);
+                    lightPos.setValue(VarType.Vector4, tmpLightPosition);
+                    tmpLightDirection.set(0, 0, 0, 0);
+                    lightDir.setValue(VarType.Vector4, tmpLightDirection);
+                    break;
+                case Point:
+                    PointLight pl = (PointLight) l;
+                    Vector3f pos = pl.getPosition();
+                    float invRadius = pl.getInvRadius();
+
+                    tmpLightPosition.set(pos.getX(), pos.getY(), pos.getZ(), invRadius);
+                    lightPos.setValue(VarType.Vector4, tmpLightPosition);
+                    tmpLightDirection.set(0, 0, 0, 0);
+                    lightDir.setValue(VarType.Vector4, tmpLightDirection);
+                    break;
+                case Spot:
+                    SpotLight sl = (SpotLight) l;
+                    Vector3f pos2 = sl.getPosition();
+                    Vector3f dir2 = sl.getDirection();
+                    float invRange = sl.getInvSpotRange();
+                    float spotAngleCos = sl.getPackedAngleCos();
+
+                    tmpLightPosition.set(pos2.getX(), pos2.getY(), pos2.getZ(), invRange);
+                    lightPos.setValue(VarType.Vector4, tmpLightPosition);
+
+                    //We transform the spot direction in view space here to save 5 varying later in the lighting shader
+                    //one vec4 less and a vec4 that becomes a vec3
+                    //the downside is that spotAngleCos decoding happens now in the frag shader.
+                    tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(), 0);
+                    renderManager.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec);
+                    tmpLightDirection.set(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos);
+
+                    lightDir.setValue(VarType.Vector4, tmpLightDirection);
+
+                    break;
+                default:
+                    throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
+            }
+            vars.release();
+            r.setShader(shader);
+            renderMeshFromGeometry(r, geometry);
+        }
+
+        if (isFirstLight) {
+            // Either there are no lights at all, or only ambient lights.
+            // Render a dummy "normal light" so we can see the ambient color.
+            ambientColor.setValue(VarType.Vector4, getAmbientColor(lights, false, ambientLightColor));
+            lightColor.setValue(VarType.Vector4, ColorRGBA.BlackNoAlpha);
+            lightPos.setValue(VarType.Vector4, NULL_DIR_LIGHT);
+            r.setShader(shader);
+            renderMeshFromGeometry(r, geometry);
+        }
+    }
+}

+ 218 - 0
jme3-core/src/main/java/com/jme3/material/SinglePassLightingLogic.java

@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2009-2015 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.DirectionalLight;
+import com.jme3.light.Light;
+import com.jme3.light.LightList;
+import com.jme3.light.PointLight;
+import com.jme3.light.SpotLight;
+import com.jme3.material.RenderState.BlendMode;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.math.Vector4f;
+import com.jme3.renderer.Caps;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.scene.Geometry;
+import com.jme3.shader.DefineList;
+import com.jme3.shader.Shader;
+import com.jme3.shader.Uniform;
+import com.jme3.shader.VarType;
+import com.jme3.util.TempVars;
+import java.util.EnumSet;
+
+public final class SinglePassLightingLogic extends DefaultTechniqueDefLogic {
+
+    private static final String DEFINE_SINGLE_PASS_LIGHTING = "SINGLE_PASS_LIGHTING";
+    private static final String DEFINE_NB_LIGHTS = "NB_LIGHTS";
+    private static final RenderState ADDITIVE_LIGHT = new RenderState();
+    
+    private final ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1);
+    
+    static {
+        ADDITIVE_LIGHT.setBlendMode(BlendMode.AlphaAdditive);
+        ADDITIVE_LIGHT.setDepthWrite(false);
+    }
+    
+    private final int singlePassLightingDefineId;
+    private final int nbLightsDefineId;
+
+    public SinglePassLightingLogic(TechniqueDef techniqueDef) {
+        super(techniqueDef);
+        singlePassLightingDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_SINGLE_PASS_LIGHTING, VarType.Boolean);
+        nbLightsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NB_LIGHTS, VarType.Int);
+    }
+    
+    @Override
+    public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager,
+            EnumSet<Caps> rendererCaps, DefineList defines) {
+        defines.set(nbLightsDefineId, renderManager.getSinglePassLightBatchSize() * 3);
+        defines.set(singlePassLightingDefineId, true);
+        return super.makeCurrent(assetManager, renderManager, rendererCaps, defines);
+    }
+    
+    /**
+     * Uploads the lights in the light list as two uniform arrays.<br/><br/> *
+     * <p>
+     * <code>uniform vec4 g_LightColor[numLights];</code><br/> //
+     * g_LightColor.rgb is the diffuse/specular color of the light.<br/> //
+     * g_Lightcolor.a is the type of light, 0 = Directional, 1 = Point, <br/> //
+     * 2 = Spot. <br/> <br/>
+     * <code>uniform vec4 g_LightPosition[numLights];</code><br/> //
+     * g_LightPosition.xyz is the position of the light (for point lights)<br/>
+     * // or the direction of the light (for directional lights).<br/> //
+     * g_LightPosition.w is the inverse radius (1/r) of the light (for
+     * attenuation) <br/> </p>
+     */
+    protected int updateLightListUniforms(Shader shader, Geometry g, LightList lightList, int numLights, RenderManager rm, int startIndex) {
+        if (numLights == 0) { // this shader does not do lighting, ignore.
+            return 0;
+        }
+
+        Uniform lightData = shader.getUniform("g_LightData");
+        lightData.setVector4Length(numLights * 3);//8 lights * max 3
+        Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
+
+
+        if (startIndex != 0) {
+            // apply additive blending for 2nd and future passes
+            rm.getRenderer().applyRenderState(ADDITIVE_LIGHT);
+            ambientColor.setValue(VarType.Vector4, ColorRGBA.Black);
+        } else {
+            ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList, true, ambientLightColor));
+        }
+
+        int lightDataIndex = 0;
+        TempVars vars = TempVars.get();
+        Vector4f tmpVec = vars.vect4f1;
+        int curIndex;
+        int endIndex = numLights + startIndex;
+        for (curIndex = startIndex; curIndex < endIndex && curIndex < lightList.size(); curIndex++) {
+
+
+                Light l = lightList.get(curIndex);
+                if(l.getType() == Light.Type.Ambient){
+                    endIndex++;
+                    continue;
+                }
+                ColorRGBA color = l.getColor();
+                //Color
+                lightData.setVector4InArray(color.getRed(),
+                        color.getGreen(),
+                        color.getBlue(),
+                        l.getType().getId(),
+                        lightDataIndex);
+                lightDataIndex++;
+
+                switch (l.getType()) {
+                    case Directional:
+                        DirectionalLight dl = (DirectionalLight) l;
+                        Vector3f dir = dl.getDirection();
+                        //Data directly sent in view space to avoid a matrix mult for each pixel
+                        tmpVec.set(dir.getX(), dir.getY(), dir.getZ(), 0.0f);
+                        rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec);
+//                        tmpVec.divideLocal(tmpVec.w);
+//                        tmpVec.normalizeLocal();
+                        lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), -1, lightDataIndex);
+                        lightDataIndex++;
+                        //PADDING
+                        lightData.setVector4InArray(0,0,0,0, lightDataIndex);
+                        lightDataIndex++;
+                        break;
+                    case Point:
+                        PointLight pl = (PointLight) l;
+                        Vector3f pos = pl.getPosition();
+                        float invRadius = pl.getInvRadius();
+                        tmpVec.set(pos.getX(), pos.getY(), pos.getZ(), 1.0f);
+                        rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec);
+                        //tmpVec.divideLocal(tmpVec.w);
+                        lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRadius, lightDataIndex);
+                        lightDataIndex++;
+                        //PADDING
+                        lightData.setVector4InArray(0,0,0,0, lightDataIndex);
+                        lightDataIndex++;
+                        break;
+                    case Spot:
+                        SpotLight sl = (SpotLight) l;
+                        Vector3f pos2 = sl.getPosition();
+                        Vector3f dir2 = sl.getDirection();
+                        float invRange = sl.getInvSpotRange();
+                        float spotAngleCos = sl.getPackedAngleCos();
+                        tmpVec.set(pos2.getX(), pos2.getY(), pos2.getZ(),  1.0f);
+                        rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec);
+                       // tmpVec.divideLocal(tmpVec.w);
+                        lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRange, lightDataIndex);
+                        lightDataIndex++;
+
+                        //We transform the spot direction in view space here to save 5 varying later in the lighting shader
+                        //one vec4 less and a vec4 that becomes a vec3
+                        //the downside is that spotAngleCos decoding happens now in the frag shader.
+                        tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(),  0.0f);
+                        rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec);
+                        tmpVec.normalizeLocal();
+                        lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos, lightDataIndex);
+                        lightDataIndex++;
+                        break;
+                    default:
+                        throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
+                }
+        }
+        vars.release();
+        //Padding of unsued buffer space
+        while(lightDataIndex < numLights * 3) {
+            lightData.setVector4InArray(0f, 0f, 0f, 0f, lightDataIndex);
+            lightDataIndex++;
+        }
+        return curIndex;
+    }
+
+    
+    @Override
+    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights) {
+        int nbRenderedLights = 0;
+        Renderer renderer = renderManager.getRenderer();
+        int batchSize = renderManager.getSinglePassLightBatchSize();
+        if (lights.size() == 0) {
+            updateLightListUniforms(shader, geometry, lights, batchSize, renderManager, 0);
+            renderer.setShader(shader);
+            renderMeshFromGeometry(renderer, geometry);
+        } else {
+            while (nbRenderedLights < lights.size()) {
+                nbRenderedLights = updateLightListUniforms(shader, geometry, lights, batchSize, renderManager, nbRenderedLights);
+                renderer.setShader(shader);
+                renderMeshFromGeometry(renderer, geometry);
+            }
+        }
+    }
+}

+ 90 - 143
jme3-core/src/main/java/com/jme3/material/Technique.java

@@ -32,26 +32,25 @@
 package com.jme3.material;
 
 import com.jme3.asset.AssetManager;
+import com.jme3.light.LightList;
+import com.jme3.material.TechniqueDef.LightMode;
 import com.jme3.renderer.Caps;
 import com.jme3.renderer.RenderManager;
-import com.jme3.shader.*;
-import java.util.ArrayList;
+import com.jme3.scene.Geometry;
+import com.jme3.shader.DefineList;
+import com.jme3.shader.Shader;
+import com.jme3.shader.VarType;
+import com.jme3.util.ListMap;
 import java.util.EnumSet;
-import java.util.List;
-import java.util.logging.Logger;
 
 /**
  * Represents a technique instance.
  */
-public class Technique /* implements Savable */ {
+public final class Technique {
 
-    private static final Logger logger = Logger.getLogger(Technique.class.getName());
-    private TechniqueDef def;
-    private Material owner;
-    private ArrayList<Uniform> worldBindUniforms;
-    private DefineList defines;
-    private Shader shader;
-    private boolean needReload = true;
+    private final TechniqueDef def;
+    private final Material owner;
+    private final DefineList dynamicDefines;
 
     /**
      * Creates a new technique instance that implements the given
@@ -63,14 +62,7 @@ public class Technique /* implements Savable */ {
     public Technique(Material owner, TechniqueDef def) {
         this.owner = owner;
         this.def = def;
-        this.worldBindUniforms = new ArrayList<Uniform>();
-        this.defines = new DefineList();
-    }
-
-    /**
-     * Serialization only. Do not use.
-     */
-    public Technique() {
+        this.dynamicDefines = def.createDefineList();
     }
 
     /**
@@ -84,158 +76,113 @@ public class Technique /* implements Savable */ {
         return def;
     }
 
-    /**
-     * Returns the shader currently used by this technique instance.
-     * <p>
-     * Shaders are typically loaded dynamically when the technique is first
-     * used, therefore, this variable will most likely be null most of the time.
-     * 
-     * @return the shader currently used by this technique instance.
-     */
-    public Shader getShader() {
-        return shader;
-    }
-
-    /**
-     * Returns a list of uniforms that implements the world parameters
-     * that were requested by the material definition.
-     * 
-     * @return a list of uniforms implementing the world parameters.
-     */
-    public List<Uniform> getWorldBindUniforms() {
-        return worldBindUniforms;
-    }
-
     /**
      * 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) {
-        // Check if there's a define binding associated with this
-        // parameter.
-        String defineName = def.getShaderParamDefine(paramName);
-        if (defineName != null) {
-            // There is a define. Change it on the define list.
-            // The "needReload" variable will determine
-            // if the shader will be reloaded when the material
-            // is rendered.
-            
-            if (value == null) {
-                // Clear the define.
-                needReload = defines.remove(defineName) || needReload;
-            } else {
-                // Set the define.
-                needReload = defines.set(defineName, type, value) || needReload;
-            }
+        Integer defineId = def.getShaderParamDefineId(paramName);
+        if (defineId == null) {
+            return;
         }
-    }
-
-    void updateUniformParam(String paramName, VarType type, Object value) {
-        if (paramName == null) {
-            throw new IllegalArgumentException();
+        
+        if (value == null) {
+            dynamicDefines.set(defineId, 0);
+            return;
         }
         
-        Uniform u = shader.getUniform(paramName);
         switch (type) {
-            case TextureBuffer:
-            case Texture2D: // fall intentional
-            case Texture3D:
-            case TextureArray:
-            case TextureCubeMap:
             case Int:
-                u.setValue(VarType.Int, value);
+                dynamicDefines.set(defineId, (Integer) value);
+                break;
+            case Float:
+                dynamicDefines.set(defineId, (Float) value);
+                break;
+            case Boolean:
+                dynamicDefines.set(defineId, ((Boolean)value));
                 break;
             default:
-                u.setValue(type, value);
+                dynamicDefines.set(defineId, 1);
                 break;
         }
     }
+    
+    /**
+     * Called by the material to tell the technique that it has been made
+     * current.
+     * The technique updates dynamic defines based on the
+     * currently set material parameters.
+     */
+    void notifyTechniqueSwitched() {
+        ListMap<String, MatParam> paramMap = owner.getParamsMap();
+        for (int i = 0; i < paramMap.size(); i++) {
+            MatParam param = paramMap.getValue(i);
+            notifyParamChanged(param.getName(), param.getVarType(), param.getValue());
+        }
+    }
 
     /**
-     * Returns true if the technique must be reloaded.
-     * <p>
-     * If a technique needs to reload, then the {@link Material} should
-     * call {@link #makeCurrent(com.jme3.asset.AssetManager) } on this
-     * technique.
+     * Called by the material to determine which shader to use for rendering.
+     * 
+     * The {@link TechniqueDefLogic} is used to determine the shader to use
+     * based on the {@link LightMode}.
      * 
-     * @return true if the technique must be reloaded.
+     * @param renderManager The render manager for which the shader is to be selected.
+     * @param rendererCaps The renderer capabilities which the shader should support.
+     * @return A compatible shader.
      */
-    public boolean isNeedReload() {
-        return needReload;
+    Shader makeCurrent(RenderManager renderManager, EnumSet<Caps> rendererCaps) {
+        TechniqueDefLogic logic = def.getLogic();
+        AssetManager assetManager = owner.getMaterialDef().getAssetManager();
+        return logic.makeCurrent(assetManager, renderManager, rendererCaps, dynamicDefines);
     }
-
+    
     /**
-     * Prepares the technique for use by loading the shader and setting
-     * the proper defines based on material parameters.
+     * Render the technique according to its {@link TechniqueDefLogic}.
      * 
-     * @param assetManager The asset manager to use for loading shaders.
+     * @param renderManager The render manager to perform the rendering against.
+     * @param shader The shader that was selected in 
+     * {@link #makeCurrent(com.jme3.renderer.RenderManager, java.util.EnumSet)}.
+     * @param geometry The geometry to render
+     * @param lights Lights which influence the geometry.
      */
-    public void makeCurrent(AssetManager assetManager, boolean techniqueSwitched, EnumSet<Caps> rendererCaps, RenderManager rm) {
-        if (techniqueSwitched) {
-            if (defines.update(owner.getParamsMap(), def)) {
-                needReload = true;
-            }
-            if (getDef().getLightMode() == TechniqueDef.LightMode.SinglePass) {
-                defines.set("SINGLE_PASS_LIGHTING", VarType.Boolean, true);
-                defines.set("NB_LIGHTS", VarType.Int, rm.getSinglePassLightBatchSize() * 3);
-            } else {
-                defines.set("SINGLE_PASS_LIGHTING", VarType.Boolean, null);
-            }
-        }
-
-        if (needReload) {
-            loadShader(assetManager,rendererCaps);
-        }
+    void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights) {
+        TechniqueDefLogic logic = def.getLogic();
+        logic.render(renderManager, shader, geometry, lights);
     }
-
-    private void loadShader(AssetManager manager,EnumSet<Caps> rendererCaps) {
-        
-        ShaderKey key = new ShaderKey(getAllDefines(),def.getShaderProgramLanguages(),def.getShaderProgramNames());
-        
-        if (getDef().isUsingShaderNodes()) {                 
-           manager.getShaderGenerator(rendererCaps).initialize(this);           
-           key.setUsesShaderNodes(true);
-        }   
-        shader = manager.loadShader(key);
-
-        // register the world bound uniforms
-        worldBindUniforms.clear();
-        if (def.getWorldBindings() != null) {
-           for (UniformBinding binding : def.getWorldBindings()) {
-               Uniform uniform = shader.getUniform("g_" + binding.name());
-               uniform.setBinding(binding);
-               worldBindUniforms.add(uniform);
-           }
-        }        
-        needReload = false;
+    
+    /**
+     * Get the {@link DefineList} for dynamic defines.
+     * 
+     * Dynamic defines are used to implement material parameter -> define
+     * bindings as well as {@link TechniqueDefLogic} specific functionality.
+     * 
+     * @return all dynamic defines.
+     */
+    public DefineList getDynamicDefines() {
+        return dynamicDefines;
     }
     
     /**
-     * Computes the define list
-     * @return the complete define list
+     * @deprecated Preset defines are precompiled into 
+     * {@link TechniqueDef#getShaderPrologue()}, whereas
+     * dynamic defines are available via {@link #getParamDefines()}.
      */
+    @Deprecated
     public DefineList getAllDefines() {
-        DefineList allDefines = new DefineList();
-        allDefines.addFrom(def.getShaderPresetDefines());
-        allDefines.addFrom(defines);
-        return allDefines;
-    } 
-    
-    /*
-    public void write(JmeExporter ex) throws IOException {
-        OutputCapsule oc = ex.getCapsule(this);
-        oc.write(def, "def", null);
-        oc.writeSavableArrayList(worldBindUniforms, "worldBindUniforms", null);
-        oc.write(defines, "defines", null);
-        oc.write(shader, "shader", null);
+        throw new UnsupportedOperationException();
     }
-
-    public void read(JmeImporter im) throws IOException {
-        InputCapsule ic = im.getCapsule(this);
-        def = (TechniqueDef) ic.readSavable("def", null);
-        worldBindUniforms = ic.readSavableArrayList("worldBindUniforms", null);
-        defines = (DefineList) ic.readSavable("defines", null);
-        shader = (Shader) ic.readSavable("shader", null);
+    
+    /**
+     * Compute the sort ID. Similar to {@link Object#hashCode()} but used
+     * for sorting geometries for rendering.
+     * 
+     * @return the sort ID for this technique instance.
+     */
+    public int getSortId() {
+       int hash = 17;
+       hash = hash * 23 + def.getSortId();
+       hash = hash * 23 + dynamicDefines.hashCode();
+       return hash;
     }
-    */
 }

+ 187 - 76
jme3-core/src/main/java/com/jme3/material/TechniqueDef.java

@@ -31,9 +31,11 @@
  */
 package com.jme3.material;
 
+import com.jme3.asset.AssetManager;
 import com.jme3.export.*;
 import com.jme3.renderer.Caps;
 import com.jme3.shader.*;
+import com.jme3.shader.Shader.ShaderType;
 
 import java.io.IOException;
 import java.util.*;
@@ -93,11 +95,17 @@ public class TechniqueDef implements Savable {
 
     private EnumSet<Caps> requiredCaps = EnumSet.noneOf(Caps.class);
     private String name;
-
+    private int sortId;
+    
     private EnumMap<Shader.ShaderType,String> shaderLanguages;
     private EnumMap<Shader.ShaderType,String> shaderNames;
 
-    private DefineList presetDefines;
+    private String shaderPrologue;
+    private ArrayList<String> defineNames;
+    private ArrayList<VarType> defineTypes;
+    private HashMap<String, Integer> paramToDefineId;
+    private final HashMap<DefineList, Shader> definesToShaderMap;
+    
     private boolean usesNodes = false;
     private List<ShaderNode> shaderNodes;
     private ShaderGenerationInfo shaderGenerationInfo;
@@ -106,10 +114,10 @@ public class TechniqueDef implements Savable {
     private RenderState renderState;
     private RenderState forcedRenderState;
 
-    private LightMode lightMode   = LightMode.Disable;
+    private LightMode lightMode = LightMode.Disable;
     private ShadowMode shadowMode = ShadowMode.Disable;
+    private TechniqueDefLogic logic;
 
-    private HashMap<String, String> defineParams;
     private ArrayList<UniformBinding> worldBinds;
 
     /**
@@ -120,17 +128,30 @@ public class TechniqueDef implements Savable {
      * @param name The name of the technique, should be set to <code>null</code>
      * for default techniques.
      */
-    public TechniqueDef(String name){
+    public TechniqueDef(String name, int sortId){
         this();
+        this.sortId = sortId;
         this.name = name == null ? "Default" : name;
     }
 
     /**
      * Serialization only. Do not use.
      */
-    public TechniqueDef(){
-        shaderLanguages=new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class);
-        shaderNames=new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class);
+    public TechniqueDef() {
+        shaderLanguages = new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class);
+        shaderNames = new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class);
+        defineNames = new ArrayList<String>();
+        defineTypes = new ArrayList<VarType>();
+        paramToDefineId = new HashMap<String, Integer>();
+        definesToShaderMap = new HashMap<DefineList, Shader>();
+    }
+    
+    /**
+     * @return A unique sort ID. 
+     * No other technique definition can have the same ID.
+     */
+    public int getSortId() {
+        return sortId;
     }
 
     /**
@@ -162,7 +183,15 @@ public class TechniqueDef implements Savable {
     public void setLightMode(LightMode lightMode) {
         this.lightMode = lightMode;
     }
+    
+    public void setLogic(TechniqueDefLogic logic) {
+        this.logic = logic;
+    }
 
+    public TechniqueDefLogic getLogic() {
+        return logic;
+    }
+    
     /**
      * Returns the shadow mode.
      * @return the shadow mode.
@@ -224,14 +253,6 @@ public class TechniqueDef implements Savable {
         return noRender;
     }
 
-    /**
-     * @deprecated jME3 always requires shaders now
-     */
-    @Deprecated
-    public boolean isUsingShaders(){
-        return true;
-    }
-
     /**
      * Returns true if this technique uses Shader Nodes, false otherwise.
      *
@@ -273,34 +294,24 @@ public class TechniqueDef implements Savable {
         requiredCaps.add(fragCap);
     }
 
-
     /**
-     * Sets the shaders that this technique definition will use.
-     *
-     * @param shaderNames EnumMap containing all shader names for this stage
-     * @param shaderLanguages EnumMap containing all shader languages for this stage
+     * Set a string which is prepended to every shader used by this technique.
+     * 
+     * Typically this is used for preset defines.
+     * 
+     * @param shaderPrologue The prologue to append before the technique's shaders.
      */
-    public void setShaderFile(EnumMap<Shader.ShaderType, String> shaderNames, EnumMap<Shader.ShaderType, String> shaderLanguages) {
-        requiredCaps.clear();
-
-        for (Shader.ShaderType shaderType : shaderNames.keySet()) {
-            String language = shaderLanguages.get(shaderType);
-            String shaderFile = shaderNames.get(shaderType);
-
-            this.shaderLanguages.put(shaderType, language);
-            this.shaderNames.put(shaderType, shaderFile);
-
-            Caps vertCap = Caps.valueOf(language);
-            requiredCaps.add(vertCap);
-
-            if (shaderType.equals(Shader.ShaderType.Geometry)) {
-                requiredCaps.add(Caps.GeometryShader);
-            } else if (shaderType.equals(Shader.ShaderType.TessellationControl)) {
-                requiredCaps.add(Caps.TesselationShader);
-            }
-        }
+    public void setShaderPrologue(String shaderPrologue) {
+        this.shaderPrologue = shaderPrologue;
     }
-
+    
+    /**
+     * @return the shader prologue which is prepended to every shader.
+     */
+    public String getShaderPrologue() {
+        return shaderPrologue;
+    }
+    
     /**
      * Returns the define name which the given material parameter influences.
      *
@@ -310,60 +321,155 @@ public class TechniqueDef implements Savable {
      * @see #addShaderParamDefine(java.lang.String, java.lang.String)
      */
     public String getShaderParamDefine(String paramName){
-        if (defineParams == null) {
+        Integer defineId = paramToDefineId.get(paramName);
+        if (defineId != null) {
+            return defineNames.get(defineId);
+        } else {
             return null;
         }
-        return defineParams.get(paramName);
+    }
+    
+    /**
+     * Get the define ID for a given define name.
+     *
+     * @param defineName The define name to lookup
+     * @return The define ID, or null if not found.
+     */
+    public Integer getShaderParamDefineId(String defineName) {
+        return paramToDefineId.get(defineName);
     }
 
+    
     /**
      * Adds a define linked to a material parameter.
      * <p>
      * Any time the material parameter on the parent material is altered,
      * the appropriate define on the technique will be modified as well.
-     * See the method
-     * {@link DefineList#set(java.lang.String, com.jme3.shader.VarType, java.lang.Object) }
-     * on the exact details of how the material parameter changes the define.
+     * When set, the material parameter will be mapped to an integer define, 
+     * typically <code>1</code> if it is set, unless it is an integer or a float,
+     * in which case it will converted into an integer.
      *
      * @param paramName The name of the material parameter to link to.
+     * @param paramType The type of the material parameter to link to.
      * @param defineName The name of the define parameter, e.g. USE_LIGHTING
      */
-    public void addShaderParamDefine(String paramName, String defineName){
-        if (defineParams == null) {
-            defineParams = new HashMap<String, String>();
+    public void addShaderParamDefine(String paramName, VarType paramType, String defineName){
+        int defineId = defineNames.size();
+        
+        if (defineId >= DefineList.MAX_DEFINES) {
+            throw new IllegalStateException("Cannot have more than " + 
+                    DefineList.MAX_DEFINES + " defines on a technique.");
         }
-        defineParams.put(paramName, defineName);
+        
+        paramToDefineId.put(paramName, defineId);
+        defineNames.add(defineName);
+        defineTypes.add(paramType);
     }
 
     /**
-     * Returns the {@link DefineList} for the preset defines.
-     *
-     * @return the {@link DefineList} for the preset defines.
-     *
-     * @see #addShaderPresetDefine(java.lang.String, com.jme3.shader.VarType, java.lang.Object)
-     */
-    public DefineList getShaderPresetDefines() {
-        return presetDefines;
+     * Add an unmapped define which can only be set by define ID.
+     * 
+     * Unmapped defines are used by technique renderers to 
+     * configure the shader internally before rendering.
+     * 
+     * @param defineName The define name to create
+     * @return The define ID of the created define
+     */
+    public int addShaderUnmappedDefine(String defineName, VarType defineType) {
+        int defineId = defineNames.size();
+        
+        if (defineId >= DefineList.MAX_DEFINES) {
+            throw new IllegalStateException("Cannot have more than " + 
+                    DefineList.MAX_DEFINES + " defines on a technique.");
+        }
+        
+        defineNames.add(defineName);
+        defineTypes.add(defineType);
+        return defineId;
     }
+    
+    /**
+     * Create a define list with the size matching the number
+     * of defines on this technique.
+     * 
+     * @return a define list with the size matching the number
+     * of defines on this technique.
+     */
+    public DefineList createDefineList() {
+        return new DefineList(defineNames.size());
+    }
+    
+    private Shader loadShader(AssetManager assetManager, EnumSet<Caps> rendererCaps, DefineList defines) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(shaderPrologue);
+        defines.generateSource(sb, defineNames, defineTypes);
+        String definesSourceCode = sb.toString();
+
+        Shader shader;
+        if (isUsingShaderNodes()) {
+            ShaderGenerator shaderGenerator = assetManager.getShaderGenerator(rendererCaps);
+            if (shaderGenerator == null) {
+                throw new UnsupportedOperationException("ShaderGenerator was not initialized, "
+                        + "make sure assetManager.getGenerator(caps) has been called");
+            }
+            shaderGenerator.initialize(this);
+            shader = shaderGenerator.generateShader(definesSourceCode);
+        } else {
+            shader = new Shader();
+            for (ShaderType type : ShaderType.values()) {
+                String language = shaderLanguages.get(type);
+                String shaderSourceAssetName = shaderNames.get(type);
+                if (language == null || shaderSourceAssetName == null) {
+                    continue;
+                }
+                String shaderSourceCode = (String) assetManager.loadAsset(shaderSourceAssetName);
+                shader.addSource(type, shaderSourceAssetName, shaderSourceCode, definesSourceCode, language);
+            }
+        }
 
+        if (getWorldBindings() != null) {
+           for (UniformBinding binding : getWorldBindings()) {
+               shader.addUniformBinding(binding);
+           }
+        }
+        
+        return shader;
+    }
+    
+    public Shader getShader(AssetManager assetManager, EnumSet<Caps> rendererCaps, DefineList defines) {
+          Shader shader = definesToShaderMap.get(defines);
+          if (shader == null) {
+              shader = loadShader(assetManager, rendererCaps, defines);
+              definesToShaderMap.put(defines.deepClone(), shader);
+          }
+          return shader;
+     }
+    
     /**
-     * Adds a preset define.
-     * <p>
-     * Preset defines do not depend upon any parameters to be activated,
-     * they are always passed to the shader as long as this technique is used.
-     *
-     * @param defineName The name of the define parameter, e.g. USE_LIGHTING
-     * @param type The type of the define. See
-     * {@link DefineList#set(java.lang.String, com.jme3.shader.VarType, java.lang.Object) }
-     * to see why it matters.
+     * Sets the shaders that this technique definition will use.
      *
-     * @param value The value of the define
+     * @param shaderNames EnumMap containing all shader names for this stage
+     * @param shaderLanguages EnumMap containing all shader languages for this stage
      */
-    public void addShaderPresetDefine(String defineName, VarType type, Object value){
-        if (presetDefines == null) {
-            presetDefines = new DefineList();
+    public void setShaderFile(EnumMap<Shader.ShaderType, String> shaderNames, EnumMap<Shader.ShaderType, String> shaderLanguages) {
+        requiredCaps.clear();
+
+        for (Shader.ShaderType shaderType : shaderNames.keySet()) {
+            String language = shaderLanguages.get(shaderType);
+            String shaderFile = shaderNames.get(shaderType);
+
+            this.shaderLanguages.put(shaderType, language);
+            this.shaderNames.put(shaderType, shaderFile);
+
+            Caps vertCap = Caps.valueOf(language);
+            requiredCaps.add(vertCap);
+
+            if (shaderType.equals(Shader.ShaderType.Geometry)) {
+                requiredCaps.add(Caps.GeometryShader);
+            } else if (shaderType.equals(Shader.ShaderType.TessellationControl)) {
+                requiredCaps.add(Caps.TesselationShader);
+            }
         }
-        presetDefines.set(defineName, type, value);
     }
 
     /**
@@ -467,7 +573,7 @@ public class TechniqueDef implements Savable {
         oc.write(shaderLanguages.get(Shader.ShaderType.TessellationControl), "tsctrlLanguage", null);
         oc.write(shaderLanguages.get(Shader.ShaderType.TessellationEvaluation), "tsevalLanguage", null);
 
-        oc.write(presetDefines, "presetDefines", null);
+        oc.write(shaderPrologue, "shaderPrologue", null);
         oc.write(lightMode, "lightMode", LightMode.Disable);
         oc.write(shadowMode, "shadowMode", ShadowMode.Disable);
         oc.write(renderState, "renderState", null);
@@ -490,7 +596,7 @@ public class TechniqueDef implements Savable {
         shaderNames.put(Shader.ShaderType.Geometry,ic.readString("geomName", null));
         shaderNames.put(Shader.ShaderType.TessellationControl,ic.readString("tsctrlName", null));
         shaderNames.put(Shader.ShaderType.TessellationEvaluation,ic.readString("tsevalName", null));
-        presetDefines = (DefineList) ic.readSavable("presetDefines", null);
+        shaderPrologue = ic.readString("shaderPrologue", null);
         lightMode = ic.readEnum("lightMode", LightMode.class, LightMode.Disable);
         shadowMode = ic.readEnum("shadowMode", ShadowMode.class, ShadowMode.Disable);
         renderState = (RenderState) ic.readSavable("renderState", null);
@@ -547,9 +653,14 @@ public class TechniqueDef implements Savable {
         this.shaderGenerationInfo = shaderGenerationInfo;
     }
 
-    //todo: make toString return something usefull
     @Override
     public String toString() {
-        return "TechniqueDef{" + "requiredCaps=" + requiredCaps + ", name=" + name /*+ ", vertName=" + vertName + ", fragName=" + fragName + ", vertLanguage=" + vertLanguage + ", fragLanguage=" + fragLanguage */+ ", presetDefines=" + presetDefines + ", usesNodes=" + usesNodes + ", shaderNodes=" + shaderNodes + ", shaderGenerationInfo=" + shaderGenerationInfo + ", renderState=" + renderState + ", forcedRenderState=" + forcedRenderState + ", lightMode=" + lightMode + ", shadowMode=" + shadowMode + ", defineParams=" + defineParams + ", worldBinds=" + worldBinds + ", noRender=" + noRender + '}';
+        return "TechniqueDef[name=" + name
+                + ", requiredCaps=" + requiredCaps
+                + ", noRender=" + noRender
+                + ", lightMode=" + lightMode
+                + ", usesNodes=" + usesNodes
+                + ", renderState=" + renderState
+                + ", forcedRenderState=" + forcedRenderState + "]";
     }
 }

+ 95 - 0
jme3-core/src/main/java/com/jme3/material/TechniqueDefLogic.java

@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2009-2015 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.material.TechniqueDef.LightMode;
+import com.jme3.renderer.Caps;
+import com.jme3.renderer.RenderManager;
+import com.jme3.scene.Geometry;
+import com.jme3.shader.DefineList;
+import com.jme3.shader.Shader;
+import com.jme3.shader.Uniform;
+import com.jme3.shader.UniformBinding;
+import com.jme3.texture.Texture;
+import java.util.EnumSet;
+
+/**
+ * <code>TechniqueDefLogic</code> is used to customize how 
+ * a material should be rendered.
+ * 
+ * Typically used to implement {@link LightMode lighting modes}.
+ * Implementations can register 
+ * {@link TechniqueDef#addShaderUnmappedDefine(java.lang.String) unmapped defines} 
+ * in their constructor and then later set them based on the geometry 
+ * or light environment being rendered.
+ * 
+ * @author Kirill Vainer
+ */
+public interface TechniqueDefLogic {
+    
+    /**
+     * Determine the shader to use for the given geometry / material combination.
+     * 
+     * @param assetManager The asset manager to use for loading shader source code,
+     * shader nodes, and and lookup textures.
+     * @param renderManager The render manager for which rendering is to be performed.
+     * @param rendererCaps Renderer capabilities. The returned shader must
+     * support these capabilities.
+     * @param defines The define list used by the technique, any 
+     * {@link TechniqueDef#addShaderUnmappedDefine(java.lang.String) unmapped defines}
+     * should be set here to change shader behavior.
+     * 
+     * @return The shader to use for rendering.
+     */
+    public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager, 
+                              EnumSet<Caps> rendererCaps, DefineList defines);
+    
+    /**
+     * Requests that the <code>TechniqueDefLogic</code> renders the given geometry.
+     * 
+     * Fixed material functionality such as {@link RenderState}, 
+     * {@link MatParam material parameters}, and 
+     * {@link UniformBinding uniform bindings}
+     * have already been applied by the material, however, 
+     * {@link RenderState}, {@link Uniform uniforms}, {@link Texture textures},
+     * can still be overriden.
+     * 
+     * @param renderManager The render manager to perform the rendering against.
+     * * @param shader The shader that was selected by this logic in 
+     * {@link #makeCurrent(com.jme3.asset.AssetManager, com.jme3.renderer.RenderManager, java.util.EnumSet, com.jme3.shader.DefineList)}.
+     * @param geometry The geometry to render
+     * @param lights Lights which influence the geometry.
+     */
+    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights);
+}

+ 9 - 4
jme3-core/src/main/java/com/jme3/renderer/RenderManager.java

@@ -49,7 +49,7 @@ import com.jme3.renderer.queue.RenderQueue;
 import com.jme3.renderer.queue.RenderQueue.Bucket;
 import com.jme3.renderer.queue.RenderQueue.ShadowMode;
 import com.jme3.scene.*;
-import com.jme3.shader.Uniform;
+import com.jme3.shader.Shader;
 import com.jme3.shader.UniformBinding;
 import com.jme3.shader.UniformBindingManager;
 import com.jme3.system.NullRenderer;
@@ -483,8 +483,8 @@ public class RenderManager {
      * Updates the given list of uniforms with {@link UniformBinding uniform bindings}
      * based on the current world state.
      */
-    public void updateUniformBindings(List<Uniform> params) {
-        uniformBindingManager.updateUniformBindings(params);
+    public void updateUniformBindings(Shader shader) {
+        uniformBindingManager.updateUniformBindings(shader);
     }
 
     /**
@@ -616,7 +616,9 @@ public class RenderManager {
 
             gm.getMaterial().preload(this);
             Mesh mesh = gm.getMesh();
-            if (mesh != null) {
+            if (mesh != null
+                    && mesh.getVertexCount() != 0
+                    && mesh.getTriangleCount() != 0) {
                 for (VertexBuffer vb : mesh.getBufferList().getArray()) {
                     if (vb.getData() != null && vb.getUsage() != VertexBuffer.Usage.CpuOnly) {
                         renderer.updateBufferData(vb);
@@ -768,6 +770,9 @@ public class RenderManager {
     }
 
     public void setSinglePassLightBatchSize(int singlePassLightBatchSize) {
+        if (singlePassLightBatchSize < 1) {
+            throw new IllegalArgumentException("batch size cannot be less than 1");
+        }
         this.singlePassLightBatchSize = singlePassLightBatchSize;
     }
     

+ 26 - 15
jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java

@@ -467,14 +467,25 @@ public final class GLRenderer implements Renderer {
                 });
 
         // Print capabilities (if fine logging is enabled)
-        if (logger.isLoggable(Level.FINE)) {
+        if (logger.isLoggable(Level.INFO)) {
             StringBuilder sb = new StringBuilder();
             sb.append("Supported capabilities: \n");
             for (Caps cap : caps)
             {
                 sb.append("\t").append(cap.toString()).append("\n");
             }
-            logger.log(Level.FINE, sb.toString());
+            
+            sb.append("\nHardware limits: \n");
+            for (Limits limit : Limits.values()) {
+                Integer value = limits.get(limit);
+                if (value == null) {
+                    value = 0;
+                }
+                sb.append("\t").append(limit.name()).append(" = ")
+                  .append(value).append("\n");
+            }
+            
+            logger.log(Level.INFO, sb.toString());
         }
 
         texUtil.initialize(caps);
@@ -1500,17 +1511,17 @@ public final class GLRenderer implements Renderer {
         }
 
         bindFrameBuffer(fb);
-
-        FrameBuffer.RenderBuffer depthBuf = fb.getDepthBuffer();
-        if (depthBuf != null) {
-            updateFrameBufferAttachment(fb, depthBuf);
-        }
-
+        
         for (int i = 0; i < fb.getNumColorBuffers(); i++) {
             FrameBuffer.RenderBuffer colorBuf = fb.getColorBuffer(i);
             updateFrameBufferAttachment(fb, colorBuf);
         }
         
+        FrameBuffer.RenderBuffer depthBuf = fb.getDepthBuffer();
+        if (depthBuf != null) {
+            updateFrameBufferAttachment(fb, depthBuf);
+        }
+
         setReadDrawBuffers(fb);
         checkFrameBufferError();
 
@@ -2433,8 +2444,7 @@ public final class GLRenderer implements Renderer {
     }
 
     public void drawTriangleArray(Mesh.Mode mode, int count, int vertCount) {
-        boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing);
-        if (useInstancing) {
+        if (count > 1) {
             glext.glDrawArraysInstancedARB(convertElementMode(mode), 0,
                     vertCount, count);
         } else {
@@ -2478,8 +2488,6 @@ public final class GLRenderer implements Renderer {
         }
 
         int vertCount = mesh.getVertexCount();
-        boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing);
-
         if (mesh.getMode() == Mode.Hybrid) {
             int[] modeStart = mesh.getModeStart();
             int[] elementLengths = mesh.getElementLengths();
@@ -2499,7 +2507,7 @@ public final class GLRenderer implements Renderer {
                 }
                 int elementLength = elementLengths[i];
 
-                if (useInstancing) {
+                if (count > 1) {
                     glext.glDrawElementsInstancedARB(elMode,
                             elementLength,
                             fmt,
@@ -2517,7 +2525,7 @@ public final class GLRenderer implements Renderer {
                 curOffset += elementLength * elSize;
             }
         } else {
-            if (useInstancing) {
+            if (count > 1) {
                 glext.glDrawElementsInstancedARB(convertElementMode(mesh.getMode()),
                         indexBuf.getData().limit(),
                         convertFormat(indexBuf.getFormat()),
@@ -2677,10 +2685,13 @@ public final class GLRenderer implements Renderer {
     }
 
     public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
-        if (mesh.getVertexCount() == 0) {
+        if (mesh.getVertexCount() == 0 || mesh.getTriangleCount() == 0 || count == 0) {
             return;
         }
 
+        if (count > 1 && !caps.contains(Caps.MeshInstancing)) {
+            throw new RendererException("Mesh instancing is not supported by the video hardware");
+        }
 
         if (context.lineWidth != mesh.getLineWidth()) {
             gl.glLineWidth(mesh.getLineWidth());

+ 10 - 0
jme3-core/src/main/java/com/jme3/renderer/queue/GeometryList.java

@@ -99,6 +99,16 @@ public class GeometryList implements Iterable<Geometry>{
         return size;
     }
 
+    /**
+     * Sets the element at the given index.
+     * 
+     * @param index The index to set
+     * @param value The value
+     */
+    public void set(int index, Geometry value) {
+        geometries[index] = value;
+    }
+    
     /**
      * Returns the element at the given index.
      *

+ 3 - 2
jme3-core/src/main/java/com/jme3/renderer/queue/OpaqueComparator.java

@@ -69,11 +69,12 @@ public class OpaqueComparator implements GeometryComparator {
         return spat.queueDistance;
     }
 
+    @Override
     public int compare(Geometry o1, Geometry o2) {
         Material m1 = o1.getMaterial();
         Material m2 = o2.getMaterial();
-
-        int compareResult = m2.getSortId() - m1.getSortId();
+        
+        int compareResult = Integer.compare(m1.getSortId(), m2.getSortId());
         if (compareResult == 0){
             // use the same shader.
             // sort front-to-back then.

+ 128 - 286
jme3-core/src/main/java/com/jme3/shader/DefineList.java

@@ -1,286 +1,128 @@
-/*
- * Copyright (c) 2009-2012 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.shader;
-
-import com.jme3.export.*;
-import com.jme3.material.MatParam;
-import com.jme3.material.TechniqueDef;
-import com.jme3.util.ListMap;
-
-import java.io.IOException;
-import java.util.Map;
-import java.util.TreeMap;
-
-public final class DefineList implements Savable, Cloneable {
-
-    private static final String ONE = "1";
-    
-    private TreeMap<String, String> defines = new TreeMap<String, String>();
-    private String compiled = null;
-    private int cachedHashCode = 0;
-
-    public void write(JmeExporter ex) throws IOException{
-        OutputCapsule oc = ex.getCapsule(this);
-
-        String[] keys = new String[defines.size()];
-        String[] vals = new String[defines.size()];
-
-        int i = 0;
-        for (Map.Entry<String, String> define : defines.entrySet()){
-            keys[i] = define.getKey();
-            vals[i] = define.getValue();
-            i++;
-        }
-
-        oc.write(keys, "keys", null);
-        oc.write(vals, "vals", null);
-    }
-
-    public void read(JmeImporter im) throws IOException{
-        InputCapsule ic = im.getCapsule(this);
-
-        String[] keys = ic.readStringArray("keys", null);
-        String[] vals = ic.readStringArray("vals", null);
-        for (int i = 0; i < keys.length; i++){
-            defines.put(keys[i], vals[i]);
-        }
-    }
-
-    public void clear() {
-        defines.clear();
-        compiled = "";
-        cachedHashCode = 0;
-    }
-
-    public String get(String key){
-        return defines.get(key);
-    }
-    
-    @Override
-    public DefineList clone() {
-        try {
-            DefineList clone = (DefineList) super.clone();
-            clone.cachedHashCode = 0;
-            clone.compiled = null;
-            clone.defines = (TreeMap<String, String>) defines.clone();
-            return clone;
-        } catch (CloneNotSupportedException ex) {
-            throw new AssertionError();
-        }
-    }
-
-    public boolean set(String key, VarType type, Object val){    
-        if (val == null){
-            defines.remove(key);
-            compiled = null;
-            cachedHashCode = 0;
-            return true;
-        }
-
-        switch (type){
-            case Boolean:
-                if (((Boolean) val).booleanValue()) {
-                    // same literal, != will work
-                    if (defines.put(key, ONE) != ONE) {
-                        compiled = null;
-                        cachedHashCode = 0;
-                        return true;
-                    }
-                } else if (defines.containsKey(key)) {
-                    defines.remove(key);
-                    compiled = null;
-                    cachedHashCode = 0;
-                    return true;
-                }
-                
-                break;
-            case Float:
-            case Int:
-                String newValue = val.toString();
-                String original = defines.put(key, newValue);
-                if (!val.equals(original)) {
-                    compiled = null;
-                    cachedHashCode = 0;
-                    return true;            
-                }
-                break;
-            default:
-                // same literal, != will work
-                if (defines.put(key, ONE) != ONE) {  
-                    compiled = null;
-                    cachedHashCode = 0;
-                    return true;            
-                }
-                break;
-        }
-        
-        return false;
-    }
-
-    public boolean remove(String key){   
-        if (defines.remove(key) != null) {
-            compiled = null;
-            cachedHashCode = 0;
-            return true;
-        }
-        return false;
-    }
-
-    public void addFrom(DefineList other){    
-        if (other == null) {
-            return;
-        }
-        compiled = null;
-        cachedHashCode = 0;
-        defines.putAll(other.defines);
-    }
-
-    public String getCompiled(){
-        if (compiled == null){
-            StringBuilder sb = new StringBuilder();
-            for (Map.Entry<String, String> entry : defines.entrySet()){
-                sb.append("#define ").append(entry.getKey()).append(" ");
-                sb.append(entry.getValue()).append('\n');
-            }
-            compiled = sb.toString();
-        }
-        return compiled;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        final DefineList other = (DefineList) obj;
-        return defines.equals(other.defines);
-    }
-    
-    /**
-     * Update defines if the define list changed based on material parameters.
-     * @param params
-     * @param def
-     * @return true if defines was updated
-     */
-    public boolean update(ListMap params, TechniqueDef def){
-        if(equalsParams(params, def)){
-            return false;
-        }
-         // Defines were changed, update define list
-        clear();
-        for(int i=0;i<params.size();i++) {
-            MatParam param = (MatParam)params.getValue(i);
-            String defineName = def.getShaderParamDefine(param.getName());
-            if (defineName != null) {
-                set(defineName, param.getVarType(), param.getValue());
-            }
-        }
-        return true;
-    }
-    
-    private boolean equalsParams(ListMap params, TechniqueDef def) {
-        
-        int size = 0;
-
-        for(int i = 0; i < params.size() ; i++ ) {
-            MatParam param = (MatParam)params.getValue(i);
-            String key = def.getShaderParamDefine(param.getName());
-            if (key != null) {
-                Object val = param.getValue();
-                if (val != null) {
-
-                    switch (param.getVarType()) {
-                    case Boolean: {
-                        String current = defines.get(key);
-                        if (((Boolean) val).booleanValue()) {
-                            if (current == null || current != ONE) {
-                                return false;
-                            }
-                            size++;
-                        } else {
-                            if (current != null) {
-                                return false;
-                            }
-                        }
-                    }
-                        break;
-                    case Float:
-                    case Int: {
-                        String newValue = val.toString();
-                        String current = defines.get(key);
-                        if (!newValue.equals(current)) {
-                            return false;
-                        }
-                        size++;
-                    }
-                        break;
-                    default: {
-                        if (!defines.containsKey(key)) {
-                            return false;
-                        }
-                        size++;
-                    }
-                        break;
-                    }
-
-                }
-
-            }
-        }
-
-        if (size != defines.size()) {
-            return false;
-        }
-
-        return true;
-    }
-    
-    @Override
-    public int hashCode() {
-        if (cachedHashCode == 0) {
-            cachedHashCode = defines.hashCode();
-        }
-        return cachedHashCode;
-    }
-
-    @Override
-    public String toString(){
-        StringBuilder sb = new StringBuilder();
-        int i = 0;
-        for (Map.Entry<String, String> entry : defines.entrySet()) {
-            sb.append(entry.getKey()).append("=").append(entry.getValue());
-            if (i != defines.size() - 1) {
-                sb.append(", ");
-            }
-            i++;
-        }
-        return sb.toString();
-    }
-
-}
+/*
+ * Copyright (c) 2009-2015 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.shader;
+
+import java.util.List;
+
+/**
+ * The new define list.
+ * 
+ * @author Kirill Vainer
+ */
+public final class DefineList implements Cloneable {
+
+    public static final int MAX_DEFINES = 64;
+    
+    public static final int SAVABLE_VERSION = 1;
+    
+    private long hash;
+    private final int[] vals;
+
+    public DefineList(int numValues) {
+        if (numValues < 0 || numValues > MAX_DEFINES) {
+            throw new IllegalArgumentException("numValues must be between 0 and 64");
+        }
+        vals = new int[numValues];
+    }
+    
+    private DefineList(DefineList original) {
+        this.hash = original.hash;
+        this.vals = new int[original.vals.length];
+        System.arraycopy(original.vals, 0, vals, 0, vals.length);
+    }
+
+    public void set(int id, int val) {
+        assert 0 <= id && id < 64;
+        if (val != 0) {
+            hash |=  (1L << id);
+        } else {
+            hash &= ~(1L << id);
+        }
+        vals[id] = val;
+    }
+    
+    public void set(int id, float val) {
+        set(id, Float.floatToIntBits(val));
+    }
+    
+    public void set(int id, boolean val) {
+        set(id, val ? 1 : 0);
+    }
+
+    @Override
+    public int hashCode() {
+        return (int)((hash >> 32) ^ hash);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+         DefineList o = (DefineList) other;
+         if (hash == o.hash) {
+             for (int i = 0; i < vals.length; i++) {
+                  if (vals[i] != o.vals[i]) return false;
+             }
+             return true;
+         }
+         return false;
+    }
+
+    public DefineList deepClone() {
+         return new DefineList(this);
+    }
+    
+    public void generateSource(StringBuilder sb, List<String> defineNames, List<VarType> defineTypes) {
+        for (int i = 0; i < vals.length; i++) {
+            if (vals[i] != 0) {
+                String defineName = defineNames.get(i);
+                
+                sb.append("#define ");
+                sb.append(defineName);
+                sb.append(" ");
+                
+                if (defineTypes != null && defineTypes.get(i) == VarType.Float) {
+                    float val = Float.intBitsToFloat(vals[i]);
+                    if (!Float.isFinite(val)) {
+                        throw new IllegalArgumentException(
+                                "GLSL does not support NaN "
+                                + "or Infinite float literals");
+                    }
+                    sb.append(val);
+                } else {
+                    sb.append(vals[i]);
+                }
+                
+                sb.append("\n");
+            }
+        }
+        System.out.println(sb.toString());
+    }
+}

+ 56 - 23
jme3-core/src/main/java/com/jme3/shader/Shader.java

@@ -45,44 +45,63 @@ public final class Shader extends NativeObject {
     /**
      * A list of all shader sources currently attached.
      */
-    private ArrayList<ShaderSource> shaderSourceList;
+    private final ArrayList<ShaderSource> shaderSourceList;
 
     /**
      * Maps uniform name to the uniform variable.
      */
-    private ListMap<String, Uniform> uniforms;
+    private final ListMap<String, Uniform> uniforms;
+    
+    /**
+     * Uniforms bound to {@link UniformBinding}s.
+     * 
+     * Managed by the {@link UniformBindingManager}.
+     */
+    private final ArrayList<Uniform> boundUniforms;
 
     /**
      * Maps attribute name to the location of the attribute in the shader.
      */
-    private IntMap<Attribute> attribs;
+    private final IntMap<Attribute> attribs;
 
     /**
      * Type of shader. The shader will control the pipeline of it's type.
      */
     public static enum ShaderType {
+
         /**
          * Control fragment rasterization. (e.g color of pixel).
          */
-        Fragment,
-
+        Fragment("frag"),
         /**
          * Control vertex processing. (e.g transform of model to clip space)
          */
-        Vertex,
-
+        Vertex("vert"),
         /**
-         * Control geometry assembly. (e.g compile a triangle list from input data)
+         * Control geometry assembly. (e.g compile a triangle list from input
+         * data)
          */
-        Geometry,
+        Geometry("geom"),
         /**
-         * Controls tesselation factor (e.g how often a input patch should be subdivided)
+         * Controls tesselation factor (e.g how often a input patch should be
+         * subdivided)
          */
-        TessellationControl,
+        TessellationControl("tsctrl"),
         /**
-         * Controls tesselation transform (e.g similar to the vertex shader, but required to mix inputs manual)
+         * Controls tesselation transform (e.g similar to the vertex shader, but
+         * required to mix inputs manual)
          */
-        TessellationEvaluation;
+        TessellationEvaluation("tseval");
+
+        private String extension;
+        
+        public String getExtension() {
+            return extension;
+        }
+        
+        private ShaderType(String extension) {
+            this.extension = extension;
+        }
     }
 
     /**
@@ -195,22 +214,16 @@ public final class Shader extends NativeObject {
         }
     }
 
-    /**
-     * Initializes the shader for use, must be called after the 
-     * constructor without arguments is used.
-     */
-    public void initialize() {
-        shaderSourceList = new ArrayList<ShaderSource>();
-        uniforms = new ListMap<String, Uniform>();
-        attribs = new IntMap<Attribute>();
-    }
-    
     /**
      * Creates a new shader, {@link #initialize() } must be called
      * after this constructor for the shader to be usable.
      */
     public Shader(){
         super();
+        shaderSourceList = new ArrayList<ShaderSource>();
+        uniforms = new ListMap<String, Uniform>();
+        attribs = new IntMap<Attribute>();
+        boundUniforms = new ArrayList<Uniform>();
     }
 
     /**
@@ -225,6 +238,10 @@ public final class Shader extends NativeObject {
         for (ShaderSource source : s.shaderSourceList){
             shaderSourceList.add( (ShaderSource)source.createDestructableClone() );
         }
+        
+        uniforms = null;
+        boundUniforms = null;
+        attribs = null;
     }
 
     /**
@@ -248,6 +265,18 @@ public final class Shader extends NativeObject {
         setUpdateNeeded();
     }
 
+    public void addUniformBinding(UniformBinding binding){
+        String uniformName = "g_" + binding.name();
+        Uniform uniform = uniforms.get(uniformName);
+        if (uniform == null) {
+            uniform = new Uniform();
+            uniform.name = uniformName;
+            uniform.binding = binding;
+            uniforms.put(uniformName, uniform);
+            boundUniforms.add(uniform);
+        }
+    }
+    
     public Uniform getUniform(String name){
         assert name.startsWith("m_") || name.startsWith("g_");
         Uniform uniform = uniforms.get(name);
@@ -277,6 +306,10 @@ public final class Shader extends NativeObject {
     public ListMap<String, Uniform> getUniformMap(){
         return uniforms;
     }
+    
+    public ArrayList<Uniform> getBoundUniforms() {
+        return boundUniforms;
+    }
 
     public Collection<ShaderSource> getSources(){
         return shaderSourceList;

+ 30 - 17
jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java

@@ -57,9 +57,9 @@ public abstract class ShaderGenerator {
      */
     protected int indent;
     /**
-     * the technique to use for the shader generation
+     * the technique def to use for the shader generation
      */
-    protected Technique technique = null;    
+    protected TechniqueDef techniqueDef = null;    
 
     /**
      * Build a shaderGenerator
@@ -70,8 +70,8 @@ public abstract class ShaderGenerator {
         this.assetManager = assetManager;        
     }
     
-    public void initialize(Technique technique){
-        this.technique = technique;
+    public void initialize(TechniqueDef techniqueDef){
+        this.techniqueDef = techniqueDef;
     }
     
     /**
@@ -79,24 +79,29 @@ public abstract class ShaderGenerator {
      *
      * @return a Shader program
      */
-    public Shader generateShader() {
-        if(technique == null){
-            throw new UnsupportedOperationException("The shaderGenerator was not properly initialized, call initialize(Technique) before any generation");
+    public Shader generateShader(String definesSourceCode) {
+        if (techniqueDef == null) {
+            throw new UnsupportedOperationException("The shaderGenerator was not "
+                    + "properly initialized, call "
+                    + "initialize(TechniqueDef) before any generation");
         }
 
-        DefineList defines = technique.getAllDefines();
-        TechniqueDef def = technique.getDef();
-        ShaderGenerationInfo info = def.getShaderGenerationInfo();
-
-        String vertexSource = buildShader(def.getShaderNodes(), info, ShaderType.Vertex);
-        String fragmentSource = buildShader(def.getShaderNodes(), info, ShaderType.Fragment);
+        String techniqueName = techniqueDef.getName();
+        ShaderGenerationInfo info = techniqueDef.getShaderGenerationInfo();
 
         Shader shader = new Shader();
-        shader.initialize();
-        shader.addSource(Shader.ShaderType.Vertex, technique.getDef().getName() + ".vert", vertexSource, defines.getCompiled(), getLanguageAndVersion(ShaderType.Vertex));
-        shader.addSource(Shader.ShaderType.Fragment, technique.getDef().getName() + ".frag", fragmentSource, defines.getCompiled(), getLanguageAndVersion(ShaderType.Fragment));
+        for (ShaderType type : ShaderType.values()) {
+            String extension = type.getExtension();
+            String language = getLanguageAndVersion(type);
+            String shaderSourceCode = buildShader(techniqueDef.getShaderNodes(), info, type);
+            
+            if (shaderSourceCode != null) {
+                String shaderSourceAssetName = techniqueName + "." + extension;
+                shader.addSource(type, shaderSourceAssetName, shaderSourceCode, definesSourceCode, language);
+            }
+        }
         
-        technique = null;
+        techniqueDef = null;
         return shader;
     }
 
@@ -109,6 +114,14 @@ public abstract class ShaderGenerator {
      * @return the code of the generated vertex shader
      */
     protected String buildShader(List<ShaderNode> shaderNodes, ShaderGenerationInfo info, ShaderType type) {
+        if (type == ShaderType.TessellationControl ||
+            type == ShaderType.TessellationEvaluation || 
+            type == ShaderType.Geometry) {
+            // TODO: Those are not supported.
+            // Too much code assumes that type is either Vertex or Fragment
+            return null;
+        }
+        
         indent = 0;
 
         StringBuilder sourceDeclaration = new StringBuilder();

+ 0 - 201
jme3-core/src/main/java/com/jme3/shader/ShaderKey.java

@@ -1,201 +0,0 @@
-/*
- * Copyright (c) 2009-2012 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.shader;
-
-import com.jme3.asset.AssetKey;
-import com.jme3.export.InputCapsule;
-import com.jme3.export.JmeExporter;
-import com.jme3.export.JmeImporter;
-import com.jme3.export.OutputCapsule;
-import java.io.IOException;
-import java.util.EnumMap;
-import java.util.Set;
-
-public class ShaderKey extends AssetKey<Shader> {
-
-    protected EnumMap<Shader.ShaderType,String> shaderLanguage;
-    protected EnumMap<Shader.ShaderType,String> shaderName;
-    protected DefineList defines;
-    protected int cachedHashedCode = 0;
-    protected boolean usesShaderNodes = false;
-
-    public ShaderKey(){
-        shaderLanguage=new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class);
-        shaderName=new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class);
-    }
-
-    public ShaderKey(DefineList defines, EnumMap<Shader.ShaderType,String> shaderLanguage,EnumMap<Shader.ShaderType,String> shaderName){
-        super("");
-        this.name = reducePath(getShaderName(Shader.ShaderType.Vertex));
-        this.shaderLanguage=new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class);
-        this.shaderName=new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class);
-        this.defines = defines;
-        for (Shader.ShaderType shaderType : shaderName.keySet()) {
-            this.shaderName.put(shaderType,shaderName.get(shaderType));
-            this.shaderLanguage.put(shaderType,shaderLanguage.get(shaderType));
-        }
-    }
-    
-    @Override
-    public ShaderKey clone() {
-        ShaderKey clone = (ShaderKey) super.clone();
-        clone.cachedHashedCode = 0;
-        clone.defines = defines.clone();
-        return clone;
-    }
-    
-    @Override
-    public String toString(){
-        //todo:
-        return "V="+name+";";
-    }
-    
-    private final String getShaderName(Shader.ShaderType type) {
-        if (shaderName == null) {
-            return "";
-        }
-        String shName = shaderName.get(type);
-        return shName != null ? shName : "";
-    }
-
-    //todo: make equals and hashCode work
-    @Override
-    public boolean equals(Object obj) {
-        final ShaderKey other = (ShaderKey) obj;
-        if (name.equals(other.name) && getShaderName(Shader.ShaderType.Fragment).equals(other.getShaderName(Shader.ShaderType.Fragment))){
-            if (defines != null && other.defines != null) {
-                return defines.equals(other.defines);
-            } else if (defines != null || other.defines != null) {
-                return false;
-            } else {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public int hashCode() {
-        if (cachedHashedCode == 0) {
-            int hash = 7;
-            hash = 41 * hash + name.hashCode();
-            hash = 41 * hash + getShaderName(Shader.ShaderType.Fragment).hashCode();
-            hash = getShaderName(Shader.ShaderType.Geometry) == null ? hash : 41 * hash + getShaderName(Shader.ShaderType.Geometry).hashCode();
-            hash = getShaderName(Shader.ShaderType.TessellationControl) == null ? hash : 41 * hash + getShaderName(Shader.ShaderType.TessellationControl).hashCode();
-            hash = getShaderName(Shader.ShaderType.TessellationEvaluation) == null ? hash : 41 * hash + getShaderName(Shader.ShaderType.TessellationEvaluation).hashCode();
-            hash = 41 * hash + (defines != null ? defines.hashCode() : 0);
-            cachedHashedCode = hash;
-        }
-        return cachedHashedCode;
-    }
-
-    public DefineList getDefines() {
-        return defines;
-    }
-
-    public String getVertName(){
-        return getShaderName(Shader.ShaderType.Vertex);
-    }
-
-    public String getFragName() {
-        return getShaderName(Shader.ShaderType.Fragment);
-    }
-
-    /**
-     * @deprecated Use {@link #getVertexShaderLanguage() } instead.
-     */
-    @Deprecated
-    public String getLanguage() {
-        return shaderLanguage.get(Shader.ShaderType.Vertex);
-    }
-    
-    public String getVertexShaderLanguage() { 
-        return shaderLanguage.get(Shader.ShaderType.Vertex);
-    }
-    
-    public String getFragmentShaderLanguage() {
-        return shaderLanguage.get(Shader.ShaderType.Vertex);
-    }
-
-    public boolean isUsesShaderNodes() {
-        return usesShaderNodes;
-    }
-
-    public void setUsesShaderNodes(boolean usesShaderNodes) {
-        this.usesShaderNodes = usesShaderNodes;
-    }
-
-    public Set<Shader.ShaderType> getUsedShaderPrograms(){
-        return shaderName.keySet();
-    }
-
-    public String getShaderProgramLanguage(Shader.ShaderType shaderType){
-        return shaderLanguage.get(shaderType);
-    }
-
-    public String getShaderProgramName(Shader.ShaderType shaderType){
-        return getShaderName(shaderType);
-    }
-
-    @Override
-    public void write(JmeExporter ex) throws IOException{
-        super.write(ex);
-        OutputCapsule oc = ex.getCapsule(this);
-        oc.write(shaderName.get(Shader.ShaderType.Fragment), "fragment_name", null);
-        oc.write(shaderName.get(Shader.ShaderType.Geometry), "geometry_name", null);
-        oc.write(shaderName.get(Shader.ShaderType.TessellationControl), "tessControl_name", null);
-        oc.write(shaderName.get(Shader.ShaderType.TessellationEvaluation), "tessEval_name", null);
-        oc.write(shaderLanguage.get(Shader.ShaderType.Vertex), "language", null);
-        oc.write(shaderLanguage.get(Shader.ShaderType.Fragment), "frag_language", null);
-        oc.write(shaderLanguage.get(Shader.ShaderType.Geometry), "geom_language", null);
-        oc.write(shaderLanguage.get(Shader.ShaderType.TessellationControl), "tsctrl_language", null);
-        oc.write(shaderLanguage.get(Shader.ShaderType.TessellationEvaluation), "tseval_language", null);
-
-    }
-
-    @Override
-    public void read(JmeImporter im) throws IOException{
-        super.read(im);
-        InputCapsule ic = im.getCapsule(this);
-        shaderName.put(Shader.ShaderType.Vertex,name);
-        shaderName.put(Shader.ShaderType.Fragment,ic.readString("fragment_name", null));
-        shaderName.put(Shader.ShaderType.Geometry,ic.readString("geometry_name", null));
-        shaderName.put(Shader.ShaderType.TessellationControl,ic.readString("tessControl_name", null));
-        shaderName.put(Shader.ShaderType.TessellationEvaluation,ic.readString("tessEval_name", null));
-        shaderLanguage.put(Shader.ShaderType.Vertex,ic.readString("language", null));
-        shaderLanguage.put(Shader.ShaderType.Fragment,ic.readString("frag_language", null));
-        shaderLanguage.put(Shader.ShaderType.Geometry,ic.readString("geom_language", null));
-        shaderLanguage.put(Shader.ShaderType.TessellationControl,ic.readString("tsctrl_language", null));
-        shaderLanguage.put(Shader.ShaderType.TessellationEvaluation,ic.readString("tseval_language", null));
-    }
-
-}

+ 3 - 2
jme3-core/src/main/java/com/jme3/shader/UniformBindingManager.java

@@ -36,7 +36,7 @@ import com.jme3.math.*;
 import com.jme3.renderer.Camera;
 import com.jme3.renderer.RenderManager;
 import com.jme3.system.Timer;
-import java.util.List;
+import java.util.ArrayList;
 
 /**
  * <code>UniformBindingManager</code> helps {@link RenderManager} to manage
@@ -84,7 +84,8 @@ public class UniformBindingManager {
      * Updates the given list of uniforms with {@link UniformBinding uniform bindings}
      * based on the current world state.
      */
-    public void updateUniformBindings(List<Uniform> params) {
+    public void updateUniformBindings(Shader shader) {
+        ArrayList<Uniform> params = shader.getBoundUniforms();
         for (int i = 0; i < params.size(); i++) {
             Uniform u = params.get(i);
             switch (u.getBinding()) {

+ 4 - 3
jme3-core/src/main/java/com/jme3/system/NullContext.java

@@ -128,12 +128,13 @@ public class NullContext implements JmeContext, Runnable {
     public void run(){
         initInThread();
 
-        while (!needClose.get()){
+        do {
             listener.update();
 
-            if (frameRate > 0)
+            if (frameRate > 0) {
                 sync(frameRate);
-        }
+            }
+        } while (!needClose.get());
 
         deinitInThread();
 

+ 1 - 1
jme3-core/src/main/java/com/jme3/system/NullRenderer.java

@@ -51,7 +51,7 @@ import com.jme3.texture.Texture;
 
 public class NullRenderer implements Renderer {
 
-    private static final EnumSet<Caps> caps = EnumSet.noneOf(Caps.class);
+    private static final EnumSet<Caps> caps = EnumSet.allOf(Caps.class);
     private static final Statistics stats = new Statistics();
 
     public void initialize() {

+ 5 - 12
jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md

@@ -115,7 +115,7 @@ MaterialDef Phong Lighting {
         Boolean UseInstancing
     }
 
- Technique {
+    Technique {
         LightMode SinglePass
         
         VertexShader GLSL100:   Common/MatDefs/Light/SPLighting.vert
@@ -147,7 +147,7 @@ MaterialDef Phong Lighting {
             SEPARATE_TEXCOORD : SeparateTexCoord
             DISCARD_ALPHA : AlphaDiscardThreshold
             USE_REFLECTION : EnvMap
-            SPHERE_MAP : SphereMap  
+            SPHERE_MAP : EnvMapAsSphereMap  
             NUM_BONES : NumberOfBones                        
             INSTANCING : UseInstancing
         }
@@ -186,7 +186,7 @@ MaterialDef Phong Lighting {
             SEPARATE_TEXCOORD : SeparateTexCoord
             DISCARD_ALPHA : AlphaDiscardThreshold
             USE_REFLECTION : EnvMap
-            SPHERE_MAP : SphereMap  
+            SPHERE_MAP : EnvMapAsSphereMap  
             NUM_BONES : NumberOfBones                        
             INSTANCING : UseInstancing
         }
@@ -207,7 +207,6 @@ MaterialDef Phong Lighting {
         }
 
         Defines {
-            COLOR_MAP : ColorMap
             DISCARD_ALPHA : AlphaDiscardThreshold
             NUM_BONES : NumberOfBones
             INSTANCING : UseInstancing
@@ -239,8 +238,7 @@ MaterialDef Phong Lighting {
             HARDWARE_SHADOWS : HardwareShadows
             FILTER_MODE : FilterMode
             PCFEDGE : PCFEdge
-            DISCARD_ALPHA : AlphaDiscardThreshold           
-            COLOR_MAP : ColorMap
+            DISCARD_ALPHA : AlphaDiscardThreshold
             SHADOWMAP_SIZE : ShadowMapSize
             FADE : FadeInfo
             PSSM : Splits
@@ -271,8 +269,7 @@ MaterialDef Phong Lighting {
             HARDWARE_SHADOWS : HardwareShadows
             FILTER_MODE : FilterMode
             PCFEDGE : PCFEdge
-            DISCARD_ALPHA : AlphaDiscardThreshold           
-            COLOR_MAP : ColorMap
+            DISCARD_ALPHA : AlphaDiscardThreshold
             SHADOWMAP_SIZE : ShadowMapSize
             FADE : FadeInfo
             PSSM : Splits
@@ -346,10 +343,6 @@ MaterialDef Phong Lighting {
         Defines {
             VERTEX_COLOR : UseVertexColor
             MATERIAL_COLORS : UseMaterialColors
-            V_TANGENT : VTangent
-            MINNAERT  : Minnaert
-            WARDISO   : WardIso
-
             DIFFUSEMAP : DiffuseMap
             NORMALMAP : NormalMap
             SPECULARMAP : SpecularMap

+ 58 - 5
jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java

@@ -40,6 +40,7 @@ import com.jme3.material.TechniqueDef.ShadowMode;
 import com.jme3.math.ColorRGBA;
 import com.jme3.math.Vector2f;
 import com.jme3.math.Vector3f;
+import com.jme3.shader.DefineList;
 import com.jme3.shader.Shader;
 import com.jme3.shader.VarType;
 import com.jme3.texture.Texture;
@@ -73,6 +74,7 @@ public class J3MLoader implements AssetLoader {
     private Material material;
     private TechniqueDef technique;
     private RenderState renderState;
+    private ArrayList<String> presetDefines = new ArrayList<String>();
 
     private EnumMap<Shader.ShaderType, String> shaderLanguage;
     private EnumMap<Shader.ShaderType, String> shaderName;
@@ -115,6 +117,10 @@ public class J3MLoader implements AssetLoader {
             throw new IOException("LightMode statement syntax incorrect");
         }
         LightMode lm = LightMode.valueOf(split[1]);
+        if (lm == LightMode.FixedPipeline) {
+            throw new UnsupportedOperationException("OpenGL1 is not supported");
+        }
+        
         technique.setLightMode(lm);
     }
 
@@ -479,10 +485,22 @@ public class J3MLoader implements AssetLoader {
     private void readDefine(String statement) throws IOException{
         String[] split = statement.split(":");
         if (split.length == 1){
-            // add preset define
-            technique.addShaderPresetDefine(split[0].trim(), VarType.Boolean, true);
+            String defineName = split[0].trim();
+            presetDefines.add(defineName);
         }else if (split.length == 2){
-            technique.addShaderParamDefine(split[1].trim(), split[0].trim());
+            String defineName = split[0].trim();
+            String paramName = split[1].trim();
+            MatParam param = materialDef.getMaterialParam(paramName);
+            if (param == null) {
+                logger.log(Level.WARNING, "In technique ''{0}'':\n"
+                        + "Define ''{1}'' mapped to non-existent"
+                        + " material parameter ''{2}'', ignoring.",
+                        new Object[]{technique.getName(), defineName, paramName});
+                return;
+            }
+            
+            VarType paramType = param.getVarType();
+            technique.addShaderParamDefine(paramName, paramType, defineName);
         }else{
             throw new IOException("Define syntax incorrect");
         }
@@ -544,15 +562,28 @@ public class J3MLoader implements AssetLoader {
         }
         material.setTransparent(parseBoolean(split[1]));
     }
+    
+    private static String createShaderPrologue(List<String> presetDefines) {
+        DefineList dl = new DefineList(presetDefines.size());
+        for (int i = 0; i < presetDefines.size(); i++) {
+            dl.set(i, 1);
+        }
+        StringBuilder sb = new StringBuilder();
+        dl.generateSource(sb, presetDefines, null);
+        return sb.toString();
+    }
 
     private void readTechnique(Statement techStat) throws IOException{
         isUseNodes = false;
         String[] split = techStat.getLine().split(whitespacePattern);
+        
         if (split.length == 1) {
-            technique = new TechniqueDef(null);
+            String techniqueUniqueName = materialDef.getAssetName() + "@Default";
+            technique = new TechniqueDef(null, techniqueUniqueName.hashCode());
         } else if (split.length == 2) {
             String techName = split[1];
-            technique = new TechniqueDef(techName);
+            String techniqueUniqueName = materialDef.getAssetName() + "@" + techName;
+            technique = new TechniqueDef(techName, techniqueUniqueName.hashCode());
         } else {
             throw new IOException("Technique statement syntax incorrect");
         }
@@ -563,18 +594,40 @@ public class J3MLoader implements AssetLoader {
 
         if(isUseNodes){
             nodesLoaderDelegate.computeConditions();
+            
             //used for caching later, the shader here is not a file.
+            
+            // KIRILL 9/19/2015
+            // Not sure if this is needed anymore, since shader caching
+            // is now done by TechniqueDef.
             technique.setShaderFile(technique.hashCode() + "", technique.hashCode() + "", "GLSL100", "GLSL100");
         }
 
         if (shaderName.containsKey(Shader.ShaderType.Vertex) && shaderName.containsKey(Shader.ShaderType.Fragment)) {
             technique.setShaderFile(shaderName, shaderLanguage);
         }
+        
+        technique.setShaderPrologue(createShaderPrologue(presetDefines));
+        
+        switch (technique.getLightMode()) {
+            case Disable:
+                technique.setLogic(new DefaultTechniqueDefLogic(technique));
+                break;
+            case MultiPass:
+                technique.setLogic(new MultiPassLightingLogic(technique));
+                break;
+            case SinglePass:
+                technique.setLogic(new SinglePassLightingLogic(technique));
+                break;
+            default:
+                throw new UnsupportedOperationException();
+        }
 
         materialDef.addTechniqueDef(technique);
         technique = null;
         shaderLanguage.clear();
         shaderName.clear();
+        presetDefines.clear();
     }
 
     private void loadFromRoot(List<Statement> roots) throws IOException{

+ 5 - 4
jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java

@@ -44,6 +44,7 @@ import com.jme3.shader.ShaderNodeDefinition;
 import com.jme3.shader.ShaderNodeVariable;
 import com.jme3.shader.ShaderUtils;
 import com.jme3.shader.UniformBinding;
+import com.jme3.shader.VarType;
 import com.jme3.shader.VariableMapping;
 import com.jme3.util.blockparser.Statement;
 import java.io.IOException;
@@ -583,7 +584,7 @@ public class ShaderNodeLoaderDelegate {
                     //multiplicity is not an int attempting to find for a material parameter.
                     MatParam mp = findMatParam(multiplicity);
                     if (mp != null) {
-                        addDefine(multiplicity);
+                        addDefine(multiplicity, VarType.Int);
                         multiplicity = multiplicity.toUpperCase();
                     } else {
                         throw new MatParseException("Wrong multiplicity for variable" + mapping.getLeftVariable().getName() + ". " + multiplicity + " should be an int or a declared material parameter.", statement);
@@ -625,9 +626,9 @@ public class ShaderNodeLoaderDelegate {
      *
      * @param paramName
      */
-    public void addDefine(String paramName) {
+    public void addDefine(String paramName, VarType paramType) {
         if (techniqueDef.getShaderParamDefine(paramName) == null) {
-            techniqueDef.addShaderParamDefine(paramName, paramName.toUpperCase());
+            techniqueDef.addShaderParamDefine(paramName, paramType, paramName.toUpperCase());
         }
     }
 
@@ -660,7 +661,7 @@ public class ShaderNodeLoaderDelegate {
         for (String string : defines) {
             MatParam param = findMatParam(string);
             if (param != null) {
-                addDefine(param.getName());
+                addDefine(param.getName(), param.getVarType());
             } else {
                 throw new MatParseException("Invalid condition, condition must match a Material Parameter named " + cond, statement);
             }

+ 52 - 0
jme3-core/src/test/java/com/jme3/asset/LoadShaderSourceTest.java

@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2009-2015 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.asset;
+
+import com.jme3.asset.plugins.ClasspathLocator;
+import com.jme3.shader.plugins.GLSLLoader;
+import com.jme3.system.JmeSystem;
+import com.jme3.system.MockJmeSystemDelegate;
+import org.junit.Test;
+
+public class LoadShaderSourceTest {
+
+    @Test
+    public void testLoadShaderSource() {
+        JmeSystem.setSystemDelegate(new MockJmeSystemDelegate());
+        AssetManager assetManager = new DesktopAssetManager();
+        assetManager.registerLocator(null, ClasspathLocator.class);
+        assetManager.registerLoader(GLSLLoader.class, "frag");
+        String showNormals = (String) assetManager.loadAsset("Common/MatDefs/Misc/ShowNormals.frag");
+        System.out.println(showNormals);
+    }
+    
+}

+ 34 - 0
jme3-core/src/test/java/com/jme3/math/FastMathTest.java

@@ -56,4 +56,38 @@ public class FastMathTest {
             assert nextPowerOf2 == nearestPowerOfTwoSlow(i);
         }
     }
+    
+    private static int fastCounterClockwise(Vector2f p0, Vector2f p1, Vector2f p2) {
+        float result = (p1.x - p0.x) * (p2.y - p1.y) - (p1.y - p0.y) * (p2.x - p1.x);
+        return (int) Math.signum(result);
+    }
+    
+    private static Vector2f randomVector() {
+        return new Vector2f(FastMath.nextRandomFloat(),
+                            FastMath.nextRandomFloat());
+    }
+    
+    @Test
+    public void testCounterClockwise() {
+        for (int i = 0; i < 100; i++) {
+            Vector2f p0 = randomVector();
+            Vector2f p1 = randomVector();
+            Vector2f p2 = randomVector();
+
+            int fastResult = fastCounterClockwise(p0, p1, p2);
+            int slowResult = FastMath.counterClockwise(p0, p1, p2);
+            
+            assert fastResult == slowResult;
+        }
+        
+        // duplicate test
+        Vector2f p0 = new Vector2f(0,0);
+        Vector2f p1 = new Vector2f(0,0);
+        Vector2f p2 = new Vector2f(0,1);
+        
+        int fastResult = fastCounterClockwise(p0, p1, p2);
+        int slowResult = FastMath.counterClockwise(p0, p1, p2);
+        
+        assert fastResult == slowResult;
+    }
 }

+ 342 - 0
jme3-core/src/test/java/com/jme3/renderer/OpaqueComparatorTest.java

@@ -0,0 +1,342 @@
+/*
+ * Copyright (c) 2009-2015 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.renderer;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.renderer.queue.GeometryList;
+import com.jme3.renderer.queue.OpaqueComparator;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.shape.Box;
+import com.jme3.system.TestUtil;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture2D;
+import com.jme3.texture.image.ColorSpace;
+import com.jme3.util.BufferUtils;
+import java.nio.ByteBuffer;
+import java.util.HashSet;
+import java.util.Set;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class OpaqueComparatorTest {
+    
+    private final Mesh mesh = new Box(1,1,1);
+    private Camera cam = new Camera(1, 1);
+    private RenderManager renderManager;
+    private AssetManager assetManager;
+    private OpaqueComparator comparator = new OpaqueComparator();
+    
+    @Before
+    public void setUp() {
+        assetManager = TestUtil.createAssetManager();
+        renderManager = TestUtil.createRenderManager();
+        comparator.setCamera(cam);
+    }
+    
+    /**
+     * Given a correctly sorted list of materials, check if the 
+     * opaque comparator can sort a reversed list of them.
+     * 
+     * Each material will be cloned so that none of them will be equal to each other
+     * in reference, forcing the comparator to compare the material sort ID.
+     * 
+     * E.g. for a list of materials A, B, C, the following list will be generated:
+     * <pre>C, B, A, C, B, A, C, B, A</pre>, it should result in
+     * <pre>A, A, A, B, B, B, C, C, C</pre>.
+     * 
+     * @param materials The pre-sorted list of materials to check sorting for.
+     */
+    private void testSort(Material ... materials) {
+        GeometryList gl = new GeometryList(comparator);
+        for (int g = 0; g < 5; g++) {
+            for (int i = materials.length - 1; i >= 0; i--) {
+                Geometry geom = new Geometry("geom", mesh);
+                Material clonedMaterial = materials[i].clone();
+                
+                if (materials[i].getActiveTechnique() != null) {
+                    String techniqueName = materials[i].getActiveTechnique().getDef().getName();
+                    clonedMaterial.selectTechnique(techniqueName, renderManager);
+                } else {
+                    clonedMaterial.selectTechnique("Default", renderManager);
+                }
+                
+                geom.setMaterial(clonedMaterial);
+                gl.add(geom);
+            }
+        }
+        gl.sort();
+        
+        for (int i = 0; i < gl.size(); i++) {
+            Material mat = gl.get(i).getMaterial();
+            String sortId = Integer.toHexString(mat.getSortId()).toUpperCase();
+            System.out.print(sortId + "\t");
+            System.out.println(mat);
+        }
+        
+        Set<String> alreadySeen = new HashSet<String>();
+        Material current = null;
+        for (int i = 0; i < gl.size(); i++) {
+            Material mat = gl.get(i).getMaterial();
+            if (current == null) {
+                current = mat;
+            } else if (!current.getName().equals(mat.getName())) {
+                assert !alreadySeen.contains(mat.getName());
+                alreadySeen.add(current.getName());
+                current = mat;
+            }
+        }
+        
+        for (int i = 0; i < materials.length; i++) {
+            for (int g = 0; g < 5; g++) {
+                int index = i * 5 + g;
+                Material mat1 = gl.get(index).getMaterial();
+                Material mat2 = materials[i];
+                assert mat1.getName().equals(mat2.getName()) : 
+                       mat1.getName() + " != " + mat2.getName() + " for index " + index;
+            }
+        }
+    }
+    
+    @Test
+    public void testSortByMaterialDef() {
+        Material lightingMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        Material particleMat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
+        Material unshadedMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        Material skyMat      = new Material(assetManager, "Common/MatDefs/Misc/Sky.j3md");
+        
+        lightingMat.setName("MatLight");
+        particleMat.setName("MatParticle");
+        unshadedMat.setName("MatUnshaded");
+        skyMat.setName("MatSky");
+        testSort(skyMat, lightingMat, particleMat, unshadedMat);
+    }
+    
+    @Test
+    public void testSortByTechnique() {
+        Material lightingMatDefault = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        Material lightingPreShadow = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        Material lightingPostShadow = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        Material lightingMatPreNormalPass = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        Material lightingMatGBuf = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        Material lightingMatGlow = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        
+        lightingMatDefault.setName("TechDefault");
+        lightingMatDefault.selectTechnique("Default", renderManager);
+        
+        lightingPostShadow.setName("TechPostShad");
+        lightingPostShadow.selectTechnique("PostShadow", renderManager);
+        
+        lightingPreShadow.setName("TechPreShad");
+        lightingPreShadow.selectTechnique("PreShadow", renderManager);
+        
+        lightingMatPreNormalPass.setName("TechNorm");
+        lightingMatPreNormalPass.selectTechnique("PreNormalPass", renderManager);
+        
+        lightingMatGBuf.setName("TechGBuf");
+        lightingMatGBuf.selectTechnique("GBuf", renderManager);
+        
+        lightingMatGlow.setName("TechGlow");
+        lightingMatGlow.selectTechnique("Glow", renderManager);
+        
+        testSort(lightingMatGlow, lightingPreShadow, lightingMatPreNormalPass,
+                 lightingMatDefault, lightingPostShadow, lightingMatGBuf);
+    }
+    
+    @Test(expected = AssertionError.class)
+    public void testNoSortByParam() {
+        Material sameMat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        Material sameMat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        
+        sameMat1.setName("MatRed");
+        sameMat1.setColor("Color", ColorRGBA.Red);
+        
+        sameMat2.setName("MatBlue");
+        sameMat2.setColor("Color", ColorRGBA.Blue);
+        
+        testSort(sameMat1, sameMat2);
+    }
+    
+    private Texture createTexture(String name) {
+        ByteBuffer bb = BufferUtils.createByteBuffer(3);
+        Image image = new Image(Format.RGB8, 1, 1, bb, ColorSpace.sRGB);
+        Texture2D texture = new Texture2D(image);
+        texture.setName(name);
+        return texture;
+    }
+    
+    @Test
+    public void testSortByTexture() {
+        Material texture1Mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        Material texture2Mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        Material texture3Mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        
+        Texture tex1 = createTexture("A");
+        tex1.getImage().setId(1);
+        
+        Texture tex2 = createTexture("B");
+        tex2.getImage().setId(2);
+        
+        Texture tex3 = createTexture("C");
+        tex3.getImage().setId(3);
+        
+        texture1Mat.setName("TexA");
+        texture1Mat.setTexture("ColorMap", tex1);
+        
+        texture2Mat.setName("TexB");
+        texture2Mat.setTexture("ColorMap", tex2);
+        
+        texture3Mat.setName("TexC");
+        texture3Mat.setTexture("ColorMap", tex3);
+        
+        testSort(texture1Mat, texture2Mat, texture3Mat);
+    }
+    
+    @Test
+    public void testSortByShaderDefines() {
+        Material lightingMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        Material lightingMatVColor = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        Material lightingMatVLight = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        Material lightingMatTC = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        Material lightingMatVColorLight = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        Material lightingMatTCVColorLight = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        
+        lightingMat.setName("DefNone");
+        
+        lightingMatVColor.setName("DefVC");
+        lightingMatVColor.setBoolean("UseVertexColor", true);
+        
+        lightingMatVLight.setName("DefVL");
+        lightingMatVLight.setBoolean("VertexLighting", true);
+        
+        lightingMatTC.setName("DefTC");
+        lightingMatTC.setBoolean("SeparateTexCoord", true);
+        
+        lightingMatVColorLight.setName("DefVCVL");
+        lightingMatVColorLight.setBoolean("UseVertexColor", true);
+        lightingMatVColorLight.setBoolean("VertexLighting", true);
+        
+        lightingMatTCVColorLight.setName("DefVCVLTC");
+        lightingMatTCVColorLight.setBoolean("UseVertexColor", true);
+        lightingMatTCVColorLight.setBoolean("VertexLighting", true);
+        lightingMatTCVColorLight.setBoolean("SeparateTexCoord", true);
+        
+        testSort(lightingMat, lightingMatVColor, lightingMatVLight,
+                 lightingMatVColorLight, lightingMatTC, lightingMatTCVColorLight);
+    }
+    
+    @Test
+    public void testSortByAll() {
+        Material matBase1 = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        Material matBase2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        
+        Texture texBase = createTexture("BASE");
+        texBase.getImage().setId(1);
+        Texture tex1 = createTexture("1");
+        tex1.getImage().setId(2);
+        Texture tex2 = createTexture("2");
+        tex2.getImage().setId(3);
+        
+        matBase1.setName("BASE");
+        matBase1.selectTechnique("Default", renderManager);
+        matBase1.setBoolean("UseVertexColor", true);
+        matBase1.setTexture("DiffuseMap", texBase);
+        
+        Material mat1100 = matBase1.clone();
+        mat1100.setName("1100");
+        mat1100.selectTechnique("PreShadow", renderManager);
+        
+        Material mat1101 = matBase1.clone();
+        mat1101.setName("1101");
+        mat1101.selectTechnique("PreShadow", renderManager);
+        mat1101.setTexture("DiffuseMap", tex1);
+        
+        Material mat1102 = matBase1.clone();
+        mat1102.setName("1102");
+        mat1102.selectTechnique("PreShadow", renderManager);
+        mat1102.setTexture("DiffuseMap", tex2);
+        
+        Material mat1110 = matBase1.clone();
+        mat1110.setName("1110");
+        mat1110.selectTechnique("PreShadow", renderManager);
+        mat1110.setFloat("AlphaDiscardThreshold", 2f);
+        
+        Material mat1120 = matBase1.clone();
+        mat1120.setName("1120");
+        mat1120.selectTechnique("PreShadow", renderManager);
+        mat1120.setBoolean("UseInstancing", true);
+        
+        Material mat1121 = matBase1.clone();
+        mat1121.setName("1121");
+        mat1121.selectTechnique("PreShadow", renderManager);
+        mat1121.setBoolean("UseInstancing", true);
+        mat1121.setTexture("DiffuseMap", tex1);
+        
+        Material mat1122 = matBase1.clone();
+        mat1122.setName("1122");
+        mat1122.selectTechnique("PreShadow", renderManager);
+        mat1122.setBoolean("UseInstancing", true);
+        mat1122.setTexture("DiffuseMap", tex2);
+        
+        Material mat1140 = matBase1.clone();
+        mat1140.setName("1140");
+        mat1140.selectTechnique("PreShadow", renderManager);
+        mat1140.setFloat("AlphaDiscardThreshold", 2f);
+        mat1140.setBoolean("UseInstancing", true);
+        
+        Material mat1200 = matBase1.clone();
+        mat1200.setName("1200");
+        mat1200.selectTechnique("PostShadow", renderManager);
+        
+        Material mat1210 = matBase1.clone();
+        mat1210.setName("1210");
+        mat1210.selectTechnique("PostShadow", renderManager);
+        mat1210.setFloat("AlphaDiscardThreshold", 2f);
+        
+        Material mat1220 = matBase1.clone();
+        mat1220.setName("1220");
+        mat1220.selectTechnique("PostShadow", renderManager);
+        mat1220.setBoolean("UseInstancing", true);
+        
+        Material mat2000 = matBase2.clone();
+        mat2000.setName("2000");
+        
+        testSort(mat1100, mat1101, mat1102, mat1110, 
+                 mat1120, mat1121, mat1122, mat1140, 
+                 mat1200, mat1210, mat1220, mat2000);
+    }
+}

+ 143 - 0
jme3-core/src/test/java/com/jme3/shader/DefineListTest.java

@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2009-2015 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.shader;
+
+import com.jme3.math.FastMath;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Test;
+
+public class DefineListTest {
+    
+    private List<String> defineNames;
+    private List<VarType> defineTypes;
+    
+    @Test
+    public void testHashCollision() {
+        DefineList dl1 = new DefineList(64);
+        DefineList dl2 = new DefineList(64);
+        
+        // Try to cause a hash collision
+        // (since bit #32 is aliased to bit #1 in 32-bit ints)
+        dl1.set(0, 123);
+        dl1.set(32, 0);
+        
+        dl2.set(32, 0);
+        dl2.set(0, 123);
+        
+        assert dl1.hashCode() == dl2.hashCode();
+        assert dl1.equals(dl2);
+    }
+    
+    private String generateSource(DefineList dl) {
+        StringBuilder sb = new StringBuilder();
+        dl.generateSource(sb, defineNames, defineTypes);
+        return sb.toString();
+    }
+    
+    @Test
+    public void testInitial() {
+        DefineList dl = new DefineList(3);
+        defineNames  = Arrays.asList("A", "B", "C");
+        defineTypes = Arrays.asList(VarType.Boolean, VarType.Int, VarType.Float);
+        
+        assert dl.hashCode() == 0;
+        assert generateSource(dl).equals("");
+    }
+    
+    @Test
+    public void testBooleanDefine() {
+        DefineList dl = new DefineList(1);
+        defineNames  = Arrays.asList("BOOL_VAR");
+        defineTypes = Arrays.asList(VarType.Boolean);
+        
+        dl.set(0, true);
+        assert dl.hashCode() == 1;
+        assert generateSource(dl).equals("#define BOOL_VAR 1\n");
+        
+        dl.set(0, false);
+        assert dl.hashCode() == 0;
+        assert generateSource(dl).equals("");
+    }
+    
+    @Test
+    public void testFloatDefine() {
+        DefineList dl = new DefineList(1);
+        defineNames  = Arrays.asList("FLOAT_VAR");
+        defineTypes = Arrays.asList(VarType.Float);
+        
+        dl.set(0, 1f);
+        assert dl.hashCode() == 1;
+        assert generateSource(dl).equals("#define FLOAT_VAR 1.0\n");
+        
+        dl.set(0, 0f);
+        assert dl.hashCode() == 0;
+        assert generateSource(dl).equals("");
+        
+        dl.set(0, -1f);
+        assert generateSource(dl).equals("#define FLOAT_VAR -1.0\n");
+        
+        dl.set(0, FastMath.FLT_EPSILON);
+        assert generateSource(dl).equals("#define FLOAT_VAR 1.1920929E-7\n");
+        
+        dl.set(0, FastMath.PI);
+        assert generateSource(dl).equals("#define FLOAT_VAR 3.1415927\n");
+        
+        try {
+            dl.set(0, Float.NaN);
+            generateSource(dl);
+            assert false;
+        } catch (IllegalArgumentException ex) { }
+        
+        try {
+            dl.set(0, Float.POSITIVE_INFINITY);
+            generateSource(dl);
+            assert false;
+        } catch (IllegalArgumentException ex) { }
+        
+        try {
+            dl.set(0, Float.NEGATIVE_INFINITY);
+            generateSource(dl);
+            assert false;
+        } catch (IllegalArgumentException ex) { }
+    }
+    
+    @Test
+    public void testSourceGeneration() {
+        DefineList dl = new DefineList(64);
+        defineNames  = Arrays.asList("BOOL_VAR",      "INT_VAR",   "FLOAT_VAR");
+        defineTypes = Arrays.asList(VarType.Boolean, VarType.Int, VarType.Float);
+        dl.set(0, true);
+        dl.set(1, -1);
+        dl.set(2, Float.NaN);
+    }
+}

+ 78 - 0
jme3-core/src/test/java/com/jme3/system/MockJmeSystemDelegate.java

@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2009-2015 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.system;
+
+import com.jme3.audio.AudioRenderer;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URL;
+import java.nio.ByteBuffer;
+
+public class MockJmeSystemDelegate extends JmeSystemDelegate {
+
+    @Override
+    public void writeImageFile(OutputStream outStream, String format, ByteBuffer imageData, int width, int height) throws IOException {
+    }
+
+    @Override
+    public void showErrorDialog(String message) {
+    }
+
+    @Override
+    public boolean showSettingsDialog(AppSettings sourceSettings, boolean loadFromRegistry) {
+        return false;
+    }
+
+    @Override
+    public URL getPlatformAssetConfigURL() {
+        return Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/General.cfg");
+    }
+
+    @Override
+    public JmeContext newContext(AppSettings settings, JmeContext.Type contextType) {
+        return null;
+    }
+
+    @Override
+    public AudioRenderer newAudioRenderer(AppSettings settings) {
+        return null;
+    }
+
+    @Override
+    public void initialize(AppSettings settings) {
+    }
+
+    @Override
+    public void showSoftKeyboard(boolean show) {
+    }
+    
+}

+ 55 - 0
jme3-core/src/test/java/com/jme3/system/TestUtil.java

@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2009-2015 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.system;
+
+import com.jme3.asset.AssetConfig;
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.DesktopAssetManager;
+import com.jme3.renderer.RenderManager;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class TestUtil {
+    
+    static {
+        JmeSystem.setSystemDelegate(new MockJmeSystemDelegate());
+    }
+    
+    public static AssetManager createAssetManager() {
+        Logger.getLogger(AssetConfig.class.getName()).setLevel(Level.OFF);
+        return new DesktopAssetManager(true);
+    }
+    
+    public static RenderManager createRenderManager() {
+        return new RenderManager(new NullRenderer());
+    }
+}

+ 12 - 12
jme3-core/src/tools/java/jme3tools/shadercheck/ShaderCheck.java

@@ -6,11 +6,12 @@ import com.jme3.asset.plugins.FileLocator;
 import com.jme3.material.MaterialDef;
 import com.jme3.material.TechniqueDef;
 import com.jme3.material.plugins.J3MLoader;
+import com.jme3.renderer.Caps;
 import com.jme3.shader.DefineList;
 import com.jme3.shader.Shader;
-import com.jme3.shader.ShaderKey;
 import com.jme3.shader.plugins.GLSLLoader;
 import com.jme3.system.JmeSystem;
+import java.util.EnumSet;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -33,23 +34,22 @@ public class ShaderCheck {
         assetManager.registerLoader(GLSLLoader.class, "vert", "frag","geom","tsctrl","tseval","glsllib");
     }
     
-    private static void checkMatDef(String matdefName){
+    private static void checkMatDef(String matdefName) {
         MaterialDef def = (MaterialDef) assetManager.loadAsset(matdefName);
-        for (TechniqueDef techDef : def.getDefaultTechniques()){
-            DefineList dl = new DefineList();
-            dl.addFrom(techDef.getShaderPresetDefines());
-            ShaderKey shaderKey = new ShaderKey(dl,techDef.getShaderProgramLanguages(),techDef.getShaderProgramNames());
-
-            Shader shader = assetManager.loadShader(shaderKey);
-
-            for (Validator validator : validators){
+        EnumSet<Caps> rendererCaps = EnumSet.noneOf(Caps.class);
+        rendererCaps.add(Caps.GLSL100);
+        for (TechniqueDef techDef : def.getDefaultTechniques()) {
+            DefineList defines = techDef.createDefineList();
+            Shader shader = techDef.getShader(assetManager, rendererCaps, defines);
+            for (Validator validator : validators) {
                 StringBuilder sb = new StringBuilder();
                 validator.validate(shader, sb);
-                System.out.println("==== Validator: " + validator.getName() + " " + 
-                                        validator.getInstalledVersion() + " ====");
+                System.out.println("==== Validator: " + validator.getName() + " "
+                        + validator.getInstalledVersion() + " ====");
                 System.out.println(sb.toString());
             }
         }
+        throw new UnsupportedOperationException();
     }
           
     public static void main(String[] args){

+ 50 - 0
jme3-desktop/src/test/java/LibraryLoaderTest.java

@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2009-2015 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.
+ */
+
+import com.jme3.system.NativeLibraryLoader;
+import java.io.File;
+import java.util.Arrays;
+import org.junit.Test;
+
+/**
+ *
+ * @author Kirill Vainer
+ */
+public class LibraryLoaderTest {
+    
+    @Test
+    public void whatever() {
+        File[] nativeJars = NativeLibraryLoader.getJarsWithNatives();
+        System.out.println(Arrays.toString(nativeJars));
+    }
+    
+}