Quellcode durchsuchen

Add the new material system

Also includes some unrelated tests
Kirill Vainer vor 10 Jahren
Ursprung
Commit
18db26292f
33 geänderte Dateien mit 2068 neuen und 1211 gelöschten Zeilen
  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.renderer.Caps;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.plugins.OBJLoader;
 import com.jme3.scene.plugins.OBJLoader;
-import com.jme3.shader.Shader;
 import com.jme3.shader.ShaderGenerator;
 import com.jme3.shader.ShaderGenerator;
-import com.jme3.shader.ShaderKey;
 import com.jme3.texture.Texture;
 import com.jme3.texture.Texture;
 import com.jme3.texture.plugins.TGALoader;
 import com.jme3.texture.plugins.TGALoader;
 import java.io.IOException;
 import java.io.IOException;
@@ -320,13 +318,6 @@ public interface AssetManager {
      */
      */
     public Material loadMaterial(String name);
     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,
      * Load a font file. Font files are in AngelCode text format,
      * and are with the extension "fnt".
      * 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;
 package com.jme3.asset;
 
 
 import com.jme3.asset.cache.AssetCache;
 import com.jme3.asset.cache.AssetCache;
-import com.jme3.asset.cache.SimpleAssetCache;
 import com.jme3.audio.AudioData;
 import com.jme3.audio.AudioData;
 import com.jme3.audio.AudioKey;
 import com.jme3.audio.AudioKey;
 import com.jme3.font.BitmapFont;
 import com.jme3.font.BitmapFont;
@@ -42,9 +41,7 @@ import com.jme3.renderer.Caps;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.Spatial;
 import com.jme3.shader.Glsl100ShaderGenerator;
 import com.jme3.shader.Glsl100ShaderGenerator;
 import com.jme3.shader.Glsl150ShaderGenerator;
 import com.jme3.shader.Glsl150ShaderGenerator;
-import com.jme3.shader.Shader;
 import com.jme3.shader.ShaderGenerator;
 import com.jme3.shader.ShaderGenerator;
-import com.jme3.shader.ShaderKey;
 import com.jme3.system.JmeSystem;
 import com.jme3.system.JmeSystem;
 import com.jme3.texture.Texture;
 import com.jme3.texture.Texture;
 import java.io.IOException;
 import java.io.IOException;
@@ -431,36 +428,6 @@ public class DesktopAssetManager implements AssetManager {
         return loadFilter(new FilterKey(name));
         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}
      * {@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;
         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
      * 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;
         return unit;
     }
     }
 
 
-    @Override
-    public void apply(Renderer r, Technique technique) {
-        TechniqueDef techDef = technique.getDef();
-        r.setTexture(getUnit(), getTextureValue());
-        technique.updateUniformParam(getPrefixedName(), getVarType(), getUnit());
-    }
 
 
     @Override
     @Override
     public void write(JmeExporter ex) throws IOException {
     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.Caps;
 import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.Renderer;
 import com.jme3.renderer.Renderer;
-import com.jme3.renderer.RendererException;
 import com.jme3.renderer.queue.RenderQueue.Bucket;
 import com.jme3.renderer.queue.RenderQueue.Bucket;
 import com.jme3.scene.Geometry;
 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.Shader;
 import com.jme3.shader.Uniform;
 import com.jme3.shader.Uniform;
+import com.jme3.shader.UniformBindingManager;
 import com.jme3.shader.VarType;
 import com.jme3.shader.VarType;
+import com.jme3.texture.Image;
 import com.jme3.texture.Texture;
 import com.jme3.texture.Texture;
 import com.jme3.texture.image.ColorSpace;
 import com.jme3.texture.image.ColorSpace;
 import com.jme3.util.ListMap;
 import com.jme3.util.ListMap;
-import com.jme3.util.TempVars;
 import java.io.IOException;
 import java.io.IOException;
 import java.util.*;
 import java.util.*;
 import java.util.logging.Level;
 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 Logger logger = Logger.getLogger(Material.class.getName());
     private static final RenderState additiveLight = new RenderState();
     private static final RenderState additiveLight = new RenderState();
     private static final RenderState depthOnly = new RenderState();
     private static final RenderState depthOnly = new RenderState();
-    private static final Quaternion nullDirLight = new Quaternion(0, -1, 0, -1);
 
 
     static {
     static {
         depthOnly.setDepthTest(true);
         depthOnly.setDepthTest(true);
@@ -175,22 +172,29 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
      * @return The sorting ID used for sorting geometries for rendering.
      * @return The sorting ID used for sorting geometries for rendering.
      */
      */
     public int getSortId() {
     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++) {
             for (int i = 0; i < paramValues.size(); i++) {
                 MatParam param = paramValues.getValue(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;
         return sortingId;
     }
     }
@@ -215,6 +219,8 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
                 mat.paramValues.put(entry.getKey(), entry.getValue().clone());
                 mat.paramValues.put(entry.getKey(), entry.getValue().clone());
             }
             }
 
 
+            mat.sortingId = -1;
+            
             return mat;
             return mat;
         } catch (CloneNotSupportedException ex) {
         } catch (CloneNotSupportedException ex) {
             throw new AssertionError(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)
      * @see #setParam(java.lang.String, com.jme3.shader.VarType, java.lang.Object)
      */
      */
-    public ListMap getParamsMap() {
+    public ListMap<String, MatParam> getParamsMap() {
         return paramValues;
         return paramValues;
     }
     }
 
 
@@ -695,257 +701,6 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
         setParam(name, VarType.Vector4, value);
         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.
      * Select the technique to use for rendering this material.
      * <p>
      * <p>
@@ -974,9 +729,8 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
         Technique tech = techniques.get(name);
         Technique tech = techniques.get(name);
         // When choosing technique, we choose one that
         // When choosing technique, we choose one that
         // supports all the caps.
         // supports all the caps.
-        EnumSet<Caps> rendererCaps = renderManager.getRenderer().getCaps();
         if (tech == null) {
         if (tech == null) {
-
+            EnumSet<Caps> rendererCaps = renderManager.getRenderer().getCaps();
             if (name.equals("Default")) {
             if (name.equals("Default")) {
                 List<TechniqueDef> techDefs = def.getDefaultTechniques();
                 List<TechniqueDef> techDefs = def.getDefaultTechniques();
                 if (techDefs == null || techDefs.isEmpty()) {
                 if (techDefs == null || techDefs.isEmpty()) {
@@ -1025,20 +779,40 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
         }
         }
 
 
         technique = tech;
         technique = tech;
-        tech.makeCurrent(def.getAssetManager(), true, rendererCaps, renderManager);
+        tech.notifyTechniqueSwitched();
 
 
         // shader was changed
         // shader was changed
         sortingId = -1;
         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 {
         } 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.
      * Preloads this material for the given render manager.
      * <p>
      * <p>
@@ -1048,18 +822,21 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
      *
      *
      * @param rm The render manager to preload for
      * @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) {
     private void clearUniformsSetByCurrent(Shader shader) {
@@ -1141,80 +918,43 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
      * </ul>
      * </ul>
      * </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 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);
         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.write(transparent, "is_transparent", false);
         oc.writeStringSavableMap(paramValues, "parameters", null);
         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 {
     public void read(JmeImporter im) throws IOException {
         InputCapsule ic = im.getCapsule(this);
         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;
 package com.jme3.material;
 
 
 import com.jme3.asset.AssetManager;
 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.Caps;
 import com.jme3.renderer.RenderManager;
 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.EnumSet;
-import java.util.List;
-import java.util.logging.Logger;
 
 
 /**
 /**
  * Represents a technique instance.
  * 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
      * Creates a new technique instance that implements the given
@@ -63,14 +62,7 @@ public class Technique /* implements Savable */ {
     public Technique(Material owner, TechniqueDef def) {
     public Technique(Material owner, TechniqueDef def) {
         this.owner = owner;
         this.owner = owner;
         this.def = def;
         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;
         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.
      * 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.
      * Specify <code>null</code> for value if the param is to be cleared.
      */
      */
     void notifyParamChanged(String paramName, VarType type, Object value) {
     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) {
         switch (type) {
-            case TextureBuffer:
-            case Texture2D: // fall intentional
-            case Texture3D:
-            case TextureArray:
-            case TextureCubeMap:
             case Int:
             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;
                 break;
             default:
             default:
-                u.setValue(type, value);
+                dynamicDefines.set(defineId, 1);
                 break;
                 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() {
     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;
 package com.jme3.material;
 
 
+import com.jme3.asset.AssetManager;
 import com.jme3.export.*;
 import com.jme3.export.*;
 import com.jme3.renderer.Caps;
 import com.jme3.renderer.Caps;
 import com.jme3.shader.*;
 import com.jme3.shader.*;
+import com.jme3.shader.Shader.ShaderType;
 
 
 import java.io.IOException;
 import java.io.IOException;
 import java.util.*;
 import java.util.*;
@@ -93,11 +95,17 @@ public class TechniqueDef implements Savable {
 
 
     private EnumSet<Caps> requiredCaps = EnumSet.noneOf(Caps.class);
     private EnumSet<Caps> requiredCaps = EnumSet.noneOf(Caps.class);
     private String name;
     private String name;
-
+    private int sortId;
+    
     private EnumMap<Shader.ShaderType,String> shaderLanguages;
     private EnumMap<Shader.ShaderType,String> shaderLanguages;
     private EnumMap<Shader.ShaderType,String> shaderNames;
     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 boolean usesNodes = false;
     private List<ShaderNode> shaderNodes;
     private List<ShaderNode> shaderNodes;
     private ShaderGenerationInfo shaderGenerationInfo;
     private ShaderGenerationInfo shaderGenerationInfo;
@@ -106,10 +114,10 @@ public class TechniqueDef implements Savable {
     private RenderState renderState;
     private RenderState renderState;
     private RenderState forcedRenderState;
     private RenderState forcedRenderState;
 
 
-    private LightMode lightMode   = LightMode.Disable;
+    private LightMode lightMode = LightMode.Disable;
     private ShadowMode shadowMode = ShadowMode.Disable;
     private ShadowMode shadowMode = ShadowMode.Disable;
+    private TechniqueDefLogic logic;
 
 
-    private HashMap<String, String> defineParams;
     private ArrayList<UniformBinding> worldBinds;
     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>
      * @param name The name of the technique, should be set to <code>null</code>
      * for default techniques.
      * for default techniques.
      */
      */
-    public TechniqueDef(String name){
+    public TechniqueDef(String name, int sortId){
         this();
         this();
+        this.sortId = sortId;
         this.name = name == null ? "Default" : name;
         this.name = name == null ? "Default" : name;
     }
     }
 
 
     /**
     /**
      * Serialization only. Do not use.
      * 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) {
     public void setLightMode(LightMode lightMode) {
         this.lightMode = lightMode;
         this.lightMode = lightMode;
     }
     }
+    
+    public void setLogic(TechniqueDefLogic logic) {
+        this.logic = logic;
+    }
 
 
+    public TechniqueDefLogic getLogic() {
+        return logic;
+    }
+    
     /**
     /**
      * Returns the shadow mode.
      * Returns the shadow mode.
      * @return the shadow mode.
      * @return the shadow mode.
@@ -224,14 +253,6 @@ public class TechniqueDef implements Savable {
         return noRender;
         return noRender;
     }
     }
 
 
-    /**
-     * @deprecated jME3 always requires shaders now
-     */
-    @Deprecated
-    public boolean isUsingShaders(){
-        return true;
-    }
-
     /**
     /**
      * Returns true if this technique uses Shader Nodes, false otherwise.
      * Returns true if this technique uses Shader Nodes, false otherwise.
      *
      *
@@ -273,34 +294,24 @@ public class TechniqueDef implements Savable {
         requiredCaps.add(fragCap);
         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.
      * 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)
      * @see #addShaderParamDefine(java.lang.String, java.lang.String)
      */
      */
     public String getShaderParamDefine(String paramName){
     public String getShaderParamDefine(String paramName){
-        if (defineParams == null) {
+        Integer defineId = paramToDefineId.get(paramName);
+        if (defineId != null) {
+            return defineNames.get(defineId);
+        } else {
             return null;
             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.
      * Adds a define linked to a material parameter.
      * <p>
      * <p>
      * Any time the material parameter on the parent material is altered,
      * Any time the material parameter on the parent material is altered,
      * the appropriate define on the technique will be modified as well.
      * 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 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
      * @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.TessellationControl), "tsctrlLanguage", null);
         oc.write(shaderLanguages.get(Shader.ShaderType.TessellationEvaluation), "tsevalLanguage", 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(lightMode, "lightMode", LightMode.Disable);
         oc.write(shadowMode, "shadowMode", ShadowMode.Disable);
         oc.write(shadowMode, "shadowMode", ShadowMode.Disable);
         oc.write(renderState, "renderState", null);
         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.Geometry,ic.readString("geomName", null));
         shaderNames.put(Shader.ShaderType.TessellationControl,ic.readString("tsctrlName", null));
         shaderNames.put(Shader.ShaderType.TessellationControl,ic.readString("tsctrlName", null));
         shaderNames.put(Shader.ShaderType.TessellationEvaluation,ic.readString("tsevalName", 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);
         lightMode = ic.readEnum("lightMode", LightMode.class, LightMode.Disable);
         shadowMode = ic.readEnum("shadowMode", ShadowMode.class, ShadowMode.Disable);
         shadowMode = ic.readEnum("shadowMode", ShadowMode.class, ShadowMode.Disable);
         renderState = (RenderState) ic.readSavable("renderState", null);
         renderState = (RenderState) ic.readSavable("renderState", null);
@@ -547,9 +653,14 @@ public class TechniqueDef implements Savable {
         this.shaderGenerationInfo = shaderGenerationInfo;
         this.shaderGenerationInfo = shaderGenerationInfo;
     }
     }
 
 
-    //todo: make toString return something usefull
     @Override
     @Override
     public String toString() {
     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.Bucket;
 import com.jme3.renderer.queue.RenderQueue.ShadowMode;
 import com.jme3.renderer.queue.RenderQueue.ShadowMode;
 import com.jme3.scene.*;
 import com.jme3.scene.*;
-import com.jme3.shader.Uniform;
+import com.jme3.shader.Shader;
 import com.jme3.shader.UniformBinding;
 import com.jme3.shader.UniformBinding;
 import com.jme3.shader.UniformBindingManager;
 import com.jme3.shader.UniformBindingManager;
 import com.jme3.system.NullRenderer;
 import com.jme3.system.NullRenderer;
@@ -483,8 +483,8 @@ public class RenderManager {
      * Updates the given list of uniforms with {@link UniformBinding uniform bindings}
      * Updates the given list of uniforms with {@link UniformBinding uniform bindings}
      * based on the current world state.
      * 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);
             gm.getMaterial().preload(this);
             Mesh mesh = gm.getMesh();
             Mesh mesh = gm.getMesh();
-            if (mesh != null) {
+            if (mesh != null
+                    && mesh.getVertexCount() != 0
+                    && mesh.getTriangleCount() != 0) {
                 for (VertexBuffer vb : mesh.getBufferList().getArray()) {
                 for (VertexBuffer vb : mesh.getBufferList().getArray()) {
                     if (vb.getData() != null && vb.getUsage() != VertexBuffer.Usage.CpuOnly) {
                     if (vb.getData() != null && vb.getUsage() != VertexBuffer.Usage.CpuOnly) {
                         renderer.updateBufferData(vb);
                         renderer.updateBufferData(vb);
@@ -768,6 +770,9 @@ public class RenderManager {
     }
     }
 
 
     public void setSinglePassLightBatchSize(int singlePassLightBatchSize) {
     public void setSinglePassLightBatchSize(int singlePassLightBatchSize) {
+        if (singlePassLightBatchSize < 1) {
+            throw new IllegalArgumentException("batch size cannot be less than 1");
+        }
         this.singlePassLightBatchSize = singlePassLightBatchSize;
         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)
         // Print capabilities (if fine logging is enabled)
-        if (logger.isLoggable(Level.FINE)) {
+        if (logger.isLoggable(Level.INFO)) {
             StringBuilder sb = new StringBuilder();
             StringBuilder sb = new StringBuilder();
             sb.append("Supported capabilities: \n");
             sb.append("Supported capabilities: \n");
             for (Caps cap : caps)
             for (Caps cap : caps)
             {
             {
                 sb.append("\t").append(cap.toString()).append("\n");
                 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);
         texUtil.initialize(caps);
@@ -1500,17 +1511,17 @@ public final class GLRenderer implements Renderer {
         }
         }
 
 
         bindFrameBuffer(fb);
         bindFrameBuffer(fb);
-
-        FrameBuffer.RenderBuffer depthBuf = fb.getDepthBuffer();
-        if (depthBuf != null) {
-            updateFrameBufferAttachment(fb, depthBuf);
-        }
-
+        
         for (int i = 0; i < fb.getNumColorBuffers(); i++) {
         for (int i = 0; i < fb.getNumColorBuffers(); i++) {
             FrameBuffer.RenderBuffer colorBuf = fb.getColorBuffer(i);
             FrameBuffer.RenderBuffer colorBuf = fb.getColorBuffer(i);
             updateFrameBufferAttachment(fb, colorBuf);
             updateFrameBufferAttachment(fb, colorBuf);
         }
         }
         
         
+        FrameBuffer.RenderBuffer depthBuf = fb.getDepthBuffer();
+        if (depthBuf != null) {
+            updateFrameBufferAttachment(fb, depthBuf);
+        }
+
         setReadDrawBuffers(fb);
         setReadDrawBuffers(fb);
         checkFrameBufferError();
         checkFrameBufferError();
 
 
@@ -2433,8 +2444,7 @@ public final class GLRenderer implements Renderer {
     }
     }
 
 
     public void drawTriangleArray(Mesh.Mode mode, int count, int vertCount) {
     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,
             glext.glDrawArraysInstancedARB(convertElementMode(mode), 0,
                     vertCount, count);
                     vertCount, count);
         } else {
         } else {
@@ -2478,8 +2488,6 @@ public final class GLRenderer implements Renderer {
         }
         }
 
 
         int vertCount = mesh.getVertexCount();
         int vertCount = mesh.getVertexCount();
-        boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing);
-
         if (mesh.getMode() == Mode.Hybrid) {
         if (mesh.getMode() == Mode.Hybrid) {
             int[] modeStart = mesh.getModeStart();
             int[] modeStart = mesh.getModeStart();
             int[] elementLengths = mesh.getElementLengths();
             int[] elementLengths = mesh.getElementLengths();
@@ -2499,7 +2507,7 @@ public final class GLRenderer implements Renderer {
                 }
                 }
                 int elementLength = elementLengths[i];
                 int elementLength = elementLengths[i];
 
 
-                if (useInstancing) {
+                if (count > 1) {
                     glext.glDrawElementsInstancedARB(elMode,
                     glext.glDrawElementsInstancedARB(elMode,
                             elementLength,
                             elementLength,
                             fmt,
                             fmt,
@@ -2517,7 +2525,7 @@ public final class GLRenderer implements Renderer {
                 curOffset += elementLength * elSize;
                 curOffset += elementLength * elSize;
             }
             }
         } else {
         } else {
-            if (useInstancing) {
+            if (count > 1) {
                 glext.glDrawElementsInstancedARB(convertElementMode(mesh.getMode()),
                 glext.glDrawElementsInstancedARB(convertElementMode(mesh.getMode()),
                         indexBuf.getData().limit(),
                         indexBuf.getData().limit(),
                         convertFormat(indexBuf.getFormat()),
                         convertFormat(indexBuf.getFormat()),
@@ -2677,10 +2685,13 @@ public final class GLRenderer implements Renderer {
     }
     }
 
 
     public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
     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;
             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()) {
         if (context.lineWidth != mesh.getLineWidth()) {
             gl.glLineWidth(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;
         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.
      * 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;
         return spat.queueDistance;
     }
     }
 
 
+    @Override
     public int compare(Geometry o1, Geometry o2) {
     public int compare(Geometry o1, Geometry o2) {
         Material m1 = o1.getMaterial();
         Material m1 = o1.getMaterial();
         Material m2 = o2.getMaterial();
         Material m2 = o2.getMaterial();
-
-        int compareResult = m2.getSortId() - m1.getSortId();
+        
+        int compareResult = Integer.compare(m1.getSortId(), m2.getSortId());
         if (compareResult == 0){
         if (compareResult == 0){
             // use the same shader.
             // use the same shader.
             // sort front-to-back then.
             // 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.
      * A list of all shader sources currently attached.
      */
      */
-    private ArrayList<ShaderSource> shaderSourceList;
+    private final ArrayList<ShaderSource> shaderSourceList;
 
 
     /**
     /**
      * Maps uniform name to the uniform variable.
      * 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.
      * 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.
      * Type of shader. The shader will control the pipeline of it's type.
      */
      */
     public static enum ShaderType {
     public static enum ShaderType {
+
         /**
         /**
          * Control fragment rasterization. (e.g color of pixel).
          * Control fragment rasterization. (e.g color of pixel).
          */
          */
-        Fragment,
-
+        Fragment("frag"),
         /**
         /**
          * Control vertex processing. (e.g transform of model to clip space)
          * 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
      * Creates a new shader, {@link #initialize() } must be called
      * after this constructor for the shader to be usable.
      * after this constructor for the shader to be usable.
      */
      */
     public Shader(){
     public Shader(){
         super();
         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){
         for (ShaderSource source : s.shaderSourceList){
             shaderSourceList.add( (ShaderSource)source.createDestructableClone() );
             shaderSourceList.add( (ShaderSource)source.createDestructableClone() );
         }
         }
+        
+        uniforms = null;
+        boundUniforms = null;
+        attribs = null;
     }
     }
 
 
     /**
     /**
@@ -248,6 +265,18 @@ public final class Shader extends NativeObject {
         setUpdateNeeded();
         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){
     public Uniform getUniform(String name){
         assert name.startsWith("m_") || name.startsWith("g_");
         assert name.startsWith("m_") || name.startsWith("g_");
         Uniform uniform = uniforms.get(name);
         Uniform uniform = uniforms.get(name);
@@ -277,6 +306,10 @@ public final class Shader extends NativeObject {
     public ListMap<String, Uniform> getUniformMap(){
     public ListMap<String, Uniform> getUniformMap(){
         return uniforms;
         return uniforms;
     }
     }
+    
+    public ArrayList<Uniform> getBoundUniforms() {
+        return boundUniforms;
+    }
 
 
     public Collection<ShaderSource> getSources(){
     public Collection<ShaderSource> getSources(){
         return shaderSourceList;
         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;
     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
      * Build a shaderGenerator
@@ -70,8 +70,8 @@ public abstract class ShaderGenerator {
         this.assetManager = assetManager;        
         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
      * @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 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;
         return shader;
     }
     }
 
 
@@ -109,6 +114,14 @@ public abstract class ShaderGenerator {
      * @return the code of the generated vertex shader
      * @return the code of the generated vertex shader
      */
      */
     protected String buildShader(List<ShaderNode> shaderNodes, ShaderGenerationInfo info, ShaderType type) {
     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;
         indent = 0;
 
 
         StringBuilder sourceDeclaration = new StringBuilder();
         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.Camera;
 import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.RenderManager;
 import com.jme3.system.Timer;
 import com.jme3.system.Timer;
-import java.util.List;
+import java.util.ArrayList;
 
 
 /**
 /**
  * <code>UniformBindingManager</code> helps {@link RenderManager} to manage
  * <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}
      * Updates the given list of uniforms with {@link UniformBinding uniform bindings}
      * based on the current world state.
      * 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++) {
         for (int i = 0; i < params.size(); i++) {
             Uniform u = params.get(i);
             Uniform u = params.get(i);
             switch (u.getBinding()) {
             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(){
     public void run(){
         initInThread();
         initInThread();
 
 
-        while (!needClose.get()){
+        do {
             listener.update();
             listener.update();
 
 
-            if (frameRate > 0)
+            if (frameRate > 0) {
                 sync(frameRate);
                 sync(frameRate);
-        }
+            }
+        } while (!needClose.get());
 
 
         deinitInThread();
         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 {
 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();
     private static final Statistics stats = new Statistics();
 
 
     public void initialize() {
     public void initialize() {

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

@@ -115,7 +115,7 @@ MaterialDef Phong Lighting {
         Boolean UseInstancing
         Boolean UseInstancing
     }
     }
 
 
- Technique {
+    Technique {
         LightMode SinglePass
         LightMode SinglePass
         
         
         VertexShader GLSL100:   Common/MatDefs/Light/SPLighting.vert
         VertexShader GLSL100:   Common/MatDefs/Light/SPLighting.vert
@@ -147,7 +147,7 @@ MaterialDef Phong Lighting {
             SEPARATE_TEXCOORD : SeparateTexCoord
             SEPARATE_TEXCOORD : SeparateTexCoord
             DISCARD_ALPHA : AlphaDiscardThreshold
             DISCARD_ALPHA : AlphaDiscardThreshold
             USE_REFLECTION : EnvMap
             USE_REFLECTION : EnvMap
-            SPHERE_MAP : SphereMap  
+            SPHERE_MAP : EnvMapAsSphereMap  
             NUM_BONES : NumberOfBones                        
             NUM_BONES : NumberOfBones                        
             INSTANCING : UseInstancing
             INSTANCING : UseInstancing
         }
         }
@@ -186,7 +186,7 @@ MaterialDef Phong Lighting {
             SEPARATE_TEXCOORD : SeparateTexCoord
             SEPARATE_TEXCOORD : SeparateTexCoord
             DISCARD_ALPHA : AlphaDiscardThreshold
             DISCARD_ALPHA : AlphaDiscardThreshold
             USE_REFLECTION : EnvMap
             USE_REFLECTION : EnvMap
-            SPHERE_MAP : SphereMap  
+            SPHERE_MAP : EnvMapAsSphereMap  
             NUM_BONES : NumberOfBones                        
             NUM_BONES : NumberOfBones                        
             INSTANCING : UseInstancing
             INSTANCING : UseInstancing
         }
         }
@@ -207,7 +207,6 @@ MaterialDef Phong Lighting {
         }
         }
 
 
         Defines {
         Defines {
-            COLOR_MAP : ColorMap
             DISCARD_ALPHA : AlphaDiscardThreshold
             DISCARD_ALPHA : AlphaDiscardThreshold
             NUM_BONES : NumberOfBones
             NUM_BONES : NumberOfBones
             INSTANCING : UseInstancing
             INSTANCING : UseInstancing
@@ -239,8 +238,7 @@ MaterialDef Phong Lighting {
             HARDWARE_SHADOWS : HardwareShadows
             HARDWARE_SHADOWS : HardwareShadows
             FILTER_MODE : FilterMode
             FILTER_MODE : FilterMode
             PCFEDGE : PCFEdge
             PCFEDGE : PCFEdge
-            DISCARD_ALPHA : AlphaDiscardThreshold           
-            COLOR_MAP : ColorMap
+            DISCARD_ALPHA : AlphaDiscardThreshold
             SHADOWMAP_SIZE : ShadowMapSize
             SHADOWMAP_SIZE : ShadowMapSize
             FADE : FadeInfo
             FADE : FadeInfo
             PSSM : Splits
             PSSM : Splits
@@ -271,8 +269,7 @@ MaterialDef Phong Lighting {
             HARDWARE_SHADOWS : HardwareShadows
             HARDWARE_SHADOWS : HardwareShadows
             FILTER_MODE : FilterMode
             FILTER_MODE : FilterMode
             PCFEDGE : PCFEdge
             PCFEDGE : PCFEdge
-            DISCARD_ALPHA : AlphaDiscardThreshold           
-            COLOR_MAP : ColorMap
+            DISCARD_ALPHA : AlphaDiscardThreshold
             SHADOWMAP_SIZE : ShadowMapSize
             SHADOWMAP_SIZE : ShadowMapSize
             FADE : FadeInfo
             FADE : FadeInfo
             PSSM : Splits
             PSSM : Splits
@@ -346,10 +343,6 @@ MaterialDef Phong Lighting {
         Defines {
         Defines {
             VERTEX_COLOR : UseVertexColor
             VERTEX_COLOR : UseVertexColor
             MATERIAL_COLORS : UseMaterialColors
             MATERIAL_COLORS : UseMaterialColors
-            V_TANGENT : VTangent
-            MINNAERT  : Minnaert
-            WARDISO   : WardIso
-
             DIFFUSEMAP : DiffuseMap
             DIFFUSEMAP : DiffuseMap
             NORMALMAP : NormalMap
             NORMALMAP : NormalMap
             SPECULARMAP : SpecularMap
             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.ColorRGBA;
 import com.jme3.math.Vector2f;
 import com.jme3.math.Vector2f;
 import com.jme3.math.Vector3f;
 import com.jme3.math.Vector3f;
+import com.jme3.shader.DefineList;
 import com.jme3.shader.Shader;
 import com.jme3.shader.Shader;
 import com.jme3.shader.VarType;
 import com.jme3.shader.VarType;
 import com.jme3.texture.Texture;
 import com.jme3.texture.Texture;
@@ -73,6 +74,7 @@ public class J3MLoader implements AssetLoader {
     private Material material;
     private Material material;
     private TechniqueDef technique;
     private TechniqueDef technique;
     private RenderState renderState;
     private RenderState renderState;
+    private ArrayList<String> presetDefines = new ArrayList<String>();
 
 
     private EnumMap<Shader.ShaderType, String> shaderLanguage;
     private EnumMap<Shader.ShaderType, String> shaderLanguage;
     private EnumMap<Shader.ShaderType, String> shaderName;
     private EnumMap<Shader.ShaderType, String> shaderName;
@@ -115,6 +117,10 @@ public class J3MLoader implements AssetLoader {
             throw new IOException("LightMode statement syntax incorrect");
             throw new IOException("LightMode statement syntax incorrect");
         }
         }
         LightMode lm = LightMode.valueOf(split[1]);
         LightMode lm = LightMode.valueOf(split[1]);
+        if (lm == LightMode.FixedPipeline) {
+            throw new UnsupportedOperationException("OpenGL1 is not supported");
+        }
+        
         technique.setLightMode(lm);
         technique.setLightMode(lm);
     }
     }
 
 
@@ -479,10 +485,22 @@ public class J3MLoader implements AssetLoader {
     private void readDefine(String statement) throws IOException{
     private void readDefine(String statement) throws IOException{
         String[] split = statement.split(":");
         String[] split = statement.split(":");
         if (split.length == 1){
         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){
         }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{
         }else{
             throw new IOException("Define syntax incorrect");
             throw new IOException("Define syntax incorrect");
         }
         }
@@ -544,15 +562,28 @@ public class J3MLoader implements AssetLoader {
         }
         }
         material.setTransparent(parseBoolean(split[1]));
         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{
     private void readTechnique(Statement techStat) throws IOException{
         isUseNodes = false;
         isUseNodes = false;
         String[] split = techStat.getLine().split(whitespacePattern);
         String[] split = techStat.getLine().split(whitespacePattern);
+        
         if (split.length == 1) {
         if (split.length == 1) {
-            technique = new TechniqueDef(null);
+            String techniqueUniqueName = materialDef.getAssetName() + "@Default";
+            technique = new TechniqueDef(null, techniqueUniqueName.hashCode());
         } else if (split.length == 2) {
         } else if (split.length == 2) {
             String techName = split[1];
             String techName = split[1];
-            technique = new TechniqueDef(techName);
+            String techniqueUniqueName = materialDef.getAssetName() + "@" + techName;
+            technique = new TechniqueDef(techName, techniqueUniqueName.hashCode());
         } else {
         } else {
             throw new IOException("Technique statement syntax incorrect");
             throw new IOException("Technique statement syntax incorrect");
         }
         }
@@ -563,18 +594,40 @@ public class J3MLoader implements AssetLoader {
 
 
         if(isUseNodes){
         if(isUseNodes){
             nodesLoaderDelegate.computeConditions();
             nodesLoaderDelegate.computeConditions();
+            
             //used for caching later, the shader here is not a file.
             //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");
             technique.setShaderFile(technique.hashCode() + "", technique.hashCode() + "", "GLSL100", "GLSL100");
         }
         }
 
 
         if (shaderName.containsKey(Shader.ShaderType.Vertex) && shaderName.containsKey(Shader.ShaderType.Fragment)) {
         if (shaderName.containsKey(Shader.ShaderType.Vertex) && shaderName.containsKey(Shader.ShaderType.Fragment)) {
             technique.setShaderFile(shaderName, shaderLanguage);
             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);
         materialDef.addTechniqueDef(technique);
         technique = null;
         technique = null;
         shaderLanguage.clear();
         shaderLanguage.clear();
         shaderName.clear();
         shaderName.clear();
+        presetDefines.clear();
     }
     }
 
 
     private void loadFromRoot(List<Statement> roots) throws IOException{
     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.ShaderNodeVariable;
 import com.jme3.shader.ShaderUtils;
 import com.jme3.shader.ShaderUtils;
 import com.jme3.shader.UniformBinding;
 import com.jme3.shader.UniformBinding;
+import com.jme3.shader.VarType;
 import com.jme3.shader.VariableMapping;
 import com.jme3.shader.VariableMapping;
 import com.jme3.util.blockparser.Statement;
 import com.jme3.util.blockparser.Statement;
 import java.io.IOException;
 import java.io.IOException;
@@ -583,7 +584,7 @@ public class ShaderNodeLoaderDelegate {
                     //multiplicity is not an int attempting to find for a material parameter.
                     //multiplicity is not an int attempting to find for a material parameter.
                     MatParam mp = findMatParam(multiplicity);
                     MatParam mp = findMatParam(multiplicity);
                     if (mp != null) {
                     if (mp != null) {
-                        addDefine(multiplicity);
+                        addDefine(multiplicity, VarType.Int);
                         multiplicity = multiplicity.toUpperCase();
                         multiplicity = multiplicity.toUpperCase();
                     } else {
                     } else {
                         throw new MatParseException("Wrong multiplicity for variable" + mapping.getLeftVariable().getName() + ". " + multiplicity + " should be an int or a declared material parameter.", statement);
                         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
      * @param paramName
      */
      */
-    public void addDefine(String paramName) {
+    public void addDefine(String paramName, VarType paramType) {
         if (techniqueDef.getShaderParamDefine(paramName) == null) {
         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) {
         for (String string : defines) {
             MatParam param = findMatParam(string);
             MatParam param = findMatParam(string);
             if (param != null) {
             if (param != null) {
-                addDefine(param.getName());
+                addDefine(param.getName(), param.getVarType());
             } else {
             } else {
                 throw new MatParseException("Invalid condition, condition must match a Material Parameter named " + cond, statement);
                 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);
             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.MaterialDef;
 import com.jme3.material.TechniqueDef;
 import com.jme3.material.TechniqueDef;
 import com.jme3.material.plugins.J3MLoader;
 import com.jme3.material.plugins.J3MLoader;
+import com.jme3.renderer.Caps;
 import com.jme3.shader.DefineList;
 import com.jme3.shader.DefineList;
 import com.jme3.shader.Shader;
 import com.jme3.shader.Shader;
-import com.jme3.shader.ShaderKey;
 import com.jme3.shader.plugins.GLSLLoader;
 import com.jme3.shader.plugins.GLSLLoader;
 import com.jme3.system.JmeSystem;
 import com.jme3.system.JmeSystem;
+import java.util.EnumSet;
 import java.util.logging.Level;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.logging.Logger;
 
 
@@ -33,23 +34,22 @@ public class ShaderCheck {
         assetManager.registerLoader(GLSLLoader.class, "vert", "frag","geom","tsctrl","tseval","glsllib");
         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);
         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();
                 StringBuilder sb = new StringBuilder();
                 validator.validate(shader, sb);
                 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());
                 System.out.println(sb.toString());
             }
             }
         }
         }
+        throw new UnsupportedOperationException();
     }
     }
           
           
     public static void main(String[] args){
     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));
+    }
+    
+}