diff --git a/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java b/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java index 83e9cf2e5..af13caed1 100644 --- a/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java +++ b/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java @@ -275,13 +275,17 @@ public class Glsl100ShaderGenerator extends ShaderGenerator { // one mapping for this variable, directly append the // other variable from the mapping to the function call VariableMapping m = maps.get(0); - ShaderNodeVariable v2 = m.getRightVariable(); - b.append(getAppendableNameSpace(v2)) - .append(v2.getPrefix()) - .append(v2.getName()); - if (m.getRightSwizzling().length() > 0) { - b.append("."); - b.append(m.getRightSwizzling()); + if(m.getRightExpression()!=null) { + b.append(m.getRightExpression()); + } else { + ShaderNodeVariable v2 = m.getRightVariable(); + b.append(getAppendableNameSpace(v2)) + .append(v2.getPrefix()) + .append(v2.getName()); + if (m.getRightSwizzling().length() > 0) { + b.append("."); + b.append(m.getRightSwizzling()); + } } } else { // 2 possible cases here @@ -652,7 +656,7 @@ public class Glsl100ShaderGenerator extends ShaderGenerator { } @Override - protected String getLanguageAndVersion(ShaderType type) { + protected String getLanguageAndVersion() { return "GLSL100"; } diff --git a/jme3-core/src/main/java/com/jme3/shader/Glsl150ShaderGenerator.java b/jme3-core/src/main/java/com/jme3/shader/Glsl150ShaderGenerator.java index 3dc6aaf9d..919def999 100644 --- a/jme3-core/src/main/java/com/jme3/shader/Glsl150ShaderGenerator.java +++ b/jme3-core/src/main/java/com/jme3/shader/Glsl150ShaderGenerator.java @@ -55,7 +55,7 @@ public class Glsl150ShaderGenerator extends Glsl100ShaderGenerator { } @Override - protected String getLanguageAndVersion(ShaderType type) { + protected String getLanguageAndVersion() { return "GLSL150"; } diff --git a/jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java b/jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java index 6f1c6aecc..e8bf5081c 100644 --- a/jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java +++ b/jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java @@ -114,7 +114,7 @@ public abstract class ShaderGenerator { Shader shader = new Shader(); for (ShaderType type : ShaderType.values()) { String extension = type.getExtension(); - String language = getLanguageAndVersion(type); + String language = getLanguageAndVersion(); String shaderSourceCode = buildShader(techniqueDef.getShaderNodes(), info, type); if (shaderSourceCode != null) { @@ -203,32 +203,36 @@ public abstract class ShaderGenerator { if (info.getUnusedNodes().contains(shaderNode.getName())) { continue; } - - if (shaderNode.getDefinition().getType() == type) { - int index = findShaderIndexFromVersion(shaderNode, type); - String shaderPath = shaderNode.getDefinition().getShadersPath().get(index); - Map sources = (Map) assetManager.loadAsset(new ShaderAssetKey(shaderPath, false)); - String loadedSource = sources.get("[main]"); - for (String name : sources.keySet()) { - if (!name.equals("[main]") && !imports.contains(name)) { - imports.add(name); - // append the imported file in place if it hasn't been imported already. - sourceDeclaration.append(sources.get(name)); + ShaderNodeDefinition def = shaderNode.getDefinition(); + if (def.getType() == type) { + String loadedSource = def.getInlinedCode(); + String shaderPath = def.getName() + "_Inlined"; + if(loadedSource == null) { + int index = findShaderIndexFromVersion(shaderNode); + shaderPath = def.getShadersPath().get(index); + Map sources = (Map) assetManager.loadAsset(new ShaderAssetKey(shaderPath, false)); + loadedSource = sources.get("[main]"); + for (String name : sources.keySet()) { + if (!name.equals("[main]") && !imports.contains(name)) { + imports.add(name); + // append the imported file in place if it hasn't been imported already. + sourceDeclaration.append(sources.get(name)); + } } } // Nodes are functions added to the declaration part of the shader // Multiple nodes may use the same definition and we don't want to declare it several times. // Also nodes can have #ifdef conditions so we need to properly merge this conditions to declare the Node function. NodeDeclaration nd = declaredNodes.get(shaderPath); - if(nd == null){ - if(!shaderNode.getDefinition().getPath().equals(shaderPath)) { + if (nd == null) { + if (def.getInlinedCode() == null && !def.getPath().equals(shaderPath)) { // old style shader node definition loadedSource = functionize(loadedSource, shaderNode.getDefinition()); } - nd = new NodeDeclaration(shaderNode.getCondition(), loadedSource); + nd = new NodeDeclaration(shaderNode.getCondition(), loadedSource); declaredNodes.put(shaderPath, nd); } else { - nd.condition = ConditionParser.mergeConditions(nd.condition, shaderNode.getCondition(), "||"); + nd.condition = ShaderUtils.mergeConditions(nd.condition, shaderNode.getCondition(), "||"); } generateNodeMainSection(source, shaderNode, loadedSource, info); @@ -293,11 +297,10 @@ public abstract class ShaderGenerator { * returns the language + version of the shader should be something like * "GLSL100" for glsl 1.0 "GLSL150" for glsl 1.5. * - * @param type the shader type for which the version should be returned. * * @return the shaderLanguage and version. */ - protected abstract String getLanguageAndVersion(Shader.ShaderType type); + protected abstract String getLanguageAndVersion(); /** * generates the uniforms declaration for a shader of the given type. @@ -379,15 +382,14 @@ public abstract class ShaderGenerator { * 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 { + protected int findShaderIndexFromVersion(ShaderNode shaderNode) throws NumberFormatException { int index = 0; List lang = shaderNode.getDefinition().getShadersLanguage(); - int genVersion = Integer.parseInt(getLanguageAndVersion(type).substring(4)); + int genVersion = Integer.parseInt(getLanguageAndVersion().substring(4)); int curVersion = 0; for (int i = 0; i < lang.size(); i++) { int version = Integer.parseInt(lang.get(i).substring(4)); diff --git a/jme3-core/src/main/java/com/jme3/shader/ShaderNodeDefinition.java b/jme3-core/src/main/java/com/jme3/shader/ShaderNodeDefinition.java index 0561d663c..e66575232 100644 --- a/jme3-core/src/main/java/com/jme3/shader/ShaderNodeDefinition.java +++ b/jme3-core/src/main/java/com/jme3/shader/ShaderNodeDefinition.java @@ -59,6 +59,7 @@ public class ShaderNodeDefinition implements Savable { private List outputs = new ArrayList(); private List params = new ArrayList<>(); private String path = null; + private String inlinedCode; private boolean noOutput = false; private String returnType; @@ -237,8 +238,14 @@ public class ShaderNodeDefinition implements Savable { this.noOutput = noOutput; } - - + public String getInlinedCode() { + return inlinedCode; + } + + public void setInlinedCode(String inlinedCode) { + this.inlinedCode = inlinedCode; + } + /** * jme serialization (not used) * diff --git a/jme3-core/src/main/java/com/jme3/shader/ShaderNodeVariable.java b/jme3-core/src/main/java/com/jme3/shader/ShaderNodeVariable.java index 5038efb70..bc02e812a 100644 --- a/jme3-core/src/main/java/com/jme3/shader/ShaderNodeVariable.java +++ b/jme3-core/src/main/java/com/jme3/shader/ShaderNodeVariable.java @@ -337,7 +337,11 @@ public class ShaderNodeVariable implements Savable, Cloneable { } @Override - public ShaderNodeVariable clone() throws CloneNotSupportedException { - return (ShaderNodeVariable) super.clone(); + public ShaderNodeVariable clone() { + try { + return (ShaderNodeVariable) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } } } diff --git a/jme3-core/src/main/java/com/jme3/shader/ShaderUtils.java b/jme3-core/src/main/java/com/jme3/shader/ShaderUtils.java index 9491f0066..f05a24894 100644 --- a/jme3-core/src/main/java/com/jme3/shader/ShaderUtils.java +++ b/jme3-core/src/main/java/com/jme3/shader/ShaderUtils.java @@ -32,9 +32,11 @@ package com.jme3.shader; import com.jme3.asset.AssetManager; +import com.jme3.material.*; +import com.jme3.material.plugins.ConditionParser; +import com.jme3.scene.VertexBuffer; import com.jme3.shader.plugins.ShaderAssetKey; -import java.io.StringReader; import java.text.ParseException; import java.util.*; import java.util.regex.Matcher; @@ -42,18 +44,14 @@ import java.util.regex.Pattern; public class ShaderUtils { - public static String convertToGLSL130(String input, boolean isFrag) { - StringBuilder sb = new StringBuilder(); - sb.append("#version 130\n"); - if (isFrag) { - input = input.replaceAll("varying", "in"); - } else { - input = input.replaceAll("attribute", "in"); - input = input.replaceAll("varying", "out"); - } - sb.append(input); - return sb.toString(); - } + private static ConditionParser parser = new ConditionParser(); + + // matches "defaults (, , ...)" + private final static Pattern defaultsPattern = Pattern.compile("defaults\\s*\\(\\s*(.*)\\s*\\)"); + // matches " (" + private final static Pattern typeNamePattern = Pattern.compile("(\\w+)\\s+(\\w+)\\s*\\("); + // matches "const? ," + private final static Pattern paramsPattern = Pattern.compile("((const)?\\s*(\\w+)\\s+(\\w+)\\s+(\\w+)\\s*[,\\)])"); /** * Check if a mapping is valid by checking the types and swizzle of both of @@ -80,8 +78,8 @@ public class ShaderUtils { return false; } - - /** + + /** * Check if a mapping is valid by checking the multiplicity of both of * the variables if they are arrays * @@ -91,16 +89,16 @@ public class ShaderUtils { public static boolean multiplicityMatch(VariableMapping mapping) { String leftMult = mapping.getLeftVariable().getMultiplicity(); String rightMult = mapping.getRightVariable().getMultiplicity(); - - if(leftMult == null){ - if(rightMult != null){ + + if (leftMult == null) { + if (rightMult != null) { return false; } - }else{ - if(rightMult == null){ + } else { + if (rightMult == null) { return false; - }else{ - if(!leftMult.equalsIgnoreCase(rightMult)){ + } else { + if (!leftMult.equalsIgnoreCase(rightMult)) { return false; } } @@ -113,7 +111,7 @@ public class ShaderUtils { * is 4 float cardinality is 1 vec4.xyz cardinality is 3. sampler2D * cardinality is 0 * - * @param type the glsl type + * @param type the glsl type * @param swizzling the swizzling of a variable * @return the cardinality */ @@ -143,15 +141,9 @@ public class ShaderUtils { * @return true if a variable of the given type can have a swizzle */ public static boolean isSwizzlable(String type) { - return type.indexOf("vec4")>-1 || type.indexOf("vec3")>-1 || type.indexOf("vec2")>-1 || type.equals("float"); + return type.indexOf("vec4") > -1 || type.indexOf("vec3") > -1 || type.indexOf("vec2") > -1 || type.equals("float"); } - private final static Pattern defaultsPattern = Pattern.compile("defaults\\s*\\(\\s*(.*)\\s*\\)"); - // matches " (" - private final static Pattern typeNamePattern = Pattern.compile("(\\w+)\\s+(\\w+)\\s*\\("); - // matches "const? ," - private final static Pattern paramsPattern = Pattern.compile("((const)?\\s*(\\w+)\\s+(\\w+)\\s+(\\w+)\\s*[,\\)])"); - public static List parseDefinitions(String glsl) throws ParseException { List defs = new ArrayList<>(); String nodesCode[] = glsl.split("#pragma ShaderNode"); @@ -244,7 +236,7 @@ public class ShaderUtils { } public static List loadSahderNodeDefinition(AssetManager assetManager, String definitionPath) throws ParseException { - Map sources = (Map) assetManager.loadAsset(new ShaderAssetKey(definitionPath, false)); + Map sources = (Map) assetManager.loadAsset(new ShaderAssetKey(definitionPath, false)); String glsl = sources.get("[main]"); List defs = ShaderUtils.parseDefinitions(glsl); Shader.ShaderType type = ShaderUtils.getShaderType(definitionPath); @@ -257,4 +249,297 @@ public class ShaderUtils { return defs; } + + public static String getDefaultAttributeType(VertexBuffer.Type type) { + switch (type) { + + case BoneWeight: + case BindPoseNormal: + case Binormal: + case Normal: + return "vec3"; + case Size: + return "float"; + case Position: + case BindPosePosition: + case BindPoseTangent: + case Tangent: + case Color: + return "vec4"; + case InterleavedData: + return "int"; + case Index: + return "uint"; + case BoneIndex: + return "uvec4"; + case TexCoord: + case TexCoord2: + case TexCoord3: + case TexCoord4: + case TexCoord5: + case TexCoord6: + case TexCoord7: + case TexCoord8: + return "vec2"; + default: + return "float"; + } + } + + /** + * TODO put this in core but this should be changed to handle all kinds of shader not just Vertex and Fragments. + * TODO Also getShaderGenerationInfo ArrayLists should be sets really, to avoid duplicated and not have to avoid them ourselves. + * This method could be in core actually and be used after loading a techniqueDef. + * It computes all the information needed to generate the shader faster, from the ShaderNodes. + * + * @param technique + */ + public static void computeShaderNodeGenerationInfo(TechniqueDef technique, MaterialDef matDef) { + + + List attributes = technique.getShaderGenerationInfo().getAttributes(); + List fragmentGlobals = technique.getShaderGenerationInfo().getFragmentGlobals(); + List fragmentUniforms = technique.getShaderGenerationInfo().getFragmentUniforms(); + List vertexUniforms = technique.getShaderGenerationInfo().getVertexUniforms(); + List varyings = technique.getShaderGenerationInfo().getVaryings(); + List unusedNodes = technique.getShaderGenerationInfo().getUnusedNodes(); + attributes.clear(); + fragmentGlobals.clear(); + fragmentUniforms.clear(); + vertexUniforms.clear(); + varyings.clear(); + unusedNodes.clear(); + + //considering that none of the nodes are used, we'll remove them from the list when we have proof they are actually used. + for (ShaderNode shaderNode : technique.getShaderNodes()) { + unusedNodes.add(shaderNode.getName()); + } + for (ShaderNode sn : technique.getShaderNodes()) { + checkDefineFromCondition(sn.getCondition(), matDef, technique); + ShaderNodeDefinition def = sn.getDefinition(); + List in = sn.getInputMapping(); + if (in != null) { + for (VariableMapping map : in) { + checkDefineFromCondition(map.getCondition(), matDef, technique); + if (map.getRightExpression() != null) { + continue; + } + ShaderNodeVariable var = map.getRightVariable(); + if (var.getNameSpace().equals("Global")) { + computeGlobals(technique, fragmentGlobals, def, var); + } else if (var.getNameSpace().equals("Attr")) { + addUnique(attributes, var); + } else if (var.getNameSpace().equals("MatParam") || var.getNameSpace().equals("WorldParam")) { + checkMultiplicity(technique, matDef, map, var); + if (def.getType() == Shader.ShaderType.Fragment) { + addUnique(fragmentUniforms, var); + } else { + addUnique(vertexUniforms, var); + } + } else { + //the nameSpace is the name of another node, if it comes from a different type of node the var is a varying + ShaderNode otherNode = null; + otherNode = findShaderNodeByName(technique, var.getNameSpace()); + if (otherNode == null) { + //we have a problem this should not happen...but let's not crash... + //TODO Maybe we could have an error list and report in it, then present the errors to the user. + continue; + } + if (otherNode.getDefinition().getType() != def.getType()) { + addUnique(varyings, var); + var.setShaderOutput(true); + for (VariableMapping variableMapping : otherNode.getInputMapping()) { + if (variableMapping.getLeftVariable().getName().equals(var.getName())) { + variableMapping.getLeftVariable().setShaderOutput(true); + } + } + } + //and this other node is apparently used so we remove it from the unusedNodes list + unusedNodes.remove(otherNode.getName()); + } + } + + } + List out = sn.getOutputMapping(); + if (out != null && !out.isEmpty()) { + for (VariableMapping map : out) { + checkDefineFromCondition(map.getCondition(), matDef, technique); + ShaderNodeVariable var = map.getLeftVariable(); + if (var.getNameSpace().equals("Global")) { + computeGlobals(technique, fragmentGlobals, def, var); + } + } + //shader has an output it's used in the shader code. + unusedNodes.remove(sn.getName()); + } else { + //some nodes has no output by design ans their def specifies so. + if (sn.getDefinition().isNoOutput()) { + unusedNodes.remove(sn.getName()); + } + } + } + } + + private static void checkDefineFromCondition(String condition, MaterialDef matDef, TechniqueDef techniqueDef) { + if (condition == null) { + return; + } + List defines = parser.extractDefines(condition); + for (String define : defines) { + MatParam param = findMatParam(define, matDef); + if (param != null) { + addDefine(techniqueDef, param.getName(), param.getVarType()); + } + } + } + + public static void checkMultiplicity(TechniqueDef technique, MaterialDef matDef, VariableMapping map, ShaderNodeVariable var) { + if (map.getLeftVariable().getMultiplicity() != null) { + MatParam param = findMatParam(map.getRightVariable().getName(), matDef); + if (!param.getVarType().name().endsWith("Array")) { + throw new IllegalArgumentException(param.getName() + " is not of Array type"); + } + String multiplicity = map.getLeftVariable().getMultiplicity(); + try { + Integer.parseInt(multiplicity); + } catch (NumberFormatException nfe) { + //multiplicity is not an int attempting to find for a material parameter. + MatParam mp = findMatParam(multiplicity, matDef); + if (mp != null) { + //It's tied to a material param, let's create a define and use this as the multiplicity + addDefine(technique, multiplicity, VarType.Int); + multiplicity = multiplicity.toUpperCase(); + map.getLeftVariable().setMultiplicity(multiplicity); + //only declare the variable if the define is defined. + map.getLeftVariable().setCondition(mergeConditions(map.getLeftVariable().getCondition(), "defined(" + multiplicity + ")", "||")); + } else { + throw new IllegalArgumentException("Wrong multiplicity for variable" + map.getLeftVariable().getName() + ". " + multiplicity + " should be an int or a declared material parameter."); + } + } + //the right variable must have the same multiplicity and the same condition. + var.setMultiplicity(multiplicity); + var.setCondition(map.getLeftVariable().getCondition()); + } + } + + /** + * 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 static String mergeConditions(String condition1, String condition2, String operator) { + if (condition1 != null) { + if (condition1.equals(condition2)) { + return condition1; + } + if (condition2 == null) { + return condition1; + } else { + String mergedCondition = "(" + condition1 + ") " + operator + " (" + condition2 + ")"; + return mergedCondition; + } + } else { + return condition2; + } + } + + public static void addDefine(TechniqueDef techniqueDef, String paramName, VarType paramType) { + if (techniqueDef.getShaderParamDefine(paramName) == null) { + techniqueDef.addShaderParamDefine(paramName, paramType, paramName.toUpperCase()); + } + } + + + public static MatParam findMatParam(String varName, MaterialDef matDef) { + for (MatParam matParam : matDef.getMaterialParams()) { + if (varName.toLowerCase().equals(matParam.getName().toLowerCase())) { + return matParam; + } + } + return null; + } + + private static void addUnique(List variables, ShaderNodeVariable var) { + for (ShaderNodeVariable variable : variables) { + if (var.equals(variable)) { + return; + } + } + variables.add(var); + } + + /** + * Retrieve a shader node by name + * + * @param technique + * @param name + * @return + */ + private static ShaderNode findShaderNodeByName(TechniqueDef technique, String name) { + for (ShaderNode shaderNode : technique.getShaderNodes()) { + if (shaderNode.getName().equals(name)) { + return shaderNode; + } + } + return null; + } + + /** + * Some parameters may not be used, or not used as an input, but as a flag to command a define. + * We didn't get them when looking into shader nodes mappings so let's do that now. + * + * @param uniforms + */ + public static void getAllUniforms(TechniqueDef technique, MaterialDef matDef, List uniforms) { + uniforms.clear(); + uniforms.addAll(technique.getShaderGenerationInfo().getFragmentUniforms()); + uniforms.addAll(technique.getShaderGenerationInfo().getVertexUniforms()); + + for (UniformBinding worldParam : technique.getWorldBindings()) { + ShaderNodeVariable var = new ShaderNodeVariable(worldParam.getGlslType(), "WorldParam", worldParam.name()); + if (!contains(uniforms, var)) { + uniforms.add(var); + } + } + + for (MatParam matParam : matDef.getMaterialParams()) { + ShaderNodeVariable var = new ShaderNodeVariable(matParam.getVarType().getGlslType(), "MatParam", matParam.getName()); + if (!contains(uniforms, var)) { + uniforms.add(var); + } + } + } + + private static void computeGlobals(TechniqueDef technique, List fragmentGlobals, ShaderNodeDefinition def, ShaderNodeVariable var) { + var.setShaderOutput(true); + if (def.getType() == Shader.ShaderType.Vertex) { + if (technique.getShaderGenerationInfo().getVertexGlobal() == null) { + technique.getShaderGenerationInfo().setVertexGlobal(var); + } + } else { + if (!contains(fragmentGlobals, var)) { + fragmentGlobals.add(var); + } + } + } + + /** + * returns true if a ShaderNode variable is already contained in a list of variables. + * TODO This could be handled with a Collection.contains, if ShaderNodeVariable had a proper equals and hashcode + * + * @param vars + * @param var + * @return + */ + public static boolean contains(List vars, ShaderNodeVariable var) { + for (ShaderNodeVariable shaderNodeVariable : vars) { + if (shaderNodeVariable.getName().equals(var.getName()) && shaderNodeVariable.getNameSpace().equals(var.getNameSpace())) { + return true; + } + } + return false; + } } diff --git a/jme3-core/src/main/java/com/jme3/shader/builder/InlineShaderNodeBuilder.java b/jme3-core/src/main/java/com/jme3/shader/builder/InlineShaderNodeBuilder.java new file mode 100644 index 000000000..834c64cbb --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/builder/InlineShaderNodeBuilder.java @@ -0,0 +1,105 @@ +package com.jme3.shader.builder; + +import com.jme3.material.TechniqueDef; +import com.jme3.shader.*; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class InlineShaderNodeBuilder extends ShaderNodeBuilder { + + + private TechniqueDef technique; + private Pattern varPattern = Pattern.compile("%(\\w+)"); + private String[] outputTypes; + + public InlineShaderNodeBuilder(String name, ShaderNodeDefinition def, String code, TechniqueDef technique) { + super(name, def); + this.technique = technique; + Matcher m = varPattern.matcher(code); + while (m.find()) { + // type will be inferred with mapping + ShaderNodeVariable v = new ShaderNodeVariable(null, m.group(1)); + def.getParams().add(v); + def.getInputs().add(v); + } + def.setInlinedCode(code.replaceAll("%", "")); + def.getOutputs().add(new ShaderNodeVariable(def.getReturnType(), "result")); + } + + @Override + public InlineShaderNodeBuilder inputs(VariableMappingBuilder... inputs) { + ShaderNodeDefinition def = getNode().getDefinition(); + for (VariableMappingBuilder map : inputs) { + ShaderNodeVariable v = findVariable(map.getName(), def.getInputs()); + + def.getInputs().add(v); + ShaderNodeVariable right = map.getVariable(); + if (right.getDefaultValue() != null) { + throw new IllegalArgumentException("Inlined expression for input " + v.getName() + + " is not supported with inline node " + getNode().getName() + + ". Please inline the expression in the node code."); + } + // infer type + int idx = right.getType().indexOf("|"); + if (idx > 0) { + // texture type, taking the first available type + String type = right.getType().substring(0, right.getType().indexOf("|")); + right.setType(type); + } + + v.setType(right.getType()); + + } + super.inputs(inputs); + return this; + } + + @Override + public InlineShaderNodeBuilder outputs(VariableMappingBuilder... outputs) { + if (outputs.length > 1 || !outputs[0].getName().equals("result")) { + throw new IllegalArgumentException("Only the 'result' output can be mapped for an inlined node"); + } + super.outputs(outputs); + return this; + } + + @Override + public void build() { + ShaderNodeDefinition def = getNode().getDefinition(); + //generate the code + StringBuilder sb = new StringBuilder(); + sb.append(def.getReturnType()).append(" ").append(def.getName()).append("("); + boolean isFirst = true; + int outTypeIndex = 0; + for (ShaderNodeVariable v : def.getParams()) { + if (!isFirst) { + sb.append(", "); + } + sb.append("const in "); + if (def.getInputs().contains(v)) { + + } else { + sb.append("out "); + if (!def.getOutputs().contains(v)) { + // the variable is not in the output list + def.getOutputs().add(v); + } + if (v.getType() == null && outTypeIndex < outputTypes.length) { + v.setType(outputTypes[outTypeIndex]); + } else { + throw new IllegalArgumentException("Output variable " + v.getName() + " has no type in node " + getNode().getName() + ". Make sure you properly declare it"); + } + } + if (v.getType() == null) { + throw new IllegalArgumentException("Unable to infer type for input variable " + v.getName() + " in node " + getNode().getName()); + } + sb.append(v.getType()).append(" "); + sb.append(v.getName()); + isFirst = false; + } + sb.append("){\n\treturn ").append(def.getInlinedCode()).append(";\n}"); + def.setInlinedCode(sb.toString()); + super.build(); + } +} diff --git a/jme3-core/src/main/java/com/jme3/shader/builder/MaterialBuilder.java b/jme3-core/src/main/java/com/jme3/shader/builder/MaterialBuilder.java new file mode 100644 index 000000000..c1b2a831c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/builder/MaterialBuilder.java @@ -0,0 +1,146 @@ +package com.jme3.shader.builder; + +import com.jme3.asset.AssetManager; +import com.jme3.material.*; +import com.jme3.scene.VertexBuffer; +import com.jme3.shader.*; +import com.jme3.texture.image.ColorSpace; + +import java.util.*; + +public class MaterialBuilder { + + private MaterialDef matDef; + private Map techBuilders = new HashMap<>(); + private AssetManager assetManager; + private TechniqueBuilder currentTechnique; + + public MaterialBuilder(AssetManager assetManager) { + matDef = new MaterialDef(assetManager, "MatDef"); + this.assetManager = assetManager; + } + + public MaterialBuilder(AssetManager assetManager, MaterialDef matDef) { + this.matDef = matDef; + this.assetManager = assetManager; + } + + public MaterialBuilder(AssetManager assetManager, String matDefName) { + this.matDef = (MaterialDef) assetManager.loadAsset(matDefName); + this.assetManager = assetManager; + } + + public TechniqueBuilder technique(String name) { + TechniqueBuilder tb = techBuilders.get(name); + if (tb == null) { + List defs = matDef.getTechniqueDefs(name); + if (defs == null || defs.isEmpty()) { + String techniqueUniqueName = matDef.getAssetName() + "@" + name; + tb = new TechniqueBuilder(assetManager, name, techniqueUniqueName.hashCode()); + matDef.addTechniqueDef(tb.getTechniqueDef()); + + } else { + tb = new TechniqueBuilder(assetManager, defs.get(0)); + } + techBuilders.put(name, tb); + } + currentTechnique = tb; + return tb; + } + + public TechniqueBuilder technique() { + return this.technique("Default"); + } + + public Material build() { + + for (Map.Entry entry : techBuilders.entrySet()) { + TechniqueBuilder tb = entry.getValue(); + tb.build(); + ShaderUtils.computeShaderNodeGenerationInfo(tb.getTechniqueDef(), matDef); + } + return new Material(matDef); + } + + public ShaderNodeVariable var(String expression) { + String[] names = expression.split("\\."); + if (names.length != 2) { + // we might have an inlined expression + ShaderNodeVariable tmp = new ShaderNodeVariable(null, null); + tmp.setDefaultValue(expression); + return tmp; + } + if (names[0].equals("MatParam")) { + MatParam param = matDef.getMaterialParam(names[1]); + if (param == null) { + throw new IllegalArgumentException("Couldn't find material parameter named " + names[1]); + } + return new ShaderNodeVariable(param.getVarType().getGlslType(), names[0], names[1], null, "m_"); + } + if (names[0].equals("WorldParam")) { + UniformBinding worldParam = UniformBinding.valueOf(names[1]); + currentTechnique.addWorldParam(worldParam.name()); + return new ShaderNodeVariable(worldParam.getGlslType(), "WorldParam", worldParam.name(), null, "g_"); + } + if (names[0].equals("Attr")) { + String n = names[1].substring(2); + VertexBuffer.Type attribute = VertexBuffer.Type.valueOf(n); + return new ShaderNodeVariable(ShaderUtils.getDefaultAttributeType(attribute), names[0], names[1]); + } + + if (names[0].equals("Global")) { + if(!names[1].equals("position") && !names[1].startsWith("color")){ + throw new IllegalArgumentException("Global output must be outPosition or outColor, got " + names[1]); + } + + return new ShaderNodeVariable("vec4", names[0], names[1]); + } + + ShaderNodeBuilder nb = currentTechnique.node(names[0]); + if (nb == null) { + throw new IllegalArgumentException("Couldn't find node named " + names[0]); + } + + ShaderNodeVariable v = nb.variable(names[1]); + if (v == null) { + throw new IllegalArgumentException("Couldn't find variable named " + names[1] + " in node " + names[0]); + } + + return v; + } + + public VariableMappingBuilder map(String param, String expression) { + return new VariableMappingBuilder(param, var(expression)); + } + + public VariableMappingBuilder map(String param, ShaderNodeVariable variable) { + return new VariableMappingBuilder(param, variable); + } + + public VariableMappingBuilder map(String param, VertexBuffer.Type attribute) { + ShaderNodeVariable variable = new ShaderNodeVariable(ShaderUtils.getDefaultAttributeType(attribute), "Attr", "in" + attribute.name()); + return new VariableMappingBuilder(param, variable); + } + + public VariableMappingBuilder map(String param, UniformBinding worldParam) { + ShaderNodeVariable variable = new ShaderNodeVariable(worldParam.getGlslType(), "WorldParam", worldParam.name(), null, "g_"); + currentTechnique.addWorldParam(worldParam.name()); + return new VariableMappingBuilder(param, variable); + } + + public void addMatParam(VarType type, String name){ + if(type.isTextureType()){ + matDef.addMaterialParamTexture(type, name, ColorSpace.sRGB); + } else { + matDef.addMaterialParam(type, name, null); + } + } + + public void addMatParamTexture(VarType type, String name, ColorSpace colorSpace){ + if(!type.isTextureType()){ + throw new IllegalArgumentException(type + "is not a texture type "); + } + matDef.addMaterialParamTexture(type, name, colorSpace); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/shader/builder/ShaderNodeBuilder.java b/jme3-core/src/main/java/com/jme3/shader/builder/ShaderNodeBuilder.java new file mode 100644 index 000000000..d0acd6d5e --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/builder/ShaderNodeBuilder.java @@ -0,0 +1,113 @@ +package com.jme3.shader.builder; + +import com.jme3.shader.*; + +import java.util.List; + +public class ShaderNodeBuilder { + + private ShaderNode node; + + protected ShaderNodeBuilder(String name, ShaderNodeDefinition def) { + node = new ShaderNode(name, def, null); + } + + protected ShaderNodeBuilder(ShaderNode node) { + this.node = node; + } + + protected ShaderNode getNode() { + return node; + } + + protected ShaderNodeVariable variable(String name){ + ShaderNodeDefinition def = node.getDefinition(); + for (ShaderNodeVariable variable : def.getParams()) { + if(variable.getName().equals(name)){ + ShaderNodeVariable var = variable.clone(); + var.setNameSpace(node.getName()); + return var; + } + } + + for (ShaderNodeVariable variable : def.getOutputs()) { + if(variable.getName().equals(name)){ + ShaderNodeVariable var = variable.clone(); + var.setNameSpace(node.getName()); + return var; + } + } + return null; + } + + public ShaderNodeBuilder inputs(VariableMappingBuilder... inputs){ + List mappings = node.getInputMapping(); + mappings.clear(); + for (VariableMappingBuilder mb : inputs) { + ShaderNodeVariable left = findVariable(mb.getName(), node.getDefinition().getInputs() ); + if(left == null){ + throw new IllegalArgumentException("Couldn't find input " + mb.getName() + " in node definition " + node.getDefinition().getName()); + } + left = left.clone(); + left.setNameSpace(node.getName()); + ShaderNodeVariable right = mb.getVariable(); + VariableMapping m = map(left, right); + mappings.add(m); + } + return this; + } + + + public ShaderNodeBuilder outputs(VariableMappingBuilder... outputs){ + List mappings = node.getOutputMapping(); + mappings.clear(); + for (VariableMappingBuilder mb : outputs) { + ShaderNodeVariable right = findVariable(mb.getName(), node.getDefinition().getOutputs() ); + if(right == null){ + throw new IllegalArgumentException("Couldn't find input " + mb.getName() + " in node definition " + node.getDefinition().getName()); + } + right = right.clone(); + right.setNameSpace(node.getName()); + ShaderNodeVariable left = mb.getVariable(); + VariableMapping m = map(left, right); + mappings.add(m); + } + return this; + } + + + private VariableMapping map(ShaderNodeVariable left, ShaderNodeVariable right) { + if(right.getType() == null){ + // tmp variable, with default value + VariableMapping m = new VariableMapping(); + m.setLeftVariable(left); + m.setRightExpression(right.getDefaultValue()); + return m; + } + int leftCard = ShaderUtils.getCardinality(left.getType(), ""); + int rightCard = ShaderUtils.getCardinality(right.getType(), ""); + String swizzle = "xyzw"; + String rightVarSwizzle = ""; + String leftVarSwizzle =""; + if (rightCard > leftCard) { + rightVarSwizzle = swizzle.substring(0, leftCard); + } else if (rightCard > rightCard) { + leftVarSwizzle = swizzle.substring(0, rightCard); + } + + return new VariableMapping(left, leftVarSwizzle, right, rightVarSwizzle, null); + } + + protected ShaderNodeVariable findVariable(String name, List list){ + for (ShaderNodeVariable variable : list) { + if(variable.getName().equals(name)){ + return variable; + } + } + return null; + } + + public void build(){ + + } +} diff --git a/jme3-core/src/main/java/com/jme3/shader/builder/TechniqueBuilder.java b/jme3-core/src/main/java/com/jme3/shader/builder/TechniqueBuilder.java new file mode 100644 index 000000000..213cb568b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/builder/TechniqueBuilder.java @@ -0,0 +1,145 @@ +package com.jme3.shader.builder; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.ShaderNodeDefinitionKey; +import com.jme3.material.ShaderGenerationInfo; +import com.jme3.material.TechniqueDef; +import com.jme3.material.logic.*; +import com.jme3.shader.*; + +import java.text.ParseException; +import java.util.*; + +public class TechniqueBuilder { + + private TechniqueDef techniqueDef; + private AssetManager assetManager; + private Map nodeBuilders = new HashMap<>(); + + protected TechniqueBuilder(AssetManager assetManager, String name, int sortId) { + techniqueDef = new TechniqueDef(name, sortId); + this.assetManager = assetManager; + } + + protected TechniqueBuilder(AssetManager assetManager, TechniqueDef techniqueDef) { + this.techniqueDef = techniqueDef; + this.assetManager = assetManager; + } + + protected TechniqueDef getTechniqueDef() { + return techniqueDef; + } + + public ShaderNodeBuilder node(String name) { + ShaderNodeBuilder b = nodeBuilders.get(name); + if (b == null){ + ShaderNode n = findShaderNode(name); + if(n == null){ + throw new IllegalArgumentException("Can't find node with name " + name + " in technique definition " + techniqueDef.getName()); + } + b = new ShaderNodeBuilder(n); + nodeBuilders.put(name, b); + } + return b; + } + + public ShaderNodeBuilder addNode(String name, String defName, String shaderNodeDefPath) { + List defs; + if(shaderNodeDefPath.endsWith(".j3sn")){ + defs = assetManager.loadAsset(new ShaderNodeDefinitionKey(shaderNodeDefPath)); + } else { + try { + defs = ShaderUtils.loadSahderNodeDefinition(assetManager, shaderNodeDefPath); + } catch (ParseException e) { + throw new IllegalArgumentException("Couldn't parse definition " + shaderNodeDefPath, e); + } + } + ShaderNodeDefinition definition = findDefinition(defName, defs); + if(definition == null){ + throw new IllegalArgumentException("Couldn't find definition " + defName + " in " + shaderNodeDefPath); + } + + ShaderNodeBuilder b = new ShaderNodeBuilder(name,definition); + if(techniqueDef.getShaderNodes() == null){ + techniqueDef.setShaderNodes(new ArrayList()); + } + techniqueDef.setShaderFile(techniqueDef.hashCode() + "", techniqueDef.hashCode() + "", "GLSL100", "GLSL100"); + techniqueDef.getShaderNodes().add(b.getNode()); + techniqueDef.setShaderGenerationInfo(new ShaderGenerationInfo()); + techniqueDef.setLogic(new DefaultTechniqueDefLogic(techniqueDef)); + techniqueDef.setShaderPrologue(""); + nodeBuilders.put(name, b); + return b; + } + + + public InlineShaderNodeBuilder inlineVertexNode(String type, String name, String code){ + return inlineNode(type, name, code, Shader.ShaderType.Vertex); + } + + public InlineShaderNodeBuilder inlineFragmentNode(String type, String name, String code){ + return inlineNode(type, name, code, Shader.ShaderType.Fragment); + } + + public InlineShaderNodeBuilder inlineNode(String returnType, String name, String code, Shader.ShaderType type){ + ShaderNodeDefinition def = new ShaderNodeDefinition(); + def.setName(name); + def.setType(type); + def.setReturnType(returnType); + InlineShaderNodeBuilder sb = new InlineShaderNodeBuilder(name, def, code, techniqueDef); + nodeBuilders.put(name, sb); + techniqueDef.getShaderNodes().add(sb.getNode()); + return sb; + } + + public boolean addWorldParam(String name){ + return techniqueDef.addWorldParam(name); + } + + private void setLightMode(TechniqueDef.LightMode mode){ + switch (techniqueDef.getLightMode()) { + case Disable: + techniqueDef.setLogic(new DefaultTechniqueDefLogic(techniqueDef)); + break; + case MultiPass: + techniqueDef.setLogic(new MultiPassLightingLogic(techniqueDef)); + break; + case SinglePass: + techniqueDef.setLogic(new SinglePassLightingLogic(techniqueDef)); + break; + case StaticPass: + techniqueDef.setLogic(new StaticPassLightingLogic(techniqueDef)); + break; + case SinglePassAndImageBased: + techniqueDef.setLogic(new SinglePassAndImageBasedLightingLogic(techniqueDef)); + break; + default: + throw new UnsupportedOperationException(); + } + } + + private ShaderNodeDefinition findDefinition(String defName, List defs) { + for (ShaderNodeDefinition def : defs) { + if(def.getName().equals(defName)){ + return def; + } + } + return null; + } + + private ShaderNode findShaderNode(String name){ + for (ShaderNode shaderNode : techniqueDef.getShaderNodes()) { + if(shaderNode.getName().equals(name)){ + return shaderNode; + } + } + return null; + } + + protected void build(){ + for (Map.Entry entry : nodeBuilders.entrySet()) { + ShaderNodeBuilder nb = entry.getValue(); + nb.build(); + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/shader/builder/VariableMappingBuilder.java b/jme3-core/src/main/java/com/jme3/shader/builder/VariableMappingBuilder.java new file mode 100644 index 000000000..2f0a3d19b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/builder/VariableMappingBuilder.java @@ -0,0 +1,23 @@ +package com.jme3.shader.builder; + +import com.jme3.shader.ShaderNodeVariable; + +public class VariableMappingBuilder { + + private String name; + private ShaderNodeVariable variable; + + protected VariableMappingBuilder(String name, ShaderNodeVariable variable) { + this.name = name; + this.variable = variable; + } + + public String getName() { + return name; + } + + public ShaderNodeVariable getVariable() { + return variable; + } + +} diff --git a/jme3-core/src/plugins/java/com/jme3/material/plugins/ConditionParser.java b/jme3-core/src/plugins/java/com/jme3/material/plugins/ConditionParser.java index 628883a86..07569ce90 100644 --- a/jme3-core/src/plugins/java/com/jme3/material/plugins/ConditionParser.java +++ b/jme3-core/src/plugins/java/com/jme3/material/plugins/ConditionParser.java @@ -104,28 +104,6 @@ public class ConditionParser { return defines; } - /** - * 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 static String mergeConditions(String condition1, String condition2, String operator) { - if (condition1 != null) { - if (condition2 == null) { - return condition1; - } else { - String mergedCondition = "(" + condition1 + ") " + operator + " (" + condition2 + ")"; - return mergedCondition; - } - } else { - return condition2; - } - } - - /** * * @return the formatted expression previously updated by extractDefines diff --git a/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java b/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java index c3ac904a5..e0acb453d 100644 --- a/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java +++ b/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java @@ -660,9 +660,8 @@ public class J3MLoader implements AssetLoader { if(isUseNodes){ //used for caching later, the shader here is not a file. - // KIRILL 9/19/2015 - // Not sure if this is needed anymore, since shader caching - // is now done by TechniqueDef. + // REMY 16/06/2018 + // this is still needed in order for the weight of the technique to be computed. technique.setShaderFile(technique.hashCode() + "", technique.hashCode() + "", "GLSL100", "GLSL100"); techniqueDefs.add(technique); }else if (shaderNames.containsKey(Shader.ShaderType.Vertex) && shaderNames.containsKey(Shader.ShaderType.Fragment)) { diff --git a/jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java b/jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java index a7b4db958..6bbc3630d 100644 --- a/jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java +++ b/jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java @@ -582,7 +582,7 @@ public class ShaderNodeLoaderDelegate { multiplicity = multiplicity.toUpperCase(); left.setMultiplicity(multiplicity); // only declare the variable if the define is defined. - left.setCondition(ConditionParser.mergeConditions(left.getCondition(), "defined(" + multiplicity + ")", "||")); + left.setCondition(ShaderUtils.mergeConditions(left.getCondition(), "defined(" + multiplicity + ")", "||")); } else { throw new MatParseException("Wrong multiplicity for variable" + left.getName() + ". " + multiplicity + " should be an int or a declared material parameter.", statement); diff --git a/jme3-examples/src/main/java/jme3test/material/TestShaderNodesApi.java b/jme3-examples/src/main/java/jme3test/material/TestShaderNodesApi.java new file mode 100644 index 000000000..187e907d5 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/material/TestShaderNodesApi.java @@ -0,0 +1,86 @@ +package jme3test.material; + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.material.Technique; +import com.jme3.material.TechniqueDef; +import com.jme3.math.*; +import com.jme3.scene.Geometry; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.shape.Box; +import com.jme3.shader.*; +import com.jme3.shader.builder.MaterialBuilder; +import com.jme3.texture.Texture; + +import java.util.logging.Level; +import java.util.logging.Logger; + +public class TestShaderNodesApi extends SimpleApplication { + + public static void main(String[] args) { + TestShaderNodesApi app = new TestShaderNodesApi(); + app.start(); + } + + @Override + public void simpleInitApp() { + flyCam.setMoveSpeed(20); + Logger.getLogger("com.jme3").setLevel(Level.WARNING); + Box boxshape1 = new Box(1f, 1f, 1f); + Geometry cube = new Geometry("A Box", boxshape1); + Texture tex = assetManager.loadTexture("Interface/Logo/Monkey.jpg"); + + MaterialBuilder mb = new MaterialBuilder(assetManager); + mb.addMatParam(VarType.Vector4, "Color"); + mb.addMatParam(VarType.Texture2D, "Texture"); + + mb.technique().addNode("CommonVert", "CommonVert", "jme3test/matdefs/CommonVert.vert") + .inputs( + mb.map("worldViewProjectionMatrix", UniformBinding.WorldViewProjectionMatrix), + mb.map("modelPosition", VertexBuffer.Type.Position)) + .outputs( + mb.map("result", "Global.position") + ); + + mb.technique().inlineVertexNode("vec2","TexCoord", "%texIn") + .inputs( + mb.map("texIn", VertexBuffer.Type.TexCoord) + ); + + mb.technique().addNode("ColorMult", "ColorMult", "jme3test/matdefs/ColorMult.frag") + .inputs( + mb.map("color1", "vec4(0.1, 0.1, 0.1, 1.0)"), + mb.map("color2", "MatParam.Color")) + .outputs( + mb.map("result", "Global.color") + ); + + mb.technique().inlineFragmentNode("vec4","InlineNode","%color1 * texture2D(%tex, %texCoord)") + .inputs( + mb.map("color1", "ColorMult.result"), + mb.map("tex", "MatParam.Texture"), + mb.map("texCoord", "TexCoord.result") + ).outputs( + mb.map("result", "Global.color") + ); + + Material mat = mb.build(); + + //Material mat = new Material(assetManager, "jme3test/matdefs/test2.j3md"); + + mat.selectTechnique(TechniqueDef.DEFAULT_TECHNIQUE_NAME, renderManager); + Technique t = mat.getActiveTechnique(); + + for (Shader.ShaderSource shaderSource : t.getDef().getShader(assetManager, renderer.getCaps(), t.getDynamicDefines()).getSources()) { + System.out.println(shaderSource.getSource()); + } + + mat.setColor("Color", ColorRGBA.Yellow); + mat.setTexture("Texture", tex); + cube.setMaterial(mat); + cube.move(0, 0, 0); + rootNode.attachChild(cube); + + + } +}