diff --git a/engine/src/core-plugins/com/jme3/material/plugins/ConditionParser.java b/engine/src/core-plugins/com/jme3/material/plugins/ConditionParser.java new file mode 100644 index 000000000..aed88c10c --- /dev/null +++ b/engine/src/core-plugins/com/jme3/material/plugins/ConditionParser.java @@ -0,0 +1,112 @@ +/* + * 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.material.plugins; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * An utility class that allows to parse a define condition in a glsl language + * style. + * + * extractDefines is able to get a list of defines in an expression and update + * the formatter expression with upercased defines + * + * @author Nehon + */ +public class ConditionParser { + + private String formattedExpression = ""; + + public static void main(String argv[]) { + ConditionParser parser = new ConditionParser(); + List defines = parser.extractDefines("(LightMap && SeparateTexCoord) || !ColorMap"); + + for (String string : defines) { + System.err.println(string); + } + System.err.println(parser.formattedExpression); + + defines = parser.extractDefines("#if (defined(LightMap) && defined(SeparateTexCoord)) || !defined(ColorMap)"); + + for (String string : defines) { + System.err.println(string); + } + System.err.println(parser.formattedExpression); + + +// System.err.println(parser.getFormattedExpression()); +// +// parser.parse("ShaderNode.var.xyz"); +// parser.parse("var.xyz"); +// parser.parse("ShaderNode.var"); +// parser.parse("var"); + } + + /** + * parse a condition and returns the list of defines of this condition. + * additionally this methods updates the formattedExpression with uppercased + * defines names + * + * supported expression syntax example: + * "(LightMap && SeparateTexCoord) || !ColorMap" + * "#if (defined(LightMap) && defined(SeparateTexCoord)) || !defined(ColorMap)" + * "#ifdef LightMap" + * "#ifdef (LightMap && SeparateTexCoord) || !ColorMap" + * + * @param expression the expression to parse + * @return the list of defines + */ + public List extractDefines(String expression) { + List defines = new ArrayList(); + expression = expression.replaceAll("#ifdef", "").replaceAll("#if", "").replaceAll("defined", ""); + Pattern pattern = Pattern.compile("(\\w+)"); + formattedExpression = expression; + Matcher m = pattern.matcher(expression); + while (m.find()) { + String match = m.group(); + defines.add(match); + formattedExpression = formattedExpression.replaceAll(match, "defined(" + match.toUpperCase() + ")"); + } + return defines; + } + + /** + * + * @return the formatted expression previously updated by extractDefines + */ + public String getFormattedExpression() { + return formattedExpression; + } +} \ No newline at end of file diff --git a/engine/src/core-plugins/com/jme3/material/plugins/J3MLoader.java b/engine/src/core-plugins/com/jme3/material/plugins/J3MLoader.java index b125b8414..4d5581df9 100644 --- a/engine/src/core-plugins/com/jme3/material/plugins/J3MLoader.java +++ b/engine/src/core-plugins/com/jme3/material/plugins/J3MLoader.java @@ -56,6 +56,9 @@ import java.util.logging.Logger; public class J3MLoader implements AssetLoader { private static final Logger logger = Logger.getLogger(J3MLoader.class.getName()); + // private ErrorLogger errors; + private ShaderNodeLoaderDelegate nodesLoaderDelegate; + boolean isUseNodes = false; private AssetManager assetManager; private AssetKey key; @@ -64,7 +67,7 @@ public class J3MLoader implements AssetLoader { private Material material; private TechniqueDef technique; private RenderState renderState; - + private String vertLanguage; private String fragLanguage; @@ -76,13 +79,6 @@ public class J3MLoader implements AssetLoader { public J3MLoader(){ } - private void throwIfNequal(String expected, String got) throws IOException { - if (expected == null) - throw new IOException("Expected a statement, got '"+got+"'!"); - - if (!expected.equals(got)) - throw new IOException("Expected '"+expected+"', got '"+got+"'!"); - } // : private void readShaderStatement(String statement) throws IOException { @@ -311,8 +307,8 @@ public class J3MLoader implements AssetLoader { return word != null && word.equals("On"); } - private void readRenderStateStatement(String statement) throws IOException{ - String[] split = statement.split(whitespacePattern); + private void readRenderStateStatement(Statement statement) throws IOException{ + String[] split = statement.getLine().split(whitespacePattern); if (split[0].equals("Wireframe")){ renderState.setWireframe(parseBoolean(split[1])); }else if (split[0].equals("FaceCull")){ @@ -334,15 +330,15 @@ public class J3MLoader implements AssetLoader { renderState.setColorWrite(parseBoolean(split[1])); }else if (split[0].equals("PointSprite")){ renderState.setPointSprite(parseBoolean(split[1])); - }else{ - throwIfNequal(null, split[0]); + } else { + throw new MatParseException(null, split[0], statement); } } private void readAdditionalRenderState(List renderStates) throws IOException{ renderState = material.getAdditionalRenderState(); for (Statement statement : renderStates){ - readRenderStateStatement(statement.getLine()); + readRenderStateStatement(statement); } renderState = null; } @@ -350,7 +346,7 @@ public class J3MLoader implements AssetLoader { private void readRenderState(List renderStates) throws IOException{ renderState = new RenderState(); for (Statement statement : renderStates){ - readRenderStateStatement(statement.getLine()); + readRenderStateStatement(statement); } technique.setRenderState(renderState); renderState = null; @@ -359,7 +355,7 @@ public class J3MLoader implements AssetLoader { private void readForcedRenderState(List renderStates) throws IOException{ renderState = new RenderState(); for (Statement statement : renderStates){ - readRenderStateStatement(statement.getLine()); + readRenderStateStatement(statement); } technique.setForcedRenderState(renderState); renderState = null; @@ -384,9 +380,9 @@ public class J3MLoader implements AssetLoader { } } - + private void readTechniqueStatement(Statement statement) throws IOException{ - String[] split = statement.getLine().split("[ \\{]"); + String[] split = statement.getLine().split("[ \\{]"); if (split[0].equals("VertexShader") || split[0].equals("FragmentShader")){ readShaderStatement(statement.getLine()); @@ -400,13 +396,28 @@ public class J3MLoader implements AssetLoader { readRenderState(statement.getContents()); }else if (split[0].equals("ForcedRenderState")){ readForcedRenderState(statement.getContents()); - }else if (split[0].equals("Defines")){ - readDefines(statement.getContents()); - }else{ - throwIfNequal(null, split[0]); + }else if (split[0].equals("Defines")){ + readDefines(statement.getContents()); + } else if (split[0].equals("ShaderNodesDefinitions")) { + initNodesLoader(); + if (isUseNodes) { + nodesLoaderDelegate.readNodesDefinitions(statement.getContents()); + } + } else if (split[0].equals("VertexShaderNodes")) { + initNodesLoader(); + if (isUseNodes) { + nodesLoaderDelegate.readVertexShaderNodes(statement.getContents()); + } + } else if (split[0].equals("FragmentShaderNodes")) { + initNodesLoader(); + if (isUseNodes) { + nodesLoaderDelegate.readFragmentShaderNodes(statement.getContents()); + } + } else { + throw new MatParseException(null, split[0], statement); } } - + private void readTransparentStatement(String statement) throws IOException{ String[] split = statement.split(whitespacePattern); if (split.length != 2){ @@ -448,6 +459,7 @@ public class J3MLoader implements AssetLoader { } private void loadFromRoot(List roots) throws IOException{ + isUseNodes = false; if (roots.size() == 2){ Statement exception = roots.get(0); String line = exception.getLine(); @@ -459,7 +471,7 @@ public class J3MLoader implements AssetLoader { }else if (roots.size() != 1){ throw new IOException("Too many roots in J3M/J3MD file"); } - + boolean extending = false; Statement materialStat = roots.get(0); String materialName = materialStat.getLine(); @@ -476,32 +488,33 @@ public class J3MLoader implements AssetLoader { String[] split = materialName.split(":", 2); if (materialName.equals("")){ - throw new IOException("Material name cannot be empty"); + throw new MatParseException("Material name cannot be empty", materialStat); } if (split.length == 2){ if (!extending){ - throw new IOException("Must use 'Material' when extending."); + throw new MatParseException("Must use 'Material' when extending.", materialStat); } String extendedMat = split[1].trim(); MaterialDef def = (MaterialDef) assetManager.loadAsset(new AssetKey(extendedMat)); - if (def == null) - throw new IOException("Extended material "+extendedMat+" cannot be found."); + if (def == null) { + throw new MatParseException("Extended material " + extendedMat + " cannot be found.", materialStat); + } material = new Material(def); material.setKey(key); // material.setAssetName(fileName); }else if (split.length == 1){ if (extending){ - throw new IOException("Expected ':', got '{'"); + throw new MatParseException("Expected ':', got '{'", materialStat); } materialDef = new MaterialDef(assetManager, materialName); // NOTE: pass file name for defs so they can be loaded later materialDef.setAssetName(key.getName()); }else{ - throw new IOException("Cannot use colon in material name/path"); + throw new MatParseException("Cannot use colon in material name/path", materialStat); } for (Statement statement : materialStat.getContents()){ @@ -521,18 +534,18 @@ public class J3MLoader implements AssetLoader { }else if (statType.equals("MaterialParameters")){ readMaterialParams(statement.getContents()); }else{ - throw new IOException("Expected material statement, got '"+statType+"'"); + throw new MatParseException("Expected material statement, got '"+statType+"'", statement); } } } } - public Object load(AssetInfo info) throws IOException { + public Object load(AssetInfo info) throws IOException { this.assetManager = info.getManager(); - InputStream in = info.openStream(); + InputStream in = info.openStream(); try { - key = info.getKey(); + key = info.getKey(); loadFromRoot(BlockLanguageParser.parse(in)); } finally { if (in != null){ @@ -551,5 +564,24 @@ public class J3MLoader implements AssetLoader { return materialDef; } } + + public MaterialDef loadMaterialDef(List roots, AssetManager manager, AssetKey key) throws IOException { + this.key = key; + this.assetManager = manager; + loadFromRoot(roots); + return materialDef; + } + + protected void initNodesLoader() { + if (!isUseNodes) { + isUseNodes = fragName == null && vertName == null; + if (isUseNodes) { + nodesLoaderDelegate = new ShaderNodeLoaderDelegate(); + nodesLoaderDelegate.setTechniqueDef(technique); + nodesLoaderDelegate.setMaterialDef(materialDef); + nodesLoaderDelegate.setAssetManager(assetManager); + } + } + } } diff --git a/engine/src/core-plugins/com/jme3/material/plugins/MatParseException.java b/engine/src/core-plugins/com/jme3/material/plugins/MatParseException.java new file mode 100644 index 000000000..ac810ae36 --- /dev/null +++ b/engine/src/core-plugins/com/jme3/material/plugins/MatParseException.java @@ -0,0 +1,90 @@ +/* + * 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.material.plugins; + +import com.jme3.util.blockparser.Statement; +import java.io.IOException; + +/** + * Custom Exception to report a j3md Material definition file parsing error. + * This exception reports the line number where the error occured. + * + * @author Nehon + */ +public class MatParseException extends IOException { + + /** + * creates a MatParseException + * + * @param expected the expected value + * @param got the actual value + * @param statement the read statement + */ + public MatParseException(String expected, String got, Statement statement) { + super("Error On line " + statement.getLineNumber() + " : " + statement.getLine() + "\n->Expected " + (expected == null ? "a statement" : expected) + ", got '" + got + "'!"); + + } + + /** + * creates a MatParseException + * + * @param text the error message + * @param statement the statement where the error occur + */ + public MatParseException(String text, Statement statement) { + super("Error On line " + statement.getLineNumber() + " : " + statement.getLine() + "\n->" + text); + } + + /** + * creates a MatParseException + * + * @param expected the expected value + * @param got the actual value + * @param statement the read statement + * @param cause the embed exception that occured + */ + public MatParseException(String expected, String got, Statement statement, Throwable cause) { + super("Error On line " + statement.getLineNumber() + " : " + statement.getLine() + "\n->Expected " + (expected == null ? "a statement" : expected) + ", got '" + got + "'!", cause); + + } + + /** + * creates a MatParseException + * + * @param text the error message + * @param statement the statement where the error occur + * @param cause the embed exception that occured + */ + public MatParseException(String text, Statement statement, Throwable cause) { + super("Error On line " + statement.getLineNumber() + " : " + statement.getLine() + "\n->" + text, cause); + } +} diff --git a/engine/src/core-plugins/com/jme3/material/plugins/ShaderNodeDefinitionLoader.java b/engine/src/core-plugins/com/jme3/material/plugins/ShaderNodeDefinitionLoader.java new file mode 100644 index 000000000..a14937f82 --- /dev/null +++ b/engine/src/core-plugins/com/jme3/material/plugins/ShaderNodeDefinitionLoader.java @@ -0,0 +1,85 @@ +/* + * 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.material.plugins; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetLoadException; +import com.jme3.asset.AssetLoader; +import com.jme3.asset.ShaderNodeDefinitionKey; +import com.jme3.util.blockparser.BlockLanguageParser; +import com.jme3.util.blockparser.Statement; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +/** + * ShaderNodeDefnition file loader (.j3sn) + * + * a j3sn file is a block style file like j3md or j3m. It must contain one + * ShaderNodeDefinition{} block that contains several ShaderNodeDefinition{} + * blocks + * + * @author Nehon + */ +public class ShaderNodeDefinitionLoader implements AssetLoader { + + private ShaderNodeLoaderDelegate loaderDelegate; + + @Override + public Object load(AssetInfo assetInfo) throws IOException { + AssetKey k = assetInfo.getKey(); + if (!(k instanceof ShaderNodeDefinitionKey)) { + throw new IOException("ShaderNodeDefinition file must be loaded via ShaderNodeDefinitionKey"); + } + ShaderNodeDefinitionKey key = (ShaderNodeDefinitionKey) k; + loaderDelegate = new ShaderNodeLoaderDelegate(); + + InputStream in = assetInfo.openStream(); + List roots = BlockLanguageParser.parse(in); + + if (roots.size() == 2) { + Statement exception = roots.get(0); + String line = exception.getLine(); + if (line.startsWith("Exception")) { + throw new AssetLoadException(line.substring("Exception ".length())); + } else { + throw new MatParseException("In multiroot shader node definition, expected first statement to be 'Exception'", exception); + } + } else if (roots.size() != 1) { + throw new MatParseException("Too many roots in J3SN file", roots.get(0)); + } + + return loaderDelegate.readNodesDefinitions(roots.get(0).getContents(), key); + + } +} diff --git a/engine/src/core-plugins/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java b/engine/src/core-plugins/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java new file mode 100644 index 000000000..b60551f92 --- /dev/null +++ b/engine/src/core-plugins/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java @@ -0,0 +1,937 @@ +/* + * 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.material.plugins; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.AssetNotFoundException; +import com.jme3.asset.ShaderNodeDefinitionKey; +import com.jme3.material.MatParam; +import com.jme3.material.MaterialDef; +import com.jme3.material.ShaderGenerationInfo; +import com.jme3.material.TechniqueDef; +import com.jme3.shader.Shader; +import com.jme3.shader.ShaderNode; +import com.jme3.shader.ShaderNodeDefinition; +import com.jme3.shader.ShaderNodeVariable; +import com.jme3.shader.ShaderUtils; +import com.jme3.shader.UniformBinding; +import com.jme3.shader.VariableMapping; +import com.jme3.util.blockparser.Statement; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This class is here to be able to load shaderNodeDefinition from both the + * J3MLoader and ShaderNodeDefinitionLoader. + * + * Also it allows to load the ShaderNodes from a j3md file and build the + * ShaderNodes list of each technique and the ShaderGenerationInfo needed to + * generate the sahders + * + * @author Nehon + */ +public class ShaderNodeLoaderDelegate { + + protected Map nodeDefinitions; + protected Map nodes; + protected ShaderNodeDefinition shaderNodeDefinition; + protected ShaderNode shaderNode; + protected TechniqueDef techniqueDef; + protected Map attributes = new HashMap(); + protected Map vertexDeclaredUniforms = new HashMap(); + protected Map fragmentDeclaredUniforms = new HashMap(); + protected Map varyings = new HashMap(); + protected MaterialDef materialDef; + protected String shaderLanguage; + protected String shaderName; + protected String varNames = ""; + protected AssetManager assetManager; + protected ConditionParser conditionParser = new ConditionParser(); + + + /** + * Read the ShaderNodesDefinitions block and returns a list of + * ShaderNodesDefinition This method is used by the j3sn loader + * + * note that the order of the definitions in the list is not guaranteed. + * + * @param statements the list statements to parse + * @param key the ShaderNodeDefinitionKey + * @return a list of ShaderNodesDefinition + * @throws IOException + */ + public List readNodesDefinitions(List statements, ShaderNodeDefinitionKey key) throws IOException { + + for (Statement statement : statements) { + String[] split = statement.getLine().split("[ \\{]"); + if (statement.getLine().startsWith("ShaderNodeDefinition")) { + String name = statement.getLine().substring("ShaderNodeDefinition".length()).trim(); + + + if (!getNodeDefinitions().containsKey(name)) { + shaderNodeDefinition = new ShaderNodeDefinition(); + getNodeDefinitions().put(name, shaderNodeDefinition); + shaderNodeDefinition.setName(name); + readShaderNodeDefinition(statement.getContents(), key); + + } + } else { + throw new MatParseException("ShaderNodeDefinition", split[0], statement); + } + } + + return new ArrayList(getNodeDefinitions().values()); + } + + /** + * Read the ShaderNodesDefinitions block and internally stores a map of + * ShaderNodesDefinition This method is used by the j3m loader. + * + * When loaded in a material, the definitions are not stored as a list, but + * they are stores in Shadernodes based onthis definition. + * + * The map is here to map the defintion to the nodes, and ovoid reloading + * already loaded definitions + * + * @param statements the list of statements to parse + * @throws IOException + */ + public void readNodesDefinitions(List statements) throws IOException { + readNodesDefinitions(statements, new ShaderNodeDefinitionKey()); + } + + /** + * effectiveliy reads the ShaderNodesDefinitions block + * + * @param statements the list of statements to parse + * @param key the ShaderNodeDefinitionKey + * @throws IOException + */ + protected void readShaderNodeDefinition(List statements, ShaderNodeDefinitionKey key) throws IOException { + boolean isLoadDoc = key instanceof ShaderNodeDefinitionKey && ((ShaderNodeDefinitionKey) key).isLoadDocumentation(); + for (Statement statement : statements) { + String[] split = statement.getLine().split("[ \\{]"); + String line = statement.getLine(); + + if (line.startsWith("Type")) { + String type = line.substring(line.lastIndexOf(':') + 1).trim(); + shaderNodeDefinition.setType(Shader.ShaderType.valueOf(type)); + } else if (line.startsWith("Shader ")) { + readShaderStatement(statement); + shaderNodeDefinition.getShadersLanguage().add(shaderLanguage); + shaderNodeDefinition.getShadersPath().add(shaderName); + } else if (line.startsWith("Documentation")) { + if (isLoadDoc) { + String doc = ""; + for (Statement statement1 : statement.getContents()) { + doc += "\n" + statement1.getLine(); + } + shaderNodeDefinition.setDocumentation(doc); + } + } else if (line.startsWith("Input")) { + varNames = ""; + for (Statement statement1 : statement.getContents()) { + shaderNodeDefinition.getInputs().add(readVariable(statement1)); + } + } else if (line.startsWith("Output")) { + varNames = ""; + for (Statement statement1 : statement.getContents()) { + shaderNodeDefinition.getOutputs().add(readVariable(statement1)); + } + } else { + throw new MatParseException("one of Type, Shader, Documentation, Input, Output", split[0], statement); + } + } + } + + /** + * reads a variable declaration statement + * + * @param statement the statement to parse + * @return a ShaderNodeVariable axtracted from the statement + * @throws IOException + */ + protected ShaderNodeVariable readVariable(Statement statement) throws IOException { + String[] splitVar = statement.getLine().trim().split("\\s"); + if (varNames.contains(splitVar[1] + ";")) { + throw new MatParseException("Duplicate variable name " + splitVar[1], statement); + } + varNames += splitVar[1] + ";"; + return new ShaderNodeVariable(splitVar[0], splitVar[1]); + } + + /** + * reads the VertexShaderNodes{} block + * + * @param statements the list of statements to parse + * @throws IOException + */ + public void readVertexShaderNodes(List statements) throws IOException { + attributes.clear(); + readNodes(statements); + } + + /** + * reads a list of ShaderNode{} blocks + * + * @param statements the list of statements to parse + * @throws IOException + */ + protected void readShaderNode(List statements) throws IOException { + for (Statement statement : statements) { + String line = statement.getLine(); + String[] split = statement.getLine().split("[ \\{]"); + if (line.startsWith("Definition")) { + ShaderNodeDefinition def = findDefinition(statement); + shaderNode.setDefinition(def); + } else if (line.startsWith("Condition")) { + String condition = line.substring(line.lastIndexOf(":") + 1).trim(); + extractCondition(condition, statement); + shaderNode.setCondition(conditionParser.getFormattedExpression()); + } else if (line.startsWith("InputMapping")) { + for (Statement statement1 : statement.getContents()) { + VariableMapping mapping = readInputMapping(statement1); + techniqueDef.getShaderGenerationInfo().getUnusedNodes().remove(mapping.getRightVariable().getNameSpace()); + shaderNode.getInputMapping().add(mapping); + } + } else if (line.startsWith("OutputMapping")) { + for (Statement statement1 : statement.getContents()) { + VariableMapping mapping = readOutputMapping(statement1); + techniqueDef.getShaderGenerationInfo().getUnusedNodes().remove(shaderNode.getName()); + shaderNode.getOutputMapping().add(mapping); + } + } else { + throw new MatParseException("ShaderNodeDefinition", split[0], statement); + } + } + + } + + /** + * reads a mapping statement. Sets the nameSpace, name and swizzling of the + * left variable. Sets the name, nameSpace and swizzling of the right + * variable types will be determined later. + * + * Format : .[.] = + * .[.][:Condition] + * + * @param statement the statement to read + * @return the read mapping + */ + protected VariableMapping parseMapping(Statement statement, boolean[] hasNameSpace) throws IOException { + VariableMapping mapping = new VariableMapping(); + String[] cond = statement.getLine().split(":"); + + String[] vars = cond[0].split("="); + checkMappingFormat(vars, statement); + ShaderNodeVariable[] variables = new ShaderNodeVariable[2]; + String[] swizzle = new String[2]; + for (int i = 0; i < vars.length; i++) { + String[] expression = vars[i].trim().split("\\."); + if (hasNameSpace[i]) { + if (expression.length <= 3) { + variables[i] = new ShaderNodeVariable("", expression[0].trim(), expression[1].trim()); + } + if (expression.length == 3) { + swizzle[i] = expression[2].trim(); + } + } else { + if (expression.length <= 2) { + variables[i] = new ShaderNodeVariable("", expression[0].trim()); + } + if (expression.length == 2) { + swizzle[i] = expression[1].trim(); + } + } + + } + + mapping.setLeftVariable(variables[0]); + mapping.setLeftSwizzling(swizzle[0] != null ? swizzle[0] : ""); + mapping.setRightVariable(variables[1]); + mapping.setRightSwizzling(swizzle[1] != null ? swizzle[1] : ""); + + if (cond.length > 1) { + extractCondition(cond[1], statement); + mapping.setCondition(conditionParser.getFormattedExpression()); + } + + return mapping; + } + + /** + * reads the FragmentShaderNodes{} block + * + * @param statements the list of statements to parse + * @throws IOException + */ + public void readFragmentShaderNodes(List statements) throws IOException { + readNodes(statements); + } + + /** + * Reads a Shader statement of this form : + * + * @param statement + * @throws IOException + */ + protected void readShaderStatement(Statement statement) throws IOException { + String[] split = statement.getLine().split(":"); + if (split.length != 2) { + throw new MatParseException("Shader statement syntax incorrect", statement); + } + String[] typeAndLang = split[0].split("\\p{javaWhitespace}+"); + if (typeAndLang.length != 2) { + throw new MatParseException("Shader statement syntax incorrect", statement); + } + shaderName = split[1].trim(); + shaderLanguage = typeAndLang[1]; + } + + /** + * Sets the technique definition currently being loaded + * + * @param techniqueDef the technique def + */ + public void setTechniqueDef(TechniqueDef techniqueDef) { + this.techniqueDef = techniqueDef; + } + + /** + * sets the material def currently being loaded + * + * @param materialDef + */ + public void setMaterialDef(MaterialDef materialDef) { + this.materialDef = materialDef; + } + + /** + * searcha variable in the given list and updates its type and namespace + * + * @param var the variable to update + * @param list the variables list + * @return true if the variable has been found and updated + */ + protected boolean updateVariableFromList(ShaderNodeVariable var, List list) { + for (ShaderNodeVariable shaderNodeVariable : list) { + if (shaderNodeVariable.getName().equals(var.getName())) { + var.setType(shaderNodeVariable.getType()); + var.setNameSpace(shaderNode.getName()); + return true; + } + } + return false; + } + + /** + * updates the type of the right variable of a mapping from the type of the + * left variable + * + * @param mapping the mapping to consider + */ + protected void updateRightTypeFromLeftType(VariableMapping mapping) { + String type = mapping.getLeftVariable().getType(); + int card = ShaderUtils.getCardinality(type, mapping.getRightSwizzling()); + if (card > 0) { + if (card == 1) { + type = "float"; + } else { + type = "vec" + card; + } + } + mapping.getRightVariable().setType(type); + } + + /** + * check if once a mapping expression is split by "=" the resulting array + * have 2 elements + * + * @param vars the array + * @param statement the statement + * @throws IOException + */ + protected void checkMappingFormat(String[] vars, Statement statement) throws IOException { + if (vars.length != 2) { + throw new MatParseException("Not a valid expression should be '[.] = .[.][:Condition]'", statement); + } + } + + /** + * finds a MatParam in the materialDef from the given name + * + * @param varName the matparam name + * @return the MatParam + */ + protected MatParam findMatParam(String varName) { + for (MatParam matParam : materialDef.getMaterialParams()) { + if (varName.equals(matParam.getName())) { + return matParam; + } + } + return null; + } + + /** + * finds an UniformBinding representing a WorldParam from the techniqueDef + * + * @param varName the name of the WorldParam + * @return the corresponding UniformBinding to the WorldParam + */ + protected UniformBinding findWorldParam(String varName) { + for (UniformBinding worldParam : techniqueDef.getWorldBindings()) { + if (varName.equals(worldParam.toString())) { + return worldParam; + } + } + return null; + } + + /** + * updates the right variable of the given mapping from a UniformBinding (a + * WorldParam) it checks if the unifrom hasn't already been loaded, add it + * to the maps if not. + * + * @param param the WorldParam UniformBinding + * @param mapping the mapping + * @param map the map of uniforms to search into + * @return true if the param was added to the map + */ + protected boolean updateRightFromUniforms(UniformBinding param, VariableMapping mapping, Map map) { + ShaderNodeVariable right = mapping.getRightVariable(); + String name = "g_" + param.toString(); + ShaderNodeVariable var = map.get(name); + if (var == null) { + right.setType(param.getGlslType()); + right.setName(name); + map.put(right.getName(), right); + mapping.setRightVariable(right); + return true; + } + mapping.setRightVariable(var); + return false; + } + + /** + * updates the right variable of the given mapping from a MatParam (a + * WorldParam) it checks if the unifrom hasn't already been loaded, add it + * to the maps if not. + * + * @param param the MatParam + * @param mapping the mapping + * @param map the map of uniforms to search into + * @return true if the param was added to the map + */ + public boolean updateRightFromUniforms(MatParam param, VariableMapping mapping, Map map) { + ShaderNodeVariable right = mapping.getRightVariable(); + ShaderNodeVariable var = map.get(param.getPrefixedName()); + if (var == null) { + right.setType(param.getVarType().getGlslType()); + right.setName(param.getPrefixedName()); + map.put(right.getName(), right); + mapping.setRightVariable(right); + return true; + } + mapping.setRightVariable(var); + return false; + } + + /** + * updates a variable from the Attribute list + * + * @param right the variable + * @param mapping the mapping + */ + public void updateVarFromAttributes(ShaderNodeVariable right, VariableMapping mapping) { + ShaderNodeVariable var = attributes.get(right.getName()); + if (var == null) { + updateRightTypeFromLeftType(mapping); + } else { + + mapping.setRightVariable(var); + } + } + + /** + * Adds a define to the techniquedef + * + * @param paramName + */ + public void addDefine(String paramName) { + if (techniqueDef.getShaderParamDefine(paramName) == null) { + techniqueDef.addShaderParamDefine(paramName, paramName.toUpperCase()); + } + } + + /** + * find a variable with the given name from the list of variable + * + * @param vars a list of shaderNodeVariables + * @param rightVarName the variable name to search for + * @return the found variable or null is not found + */ + public ShaderNodeVariable findNodeOutput(List vars, String rightVarName) { + ShaderNodeVariable var = null; + for (ShaderNodeVariable variable : vars) { + if (variable.getName().equals(rightVarName)) { + var = variable; + } + } + return var; + } + + /** + * extract and check a condition expression + * + * @param cond the condition expression + * @param statement the statement being read + * @throws IOException + */ + public void extractCondition(String cond, Statement statement) throws IOException { + List defines = conditionParser.extractDefines(cond); + for (String string : defines) { + MatParam param = findMatParam(string); + if (param != null) { + addDefine(param.getName()); + } else { + throw new MatParseException("Invalid condition, condition must match a Material Parameter named " + cond, statement); + } + } + } + + /** + * reads an input mapping + * + * @param statement1 the statement being read + * @return the mapping + * @throws IOException + */ + public VariableMapping readInputMapping(Statement statement1) throws IOException { + VariableMapping mapping = null; + try { + mapping = parseMapping(statement1, new boolean[]{false, true}); + } catch (Exception e) { + throw new MatParseException("Unexpected mapping format", statement1, e); + } + ShaderNodeVariable left = mapping.getLeftVariable(); + ShaderNodeVariable right = mapping.getRightVariable(); + if (!updateVariableFromList(left, shaderNode.getDefinition().getInputs())) { + throw new MatParseException(left.getName() + " is not an input variable of " + shaderNode.getDefinition().getName(), statement1); + } + + if (left.getType().startsWith("sampler") && !right.getNameSpace().equals("MatParam")) { + throw new MatParseException("Samplers can only be assigned to MatParams", statement1); + } + + if (right.getNameSpace().equals("Global")) { + right.setType("vec4");//Globals are all vec4 for now (maybe forever...) + updateCondition(right, mapping); + storeGlobal(right, statement1); + + } else if (right.getNameSpace().equals("Attr")) { + if (shaderNode.getDefinition().getType() == Shader.ShaderType.Fragment) { + throw new MatParseException("Cannot have an attribute as input in a fragment shader" + right.getName(), statement1); + } + updateVarFromAttributes(mapping.getRightVariable(), mapping); + updateCondition(mapping.getRightVariable(), mapping); + storeAttribute(mapping.getRightVariable()); + } else if (right.getNameSpace().equals("MatParam")) { + MatParam param = findMatParam(right.getName()); + if (param == null) { + throw new MatParseException("Could not find a Material Parameter named " + right.getName(), statement1); + } + if (shaderNode.getDefinition().getType() == Shader.ShaderType.Vertex) { + if (updateRightFromUniforms(param, mapping, vertexDeclaredUniforms)) { + updateCondition(mapping.getRightVariable(), mapping); + storeVertexUniform(mapping.getRightVariable()); + } + } else { + if (updateRightFromUniforms(param, mapping, fragmentDeclaredUniforms)) { + if (mapping.getRightVariable().getType().contains("|")) { + String type = fixSamplerType(left.getType(), mapping.getRightVariable().getType()); + if (type != null) { + mapping.getRightVariable().setType(type); + } else { + throw new MatParseException(param.getVarType().toString() + " can only be matched to one of " + param.getVarType().getGlslType().replaceAll("\\|", ",") + " found " + left.getType(), statement1); + } + } + updateCondition(mapping.getRightVariable(), mapping); + storeFragmentUniform(mapping.getRightVariable()); + } + } + + } else if (right.getNameSpace().equals("WorldParam")) { + UniformBinding worldParam = findWorldParam(right.getName()); + if (worldParam == null) { + throw new MatParseException("Could not find a World Parameter named " + right.getName(), statement1); + } + if (shaderNode.getDefinition().getType() == Shader.ShaderType.Vertex) { + if (updateRightFromUniforms(worldParam, mapping, vertexDeclaredUniforms)) { + updateCondition(mapping.getRightVariable(), mapping); + storeVertexUniform(mapping.getRightVariable()); + } + } else { + if (updateRightFromUniforms(worldParam, mapping, fragmentDeclaredUniforms)) { + updateCondition(mapping.getRightVariable(), mapping); + storeFragmentUniform(mapping.getRightVariable()); + } + } + + } else { + ShaderNode node = nodes.get(right.getNameSpace()); + if (node == null) { + throw new MatParseException("Undeclared node" + right.getNameSpace() + ". Make sure this node is declared before the current node", statement1); + } + ShaderNodeVariable var = findNodeOutput(node.getDefinition().getOutputs(), right.getName()); + if (var == null) { + throw new MatParseException("Cannot find output variable" + right.getName() + " form ShaderNode " + node.getName(), statement1); + } + right.setNameSpace(node.getName()); + right.setType(var.getType()); + mapping.setRightVariable(right); + updateCondition(mapping.getRightVariable(), mapping); + storeVaryings(node, mapping.getRightVariable()); + + } + + checkTypes(mapping, statement1); + + return mapping; + } + + /** + * reads an output mapping + * + * @param statement1 the staement being read + * @return the mapping + * @throws IOException + */ + public VariableMapping readOutputMapping(Statement statement1) throws IOException { + VariableMapping mapping = null; + try { + mapping = parseMapping(statement1, new boolean[]{true, false}); + } catch (Exception e) { + throw new MatParseException("Unexpected mapping format", statement1, e); + } + ShaderNodeVariable left = mapping.getLeftVariable(); + ShaderNodeVariable right = mapping.getRightVariable(); + + + if (left.getType().startsWith("sampler") || right.getType().startsWith("sampler")) { + throw new MatParseException("Samplers can only be inputs", statement1); + } + + if (left.getNameSpace().equals("Global")) { + left.setType("vec4");//Globals are all vec4 for now (maybe forever...) + updateCondition(left, mapping); + storeGlobal(left, statement1); + } else { + throw new MatParseException("Only Global nameSpace is allowed for outputMapping, got" + left.getNameSpace(), statement1); + } + + if (!updateVariableFromList(right, shaderNode.getDefinition().getOutputs())) { + throw new MatParseException(right.getName() + " is not an output variable of " + shaderNode.getDefinition().getName(), statement1); + } + + checkTypes(mapping, statement1); + + return mapping; + } + + /** + * Reads alist of ShaderNodes + * + * @param statements the list of statements to read + * @throws IOException + */ + public void readNodes(List statements) throws IOException { + if (techniqueDef.getShaderNodes() == null) { + techniqueDef.setShaderNodes(new ArrayList()); + techniqueDef.setShaderGenerationInfo(new ShaderGenerationInfo()); + } + + for (Statement statement : statements) { + String[] split = statement.getLine().split("[ \\{]"); + if (statement.getLine().startsWith("ShaderNode ")) { + String name = statement.getLine().substring("ShaderNode".length()).trim(); + if (nodes == null) { + nodes = new HashMap(); + } + if (!nodes.containsKey(name)) { + shaderNode = new ShaderNode(); + shaderNode.setName(name); + techniqueDef.getShaderGenerationInfo().getUnusedNodes().add(name); + + readShaderNode(statement.getContents()); + nodes.put(name, shaderNode); + techniqueDef.getShaderNodes().add(shaderNode); + } else { + throw new MatParseException("ShaderNode " + name + " is already defined", statement); + } + + } else { + throw new MatParseException("ShaderNode", split[0], statement); + } + } + } + + /** + * retrieve the leftType corresponding sampler type from the rightType + * + * @param leftType the left samplerType + * @param rightType the right sampler type (can be multiple types sparated + * by "|" + * @return the type or null if not found + */ + public String fixSamplerType(String leftType, String rightType) { + String[] types = rightType.split("\\|"); + for (String string : types) { + if (leftType.equals(string)) { + return string; + } + } + return null; + } + + /** + * stores a global output + * + * @param var the variable to store + * @param statement1 the statement being read + * @throws IOException + */ + public void storeGlobal(ShaderNodeVariable var, Statement statement1) throws IOException { + var.setShaderOutput(true); + if (shaderNode.getDefinition().getType() == Shader.ShaderType.Vertex) { + ShaderNodeVariable global = techniqueDef.getShaderGenerationInfo().getVertexGlobal(); + if (global != null) { + global.setCondition(mergeConditions(global.getCondition(), var.getCondition(), "||")); + var.setCondition(global.getCondition()); + if (!global.getName().equals(var.getName())) { + throw new MatParseException("A global output is already defined for the vertex shader: " + global.getName() + ". vertex shader can only have one global output", statement1); + } + } else { + techniqueDef.getShaderGenerationInfo().setVertexGlobal(var); + } + } else if (shaderNode.getDefinition().getType() == Shader.ShaderType.Fragment) { + mergeConditionsAndStoreVariable(var, techniqueDef.getShaderGenerationInfo().getFragmentGlobals()); + } + } + + /** + * store an attribute + * + * @param var the variable ot store + */ + public void storeAttribute(ShaderNodeVariable var) { + mergeConditionsAndStoreVariable(var, techniqueDef.getShaderGenerationInfo().getAttributes()); + } + + /** + * store a vertex uniform + * + * @param var the variable ot store + */ + public void storeVertexUniform(ShaderNodeVariable var) { + mergeConditionsAndStoreVariable(var, techniqueDef.getShaderGenerationInfo().getVertexUniforms()); + + } + + /** + * store a fragment uniform + * + * @param var the variable ot store + */ + public void storeFragmentUniform(ShaderNodeVariable var) { + mergeConditionsAndStoreVariable(var, techniqueDef.getShaderGenerationInfo().getFragmentUniforms()); + + } + + /** + * sets the assetManager + * + * @param assetManager + */ + public void setAssetManager(AssetManager assetManager) { + this.assetManager = assetManager; + } + + /** + * find the definiton from this statement (loads it if necessary) + * + * @param statement the statement being read + * @return the definition + * @throws IOException + */ + public ShaderNodeDefinition findDefinition(Statement statement) throws IOException { + String defLine[] = statement.getLine().split(":"); + String defName = defLine[1].trim(); + + ShaderNodeDefinition def = getNodeDefinitions().get(defName); + if (def == null) { + if (defLine.length == 3) { + List defs = null; + try { + defs = assetManager.loadAsset(new ShaderNodeDefinitionKey(defLine[2].trim())); + } catch (AssetNotFoundException e) { + throw new MatParseException("Couldn't find " + defLine[2].trim(), statement, e); + } + + for (ShaderNodeDefinition definition : defs) { + definition.setPath(defLine[2].trim()); + if (defName.equals(definition.getName())) { + def = definition; + } + if (!(getNodeDefinitions().containsKey(definition.getName()))) { + getNodeDefinitions().put(definition.getName(), definition); + } + } + } + if (def == null) { + throw new MatParseException(defName + " is not a declared as Shader Node Definition", statement); + } + } + return def; + } + + /** + * updates a variable condition form a mapping condition + * + * @param var the variable + * @param mapping the mapping + */ + public void updateCondition(ShaderNodeVariable var, VariableMapping mapping) { + + String condition = mergeConditions(shaderNode.getCondition(), mapping.getCondition(), "&&"); + + if (var.getCondition() == null) { + var.setCondition(condition); + } else { + var.setCondition(mergeConditions(var.getCondition(), condition, "||")); + } + } + + /** + * store a varying + * + * @param node the shaderNode + * @param variable the variable to store + */ + public void storeVaryings(ShaderNode node, ShaderNodeVariable variable) { + variable.setShaderOutput(true); + if (node.getDefinition().getType() == Shader.ShaderType.Vertex && shaderNode.getDefinition().getType() == Shader.ShaderType.Fragment) { + ShaderNodeVariable var = varyings.get(variable.getName()); + if (var == null) { + techniqueDef.getShaderGenerationInfo().getVaryings().add(variable); + varyings.put(variable.getName(), variable); + } else { + var.setCondition(mergeConditions(var.getCondition(), variable.getCondition(), "||")); + variable.setCondition(var.getCondition()); + } + //if a variable is declared with the same name as an input and an output and is a varying, set it as a shader output so it's declared as a varying only once. + for (VariableMapping variableMapping : node.getInputMapping()) { + if (variableMapping.getLeftVariable().getName().equals(variable.getName())) { + variableMapping.getLeftVariable().setShaderOutput(true); + } + } + } + + } + + /** + * merges 2 condition with the given operator + * + * @param condition1 the first condition + * @param condition2 the second condition + * @param operator the operator ("&&" or "||&) + * @return the merged condition + */ + public String mergeConditions(String condition1, String condition2, String operator) { + if (operator.equals("||") && (condition1 == null || condition2 == null)) { + return null; + } + if (condition1 != null) { + if (condition2 == null) { + return condition1; + } else { + String mergedCondition = "(" + condition1 + ") " + operator + " (" + condition2 + ")"; + return mergedCondition; + } + } else { + return condition2; + } + } + + /** + * search a variable in a list from its name and merge the conditions of the + * variables + * + * @param variable the variable + * @param varList the variable list + */ + public void mergeConditionsAndStoreVariable(ShaderNodeVariable variable, List varList) { + for (ShaderNodeVariable var : varList) { + if (var.getName().equals(variable.getName())) { + var.setCondition(mergeConditions(var.getCondition(), variable.getCondition(), "||")); + variable.setCondition(var.getCondition()); + return; + } + } + varList.add(variable); + } + + /** + * check the types of a mapping, left type must match right type tkae the + * swizzle into account + * + * @param mapping the mapping + * @param statement1 the statement being read + * @throws MatParseException + */ + protected void checkTypes(VariableMapping mapping, Statement statement1) throws MatParseException { + if (!ShaderUtils.typesMatch(mapping)) { + String ls = mapping.getLeftSwizzling().length() == 0 ? "" : "." + mapping.getLeftSwizzling(); + String rs = mapping.getRightSwizzling().length() == 0 ? "" : "." + mapping.getRightSwizzling(); + throw new MatParseException("Type mismatch, cannot convert" + mapping.getLeftVariable().getType() + ls + " to " + mapping.getRightVariable().getType() + rs, statement1); + } + } + + private Map getNodeDefinitions() { + if (nodeDefinitions == null) { + nodeDefinitions = new HashMap(); + } + return nodeDefinitions; + } +} diff --git a/engine/src/core/com/jme3/asset/AssetManager.java b/engine/src/core/com/jme3/asset/AssetManager.java index 4965211a2..16fa9e0f5 100644 --- a/engine/src/core/com/jme3/asset/AssetManager.java +++ b/engine/src/core/com/jme3/asset/AssetManager.java @@ -38,12 +38,15 @@ import com.jme3.audio.AudioKey; import com.jme3.font.BitmapFont; import com.jme3.material.Material; import com.jme3.post.FilterPostProcessor; +import com.jme3.renderer.Caps; import com.jme3.scene.Spatial; import com.jme3.scene.plugins.OBJLoader; import com.jme3.shader.Shader; +import com.jme3.shader.ShaderGenerator; import com.jme3.shader.ShaderKey; import com.jme3.texture.Texture; import com.jme3.texture.plugins.TGALoader; +import java.util.EnumSet; import java.util.List; /** @@ -364,4 +367,17 @@ public interface AssetManager { * @see AssetManager#loadAsset(com.jme3.asset.AssetKey) */ public FilterPostProcessor loadFilter(String name); + + /** + * Sets the shaderGenerator to generate shaders based on shaderNodes. + * @param generator the shaderGenerator + */ + public void setShaderGenerator(ShaderGenerator generator); + + /** + * Returns the shaderGenerator responsible for generating the shaders + * @return the shaderGenerator + */ + public ShaderGenerator getShaderGenerator(EnumSet caps); + } diff --git a/engine/src/core/com/jme3/asset/Desktop.cfg b/engine/src/core/com/jme3/asset/Desktop.cfg index 13b8a0634..18c82f5bc 100644 --- a/engine/src/core/com/jme3/asset/Desktop.cfg +++ b/engine/src/core/com/jme3/asset/Desktop.cfg @@ -6,6 +6,7 @@ LOADER com.jme3.audio.plugins.OGGLoader : ogg LOADER com.jme3.cursors.plugins.CursorLoader : ani, cur, ico LOADER com.jme3.material.plugins.J3MLoader : j3m LOADER com.jme3.material.plugins.J3MLoader : j3md +LOADER com.jme3.material.plugins.ShaderNodeDefinitionLoader : j3sn LOADER com.jme3.font.plugins.BitmapFontLoader : fnt LOADER com.jme3.texture.plugins.DDSLoader : dds LOADER com.jme3.texture.plugins.PFMLoader : pfm diff --git a/engine/src/core/com/jme3/asset/DesktopAssetManager.java b/engine/src/core/com/jme3/asset/DesktopAssetManager.java index 790f728f5..f2f3a7852 100644 --- a/engine/src/core/com/jme3/asset/DesktopAssetManager.java +++ b/engine/src/core/com/jme3/asset/DesktopAssetManager.java @@ -38,8 +38,12 @@ import com.jme3.audio.AudioKey; import com.jme3.font.BitmapFont; import com.jme3.material.Material; import com.jme3.post.FilterPostProcessor; +import com.jme3.renderer.Caps; import com.jme3.scene.Spatial; +import com.jme3.shader.Glsl100ShaderGenerator; +import com.jme3.shader.Glsl150ShaderGenerator; import com.jme3.shader.Shader; +import com.jme3.shader.ShaderGenerator; import com.jme3.shader.ShaderKey; import com.jme3.texture.Texture; import java.io.IOException; @@ -48,6 +52,7 @@ import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.EnumSet; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.logging.Level; @@ -62,6 +67,7 @@ import java.util.logging.Logger; public class DesktopAssetManager implements AssetManager { private static final Logger logger = Logger.getLogger(AssetManager.class.getName()); + private ShaderGenerator shaderGenerator; private final ImplHandler handler = new ImplHandler(this); @@ -83,7 +89,7 @@ public class DesktopAssetManager implements AssetManager { public DesktopAssetManager(URL configFile){ if (configFile != null){ loadConfigFile(configFile); - } + } logger.fine("DesktopAssetManager created."); } @@ -407,4 +413,29 @@ public class DesktopAssetManager implements AssetManager { } return shader; } + + /** + * {@inheritDoc} + */ + @Override + public ShaderGenerator getShaderGenerator(EnumSet caps) { + if (shaderGenerator == null) { + if(caps.contains(Caps.GLSL150)){ + shaderGenerator = new Glsl150ShaderGenerator(this); + }else{ + shaderGenerator = new Glsl100ShaderGenerator(this); + } + } + return shaderGenerator; + } + + /** + * {@inheritDoc} + */ + @Override + public void setShaderGenerator(ShaderGenerator shaderGenerator) { + this.shaderGenerator = shaderGenerator; + } + + } diff --git a/engine/src/core/com/jme3/asset/ShaderNodeDefinitionKey.java b/engine/src/core/com/jme3/asset/ShaderNodeDefinitionKey.java new file mode 100644 index 000000000..db25a1217 --- /dev/null +++ b/engine/src/core/com/jme3/asset/ShaderNodeDefinitionKey.java @@ -0,0 +1,89 @@ +/* + * 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.asset; + +import com.jme3.asset.cache.AssetCache; +import com.jme3.shader.ShaderNodeDefinition; +import java.util.List; + +/** + * Used for loading {@link ShaderNodeDefinition shader nodes definition} + * + * Tells if the defintion has to be loaded with or without its documentation + * + * @author Kirill Vainer + */ +public class ShaderNodeDefinitionKey extends AssetKey> { + + private boolean loadDocumentation = false; + + /** + * creates a ShaderNodeDefinitionKey + * + * @param name the name of the asset to load + */ + public ShaderNodeDefinitionKey(String name) { + super(name); + } + + /** + * creates a ShaderNodeDefinitionKey + */ + public ShaderNodeDefinitionKey() { + super(); + } + + @Override + public Class getCacheType() { + return null; + } + + /** + * + * @return true if the asset loaded with this key will contain its + * documentation + */ + public boolean isLoadDocumentation() { + return loadDocumentation; + } + + /** + * sets to true to load the documentation along with the + * ShaderNodeDefinition + * + * @param loadDocumentation true to load the documentation along with the + * ShaderNodeDefinition + */ + public void setLoadDocumentation(boolean loadDocumentation) { + this.loadDocumentation = loadDocumentation; + } +} diff --git a/engine/src/core/com/jme3/material/Material.java b/engine/src/core/com/jme3/material/Material.java index e4edd4b79..cea605934 100644 --- a/engine/src/core/com/jme3/material/Material.java +++ b/engine/src/core/com/jme3/material/Material.java @@ -873,10 +873,10 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { public void selectTechnique(String name, RenderManager renderManager) { // check if already created Technique tech = techniques.get(name); + // When choosing technique, we choose one that + // supports all the caps. + EnumSet rendererCaps = renderManager.getRenderer().getCaps(); if (tech == null) { - // When choosing technique, we choose one that - // supports all the caps. - EnumSet rendererCaps = renderManager.getRenderer().getCaps(); if (name.equals("Default")) { List techDefs = def.getDefaultTechniques(); @@ -923,7 +923,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { } technique = tech; - tech.makeCurrent(def.getAssetManager(), true); + tech.makeCurrent(def.getAssetManager(), true, rendererCaps); // shader was changed sortingId = -1; @@ -933,7 +933,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { if (technique == null) { selectTechnique("Default", rm); } else { - technique.makeCurrent(def.getAssetManager(), false); + technique.makeCurrent(def.getAssetManager(), false, rm.getRenderer().getCaps()); } } diff --git a/engine/src/core/com/jme3/material/ShaderGenerationInfo.java b/engine/src/core/com/jme3/material/ShaderGenerationInfo.java new file mode 100644 index 000000000..2958f2aa0 --- /dev/null +++ b/engine/src/core/com/jme3/material/ShaderGenerationInfo.java @@ -0,0 +1,190 @@ +/* + * 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.material; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.shader.ShaderNodeVariable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * this class is basically a struct that contains the ShaderNodes informations + * in an appropriate way to ease the shader generation process and make it + * faster. + * + * @author Nehon + */ +public class ShaderGenerationInfo implements Savable { + + /** + * the list of attributes of the vertex shader + */ + protected List attributes = new ArrayList(); + /** + * the list of all the uniforms to declare in the vertex shader + */ + protected List vertexUniforms = new ArrayList(); + /** + * the global output of the vertex shader (to assign ot gl_Position) + */ + protected ShaderNodeVariable vertexGlobal = null; + /** + * the list of varyings + */ + protected List varyings = new ArrayList(); + /** + * the list of all the uniforms to declare in the fragment shader + */ + protected List fragmentUniforms = new ArrayList(); + /** + * the list of all the fragment shader global outputs (to assign ot gl_FragColor or gl_Fragdata[n]) + */ + protected List fragmentGlobals = new ArrayList(); + /** + * the unused node names of this shader (node whose output are never used) + */ + protected List unusedNodes = new ArrayList(); + + /** + * + * @return the attributes + */ + public List getAttributes() { + return attributes; + } + + /** + * + * @return the vertex shader uniforms + */ + public List getVertexUniforms() { + return vertexUniforms; + } + + /** + * + * @return the fragment shader uniforms + */ + public List getFragmentUniforms() { + return fragmentUniforms; + } + + /** + * + * @return the vertex shader global ouput + */ + public ShaderNodeVariable getVertexGlobal() { + return vertexGlobal; + } + + /** + * + * @return the fragment shader global outputs + */ + public List getFragmentGlobals() { + return fragmentGlobals; + } + + /** + * + * @return the varyings + */ + public List getVaryings() { + return varyings; + } + + /** + * sets the vertex shader global output + * + * @param vertexGlobal the global output + */ + public void setVertexGlobal(ShaderNodeVariable vertexGlobal) { + this.vertexGlobal = vertexGlobal; + } + + /** + * + * @return the list on unused node names + */ + public List getUnusedNodes() { + return unusedNodes; + } + + /** + * the list of unused node names + * @param unusedNodes + */ + public void setUnusedNodes(List unusedNodes) { + this.unusedNodes = unusedNodes; + } + + /** + * convenient toString method + * + * @return the informations + */ + @Override + public String toString() { + return "ShaderGenerationInfo{" + "attributes=" + attributes + ", vertexUniforms=" + vertexUniforms + ", vertexGlobal=" + vertexGlobal + ", varyings=" + varyings + ", fragmentUniforms=" + fragmentUniforms + ", fragmentGlobals=" + fragmentGlobals + '}'; + } + + + + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.writeSavableArrayList((ArrayList) attributes, "attributes", new ArrayList()); + oc.writeSavableArrayList((ArrayList) vertexUniforms, "vertexUniforms", new ArrayList()); + oc.writeSavableArrayList((ArrayList) varyings, "varyings", new ArrayList()); + oc.writeSavableArrayList((ArrayList) fragmentUniforms, "fragmentUniforms", new ArrayList()); + oc.writeSavableArrayList((ArrayList) fragmentGlobals, "fragmentGlobals", new ArrayList()); + oc.write(vertexGlobal, "vertexGlobal", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + attributes = ic.readSavableArrayList("attributes", new ArrayList()); + vertexUniforms = ic.readSavableArrayList("vertexUniforms", new ArrayList()); + varyings = ic.readSavableArrayList("varyings", new ArrayList()); + fragmentUniforms = ic.readSavableArrayList("fragmentUniforms", new ArrayList()); + fragmentGlobals = ic.readSavableArrayList("fragmentGlobals", new ArrayList()); + vertexGlobal = (ShaderNodeVariable) ic.readSavable("vertexGlobal", null); + + } +} diff --git a/engine/src/core/com/jme3/material/Technique.java b/engine/src/core/com/jme3/material/Technique.java index 556339407..d08e2f64b 100644 --- a/engine/src/core/com/jme3/material/Technique.java +++ b/engine/src/core/com/jme3/material/Technique.java @@ -32,9 +32,11 @@ package com.jme3.material; import com.jme3.asset.AssetManager; +import com.jme3.renderer.Caps; import com.jme3.shader.*; import java.util.ArrayList; import java.util.Collection; +import java.util.EnumSet; import java.util.List; import java.util.logging.Logger; @@ -170,7 +172,7 @@ public class Technique /* implements Savable */ { * * @param assetManager The asset manager to use for loading shaders. */ - public void makeCurrent(AssetManager assetManager, boolean techniqueSwitched) { + public void makeCurrent(AssetManager assetManager, boolean techniqueSwitched, EnumSet rendererCaps) { if (!def.isUsingShaders()) { // No shaders are used, no processing is neccessary. return; @@ -197,23 +199,23 @@ public class Technique /* implements Savable */ { } if (needReload) { - loadShader(assetManager); + loadShader(assetManager,rendererCaps); } } - private void loadShader(AssetManager manager) { - // recompute define list - DefineList allDefines = new DefineList(); - allDefines.addFrom(def.getShaderPresetDefines()); - allDefines.addFrom(defines); - - ShaderKey key = new ShaderKey(def.getVertexShaderName(), - def.getFragmentShaderName(), - allDefines, - def.getVertexShaderLanguage(), - def.getFragmentShaderLanguage()); - shader = manager.loadShader(key); - + private void loadShader(AssetManager manager,EnumSet rendererCaps) { + + if (getDef().isUsingShaderNodes()) { + shader = manager.getShaderGenerator(rendererCaps).generateShader(this); + } else { + ShaderKey key = new ShaderKey(def.getVertexShaderName(), + def.getFragmentShaderName(), + getAllDefines(), + def.getVertexShaderLanguage(), + def.getFragmentShaderLanguage()); + shader = manager.loadShader(key); + + } // register the world bound uniforms worldBindUniforms.clear(); if (def.getWorldBindings() != null) { @@ -224,10 +226,21 @@ public class Technique /* implements Savable */ { worldBindUniforms.add(uniform); } } - } + } needReload = false; } + /** + * Computes the define list + * @return the complete define list + */ + 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); diff --git a/engine/src/core/com/jme3/material/TechniqueDef.java b/engine/src/core/com/jme3/material/TechniqueDef.java index d08034bce..e939c581f 100644 --- a/engine/src/core/com/jme3/material/TechniqueDef.java +++ b/engine/src/core/com/jme3/material/TechniqueDef.java @@ -35,6 +35,7 @@ import com.jme3.export.*; import com.jme3.renderer.Caps; import com.jme3.renderer.Renderer; import com.jme3.shader.DefineList; +import com.jme3.shader.ShaderNode; import com.jme3.shader.UniformBinding; import com.jme3.shader.VarType; import java.io.IOException; @@ -110,6 +111,9 @@ public class TechniqueDef implements Savable { private DefineList presetDefines; private boolean usesShaders; + private boolean usesNodes = false; + private List shaderNodes; + private ShaderGenerationInfo shaderGenerationInfo; private RenderState renderState; private RenderState forcedRenderState; @@ -217,6 +221,16 @@ public class TechniqueDef implements Savable { public boolean isUsingShaders(){ return usesShaders; } + + /** + * Returns true if this technique uses Shader Nodes, false otherwise. + * + * @return true if this technique uses Shader Nodes, false otherwise. + * + */ + public boolean isUsingShaderNodes(){ + return usesNodes; + } /** * Gets the {@link Caps renderer capabilities} that are required @@ -408,6 +422,9 @@ public class TechniqueDef implements Savable { oc.write(shadowMode, "shadowMode", ShadowMode.Disable); oc.write(renderState, "renderState", null); oc.write(usesShaders, "usesShaders", false); + oc.write(usesNodes, "usesNodes", false); + oc.writeSavableArrayList((ArrayList)shaderNodes,"shaderNodes", null); + oc.write(shaderGenerationInfo, "shaderGenerationInfo", null); // TODO: Finish this when Map export is available // oc.write(defineParams, "defineParams", null); @@ -435,6 +452,32 @@ public class TechniqueDef implements Savable { vertLanguage = ic.readString("vertLanguage", null); fragLanguage = ic.readString("fragLanguage", null);; } + + usesNodes = ic.readBoolean("usesNodes", false); + shaderNodes = ic.readSavableArrayList("shaderNodes", null); + shaderGenerationInfo = (ShaderGenerationInfo) ic.readSavable("shaderGenerationInfo", null); } - + + public List getShaderNodes() { + return shaderNodes; + } + + public void setShaderNodes(List shaderNodes) { + this.shaderNodes = shaderNodes; + usesNodes = true; + usesShaders = true; + } + + public ShaderGenerationInfo getShaderGenerationInfo() { + return shaderGenerationInfo; + } + + public void setShaderGenerationInfo(ShaderGenerationInfo shaderGenerationInfo) { + this.shaderGenerationInfo = shaderGenerationInfo; + } + + @Override + public String toString() { + return "TechniqueDef{" + "requiredCaps=" + requiredCaps + ", name=" + name + ", vertName=" + vertName + ", fragName=" + fragName + ", vertLanguage=" + vertLanguage + ", fragLanguage=" + fragLanguage + ", presetDefines=" + presetDefines + ", usesShaders=" + usesShaders + ", usesNodes=" + usesNodes + ", shaderNodes=" + shaderNodes + ", shaderGenerationInfo=" + shaderGenerationInfo + ", renderState=" + renderState + ", forcedRenderState=" + forcedRenderState + ", lightMode=" + lightMode + ", shadowMode=" + shadowMode + ", defineParams=" + defineParams + ", worldBinds=" + worldBinds + '}'; + } } diff --git a/engine/src/core/com/jme3/renderer/queue/GeometryList.java b/engine/src/core/com/jme3/renderer/queue/GeometryList.java index 1f624a6e1..d42d259df 100644 --- a/engine/src/core/com/jme3/renderer/queue/GeometryList.java +++ b/engine/src/core/com/jme3/renderer/queue/GeometryList.java @@ -33,6 +33,7 @@ package com.jme3.renderer.queue; import com.jme3.renderer.Camera; import com.jme3.scene.Geometry; +import com.jme3.util.ListSort; import com.jme3.util.SortUtil; /** @@ -48,9 +49,15 @@ public class GeometryList { private static final int DEFAULT_SIZE = 32; private Geometry[] geometries; - private Geometry[] geometries2; + // private Geometry[] geometries2; + private ListSort listSort; private int size; private GeometryComparator comparator; + + /*static private int count =0; + static private int cpt =0; + static private long time = 0; + */ /** * Initializes the GeometryList to use the given {@link GeometryComparator} @@ -61,8 +68,9 @@ public class GeometryList { public GeometryList(GeometryComparator comparator) { size = 0; geometries = new Geometry[DEFAULT_SIZE]; - geometries2 = new Geometry[DEFAULT_SIZE]; + // geometries2 = new Geometry[DEFAULT_SIZE]; this.comparator = comparator; + listSort = new ListSort(); } /** @@ -115,9 +123,9 @@ public class GeometryList { System.arraycopy(geometries, 0, temp, 0, size); geometries = temp; // original list replaced by double-size list - geometries2 = new Geometry[size * 2]; + // geometries2 = new Geometry[size * 2]; } - geometries[size++] = g; + geometries[size++] = g; } /** @@ -128,7 +136,7 @@ public class GeometryList { geometries[i] = null; } - size = 0; + size = 0; } /** @@ -137,14 +145,34 @@ public class GeometryList { public void sort() { if (size > 1) { // sort the spatial list using the comparator +// count++; +// long t = System.nanoTime(); -// SortUtil.qsort(geometries, 0, size, comparator); -// Arrays.sort(geometries, 0, size, comparator); + if(listSort.getLength() != size){ + listSort.allocateStack(size); + } + listSort.sort(geometries,comparator); - System.arraycopy(geometries, 0, geometries2, 0, size); - SortUtil.msort(geometries2, geometries, 0, size-1, comparator); +// time += System.nanoTime() - t; - + + +// count++; +// long t = System.nanoTime(); +// System.arraycopy(geometries, 0, geometries2, 0, size); +// SortUtil.msort(geometries2, geometries, 0, size-1, comparator); +// time += System.nanoTime() - t; +// +// count++; +// long t = System.nanoTime(); +// Arrays.sort(geometries,0,size, comparator); +// time += System.nanoTime() - t; } + +// if( count>50){ +// count = 0; +// cpt++; +// System.err.println(50*cpt+"\t"+time/1000000); +// } } } \ No newline at end of file diff --git a/engine/src/core/com/jme3/shader/Glsl100ShaderGenerator.java b/engine/src/core/com/jme3/shader/Glsl100ShaderGenerator.java new file mode 100644 index 000000000..5340de86a --- /dev/null +++ b/engine/src/core/com/jme3/shader/Glsl100ShaderGenerator.java @@ -0,0 +1,572 @@ +/* + * 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.AssetManager; +import com.jme3.material.ShaderGenerationInfo; +import com.jme3.material.plugins.ConditionParser; +import com.jme3.shader.Shader.ShaderType; +import java.util.ArrayList; +import java.util.List; + +/** + * This shader Generator can generate Vertex and Fragment shaders from + * shadernodes for GLSL 1.0 + * + * @author Nehon + */ +public class Glsl100ShaderGenerator extends ShaderGenerator { + + /** + * the indentation characters 1à tabulation characters + */ + private final static String INDENTCHAR = "\t\t\t\t\t\t\t\t\t\t"; + + /** + * creates a Glsl100ShaderGenerator + * @param assetManager the assetManager + */ + public Glsl100ShaderGenerator(AssetManager assetManager) { + super(assetManager); + } + + @Override + protected void generateUniforms(StringBuilder source, ShaderGenerationInfo info, ShaderType type) { + generateUniforms(source, type == ShaderType.Vertex ? info.getVertexUniforms() : info.getFragmentUniforms()); + } + + /** + * decalre a list of uniforms + * + * @param source the source to append to + * @param uniforms the list of uniforms + */ + protected void generateUniforms(StringBuilder source, List uniforms) { + source.append("\n"); + for (ShaderNodeVariable var : uniforms) { + declareVariable(source, var, false, "uniform"); + } + } + + /** + * {@inheritDoc} + * + * attributes are all declared, inPositon is decalred even if it's not in + * the list and it's condition is nulled. + */ + @Override + protected void generateAttributes(StringBuilder source, ShaderGenerationInfo info) { + source.append("\n"); + boolean inPosition = false; + for (ShaderNodeVariable var : info.getAttributes()) { + if (var.getName().equals("inPosition")) { + inPosition = true; + var.setCondition(null); + } + declareAttribute(source, var); + + } + if (!inPosition) { + declareAttribute(source, new ShaderNodeVariable("vec4", "inPosition")); + } + + } + + @Override + protected void generateVaryings(StringBuilder source, ShaderGenerationInfo info, ShaderType type) { + source.append("\n"); + for (ShaderNodeVariable var : info.getVaryings()) { + declareVarying(source, var, type == ShaderType.Vertex ? false : true); + } + } + + /** + * {@inheritDoc} + * + * if the declaration contains no code nothing is done, else it's appended + */ + @Override + protected void generateDeclarativeSection(StringBuilder source, ShaderNode shaderNode, String nodeSource, ShaderGenerationInfo info) { + if (nodeSource.replaceAll("\\n", "").trim().length() > 0) { + nodeSource = updateDefinesName(nodeSource, shaderNode); + source.append("\n"); + unIndent(); + startCondition(shaderNode.getCondition(), source); + source.append(nodeSource); + endCondition(shaderNode.getCondition(), source); + indent(); + } + } + + /** + * {@inheritDoc} + * + * Shader outputs are declared and initialized inside the main section + */ + @Override + protected void generateStartOfMainSection(StringBuilder source, ShaderGenerationInfo info, ShaderType type) { + source.append("\n"); + source.append("void main(){\n"); + indent(); + appendIndent(source); + if (type == ShaderType.Vertex) { + declareVariable(source, info.getVertexGlobal(), "inPosition"); + } else if (type == ShaderType.Fragment) { + for (ShaderNodeVariable global : info.getFragmentGlobals()) { + declareVariable(source, global, "vec4(1.0)"); + } + } + source.append("\n"); + } + + /** + * {@inheritDoc} + * + * outputs are assigned to built in glsl output. then the main section is + * closed + * + * This code accounts for multi render target and correctly output to + * gl_FragData if several output are declared for the fragment shader + */ + @Override + protected void generateEndOfMainSection(StringBuilder source, ShaderGenerationInfo info, ShaderType type) { + source.append("\n"); + if (type == ShaderType.Vertex) { + appendOutput(source, "gl_Position", info.getVertexGlobal()); + } else if (type == ShaderType.Fragment) { + List globals = info.getFragmentGlobals(); + if (globals.size() == 1) { + appendOutput(source, "gl_FragColor", globals.get(0)); + } else { + int i = 0; + //Multi Render Target + for (ShaderNodeVariable global : globals) { + appendOutput(source, "gl_FragData[" + i + "]", global); + i++; + } + } + } + unIndent(); + appendIndent(source); + source.append("}\n"); + } + + /** + * Appends an ouput assignment to a shader globalOutputName = + * nameSpace_varName; + * + * @param source the source StringBuilter to append the code. + * @param globalOutputName the name of the global output (can be gl_Position + * or gl_FragColor etc...). + * @param var the variable to assign to the output. + */ + protected void appendOutput(StringBuilder source, String globalOutputName, ShaderNodeVariable var) { + appendIndent(source); + source.append(globalOutputName); + source.append(" = "); + source.append(var.getNameSpace()); + source.append("_"); + source.append(var.getName()); + source.append(";\n"); + } + + /** + * {@inheritDoc} + * + * this methods does things in this order : + * + * 1. declaring and mapping input
+ * variables : variable replaced with MatParams or WorldParams are not + * declared and are replaced by the parma acual name in the code. For others + * variables, the name space is appended with a "_" before the variable name + * in the code to avoid names collision between shaderNodes.
+ * + * 2. declaring output variables :
+ * variables are declared if they were not already + * declared as input (inputs can also be outputs) or if they are not + * declared as varyings. The variable name is also prefixed with the s=name + * space and "_" in the shaderNode code
+ * + * 3. append of the actual ShaderNode code
+ * + * 4. mapping outputs to global output if needed
+ * + *
+ * All of this is embed in a #if coditional statement if needed + */ + @Override + protected void generateNodeMainSection(StringBuilder source, ShaderNode shaderNode, String nodeSource, ShaderGenerationInfo info) { + + nodeSource = updateDefinesName(nodeSource, shaderNode); + source.append("\n"); + comment(source, shaderNode, "Begin"); + startCondition(shaderNode.getCondition(), source); + + List declaredInputs = new ArrayList(); + for (VariableMapping mapping : shaderNode.getInputMapping()) { + + //all variables fed with a matparam or world param are replaced but the matparam itself + //it avoids issue with samplers that have to be uniforms, and it optimize a but the shader code. + if (isWorldOrMaterialParam(mapping.getRightVariable())) { + nodeSource = replace(nodeSource, mapping.getLeftVariable(), mapping.getRightVariable().getName()); + } else { + if (mapping.getLeftVariable().getType().startsWith("sampler")) { + throw new IllegalArgumentException("a Sampler must be a uniform"); + } + map(mapping, source); + String newName = shaderNode.getName() + "_" + mapping.getLeftVariable().getName(); + if (!declaredInputs.contains(newName)) { + nodeSource = replace(nodeSource, mapping.getLeftVariable(), newName); + declaredInputs.add(newName); + } + } + } + + for (ShaderNodeVariable var : shaderNode.getDefinition().getOutputs()) { + ShaderNodeVariable v = new ShaderNodeVariable(var.getType(), shaderNode.getName(), var.getName()); + if (!declaredInputs.contains(shaderNode.getName() + "_" + var.getName())) { + if (!isVarying(info, v)) { + declareVariable(source, v); + } + nodeSource = replaceVariableName(nodeSource, v); + } + } + + source.append(nodeSource); + + for (VariableMapping mapping : shaderNode.getOutputMapping()) { + map(mapping, source); + } + endCondition(shaderNode.getCondition(), source); + comment(source, shaderNode, "End"); + } + + /** + * declares a variable, embed in a conditional block if needed + * @param source the StringBuilder to use + * @param var the variable to declare + * @param appendNameSpace true to append the nameSpace + "_" + */ + protected void declareVariable(StringBuilder source, ShaderNodeVariable var, boolean appendNameSpace) { + declareVariable(source, var, appendNameSpace, null); + } + + /** + * declares a variable, embed in a conditional block if needed. the namespace is appended with "_" + * @param source the StringBuilder to use + * @param var the variable to declare + */ + protected void declareVariable(StringBuilder source, ShaderNodeVariable var) { + declareVariable(source, var, true, null); + } + + /** + * declares a variable, embed in a conditional block if needed. the namespace is appended with "_" + * @param source the StringBuilder to use + * @param var the variable to declare + * @param value the initialization value to assign the the variable + */ + protected void declareVariable(StringBuilder source, ShaderNodeVariable var, String value) { + declareVariable(source, var, value, true, null); + } + + /** + * declares a variable, embed in a conditional block if needed. + * @param source the StringBuilder to use + * @param var the variable to declare + * @param appendNameSpace true to append the nameSpace + "_" + * @param modifier the modifier of the variable (attribute, varying, in , out,...) + */ + protected void declareVariable(StringBuilder source, ShaderNodeVariable var, boolean appendNameSpace, String modifier) { + declareVariable(source, var, null, appendNameSpace, modifier); + } + + /** + * declares a variable, embed in a conditional block if needed. + * @param source the StringBuilder to use + * @param var the variable to declare + * @param value the initialization value to assign the the variable + * @param appendNameSpace true to append the nameSpace + "_" + * @param modifier the modifier of the variable (attribute, varying, in , out,...) + */ + protected void declareVariable(StringBuilder source, ShaderNodeVariable var, String value, boolean appendNameSpace, String modifier) { + startCondition(var.getCondition(), source); + appendIndent(source); + if (modifier != null) { + source.append(modifier); + source.append(" "); + } + + source.append(var.getType()); + source.append(" "); + if (appendNameSpace) { + source.append(var.getNameSpace()); + source.append("_"); + } + source.append(var.getName()); + if (value != null) { + source.append(" = "); + source.append(value); + } + source.append(";\n"); + endCondition(var.getCondition(), source); + } + + /** + * Starts a conditional block + * @param condition the block condition + * @param source the StringBuilder to use + */ + protected void startCondition(String condition, StringBuilder source) { + if (condition != null) { + appendIndent(source); + source.append("#if "); + source.append(condition); + source.append("\n"); + indent(); + } + } + + /** + * Ends a conditional block + * @param condition the block condition + * @param source the StringBuilder to use + */ + protected void endCondition(String condition, StringBuilder source) { + if (condition != null) { + unIndent(); + appendIndent(source); + source.append("#endif\n"); + + } + } + + /** + * Appends a mapping to the source, embed in a conditional block if needed, + * with variables nameSpaces and swizzle. + * @param mapping the VariableMapping to append + * @param source the StringBuilder to use + */ + protected void map(VariableMapping mapping, StringBuilder source) { + startCondition(mapping.getCondition(), source); + appendIndent(source); + if (!mapping.getLeftVariable().isShaderOutput()) { + source.append(mapping.getLeftVariable().getType()); + source.append(" "); + } + source.append(mapping.getLeftVariable().getNameSpace()); + source.append("_"); + source.append(mapping.getLeftVariable().getName()); + if (mapping.getLeftSwizzling().length() > 0) { + source.append("."); + source.append(mapping.getLeftSwizzling()); + } + source.append(" = "); + String namePrefix = getAppendableNameSpace(mapping.getRightVariable()); + source.append(namePrefix); + source.append(mapping.getRightVariable().getName()); + if (mapping.getRightSwizzling().length() > 0) { + source.append("."); + source.append(mapping.getRightSwizzling()); + } + source.append(";\n"); + endCondition(mapping.getCondition(), source); + } + + /** + * replaces a variable name in a shaderNode source code by prefixing it + * with its nameSpace and "_" if needed. + * @param nodeSource the source ot modify + * @param var the variable to replace + * @return the modified source + */ + protected String replaceVariableName(String nodeSource, ShaderNodeVariable var) { + String namePrefix = getAppendableNameSpace(var); + String newName = namePrefix + var.getName(); + nodeSource = replace(nodeSource, var, newName); + return nodeSource; + } + + /** + * Finds if a variable is a varying + * @param info the ShaderGenerationInfo + * @param v the variable + * @return true is the given variable is a varying + */ + protected boolean isVarying(ShaderGenerationInfo info, ShaderNodeVariable v) { + boolean isVarying = false; + for (ShaderNodeVariable shaderNodeVariable : info.getVaryings()) { + if (shaderNodeVariable.equals(v)) { + isVarying = true; + } + } + return isVarying; + } + + /** + * Appends a comment to the generated code + * @param source the StringBuilder to use + * @param shaderNode the shader node being processed (to append its name) + * @param comment the comment to append + */ + protected void comment(StringBuilder source, ShaderNode shaderNode, String comment) { + appendIndent(source); + source.append("//"); + source.append(shaderNode.getName()); + source.append(" : "); + source.append(comment); + source.append("\n"); + } + + /** + * returns the name space to append for a variable. + * Attributes, WorldParam and MatParam names psace must not be appended + * @param var the variable + * @return the namespace to append for this variable + */ + protected String getAppendableNameSpace(ShaderNodeVariable var) { + String namePrefix = var.getNameSpace() + "_"; + if (namePrefix.equals("Attr_") || namePrefix.equals("WorldParam_") || namePrefix.equals("MatParam_")) { + namePrefix = ""; + } + return namePrefix; + } + + /** + * transforms defines name is the shader node code. + * One can use a #if defined(inputVariableName) in a shaderNode code. + * This method is responsible for changing the variable name with the + * appropriate defined based on the mapping condition of this variable. + * Complex condition synthax are handled. + * + * @param nodeSource the sahderNode source code + * @param shaderNode the ShaderNode being processed + * @return the modified shaderNode source. + */ + protected String updateDefinesName(String nodeSource, ShaderNode shaderNode) { + String[] lines = nodeSource.split("\\n"); + ConditionParser parser = new ConditionParser(); + for (String line : lines) { + + if (line.trim().startsWith("#if")) { + List params = parser.extractDefines(line.trim()); + String l = line.trim().replaceAll("defined", "").replaceAll("#if ", "").replaceAll("#ifdef", "");//parser.getFormattedExpression(); + boolean match = false; + for (String param : params) { + for (VariableMapping map : shaderNode.getInputMapping()) { + if ((map.getLeftVariable().getName()).equals(param)) { + if (map.getCondition() != null) { + l = l.replaceAll(param, map.getCondition()); + match = true; + } + } + } + } + if (match) { + nodeSource = nodeSource.replace(line.trim(), "#if " + l); + } + } + } + return nodeSource; + } + + /** + * replaced a variable name in a source code with the given name + * @param nodeSource the source to use + * @param var the variable + * @param newName the new name of the variable + * @return the modified source code + */ + protected String replace(String nodeSource, ShaderNodeVariable var, String newName) { + nodeSource = nodeSource.replaceAll("(\\W)" + var.getName() + "(\\W)", "$1" + newName + "$2"); + return nodeSource; + } + + /** + * Finds if a variable is a world or a material parameter + * @param var the variable + * @return true if the variable is a Word or material parameter + */ + protected boolean isWorldOrMaterialParam(ShaderNodeVariable var) { + return var.getNameSpace().equals("MatParam") || var.getNameSpace().equals("WorldParam"); + } + + @Override + protected String getLanguageAndVersion(ShaderType type) { + return "GLSL100"; + } + + /** + * appends indentation. + * @param source + */ + protected void appendIndent(StringBuilder source) { + source.append(INDENTCHAR.substring(0, indent)); + } + + /** + * Declares an attribute + * @param source the StringBuilder to use + * @param var the variable to declare as an attribute + */ + protected void declareAttribute(StringBuilder source, ShaderNodeVariable var) { + declareVariable(source, var, false, "attribute"); + } + + /** + * Declares a varying + * @param source the StringBuilder to use + * @param var the variable to declare as an varying + * @param input a boolean set to true if the this varying is an input. + * this in not used in this implementaiton but can be used in overidings + * implementation + */ + protected void declareVarying(StringBuilder source, ShaderNodeVariable var, boolean input) { + declareVariable(source, var, true, "varying"); + } + + /** + * Decrease indentation with a check so the indent is never negative. + */ + protected void unIndent() { + indent--; + indent = Math.max(0, indent); + } + + /** + * increase indentation with a check so that indentation is never over 10 + */ + protected void indent() { + indent++; + indent = Math.min(10, indent); + } +} diff --git a/engine/src/core/com/jme3/shader/Glsl150ShaderGenerator.java b/engine/src/core/com/jme3/shader/Glsl150ShaderGenerator.java new file mode 100644 index 000000000..8930d6281 --- /dev/null +++ b/engine/src/core/com/jme3/shader/Glsl150ShaderGenerator.java @@ -0,0 +1,146 @@ +/* + * 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.AssetManager; +import com.jme3.material.ShaderGenerationInfo; +import com.jme3.shader.Shader.ShaderType; + +/** + * This shader Generator can generate Vertex and Fragment shaders from + * ShaderNodes for GLSL 1.5 + * + * @author Nehon + */ +public class Glsl150ShaderGenerator extends Glsl100ShaderGenerator { + + /** + * Creates a Glsl150ShaderGenerator + * + * @param assetManager the assetmanager + */ + public Glsl150ShaderGenerator(AssetManager assetManager) { + super(assetManager); + } + + @Override + protected String getLanguageAndVersion(ShaderType type) { + return "GLSL150"; + } + + /** + * {@inheritDoc} in glsl 1.5 attributes are prefixed with the "in" keyword + * and not the "attribute" keyword + */ + @Override + protected void declareAttribute(StringBuilder source, ShaderNodeVariable var) { + declareVariable(source, var, false, "in"); + } + + /** + * {@inheritDoc} in glsl 1.5 varying are prefixed with the "in" or "out" + * keyword and not the "varying" keyword. + * + * "in" is used for Fragment shader (maybe Geometry shader later) "out" is + * used for Vertex shader (maybe Geometry shader later) + */ + @Override + protected void declareVarying(StringBuilder source, ShaderNodeVariable var, boolean input) { + declareVariable(source, var, true, input ? "in" : "out"); + } + + /** + * {@inheritDoc} + * + * Fragment shader outputs are declared before the "void main(){" with the + * "out" keyword. + * + * after the "void main(){", the vertex output are declared and initialized + * and the frgament outputs are declared + */ + @Override + protected void generateStartOfMainSection(StringBuilder source, ShaderGenerationInfo info, Shader.ShaderType type) { + source.append("\n"); + + if (type == Shader.ShaderType.Fragment) { + for (ShaderNodeVariable global : info.getFragmentGlobals()) { + declareVariable(source, global, null, true, "out"); + } + } + source.append("\n"); + + appendIndent(source); + source.append("void main(){\n"); + indent(); + + if (type == Shader.ShaderType.Vertex) { + declareVariable(source, info.getVertexGlobal(), "inPosition"); + } else if (type == Shader.ShaderType.Fragment) { + for (ShaderNodeVariable global : info.getFragmentGlobals()) { + initVariable(source, global, "vec4(1.0)"); + } + } + } + + /** + * {@inheritDoc} + * + * only vertex shader output are mapped here, since fragment shader outputs + * must have been mapped in the main section. + */ + @Override + protected void generateEndOfMainSection(StringBuilder source, ShaderGenerationInfo info, Shader.ShaderType type) { + if (type == Shader.ShaderType.Vertex) { + appendOutput(source, "gl_Position", info.getVertexGlobal()); + } + unIndent(); + appendIndent(source); + source.append("}\n"); + } + + /** + * Append a variable initialization to the code + * + * @param source the StringBuilder to use + * @param var the variable to initialize + * @param initValue the init value to assign to the variable + */ + protected void initVariable(StringBuilder source, ShaderNodeVariable var, String initValue) { + appendIndent(source); + source.append(var.getNameSpace()); + source.append("_"); + source.append(var.getName()); + source.append(" = "); + source.append(initValue); + source.append(";\n"); + } +} diff --git a/engine/src/core/com/jme3/shader/ShaderGenerator.java b/engine/src/core/com/jme3/shader/ShaderGenerator.java new file mode 100644 index 000000000..ce509dba1 --- /dev/null +++ b/engine/src/core/com/jme3/shader/ShaderGenerator.java @@ -0,0 +1,290 @@ +/* + * 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.asset.AssetManager; +import com.jme3.material.ShaderGenerationInfo; +import com.jme3.material.Technique; +import com.jme3.material.TechniqueDef; +import com.jme3.shader.Shader.ShaderType; +import java.util.List; + +/** + * This class is the base for a shader generator using the ShaderNodes system, + * it contains basis mechnaism of generation, but no actual generation code. + * This class is abstract, any Shader generator must extend it. + * + * @author Nehon + */ +public abstract class ShaderGenerator { + + //the asset manager + protected AssetManager assetManager; + //indentation value for generation + protected int indent; + + /** + * Build a shaderGenerator + * + * @param assetManager + */ + protected ShaderGenerator(AssetManager assetManager) { + this.assetManager = assetManager; + } + + /** + * Generate vertex and fragment shaders for the given technique + * + * @param technique the technique to use to generate the shaders + * @return a Shader program + */ + public Shader generateShader(Technique technique) { + + 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); + + 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)); + + return shader; + } + + /** + * This method is responsible for the shader generation. + * + * @param shaderNodes the list of shader nodes + * @param info the ShaderGenerationInfo filled during the Technique loading + * @param type the type of shader to generate + * @return the code of the generated vertex shader + */ + protected String buildShader(List shaderNodes, ShaderGenerationInfo info, ShaderType type) { + indent = 0; + + StringBuilder sourceDeclaration = new StringBuilder(); + StringBuilder source = new StringBuilder(); + + generateUniforms(sourceDeclaration, info, type); + + if (type == ShaderType.Vertex) { + generateAttributes(sourceDeclaration, info); + } + generateVaryings(sourceDeclaration, info, type); + + generateStartOfMainSection(source, info, type); + + generateDeclarationAndMainBody(shaderNodes, sourceDeclaration, source, info, type); + + generateEndOfMainSection(source, info, type); + + sourceDeclaration.append(source); + + return sourceDeclaration.toString(); + } + + /** + * iterates through shader nodes to load them and generate the shader + * declaration part and main body extracted from the shader nodes, for the + * given shader type + * + * @param shaderNodes the list of shader nodes + * @param sourceDeclaration the declaration part StringBuilder of the shader + * to generate + * @param source the main part StringBuilder of the shader to generate + * @param info the ShaderGenerationInfo + * @param type the Shader type + */ + protected void generateDeclarationAndMainBody(List shaderNodes, StringBuilder sourceDeclaration, StringBuilder source, ShaderGenerationInfo info, Shader.ShaderType type) { + for (ShaderNode shaderNode : shaderNodes) { + if (info.getUnusedNodes().contains(shaderNode.getName())) { + continue; + } + if (shaderNode.getDefinition().getType() == type) { + int index = findShaderIndexFromVersion(shaderNode, type); + String loadedSource = (String) assetManager.loadAsset(new AssetKey(shaderNode.getDefinition().getShadersPath().get(index))); + appendNodeDeclarationAndMain(loadedSource, sourceDeclaration, source, shaderNode, info); + } + } + } + + /** + * Appends declaration and main part of a node to the shader declaration and + * main part. the loadedSource is split by "void main(){" to split + * declaration from main part of the node source code.The trailing "}" is + * removed from the main part. Each part is then respectively passed to + * generateDeclarativeSection and generateNodeMainSection. + * + * @see ShaderGenerator#generateDeclarativeSection + * @see ShaderGenerator#generateNodeMainSection + * + * @param loadedSource the actual source code loaded for this node. + * @param sourceDeclaration the Shader declaration part string builder. + * @param source the Shader main part StringBuilder. + * @param shaderNode the shader node. + * @param info the ShaderGenerationInfo. + */ + protected void appendNodeDeclarationAndMain(String loadedSource, StringBuilder sourceDeclaration, StringBuilder source, ShaderNode shaderNode, ShaderGenerationInfo info) { + if (loadedSource.length() > 1) { + loadedSource = loadedSource.substring(0, loadedSource.lastIndexOf("}")); + String[] sourceParts = loadedSource.split("void main\\(\\)\\{"); + generateDeclarativeSection(sourceDeclaration, shaderNode, sourceParts[0], info); + generateNodeMainSection(source, shaderNode, sourceParts[1], info); + } else { + //if source is empty, we still call generateNodeMainSection so that mappings can be done. + generateNodeMainSection(source, shaderNode, loadedSource, info); + } + + } + + /** + * returns the laguage + version of the shader should be somthing like + * "GLSL100" for glsl 1.0 "GLSL150" for glsl 1.5. + * + * @param type the shader type for wich the version should be returned. + * + * @return the shaderLanguage and version. + */ + protected abstract String getLanguageAndVersion(Shader.ShaderType type); + + /** + * generates the uniforms declaration for a shader of the given type. + * + * @param source the source StringBuilder to append generated code. + * @param info the ShaderGenerationInfo. + * @param type the shader type the uniforms have to be generated for. + */ + protected abstract void generateUniforms(StringBuilder source, ShaderGenerationInfo info, ShaderType type); + + /** + * generates the attributes declaration for the vertex shader. There is no + * Shader type passed here as attributes are only used in vertex shaders + * + * @param source the source StringBuilder to append generated code. + * @param info the ShaderGenerationInfo. + */ + protected abstract void generateAttributes(StringBuilder source, ShaderGenerationInfo info); + + /** + * generates the varyings for the given shader type shader. Note that + * varyings are deprecated in glsl 1.3, but this method will still be called + * to generate all non global inputs and output of the shaders. + * + * @param source the source StringBuilder to append generated code. + * @param info the ShaderGenerationInfo. + * @param type the shader type the varyings have to be generated for. + */ + protected abstract void generateVaryings(StringBuilder source, ShaderGenerationInfo info, ShaderType type); + + /** + * Appends the given shaderNode declarative part to the shader declarative + * part. If needed the sahder type can be determined by fetching the + * shaderNode's definition type. + * + * @see ShaderNode#getDefinition() + * @see ShaderNodeDefinition#getType() + * + * @param source the StringBuilder to append generated code. + * @param shaderNode the shaderNode. + * @param nodeSource the declaration part of the loaded shaderNode source. + * @param info the ShaderGenerationInfo. + */ + protected abstract void generateDeclarativeSection(StringBuilder source, ShaderNode shaderNode, String nodeDecalarationSource, ShaderGenerationInfo info); + + /** + * generates the start of the shader main section. this method is + * responsible of appending the "void main(){" in the shader and declaring + * all global outputs of the shader + * + * @param source the StringBuilder to append generated code. + * @param info the ShaderGenerationInfo. + * @param type the shader type the section has to be generated for. + */ + protected abstract void generateStartOfMainSection(StringBuilder source, ShaderGenerationInfo info, ShaderType type); + + /** + * generates the end of the shader main section. this method is responsible + * of appending the last "}" in the shader and mapping all global outputs of + * the shader + * + * @param source the StringBuilder to append generated code. + * @param info the ShaderGenerationInfo. + * @param type the shader type the section has to be generated for. + */ + protected abstract void generateEndOfMainSection(StringBuilder source, ShaderGenerationInfo info, ShaderType type); + + /** + * Appends the given shaderNode main part to the shader declarative part. If + * needed the sahder type can be determined by fetching the shaderNode's + * definition type. + * + * @see ShaderNode#getDefinition() + * @see ShaderNodeDefinition#getType() + * + * @param source the StringBuilder to append generated code. + * @param shaderNode the shaderNode. + * @param nodeSource the declaration part of the loaded shaderNode source. + * @param info the ShaderGenerationInfo. + */ + protected abstract void generateNodeMainSection(StringBuilder source, ShaderNode shaderNode, String nodeSource, ShaderGenerationInfo info); + + /** + * returns the shaderpath index according to the version of the generator. + * This allow to select the higher version of the shader that the generator + * can handle + * + * @param shaderNode the shaderNode being processed + * @param type the shaderType + * @return the index of the shader path in ShaderNodeDefinition shadersPath + * list + * @throws NumberFormatException + */ + protected int findShaderIndexFromVersion(ShaderNode shaderNode, ShaderType type) throws NumberFormatException { + int index = 0; + List lang = shaderNode.getDefinition().getShadersLanguage(); + int genVersion = Integer.parseInt(getLanguageAndVersion(type).substring(4)); + int curVersion = 0; + for (int i = 0; i < lang.size(); i++) { + int version = Integer.parseInt(lang.get(i).substring(4)); + if (version > curVersion && version <= genVersion) { + curVersion = version; + index = i; + } + } + return index; + } +} diff --git a/engine/src/core/com/jme3/shader/ShaderNode.java b/engine/src/core/com/jme3/shader/ShaderNode.java new file mode 100644 index 000000000..41346104b --- /dev/null +++ b/engine/src/core/com/jme3/shader/ShaderNode.java @@ -0,0 +1,215 @@ +/* + * 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.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * A ShaderNode is the unit brick part of a shader program. A shader can be + * describe with several shader nodes that are plugged together through inputs + * and outputs. + * + * A ShaderNode is based on a definition that has a shader code, inputs and + * output variables. This node can be activated based on a condition, and has + * input and ouput mapping. + * + * This class is not intended to be used by JME users directly. It's the + * stucture for loading shader nodes from a J3md ùaterial definition file + * + * @author Nehon + */ +public class ShaderNode implements Savable { + + private String name; + private ShaderNodeDefinition definition; + private String condition; + private List inputMapping = new ArrayList(); + private List outputMapping = new ArrayList(); + + /** + * creates a ShaderNode + * + * @param name the name + * @param definition the ShaderNodeDefinition + * @param condition the conditionto activate this node + */ + public ShaderNode(String name, ShaderNodeDefinition definition, String condition) { + this.name = name; + this.definition = definition; + this.condition = condition; + } + + /** + * creates a ShaderNode + */ + public ShaderNode() { + } + + /** + * + * @return the name of the node + */ + public String getName() { + return name; + } + + /** + * sets the name of th node + * + * @param name the name + */ + public void setName(String name) { + this.name = name; + } + + /** + * returns the definition + * + * @return the ShaderNodeDefinition + */ + public ShaderNodeDefinition getDefinition() { + return definition; + } + + /** + * sets the definition + * + * @param definition the ShaderNodeDefinition + */ + public void setDefinition(ShaderNodeDefinition definition) { + this.definition = definition; + } + + /** + * + * @return the condition + */ + public String getCondition() { + return condition; + } + + /** + * sets the ocndition + * + * @param condition the condition + */ + public void setCondition(String condition) { + this.condition = condition; + } + + /** + * return a list of VariableMapping representing the input mappings of this + * node + * + * @return the input mappings + */ + public List getInputMapping() { + return inputMapping; + } + + /** + * sets the input mappings + * + * @param inputMapping the input mappings + */ + public void setInputMapping(List inputMapping) { + this.inputMapping = inputMapping; + } + + /** + * return a list of VariableMapping representing the output mappings of this + * node + * + * @return the output mappings + */ + public List getOutputMapping() { + return outputMapping; + } + + /** + * sets the output mappings + * + * @param inputMapping the output mappings + */ + public void setOutputMapping(List outputMapping) { + this.outputMapping = outputMapping; + } + + /** + * jme seralization + * + * @param ex the exporter + * @throws IOException + */ + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = (OutputCapsule) ex.getCapsule(this); + oc.write(name, "name", ""); + oc.write(definition, "definition", null); + oc.write(condition, "condition", null); + oc.writeSavableArrayList((ArrayList) inputMapping, "inputMapping", new ArrayList()); + oc.writeSavableArrayList((ArrayList) outputMapping, "outputMapping", new ArrayList()); + } + + /** + * jme seralization + * + * @param im the importer + * @throws IOException + */ + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule ic = (InputCapsule) im.getCapsule(this); + name = ic.readString("name", ""); + definition = (ShaderNodeDefinition) ic.readSavable("definition", null); + condition = ic.readString("condition", null); + inputMapping = (List) ic.readSavableArrayList("inputMapping", new ArrayList()); + outputMapping = (List) ic.readSavableArrayList("outputMapping", new ArrayList()); + } + + /** + * convenience tostring + * + * @return a string + */ + @Override + public String toString() { + return "\nShaderNode{" + "\nname=" + name + ", \ndefinition=" + definition.getName() + ", \ncondition=" + condition + ", \ninputMapping=" + inputMapping + ", \noutputMapping=" + outputMapping + '}'; + } +} diff --git a/engine/src/core/com/jme3/shader/ShaderNodeDefinition.java b/engine/src/core/com/jme3/shader/ShaderNodeDefinition.java new file mode 100644 index 000000000..46b5eeb71 --- /dev/null +++ b/engine/src/core/com/jme3/shader/ShaderNodeDefinition.java @@ -0,0 +1,253 @@ +/* + * 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.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.shader.Shader.ShaderType; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Shader node definition structure meant for holding loaded datat from a + * material definition j3md file + * + * @author Nehon + */ +public class ShaderNodeDefinition implements Savable { + + private String name; + private Shader.ShaderType type; + private List shadersLanguage = new ArrayList(); + private List shadersPath = new ArrayList(); + private String documentation; + private List inputs = new ArrayList(); + private List outputs = new ArrayList(); + private String path = null; + + /** + * creates a ShaderNodeDefinition + * + * @param name the name of the definition + * @param type the type of the shader + * @param shaderPath the path of the shader + * @param shaderLanguage the shader language (minimum required for this + * definition) + */ + public ShaderNodeDefinition(String name, ShaderType type, String shaderPath, String shaderLanguage) { + this.name = name; + this.type = type; + shadersLanguage.add(shaderLanguage); + shadersPath.add(shaderPath); + } + + /** + * creates a ShaderNodeDefinition + */ + public ShaderNodeDefinition() { + } + + /** + * returns the name of the definition + * + * @return the name + */ + public String getName() { + return name; + } + + /** + * sets the name of the definition + * + * @param name the name + */ + public void setName(String name) { + this.name = name; + } + + /** + * + * @return the type of shader the definition applies to + */ + public ShaderType getType() { + return type; + } + + /** + * sets the type of shader this def applies to + * + * @param type the type + */ + public void setType(ShaderType type) { + this.type = type; + } + + /** + * + * @return the docuentation for tthis definition + */ + public String getDocumentation() { + return documentation; + } + + /** + * sets the dcumentation + * + * @param documentation the documentation + */ + public void setDocumentation(String documentation) { + this.documentation = documentation; + } + + /** + * + * @return the input variables of this definition + */ + public List getInputs() { + return inputs; + } + + /** + * sets the input variables of this definition + * + * @param inputs the inputs + */ + public void setInputs(List inputs) { + this.inputs = inputs; + } + + /** + * + * @return the output variables of this definition + */ + public List getOutputs() { + return outputs; + } + + /** + * sets the output variables of this definition + * + * @param inputs the output + */ + public void setOutputs(List outputs) { + this.outputs = outputs; + } + + /** + * retrun the path of this definition + * @return + */ + public String getPath() { + return path; + } + + /** + * sets the path of this definition + * @param path + */ + public void setPath(String path) { + this.path = path; + } + + + + /** + * jme seralization (not used) + * + * @param ex the exporter + * @throws IOException + */ + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = (OutputCapsule) ex.getCapsule(this); + oc.write(name, "name", ""); + String[] str = new String[shadersLanguage.size()]; + oc.write(shadersLanguage.toArray(str), "shadersLanguage", null); + oc.write(shadersPath.toArray(str), "shadersPath", null); + oc.write(type, "type", null); + oc.writeSavableArrayList((ArrayList) inputs, "inputs", new ArrayList()); + oc.writeSavableArrayList((ArrayList) outputs, "inputs", new ArrayList()); + } + + public List getShadersLanguage() { + return shadersLanguage; + } + + public List getShadersPath() { + return shadersPath; + } + + + + /** + * jme seralization (not used) + * + * @param im the importer + * @throws IOException + */ + public void read(JmeImporter im) throws IOException { + InputCapsule ic = (InputCapsule) im.getCapsule(this); + name = ic.readString("name", ""); + + String[] str = ic.readStringArray("shadersLanguage", null); + if (str != null) { + shadersLanguage = Arrays.asList(str); + } else { + shadersLanguage = new ArrayList(); + } + + str = ic.readStringArray("shadersPath", null); + if (str != null) { + shadersPath = Arrays.asList(str); + } else { + shadersPath = new ArrayList(); + } + + type = ic.readEnum("type", Shader.ShaderType.class, null); + inputs = (List) ic.readSavableArrayList("inputs", new ArrayList()); + outputs = (List) ic.readSavableArrayList("outputs", new ArrayList()); + } + + /** + * convenience tostring + * + * @return a string + */ + @Override + public String toString() { + return "\nShaderNodeDefinition{\n" + "name=" + name + "\ntype=" + type + "\nshaderPath=" + shadersPath + "\nshaderLanguage=" + shadersLanguage + "\ndocumentation=" + documentation + "\ninputs=" + inputs + ",\noutputs=" + outputs + '}'; + } +} diff --git a/engine/src/core/com/jme3/shader/ShaderNodeVariable.java b/engine/src/core/com/jme3/shader/ShaderNodeVariable.java new file mode 100644 index 000000000..5a24473e9 --- /dev/null +++ b/engine/src/core/com/jme3/shader/ShaderNodeVariable.java @@ -0,0 +1,234 @@ +/* + * 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.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import java.io.IOException; + +/** + * A shader node variable + * + * @author Nehon + */ +public class ShaderNodeVariable implements Savable, Cloneable { + + private String name; + private String type; + private String nameSpace; + private String condition; + private boolean shaderOutput = false; + + /** + * creates a ShaderNodeVariable + * + * @param type the glsl type of the variable + * @param name the name of the variable + */ + public ShaderNodeVariable(String type, String name) { + this.name = name; + this.type = type; + } + + /** + * creates a ShaderNodeVariable + * + * @param type the glsl type of the variable + * @param nameSpace the nameSpace (can be the name of the shaderNode or + * Globel,Attr,MatParam,WorldParam) + * @param name the name of the variable + */ + public ShaderNodeVariable(String type, String nameSpace, String name) { + this.name = name; + this.nameSpace = nameSpace; + this.type = type; + } + + /** + * returns the name + * + * @return the name + */ + public String getName() { + return name; + } + + /** + * sets the name + * + * @param name the name + */ + public void setName(String name) { + this.name = name; + } + + /** + * + * @return the glsl type + */ + public String getType() { + return type; + } + + /** + * sets the glsl type + * + * @param type the type + */ + public void setType(String type) { + this.type = type; + } + + /** + * + * @return the name space (can be the name of the shaderNode or + * Globel,Attr,MatParam,WorldParam) + */ + public String getNameSpace() { + return nameSpace; + } + + /** + * sets the nameSpace (can be the name of the shaderNode or + * Globel,Attr,MatParam,WorldParam) + * + * @param nameSpace + */ + public void setNameSpace(String nameSpace) { + this.nameSpace = nameSpace; + } + + @Override + public int hashCode() { + int hash = 5; + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ShaderNodeVariable other = (ShaderNodeVariable) obj; + if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) { + return false; + } + if ((this.type == null) ? (other.type != null) : !this.type.equals(other.type)) { + return false; + } + if ((this.nameSpace == null) ? (other.nameSpace != null) : !this.nameSpace.equals(other.nameSpace)) { + return false; + } + return true; + } + + /** + * jme seralization (not used) + * + * @param ex the exporter + * @throws IOException + */ + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = (OutputCapsule) ex.getCapsule(this); + oc.write(name, "name", ""); + oc.write(type, "type", ""); + oc.write(nameSpace, "nameSpace", ""); + oc.write(condition, "condition", null); + oc.write(shaderOutput, "shaderOutput", false); + + } + + /** + * jme seralization (not used) + * + * @param im the importer + * @throws IOException + */ + public void read(JmeImporter im) throws IOException { + InputCapsule ic = (InputCapsule) im.getCapsule(this); + name = ic.readString("name", ""); + type = ic.readString("type", ""); + nameSpace = ic.readString("nameSpace", ""); + condition = ic.readString("condition", null); + shaderOutput = ic.readBoolean("shaderOutput", false); + } + + /** + * + * @return the condition for this variable to be declared + */ + public String getCondition() { + return condition; + } + + /** + * sets the condition + * + * @param condition the condition + */ + public void setCondition(String condition) { + this.condition = condition; + } + + @Override + public String toString() { + return "\n" + type + ' ' + (nameSpace != null ? (nameSpace + '.') : "") + name; + } + + @Override + public ShaderNodeVariable clone() { + return new ShaderNodeVariable(type, nameSpace, name); + } + + /** + * + * @return true if this variable is a shader output + */ + public boolean isShaderOutput() { + return shaderOutput; + } + + /** + * sets to true if this variable is a shader output + * + * @param shaderOutput true if this variable is a shader output + */ + public void setShaderOutput(boolean shaderOutput) { + this.shaderOutput = shaderOutput; + } +} diff --git a/engine/src/core/com/jme3/shader/ShaderUtils.java b/engine/src/core/com/jme3/shader/ShaderUtils.java index a5662777d..f7b879d66 100644 --- a/engine/src/core/com/jme3/shader/ShaderUtils.java +++ b/engine/src/core/com/jme3/shader/ShaderUtils.java @@ -33,12 +33,12 @@ package com.jme3.shader; public class ShaderUtils { - public static String convertToGLSL130(String input, boolean isFrag){ + public static String convertToGLSL130(String input, boolean isFrag) { StringBuilder sb = new StringBuilder(); sb.append("#version 130\n"); - if (isFrag){ + if (isFrag) { input = input.replaceAll("varying", "in"); - }else{ + } else { input = input.replaceAll("attribute", "in"); input = input.replaceAll("varying", "out"); } @@ -46,4 +46,71 @@ public class ShaderUtils { return sb.toString(); } + /** + * Check if a mapping is valid by checking the types and swizzle of both of + * the variables + * + * @param mapping the mapping + * @return true if this mapping is valid + */ + public static boolean typesMatch(VariableMapping mapping) { + String leftType = mapping.getLeftVariable().getType(); + String rightType = mapping.getRightVariable().getType(); + String leftSwizzling = mapping.getLeftSwizzling(); + String rightSwizzling = mapping.getRightSwizzling(); + + //types match : no error + if (leftType.equals(rightType) && leftSwizzling.length() == rightSwizzling.length()) { + return true; + } + if (isSwizzlable(leftType) && isSwizzlable(rightType)) { + if (getCardinality(leftType, leftSwizzling) == getCardinality(rightType, rightSwizzling)) { + return true; + } + } + + return false; + } + + /** + * return the cardinality of a type and a swizzle example : vec4 cardinality + * is 4 float cardinality is 1 vec4.xyz cardinality is 3. sampler2D + * cardinality is 0 + * + * @param type the glsl type + * @param swizzling the swizzling of a variable + * @return the cardinality + */ + public static int getCardinality(String type, String swizzling) { + int card = 0; + if (isSwizzlable(type)) { + if (type.equals("float")) { + card = 1; + if (swizzling.length() != 0) { + card = 0; + } + } else { + card = Integer.parseInt(type.replaceAll("vec", "")); + + if (swizzling.length() > 0) { + if (card >= swizzling.length()) { + card = swizzling.length(); + } else { + card = 0; + } + } + } + } + return card; + } + + /** + * returns true if a variable of the given type can have a swizzle + * + * @param type the glsl type + * @return true if a variable of the given type can have a swizzle + */ + public static boolean isSwizzlable(String type) { + return type.equals("vec4") || type.equals("vec3") || type.equals("vec2") || type.equals("float"); + } } diff --git a/engine/src/core/com/jme3/shader/UniformBinding.java b/engine/src/core/com/jme3/shader/UniformBinding.java index fca9a1a49..28fd0c405 100644 --- a/engine/src/core/com/jme3/shader/UniformBinding.java +++ b/engine/src/core/com/jme3/shader/UniformBinding.java @@ -37,63 +37,63 @@ public enum UniformBinding { * The world matrix. Converts Model space to World space. * Type: mat4 */ - WorldMatrix, + WorldMatrix("mat4"), /** * The view matrix. Converts World space to View space. * Type: mat4 */ - ViewMatrix, + ViewMatrix("mat4"), /** * The projection matrix. Converts View space to Clip/Projection space. * Type: mat4 */ - ProjectionMatrix, + ProjectionMatrix("mat4"), /** * The world view matrix. Converts Model space to View space. * Type: mat4 */ - WorldViewMatrix, + WorldViewMatrix("mat4"), /** * The normal matrix. The inverse transpose of the worldview matrix. * Converts normals from model space to view space. * Type: mat3 */ - NormalMatrix, + NormalMatrix("mat3"), /** * The world view projection matrix. Converts Model space to Clip/Projection * space. * Type: mat4 */ - WorldViewProjectionMatrix, + WorldViewProjectionMatrix("mat4"), /** * The view projection matrix. Converts World space to Clip/Projection * space. * Type: mat4 */ - ViewProjectionMatrix, + ViewProjectionMatrix("mat4"), /** * The world matrix inverse transpose. Converts a normals from Model space * to world space. * Type: mat3 */ - WorldMatrixInverseTranspose, + WorldMatrixInverseTranspose("mat3"), - WorldMatrixInverse, - ViewMatrixInverse, - ProjectionMatrixInverse, - ViewProjectionMatrixInverse, - WorldViewMatrixInverse, - NormalMatrixInverse, - WorldViewProjectionMatrixInverse, + WorldMatrixInverse("mat4"), + ViewMatrixInverse("mat4"), + ProjectionMatrixInverse("mat4"), + ViewProjectionMatrixInverse("mat4"), + WorldViewMatrixInverse("mat4"), + NormalMatrixInverse("mat3"), + WorldViewProjectionMatrixInverse("mat4"), /** * Contains the four viewport parameters in this order: @@ -103,7 +103,7 @@ public enum UniformBinding { * W = Bottom. * Type: vec4 */ - ViewPort, + ViewPort("vec4"), /** * The near and far values for the camera frustum. @@ -111,65 +111,80 @@ public enum UniformBinding { * Y = Far. * Type: vec2 */ - FrustumNearFar, + FrustumNearFar("vec2"), /** * The width and height of the camera. * Type: vec2 */ - Resolution, + Resolution("vec2"), /** * The inverse of the resolution, 1/width and 1/height. * Type: vec2 */ - ResolutionInverse, + ResolutionInverse("vec2"), /** * Aspect ratio of the resolution currently set. Width/Height. * Type: float */ - Aspect, + Aspect("float"), /** * Camera position in world space. * Type: vec3 */ - CameraPosition, + CameraPosition("vec3"), /** * Direction of the camera. * Type: vec3 */ - CameraDirection, + CameraDirection("vec3"), /** * Left vector of the camera. * Type: vec3 */ - CameraLeft, + CameraLeft("vec3"), /** * Up vector of the camera. * Type: vec3 */ - CameraUp, + CameraUp("vec3"), /** * Time in seconds since the application was started. * Type: float */ - Time, + Time("float"), /** * Time in seconds that the last frame took. * Type: float */ - Tpf, + Tpf("float"), /** * Frames per second. * Type: float */ - FrameRate, + FrameRate("float"); + + String glslType; + + private UniformBinding() { + } + + private UniformBinding(String glslType) { + this.glslType = glslType; + } + + public String getGlslType() { + return glslType; + } + + } diff --git a/engine/src/core/com/jme3/shader/VarType.java b/engine/src/core/com/jme3/shader/VarType.java index 4ef811576..e8310e658 100644 --- a/engine/src/core/com/jme3/shader/VarType.java +++ b/engine/src/core/com/jme3/shader/VarType.java @@ -33,40 +33,44 @@ package com.jme3.shader; public enum VarType { - Float, - Vector2, - Vector3, - Vector4, + Float("float"), + Vector2("vec2"), + Vector3("vec3"), + Vector4("vec4"), - FloatArray(true,false), - Vector2Array(true,false), - Vector3Array(true,false), - Vector4Array(true,false), + FloatArray(true,false,"float[]"), + Vector2Array(true,false,"vec2[]"), + Vector3Array(true,false,"vec3[]"), + Vector4Array(true,false,"vec4[]"), - Boolean, + Boolean("bool"), - Matrix3(true,false), - Matrix4(true,false), + Matrix3(true,false,"mat3"), + Matrix4(true,false,"mat4"), - Matrix3Array(true,false), - Matrix4Array(true,false), - - TextureBuffer(false,true), - Texture2D(false,true), - Texture3D(false,true), - TextureArray(false,true), - TextureCubeMap(false,true), - Int; + Matrix3Array(true,false,"mat3[]"), + Matrix4Array(true,false,"mat4[]"), + + TextureBuffer(false,true,"sampler1D|sampler1DShadow"), + Texture2D(false,true,"sampler2D|sampler2DShadow"), + Texture3D(false,true,"sampler3D"), + TextureArray(false,true,"sampler2DArray"), + TextureCubeMap(false,true,"samplerCube"), + Int("int"); private boolean usesMultiData = false; private boolean textureType = false; + private String glslType; - VarType(){ + + VarType(String glslType){ + this.glslType = glslType; } - VarType(boolean multiData, boolean textureType){ + VarType(boolean multiData, boolean textureType,String glslType){ usesMultiData = multiData; this.textureType = textureType; + this.glslType = glslType; } public boolean isTextureType() { @@ -77,4 +81,8 @@ public enum VarType { return usesMultiData; } + public String getGlslType() { + return glslType; + } + } diff --git a/engine/src/core/com/jme3/shader/VariableMapping.java b/engine/src/core/com/jme3/shader/VariableMapping.java new file mode 100644 index 000000000..032411e2d --- /dev/null +++ b/engine/src/core/com/jme3/shader/VariableMapping.java @@ -0,0 +1,196 @@ +/* + * 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.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import java.io.IOException; + +/** + * represents a mapping between 2 ShaderNodeVariables + * + * @author Nehon + */ +public class VariableMapping implements Savable { + + private ShaderNodeVariable leftVariable; + private ShaderNodeVariable rightVariable; + private String condition; + private String leftSwizzling = ""; + private String rightSwizzling = ""; + + /** + * creates a VariableMapping + */ + public VariableMapping() { + } + + /** + * creates a VariableMapping + * + * @param leftVariable the left hand side variable of the expression + * @param leftSwizzling the swizzling of the left variable + * @param rightVariable the right hand side variable of the expression + * @param rightSwizzling the swizzling of the right variable + * @param condition the condition for this mapping + */ + public VariableMapping(ShaderNodeVariable leftVariable, String leftSwizzling, ShaderNodeVariable rightVariable, String rightSwizzling, String condition) { + this.leftVariable = leftVariable; + this.rightVariable = rightVariable; + this.condition = condition; + this.leftSwizzling = leftSwizzling; + this.rightSwizzling = rightSwizzling; + } + + /** + * + * @return the left variable + */ + public ShaderNodeVariable getLeftVariable() { + return leftVariable; + } + + /** + * sets the left variable + * + * @param leftVariable the left variable + */ + public void setLeftVariable(ShaderNodeVariable leftVariable) { + this.leftVariable = leftVariable; + } + + /** + * + * @return the right variable + */ + public ShaderNodeVariable getRightVariable() { + return rightVariable; + } + + /** + * sets the right variable + * + * @param leftVariable the right variable + */ + public void setRightVariable(ShaderNodeVariable rightVariable) { + this.rightVariable = rightVariable; + } + + /** + * + * @return the condition + */ + public String getCondition() { + return condition; + } + + /** + * sets the condition + * + * @param condition the condition + */ + public void setCondition(String condition) { + this.condition = condition; + } + + /** + * + * @return the left swizzle + */ + public String getLeftSwizzling() { + return leftSwizzling; + } + + /** + * sets the left swizzle + * + * @param leftSwizzling the left swizzle + */ + public void setLeftSwizzling(String leftSwizzling) { + this.leftSwizzling = leftSwizzling; + } + + /** + * + * @return the right swizzle + */ + public String getRightSwizzling() { + return rightSwizzling; + } + + /** + * sets the right swizzle + * + * @param leftSwizzling the right swizzle + */ + public void setRightSwizzling(String rightSwizzling) { + this.rightSwizzling = rightSwizzling; + } + + /** + * jme seralization (not used) + * + * @param ex the exporter + * @throws IOException + */ + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = (OutputCapsule) ex.getCapsule(this); + oc.write(leftVariable, "leftVariable", null); + oc.write(rightVariable, "rightVariable", null); + oc.write(condition, "condition", ""); + oc.write(leftSwizzling, "leftSwizzling", ""); + oc.write(rightSwizzling, "rightSwizzling", ""); + } + + /** + * jme seralization (not used) + * + * @param im the importer + * @throws IOException + */ + public void read(JmeImporter im) throws IOException { + InputCapsule ic = (InputCapsule) im.getCapsule(this); + leftVariable = (ShaderNodeVariable) ic.readSavable("leftVariable", null); + rightVariable = (ShaderNodeVariable) ic.readSavable("rightVariable", null); + condition = ic.readString("condition", ""); + leftSwizzling = ic.readString("leftSwizzling", ""); + rightSwizzling = ic.readString("rightSwizzling", ""); + } + + @Override + public String toString() { + return "\n{" + leftVariable.toString() + (leftSwizzling.length() > 0 ? ("." + leftSwizzling) : "") + " = " + rightVariable.getType() + " " + rightVariable.getNameSpace() + "." + rightVariable.getName() + (rightSwizzling.length() > 0 ? ("." + rightSwizzling) : "") + " : " + condition + "}"; + } +} diff --git a/engine/src/core/com/jme3/util/blockparser/Statement.java b/engine/src/core/com/jme3/util/blockparser/Statement.java index 9af35c410..bf6bfcd3d 100644 --- a/engine/src/core/com/jme3/util/blockparser/Statement.java +++ b/engine/src/core/com/jme3/util/blockparser/Statement.java @@ -35,27 +35,28 @@ import java.util.ArrayList; import java.util.List; public class Statement { - - private int lineNumber; - private String line; - private List contents = new ArrayList(); - Statement(int lineNumber, String line) { + protected int lineNumber; + protected String line; + protected List contents = new ArrayList(); + + protected Statement(int lineNumber, String line) { this.lineNumber = lineNumber; this.line = line; } - - void addStatement(Statement statement){ -// if (contents == null){ -// contents = new ArrayList(); -// } + + protected void addStatement(Statement statement) { contents.add(statement); } - public int getLineNumber(){ + protected void addStatement(int index, Statement statement) { + contents.add(index, statement); + } + + public int getLineNumber() { return lineNumber; } - + public String getLine() { return line; } @@ -63,19 +64,19 @@ public class Statement { public List getContents() { return contents; } - - private String getIndent(int indent){ + + protected String getIndent(int indent) { return " ".substring(0, indent); } - - private String toString(int indent){ + + protected String toString(int indent) { StringBuilder sb = new StringBuilder(); sb.append(getIndent(indent)); sb.append(line); - if (contents != null){ + if (contents != null) { sb.append(" {\n"); - for (Statement statement : contents){ - sb.append(statement.toString(indent+4)); + for (Statement statement : contents) { + sb.append(statement.toString(indent + 4)); sb.append("\n"); } sb.append(getIndent(indent)); @@ -83,10 +84,9 @@ public class Statement { } return sb.toString(); } - + @Override - public String toString(){ + public String toString() { return toString(0); } - }