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 edb780344..83e9cf2e5 100644 --- a/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java +++ b/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java @@ -36,6 +36,7 @@ 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; import java.util.logging.Level; import java.util.logging.Logger; @@ -231,8 +232,6 @@ public class Glsl100ShaderGenerator extends ShaderGenerator { */ @Override protected void generateNodeMainSection(StringBuilder source, ShaderNode shaderNode, String nodeSource, ShaderGenerationInfo info) { - - source.append("\n"); comment(source, shaderNode, ""); startCondition(shaderNode.getCondition(), source); @@ -243,13 +242,14 @@ public class Glsl100ShaderGenerator extends ShaderGenerator { appendIndent(b); b.append(definition.getName()).append("("); boolean isFirst = true; + List maps = new ArrayList<>(); for (ShaderNodeVariable v : definition.getParams()) { if (!isFirst) { b.append(", "); } if (definition.getInputs().contains(v)) { - List maps = shaderNode.getInputMapping(v.getName()); + shaderNode.getInputMapping(v.getName(), maps); boolean declared = false; for (VariableMapping m : maps) { @@ -267,6 +267,7 @@ public class Glsl100ShaderGenerator extends ShaderGenerator { b.append(v.getDefaultValue()); } else { // no default value, construct a variable with the proper type and dummy value and raise a warning + b.append("/*UNMAPPED_").append(v.getName()).append("*/ "); b.append(getConstructor(v.getType())); log.log(Level.WARNING, "No input defined for variable " + v.getName() + " on shader node " + shaderNode.getName()); } @@ -291,28 +292,28 @@ public class Glsl100ShaderGenerator extends ShaderGenerator { for (VariableMapping mapping : maps) { map(mapping, source, true); } - b.append(shaderNode.getName()) - .append("_") - .append(v.getName()); + appendVariable(shaderNode.getName(), b, v); } } else { // outputs - String name = shaderNode.getName() + "_" + v.getName(); - // if the output is not a varying (already declared) we declare it) - if (!isVarying(info, name)) { - appendIndent(source); - source.append(v.getType()).append(" ").append(name).append(";\n"); - } + declareOutput(source, shaderNode.getName(), info, v); // append the variable to the function call - b.append(shaderNode.getName()) - .append("_") - .append(v.getName()); + appendVariable(shaderNode.getName(), b, v); } isFirst = false; } b.append(");\n"); + if(!definition.getReturnType().equals("void")){ + // non void return type, the first output is the result + ShaderNodeVariable v = definition.getOutputs().get(0); + declareOutput(source, shaderNode.getName(), info, v); + appendIndent(source); + appendVariable(shaderNode.getName(), source, v); + source.append(" ="); + } + // Map any output to global output. for (VariableMapping mapping : shaderNode.getOutputMapping()) { map(mapping, b, false); @@ -322,6 +323,19 @@ public class Glsl100ShaderGenerator extends ShaderGenerator { endCondition(shaderNode.getCondition(), source); } + private void declareOutput(StringBuilder source, String nameSpace, ShaderGenerationInfo info, ShaderNodeVariable v) { + String name = nameSpace + "_" + v.getName(); + // if the output is not a varying (already declared) we declare it) + if (!isVarying(info, name)) { + appendIndent(source); + source.append(v.getType()).append(" ").append(name).append(";\n"); + } + } + + private void appendVariable(String nameSpace, StringBuilder b, ShaderNodeVariable v) { + b.append(nameSpace).append("_").append(v.getName()); + } + /** * Returns a proper constructor call for a given type * @param type diff --git a/jme3-core/src/main/java/com/jme3/shader/Shader.java b/jme3-core/src/main/java/com/jme3/shader/Shader.java index 05264f8db..c52a5b100 100644 --- a/jme3-core/src/main/java/com/jme3/shader/Shader.java +++ b/jme3-core/src/main/java/com/jme3/shader/Shader.java @@ -103,6 +103,15 @@ public final class Shader extends NativeObject { public String getExtension() { return extension; } + + public static ShaderType fromExtention(String ext){ + for (ShaderType shaderType : values()) { + if(shaderType.extension.equals(ext)){ + return shaderType; + } + } + return null; + } private ShaderType(String extension) { this.extension = extension; 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 78a20c7fd..6f1c6aecc 100644 --- a/jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java +++ b/jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java @@ -219,11 +219,14 @@ public abstract class ShaderGenerator { // 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(shaderNode.getDefinition().getName()); - loadedSource = functionize(loadedSource, shaderNode.getDefinition()); + NodeDeclaration nd = declaredNodes.get(shaderPath); if(nd == null){ + if(!shaderNode.getDefinition().getPath().equals(shaderPath)) { + // old style shader node definition + loadedSource = functionize(loadedSource, shaderNode.getDefinition()); + } nd = new NodeDeclaration(shaderNode.getCondition(), loadedSource); - declaredNodes.put(shaderNode.getDefinition().getName(), nd); + declaredNodes.put(shaderPath, nd); } else { nd.condition = ConditionParser.mergeConditions(nd.condition, shaderNode.getCondition(), "||"); } @@ -237,7 +240,7 @@ public abstract class ShaderGenerator { } /** - * Tuns old style shader node code into a proper function so that it can be appended to the declarative sectio. + * Turns old style shader node code into a proper function so that it can be appended to the declarative section. * Note that this only needed for nodes coming from a j3sn file. * @param source * @param def diff --git a/jme3-core/src/main/java/com/jme3/shader/ShaderNode.java b/jme3-core/src/main/java/com/jme3/shader/ShaderNode.java index 15b3cc564..85a586572 100644 --- a/jme3-core/src/main/java/com/jme3/shader/ShaderNode.java +++ b/jme3-core/src/main/java/com/jme3/shader/ShaderNode.java @@ -148,14 +148,13 @@ public class ShaderNode implements Savable, Cloneable { * * @return the input mappings. */ - public List getInputMapping(String varName) { - List list = new ArrayList<>(); + public void getInputMapping(String varName, List list) { + list.clear(); for (VariableMapping v : inputMapping) { if (v.getLeftVariable().getName().equals(varName)){ list.add(v); } } - return list; } /** 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 13bde3c71..9491f0066 100644 --- a/jme3-core/src/main/java/com/jme3/shader/ShaderUtils.java +++ b/jme3-core/src/main/java/com/jme3/shader/ShaderUtils.java @@ -31,6 +31,15 @@ */ package com.jme3.shader; +import com.jme3.asset.AssetManager; +import com.jme3.shader.plugins.ShaderAssetKey; + +import java.io.StringReader; +import java.text.ParseException; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + public class ShaderUtils { public static String convertToGLSL130(String input, boolean isFrag) { @@ -136,4 +145,116 @@ public class ShaderUtils { public static boolean isSwizzlable(String type) { 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"); + for (String code : nodesCode) { + if (code.trim().length() == 0) { + continue; + } + int firstCr = code.indexOf("\n"); + int firstBracket = code.indexOf("{"); + String pragma = code.substring(0, firstCr); + Matcher m1 = defaultsPattern.matcher(pragma); + String[] defaults = null; + if (m1.find()) { + defaults = m1.group(1).split(","); + } + + code = code.substring(firstCr + 1, firstBracket); + + Matcher m = typeNamePattern.matcher(code); + + String returnType = null; + String functionName = null; + while (m.find()) { + returnType = m.group(1); + functionName = m.group(2); + } + if (returnType == null || functionName == null) { + throw new ParseException("Unmatched return type or function name in \n" + code, firstCr + 1); + } + + ShaderNodeDefinition def = new ShaderNodeDefinition(); + def.setName(functionName); + def.setReturnType(returnType); + + m.reset(); + m.usePattern(paramsPattern); + + List inputs = new ArrayList<>(); + List outputs = new ArrayList<>(); + List params = new ArrayList<>(); + + if (!returnType.equals("void")) { + ShaderNodeVariable result = new ShaderNodeVariable(returnType, "result"); + outputs.add(result); + } + + int cpt = 0; + while (m.find()) { + String dir = m.group(3); + String type = m.group(4); + String varName = m.group(5); + ShaderNodeVariable v = new ShaderNodeVariable(type, varName); + params.add(v); + String defVal = null; + if (defaults != null && defaults.length > cpt) { + defVal = defaults[cpt].trim(); + defVal = defVal.isEmpty() ? null : defVal; + } + v.setDefaultValue(defVal); + switch (dir) { + case "in": + inputs.add(v); + break; + case "out": + outputs.add(v); + break; + default: + throw new ParseException("Missing in or out keyword for variable " + varName + " in function " + functionName, m.start()); + } + cpt++; + } + + def.setParams(params); + def.setInputs(inputs); + if (outputs.isEmpty()) { + def.setNoOutput(true); + } else { + def.setOutputs(outputs); + } + + defs.add(def); + } + + return defs; + } + + public static Shader.ShaderType getShaderType(String shaderPath) { + String ext = shaderPath.substring(shaderPath.lastIndexOf(".") + 1); + return Shader.ShaderType.fromExtention(ext); + } + + public static List loadSahderNodeDefinition(AssetManager assetManager, String definitionPath) throws ParseException { + 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); + for (ShaderNodeDefinition d : defs) { + d.setType(type); + d.getShadersLanguage().add("GLSL100"); + d.setPath(definitionPath); + d.getShadersPath().add(definitionPath); + } + + return defs; + } } 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 b18a08904..a7b4db958 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 @@ -40,9 +40,11 @@ import com.jme3.material.ShaderGenerationInfo; import com.jme3.material.TechniqueDef; import com.jme3.shader.Shader.ShaderType; import com.jme3.shader.*; +import com.jme3.shader.plugins.ShaderAssetKey; import com.jme3.util.blockparser.Statement; import java.io.IOException; +import java.text.ParseException; import java.util.*; /** @@ -996,9 +998,15 @@ public class ShaderNodeLoaderDelegate { List defs; try { - defs = assetManager.loadAsset(new ShaderNodeDefinitionKey(definitionPath)); + if(definitionPath.endsWith(".j3sn")) { + defs = assetManager.loadAsset(new ShaderNodeDefinitionKey(definitionPath)); + } else { + defs = ShaderUtils.loadSahderNodeDefinition(assetManager, definitionPath); + } } catch (final AssetNotFoundException e) { throw new MatParseException("Couldn't find " + definitionPath, statement, e); + } catch (ParseException e) { + throw new MatParseException("Error while loading shader node definition" + definitionPath, statement, e); } for (final ShaderNodeDefinition definition : defs) { diff --git a/jme3-examples/src/main/java/jme3test/material/TestShaderNodes.java b/jme3-examples/src/main/java/jme3test/material/TestShaderNodes.java index 9b8d0511e..d677e8f82 100644 --- a/jme3-examples/src/main/java/jme3test/material/TestShaderNodes.java +++ b/jme3-examples/src/main/java/jme3test/material/TestShaderNodes.java @@ -24,7 +24,7 @@ public class TestShaderNodes extends SimpleApplication { flyCam.setMoveSpeed(20); Logger.getLogger("com.jme3").setLevel(Level.WARNING); Box boxshape1 = new Box(1f, 1f, 1f); - Geometry cube_tex = new Geometry("A Textured Box", boxshape1); + Geometry cube = new Geometry("A Box", boxshape1); Texture tex = assetManager.loadTexture("Interface/Logo/Monkey.jpg"); Material mat = new Material(assetManager, "Common/MatDefs/Misc/UnshadedNodes.j3md"); @@ -37,7 +37,25 @@ public class TestShaderNodes extends SimpleApplication { mat.setColor("Color", ColorRGBA.Yellow); mat.setTexture("ColorMap", tex); - cube_tex.setMaterial(mat); - rootNode.attachChild(cube_tex); + cube.setMaterial(mat); + cube.move(-2.5f,0,0); + rootNode.attachChild(cube); + + + cube = cube.clone(); + mat = new Material(assetManager, "jme3test/matdefs/test.j3md"); + mat.selectTechnique(TechniqueDef.DEFAULT_TECHNIQUE_NAME, renderManager); + 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("ColorMap", tex); + cube.setMaterial(mat); + cube.move(2.5f,0,0); + rootNode.attachChild(cube); + } } diff --git a/jme3-examples/src/main/resources/jme3test/matdefs/ColorMult.frag b/jme3-examples/src/main/resources/jme3test/matdefs/ColorMult.frag new file mode 100644 index 000000000..ae3d50477 --- /dev/null +++ b/jme3-examples/src/main/resources/jme3test/matdefs/ColorMult.frag @@ -0,0 +1,5 @@ +#pragma ShaderNode defaults(vec4(1.0), ,vec4(1.0)) +vec4 ColorMult(const in vec4 color1, const in vec4 color2, const in vec4 color3){ + return color1 * color2 * color3; +} + diff --git a/jme3-examples/src/main/resources/jme3test/matdefs/CommonVert.vert b/jme3-examples/src/main/resources/jme3test/matdefs/CommonVert.vert new file mode 100644 index 000000000..13ec1fc87 --- /dev/null +++ b/jme3-examples/src/main/resources/jme3test/matdefs/CommonVert.vert @@ -0,0 +1,9 @@ +#pragma ShaderNode +vec4 CommonVert(const in mat4 worldViewProjectionMatrix, const in vec3 modelPosition){ + return worldViewProjectionMatrix * vec4(modelPosition, 1.0); +} + +#pragma ShaderNode +vec4 DoThing( in vec4 color ){ + return color * 0.1; +} \ No newline at end of file diff --git a/jme3-examples/src/main/resources/jme3test/matdefs/test.j3md b/jme3-examples/src/main/resources/jme3test/matdefs/test.j3md new file mode 100644 index 000000000..6b743a065 --- /dev/null +++ b/jme3-examples/src/main/resources/jme3test/matdefs/test.j3md @@ -0,0 +1,45 @@ +MaterialDef Simple { + MaterialParameters { + Vector4 Color + } + + Technique { + WorldParameters { + WorldViewProjectionMatrix + } + + VertexShaderNodes { + ShaderNode CommonVert { + Definition: CommonVert: jme3test/matdefs/CommonVert.vert + InputMappings{ + worldViewProjectionMatrix = WorldParam.WorldViewProjectionMatrix + modelPosition = Attr.inPosition + } + OutputMappings{ + Global.position = result + } + } + ShaderNode ColorStuff { + Definition: DoThing: jme3test/matdefs/CommonVert.vert + InputMappings{ + color = MatParam.Color + } + OutputMappings{ + } + } + } + + FragmentShaderNodes { + ShaderNode ColorMult { + Definition: ColorMult: jme3test/matdefs/ColorMult.frag + InputMappings{ + color2 = ColorStuff.result + } + OutputMappings{ + Global.color = result + } + } + } + + } +}