Adds a new java API for shader nodes

shader-nodes-enhancement
Nehon 7 years ago
parent 9f9edee332
commit b57ecf35ea
  1. 20
      jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java
  2. 2
      jme3-core/src/main/java/com/jme3/shader/Glsl150ShaderGenerator.java
  3. 44
      jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java
  4. 7
      jme3-core/src/main/java/com/jme3/shader/ShaderNodeDefinition.java
  5. 8
      jme3-core/src/main/java/com/jme3/shader/ShaderNodeVariable.java
  6. 343
      jme3-core/src/main/java/com/jme3/shader/ShaderUtils.java
  7. 105
      jme3-core/src/main/java/com/jme3/shader/builder/InlineShaderNodeBuilder.java
  8. 146
      jme3-core/src/main/java/com/jme3/shader/builder/MaterialBuilder.java
  9. 113
      jme3-core/src/main/java/com/jme3/shader/builder/ShaderNodeBuilder.java
  10. 145
      jme3-core/src/main/java/com/jme3/shader/builder/TechniqueBuilder.java
  11. 23
      jme3-core/src/main/java/com/jme3/shader/builder/VariableMappingBuilder.java
  12. 22
      jme3-core/src/plugins/java/com/jme3/material/plugins/ConditionParser.java
  13. 5
      jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java
  14. 2
      jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java
  15. 86
      jme3-examples/src/main/java/jme3test/material/TestShaderNodesApi.java

@ -275,13 +275,17 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
// one mapping for this variable, directly append the // one mapping for this variable, directly append the
// other variable from the mapping to the function call // other variable from the mapping to the function call
VariableMapping m = maps.get(0); VariableMapping m = maps.get(0);
ShaderNodeVariable v2 = m.getRightVariable(); if(m.getRightExpression()!=null) {
b.append(getAppendableNameSpace(v2)) b.append(m.getRightExpression());
.append(v2.getPrefix()) } else {
.append(v2.getName()); ShaderNodeVariable v2 = m.getRightVariable();
if (m.getRightSwizzling().length() > 0) { b.append(getAppendableNameSpace(v2))
b.append("."); .append(v2.getPrefix())
b.append(m.getRightSwizzling()); .append(v2.getName());
if (m.getRightSwizzling().length() > 0) {
b.append(".");
b.append(m.getRightSwizzling());
}
} }
} else { } else {
// 2 possible cases here // 2 possible cases here
@ -652,7 +656,7 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
} }
@Override @Override
protected String getLanguageAndVersion(ShaderType type) { protected String getLanguageAndVersion() {
return "GLSL100"; return "GLSL100";
} }

@ -55,7 +55,7 @@ public class Glsl150ShaderGenerator extends Glsl100ShaderGenerator {
} }
@Override @Override
protected String getLanguageAndVersion(ShaderType type) { protected String getLanguageAndVersion() {
return "GLSL150"; return "GLSL150";
} }

@ -114,7 +114,7 @@ public abstract class ShaderGenerator {
Shader shader = new Shader(); Shader shader = new Shader();
for (ShaderType type : ShaderType.values()) { for (ShaderType type : ShaderType.values()) {
String extension = type.getExtension(); String extension = type.getExtension();
String language = getLanguageAndVersion(type); String language = getLanguageAndVersion();
String shaderSourceCode = buildShader(techniqueDef.getShaderNodes(), info, type); String shaderSourceCode = buildShader(techniqueDef.getShaderNodes(), info, type);
if (shaderSourceCode != null) { if (shaderSourceCode != null) {
@ -203,32 +203,36 @@ public abstract class ShaderGenerator {
if (info.getUnusedNodes().contains(shaderNode.getName())) { if (info.getUnusedNodes().contains(shaderNode.getName())) {
continue; continue;
} }
ShaderNodeDefinition def = shaderNode.getDefinition();
if (shaderNode.getDefinition().getType() == type) { if (def.getType() == type) {
int index = findShaderIndexFromVersion(shaderNode, type); String loadedSource = def.getInlinedCode();
String shaderPath = shaderNode.getDefinition().getShadersPath().get(index); String shaderPath = def.getName() + "_Inlined";
Map<String, String> sources = (Map<String, String>) assetManager.loadAsset(new ShaderAssetKey(shaderPath, false)); if(loadedSource == null) {
String loadedSource = sources.get("[main]"); int index = findShaderIndexFromVersion(shaderNode);
for (String name : sources.keySet()) { shaderPath = def.getShadersPath().get(index);
if (!name.equals("[main]") && !imports.contains(name)) { Map<String, String> sources = (Map<String, String>) assetManager.loadAsset(new ShaderAssetKey(shaderPath, false));
imports.add(name); loadedSource = sources.get("[main]");
// append the imported file in place if it hasn't been imported already. for (String name : sources.keySet()) {
sourceDeclaration.append(sources.get(name)); 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 // 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. // 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. // Also nodes can have #ifdef conditions so we need to properly merge this conditions to declare the Node function.
NodeDeclaration nd = declaredNodes.get(shaderPath); NodeDeclaration nd = declaredNodes.get(shaderPath);
if(nd == null){ if (nd == null) {
if(!shaderNode.getDefinition().getPath().equals(shaderPath)) { if (def.getInlinedCode() == null && !def.getPath().equals(shaderPath)) {
// old style shader node definition // old style shader node definition
loadedSource = functionize(loadedSource, shaderNode.getDefinition()); loadedSource = functionize(loadedSource, shaderNode.getDefinition());
} }
nd = new NodeDeclaration(shaderNode.getCondition(), loadedSource); nd = new NodeDeclaration(shaderNode.getCondition(), loadedSource);
declaredNodes.put(shaderPath, nd); declaredNodes.put(shaderPath, nd);
} else { } else {
nd.condition = ConditionParser.mergeConditions(nd.condition, shaderNode.getCondition(), "||"); nd.condition = ShaderUtils.mergeConditions(nd.condition, shaderNode.getCondition(), "||");
} }
generateNodeMainSection(source, shaderNode, loadedSource, info); generateNodeMainSection(source, shaderNode, loadedSource, info);
@ -293,11 +297,10 @@ public abstract class ShaderGenerator {
* returns the language + version of the shader should be something like * returns the language + version of the shader should be something like
* "GLSL100" for glsl 1.0 "GLSL150" for glsl 1.5. * "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. * @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. * generates the uniforms declaration for a shader of the given type.
@ -379,15 +382,14 @@ public abstract class ShaderGenerator {
* can handle * can handle
* *
* @param shaderNode the shaderNode being processed * @param shaderNode the shaderNode being processed
* @param type the shaderType
* @return the index of the shader path in ShaderNodeDefinition shadersPath * @return the index of the shader path in ShaderNodeDefinition shadersPath
* list * list
* @throws NumberFormatException * @throws NumberFormatException
*/ */
protected int findShaderIndexFromVersion(ShaderNode shaderNode, ShaderType type) throws NumberFormatException { protected int findShaderIndexFromVersion(ShaderNode shaderNode) throws NumberFormatException {
int index = 0; int index = 0;
List<String> lang = shaderNode.getDefinition().getShadersLanguage(); List<String> lang = shaderNode.getDefinition().getShadersLanguage();
int genVersion = Integer.parseInt(getLanguageAndVersion(type).substring(4)); int genVersion = Integer.parseInt(getLanguageAndVersion().substring(4));
int curVersion = 0; int curVersion = 0;
for (int i = 0; i < lang.size(); i++) { for (int i = 0; i < lang.size(); i++) {
int version = Integer.parseInt(lang.get(i).substring(4)); int version = Integer.parseInt(lang.get(i).substring(4));

@ -59,6 +59,7 @@ public class ShaderNodeDefinition implements Savable {
private List<ShaderNodeVariable> outputs = new ArrayList<ShaderNodeVariable>(); private List<ShaderNodeVariable> outputs = new ArrayList<ShaderNodeVariable>();
private List<ShaderNodeVariable> params = new ArrayList<>(); private List<ShaderNodeVariable> params = new ArrayList<>();
private String path = null; private String path = null;
private String inlinedCode;
private boolean noOutput = false; private boolean noOutput = false;
private String returnType; private String returnType;
@ -237,7 +238,13 @@ public class ShaderNodeDefinition implements Savable {
this.noOutput = noOutput; this.noOutput = noOutput;
} }
public String getInlinedCode() {
return inlinedCode;
}
public void setInlinedCode(String inlinedCode) {
this.inlinedCode = inlinedCode;
}
/** /**
* jme serialization (not used) * jme serialization (not used)

@ -337,7 +337,11 @@ public class ShaderNodeVariable implements Savable, Cloneable {
} }
@Override @Override
public ShaderNodeVariable clone() throws CloneNotSupportedException { public ShaderNodeVariable clone() {
return (ShaderNodeVariable) super.clone(); try {
return (ShaderNodeVariable) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
} }
} }

@ -32,9 +32,11 @@
package com.jme3.shader; package com.jme3.shader;
import com.jme3.asset.AssetManager; 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 com.jme3.shader.plugins.ShaderAssetKey;
import java.io.StringReader;
import java.text.ParseException; import java.text.ParseException;
import java.util.*; import java.util.*;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -42,18 +44,14 @@ import java.util.regex.Pattern;
public class ShaderUtils { public class ShaderUtils {
public static String convertToGLSL130(String input, boolean isFrag) { private static ConditionParser parser = new ConditionParser();
StringBuilder sb = new StringBuilder();
sb.append("#version 130\n"); // matches "defaults (<defaultParam1>, <defaultParam2>, ...)"
if (isFrag) { private final static Pattern defaultsPattern = Pattern.compile("defaults\\s*\\(\\s*(.*)\\s*\\)");
input = input.replaceAll("varying", "in"); // matches "<type> <functionName>("
} else { private final static Pattern typeNamePattern = Pattern.compile("(\\w+)\\s+(\\w+)\\s*\\(");
input = input.replaceAll("attribute", "in"); // matches "const? <in/out> <type> <parmaName>,"
input = input.replaceAll("varying", "out"); private final static Pattern paramsPattern = Pattern.compile("((const)?\\s*(\\w+)\\s+(\\w+)\\s+(\\w+)\\s*[,\\)])");
}
sb.append(input);
return sb.toString();
}
/** /**
* Check if a mapping is valid by checking the types and swizzle of both of * Check if a mapping is valid by checking the types and swizzle of both of
@ -81,7 +79,7 @@ public class ShaderUtils {
return false; return false;
} }
/** /**
* Check if a mapping is valid by checking the multiplicity of both of * Check if a mapping is valid by checking the multiplicity of both of
* the variables if they are arrays * the variables if they are arrays
* *
@ -92,15 +90,15 @@ public class ShaderUtils {
String leftMult = mapping.getLeftVariable().getMultiplicity(); String leftMult = mapping.getLeftVariable().getMultiplicity();
String rightMult = mapping.getRightVariable().getMultiplicity(); String rightMult = mapping.getRightVariable().getMultiplicity();
if(leftMult == null){ if (leftMult == null) {
if(rightMult != null){ if (rightMult != null) {
return false; return false;
} }
}else{ } else {
if(rightMult == null){ if (rightMult == null) {
return false; return false;
}else{ } else {
if(!leftMult.equalsIgnoreCase(rightMult)){ if (!leftMult.equalsIgnoreCase(rightMult)) {
return false; return false;
} }
} }
@ -113,7 +111,7 @@ public class ShaderUtils {
* is 4 float cardinality is 1 vec4.xyz cardinality is 3. sampler2D * is 4 float cardinality is 1 vec4.xyz cardinality is 3. sampler2D
* cardinality is 0 * cardinality is 0
* *
* @param type the glsl type * @param type the glsl type
* @param swizzling the swizzling of a variable * @param swizzling the swizzling of a variable
* @return the cardinality * @return the cardinality
*/ */
@ -143,15 +141,9 @@ public class ShaderUtils {
* @return true if a variable of the given type can have a swizzle * @return true if a variable of the given type can have a swizzle
*/ */
public static boolean isSwizzlable(String type) { 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 "<type> <functionName>("
private final static Pattern typeNamePattern = Pattern.compile("(\\w+)\\s+(\\w+)\\s*\\(");
// matches "const? <in/out> <type> <parmaName>,"
private final static Pattern paramsPattern = Pattern.compile("((const)?\\s*(\\w+)\\s+(\\w+)\\s+(\\w+)\\s*[,\\)])");
public static List<ShaderNodeDefinition> parseDefinitions(String glsl) throws ParseException { public static List<ShaderNodeDefinition> parseDefinitions(String glsl) throws ParseException {
List<ShaderNodeDefinition> defs = new ArrayList<>(); List<ShaderNodeDefinition> defs = new ArrayList<>();
String nodesCode[] = glsl.split("#pragma ShaderNode"); String nodesCode[] = glsl.split("#pragma ShaderNode");
@ -244,7 +236,7 @@ public class ShaderUtils {
} }
public static List<ShaderNodeDefinition> loadSahderNodeDefinition(AssetManager assetManager, String definitionPath) throws ParseException { public static List<ShaderNodeDefinition> loadSahderNodeDefinition(AssetManager assetManager, String definitionPath) throws ParseException {
Map<String, String> sources = (Map<String, String>) assetManager.loadAsset(new ShaderAssetKey(definitionPath, false)); Map<String, String> sources = (Map<String, String>) assetManager.loadAsset(new ShaderAssetKey(definitionPath, false));
String glsl = sources.get("[main]"); String glsl = sources.get("[main]");
List<ShaderNodeDefinition> defs = ShaderUtils.parseDefinitions(glsl); List<ShaderNodeDefinition> defs = ShaderUtils.parseDefinitions(glsl);
Shader.ShaderType type = ShaderUtils.getShaderType(definitionPath); Shader.ShaderType type = ShaderUtils.getShaderType(definitionPath);
@ -257,4 +249,297 @@ public class ShaderUtils {
return defs; 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<ShaderNodeVariable> attributes = technique.getShaderGenerationInfo().getAttributes();
List<ShaderNodeVariable> fragmentGlobals = technique.getShaderGenerationInfo().getFragmentGlobals();
List<ShaderNodeVariable> fragmentUniforms = technique.getShaderGenerationInfo().getFragmentUniforms();
List<ShaderNodeVariable> vertexUniforms = technique.getShaderGenerationInfo().getVertexUniforms();
List<ShaderNodeVariable> varyings = technique.getShaderGenerationInfo().getVaryings();
List<String> 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<VariableMapping> 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<VariableMapping> 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<String> 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<ShaderNodeVariable> 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<ShaderNodeVariable> 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<ShaderNodeVariable> 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<ShaderNodeVariable> vars, ShaderNodeVariable var) {
for (ShaderNodeVariable shaderNodeVariable : vars) {
if (shaderNodeVariable.getName().equals(var.getName()) && shaderNodeVariable.getNameSpace().equals(var.getNameSpace())) {
return true;
}
}
return false;
}
} }

@ -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();
}
}

@ -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<String, TechniqueBuilder> 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<TechniqueDef> 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<String, TechniqueBuilder> 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);
}
}

@ -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<VariableMapping> 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<VariableMapping> 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<ShaderNodeVariable> list){
for (ShaderNodeVariable variable : list) {
if(variable.getName().equals(name)){
return variable;
}
}
return null;
}
public void build(){
}
}

@ -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<String, ShaderNodeBuilder> 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<ShaderNodeDefinition> 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<ShaderNode>());
}
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<ShaderNodeDefinition> 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<String, ShaderNodeBuilder> entry : nodeBuilders.entrySet()) {
ShaderNodeBuilder nb = entry.getValue();
nb.build();
}
}
}

@ -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;
}
}

@ -104,28 +104,6 @@ public class ConditionParser {
return defines; 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 * @return the formatted expression previously updated by extractDefines

@ -660,9 +660,8 @@ public class J3MLoader implements AssetLoader {
if(isUseNodes){ if(isUseNodes){
//used for caching later, the shader here is not a file. //used for caching later, the shader here is not a file.
// KIRILL 9/19/2015 // REMY 16/06/2018
// Not sure if this is needed anymore, since shader caching // this is still needed in order for the weight of the technique to be computed.
// is now done by TechniqueDef.
technique.setShaderFile(technique.hashCode() + "", technique.hashCode() + "", "GLSL100", "GLSL100"); technique.setShaderFile(technique.hashCode() + "", technique.hashCode() + "", "GLSL100", "GLSL100");
techniqueDefs.add(technique); techniqueDefs.add(technique);
}else if (shaderNames.containsKey(Shader.ShaderType.Vertex) && shaderNames.containsKey(Shader.ShaderType.Fragment)) { }else if (shaderNames.containsKey(Shader.ShaderType.Vertex) && shaderNames.containsKey(Shader.ShaderType.Fragment)) {

@ -582,7 +582,7 @@ public class ShaderNodeLoaderDelegate {
multiplicity = multiplicity.toUpperCase(); multiplicity = multiplicity.toUpperCase();
left.setMultiplicity(multiplicity); left.setMultiplicity(multiplicity);
// only declare the variable if the define is defined. // 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 { } else {
throw new MatParseException("Wrong multiplicity for variable" + left.getName() + ". " + throw new MatParseException("Wrong multiplicity for variable" + left.getName() + ". " +
multiplicity + " should be an int or a declared material parameter.", statement); multiplicity + " should be an int or a declared material parameter.", statement);

@ -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);
}
}
Loading…
Cancel
Save